Skip to content

Commit bdc9663

Browse files
committed
Add Factory for MF2
Also add unit test and data driven unit test Test data based on inflection/test/resources/inflection/dialog/inflection/*.xml Test formatter while the <result> is not empty Test Selector while the <result> is empty, and there are one attribute which is not "exists". Uppercase the factory function Change copyright, remove iostream, add comments Fix build issue if the icu is < 77 Remove Data Driven test Fix Locale building Remove version Check
1 parent 8fbf081 commit bdc9663

File tree

3 files changed

+435
-0
lines changed

3 files changed

+435
-0
lines changed
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
* Copyright 2025 Unicode Incorporated and others. All rights reserved.
3+
*/
4+
#include "inflection/message2/MF2Factory.hpp"
5+
6+
#include "inflection/dialog/InflectableStringConcept.hpp"
7+
#include "inflection/dialog/LocalizedCommonConceptFactoryProvider.hpp"
8+
#include "inflection/dialog/SemanticFeatureModel.hpp"
9+
#include "inflection/dialog/SpeakableString.hpp"
10+
#include "inflection/lang/features/LanguageGrammarFeatures.hpp"
11+
#include "inflection/util/ULocale.hpp"
12+
13+
#include <unicode/locid.h>
14+
#include <unicode/messageformat2.h>
15+
#include <unicode/messageformat2_function_registry.h>
16+
#include <unicode/messageformat2_formattable.h>
17+
18+
using U_ICU_NAMESPACE::Locale;
19+
using U_ICU_NAMESPACE::UnicodeString;
20+
using U_ICU_NAMESPACE::message2::Formatter;
21+
using U_ICU_NAMESPACE::message2::Formattable;
22+
using U_ICU_NAMESPACE::message2::FormattedValue;
23+
using U_ICU_NAMESPACE::message2::FormattedPlaceholder;
24+
using U_ICU_NAMESPACE::message2::FormatterFactory;
25+
using U_ICU_NAMESPACE::message2::FunctionOptions;
26+
using U_ICU_NAMESPACE::message2::FunctionOptionsMap;
27+
using U_ICU_NAMESPACE::message2::MessageArguments;
28+
using U_ICU_NAMESPACE::message2::MessageFormatter;
29+
using U_ICU_NAMESPACE::message2::MFFunctionRegistry;
30+
using U_ICU_NAMESPACE::message2::Selector;
31+
using U_ICU_NAMESPACE::message2::SelectorFactory;
32+
33+
namespace inflection::message2 {
34+
35+
class InflectionFormatterFactory : public FormatterFactory {
36+
public:
37+
Formatter* createFormatter(const Locale&, UErrorCode&) override;
38+
};
39+
40+
class InflectionSelectorFactory : public SelectorFactory {
41+
public:
42+
Selector* createSelector(const Locale&, UErrorCode&) const override;
43+
};
44+
45+
icu::message2::FormatterFactory* MF2Factory::CreateFormatterFactory() {
46+
return new InflectionFormatterFactory();
47+
}
48+
49+
icu::message2::SelectorFactory* MF2Factory::CreateSelectorFactory() {
50+
return new InflectionSelectorFactory();
51+
}
52+
53+
const inflection::dialog::SemanticFeatureModel* GetSemanticFeatureModel(
54+
const Locale& locale) {
55+
return ::inflection::dialog::LocalizedCommonConceptFactoryProvider
56+
::getDefaultCommonConceptFactoryProvider()
57+
->getCommonConceptFactory(
58+
inflection::util::ULocale(locale.getName()))
59+
->getSemanticFeatureModel();
60+
}
61+
62+
class InflectionFormatter : public Formatter {
63+
public:
64+
FormattedPlaceholder format(FormattedPlaceholder&&, FunctionOptions&& opts,
65+
UErrorCode& errorCode) const override;
66+
InflectionFormatter(const inflection::dialog::SemanticFeatureModel* model)
67+
: model(model) {
68+
}
69+
private:
70+
const ::inflection::dialog::SemanticFeatureModel* model;
71+
};
72+
73+
Formatter* InflectionFormatterFactory::createFormatter(
74+
const Locale& locale, UErrorCode& errorCode) {
75+
if (U_FAILURE(errorCode)) { return nullptr; }
76+
77+
Formatter* result = new InflectionFormatter(GetSemanticFeatureModel(locale));
78+
if (result == nullptr) {
79+
errorCode = U_MEMORY_ALLOCATION_ERROR;
80+
}
81+
return result;
82+
}
83+
84+
FormattedPlaceholder InflectionFormatter::format(
85+
FormattedPlaceholder&& arg, FunctionOptions&& options,
86+
UErrorCode& errorCode) const {
87+
if (U_FAILURE(errorCode)) { return {}; }
88+
89+
// Argument must be present
90+
if (!arg.canFormat()) {
91+
errorCode = U_MF_FORMATTING_ERROR;
92+
return FormattedPlaceholder("inflection");
93+
}
94+
95+
// Assumes the argument is not-yet-formatted
96+
const Formattable& toFormat = arg.asFormattable();
97+
UnicodeString result;
98+
99+
switch (toFormat.getType()) {
100+
case UFMT_STRING: {
101+
inflection::dialog::SpeakableString input(toFormat.getString(errorCode));
102+
inflection::dialog::InflectableStringConcept stringConcept(model, input);
103+
for (const auto& [key, value] : options.getOptions()) {
104+
auto constraint = model->getFeature(key);
105+
if (constraint != nullptr) {
106+
stringConcept.putConstraint(*constraint, value.getString(errorCode));
107+
}
108+
}
109+
result += stringConcept.toSpeakableString()->getPrint();
110+
break;
111+
}
112+
default: {
113+
result += toFormat.getString(errorCode);
114+
break;
115+
}
116+
}
117+
118+
return FormattedPlaceholder(arg, FormattedValue(std::move(result)));
119+
}
120+
121+
class InflectionSelector : public Selector {
122+
public:
123+
void selectKey(FormattedPlaceholder &&arg, FunctionOptions &&options,
124+
const UnicodeString *keys, int32_t keysLen,
125+
UnicodeString *prefs, int32_t &prefsLen, UErrorCode &status) const override;
126+
127+
InflectionSelector(const inflection::dialog::SemanticFeatureModel* model)
128+
: model(model) {
129+
}
130+
131+
private:
132+
const ::inflection::dialog::SemanticFeatureModel* model;
133+
};
134+
135+
Selector* InflectionSelectorFactory::createSelector(
136+
const Locale& locale, UErrorCode& errorCode) const {
137+
if (U_FAILURE(errorCode)) { return nullptr; }
138+
139+
Selector* result = new InflectionSelector(GetSemanticFeatureModel(locale));
140+
if (result == nullptr) {
141+
errorCode = U_MEMORY_ALLOCATION_ERROR;
142+
}
143+
return result;
144+
}
145+
146+
void InflectionSelector::selectKey(
147+
FormattedPlaceholder &&arg, FunctionOptions &&options,
148+
const UnicodeString *keys, int32_t keysLen,
149+
UnicodeString *prefs, int32_t &prefsLen, UErrorCode &errorCode) const {
150+
if (U_FAILURE(errorCode)) { return; }
151+
// Argument must be present
152+
if (!arg.canFormat()) {
153+
errorCode = U_MF_SELECTOR_ERROR;
154+
return;
155+
}
156+
157+
// Assumes the argument is not-yet-formatted
158+
const Formattable& toFormat = arg.asFormattable();
159+
prefsLen = 0;
160+
auto opt = options.getOptions();
161+
if (toFormat.getType() == UFMT_STRING) {
162+
inflection::dialog::SpeakableString input(toFormat.getString(errorCode));
163+
inflection::dialog::InflectableStringConcept stringConcept(model, input);
164+
if (!opt.contains(u"select")) {
165+
errorCode = U_MF_SELECTOR_ERROR;
166+
return;
167+
}
168+
for (const auto& [key, value] : options.getOptions()) {
169+
auto constraint = model->getFeature(key);
170+
if (constraint != nullptr) {
171+
stringConcept.putConstraint(*constraint, value.getString(errorCode));
172+
}
173+
}
174+
auto value = model->getFeature(opt.at(u"select").getString(errorCode));
175+
UnicodeString feature;
176+
if (value != nullptr) {
177+
auto result = stringConcept.getFeatureValue(*value);
178+
if (result != nullptr) {
179+
feature = result->getPrint();
180+
}
181+
}
182+
183+
for (int i = 0; i < keysLen; i++) {
184+
if (feature == keys[i]) {
185+
prefs[prefsLen++] = keys[i];
186+
}
187+
}
188+
}
189+
return;
190+
}
191+
192+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2025 Unicode Incorporated and others. All rights reserved.
3+
*/
4+
#pragma once
5+
6+
#include <inflection/api.h>
7+
// INFLECTION_CLASS_API
8+
9+
#include <unicode/uversion.h>
10+
11+
namespace U_ICU_NAMESPACE::message2 {
12+
class FormatterFactory;
13+
class SelectorFactory;
14+
}
15+
16+
namespace inflection::message2 {
17+
18+
/**
19+
* The MF2Factory provide factory method to create custom formatter and selector
20+
* factory to work with icu::message2 library.
21+
* The intend usage is to creat icu::message2::FormatterFactory
22+
* or icu::message2::SelectorFactory and use it with
23+
* icu::message2::MFFunctionRegistry::Builder
24+
* to construct a custom function registry to build MessageFormatter
25+
* with MessageFormatter::Builder.
26+
*
27+
* For example:
28+
*
29+
* auto customRegistry = icu::message2::MFFunctionRegistry::Builder(errorCode)
30+
* .adoptFormatter(FunctionName("inflection"),
31+
* MF2Factory::CreateFormatterFactory(), errorCode)
32+
* .adoptSelector(FunctionName("inflection"),
33+
* MF2Factory::CreateSelectorFactory(), errorCode)
34+
* .build();
35+
*
36+
* UParseError pe;
37+
* auto mf1 = icu::message2::MessageFormatter::Builder(errorCode)
38+
* .setFunctionRegistry(customRegistry)
39+
* .setLocale(Locale::forLanguageTag("es-MX", errorCode))
40+
* .setPattern("Location is {$name :inflection hello=world \
41+
* definiteness=definite number=plural gender=feminine}",
42+
* pe, errorCode)
43+
* .build(errorCode);
44+
*
45+
* auto mf2 = icu::message2::MessageFormatter::Builder(errorCode)
46+
* .setFunctionRegistry(customRegistry)
47+
* .setLocale(Locale::forLanguageTag("es-MX", errorCode))
48+
* .setPattern(".local $gender = {$name :inflection feature=gender} \
49+
* .local $number = {$name :inflection feature=number} \
50+
* .match $gender $number \
51+
* feminine singular {{Feminine Singular {$name}}} \
52+
* masculine singular {{Masculine Singular {$name}}} \
53+
* * * {{other {$name} }}\n",
54+
* pe, errorCode)
55+
* .build(errorCode);
56+
*/
57+
class INFLECTION_CLASS_API MF2Factory {
58+
public:
59+
/**
60+
* Create an implementation of icu::message2::FormatterFactory*, based on the
61+
* infleciton library, which can be passed to
62+
* icu::messsage2::MFFunctionRegistry::Builder::adoptFormatter
63+
* to register a custom formatter factory.
64+
*/
65+
static icu::message2::FormatterFactory* CreateFormatterFactory();
66+
67+
/**
68+
* Create an implementation of icu::message2::SelectorFactory*, based on the
69+
* infleciton library, which can be passed to
70+
* icu::messsage2::MFFunctionRegistry::Builder::adoptSelector
71+
* to register a custom selector factory.
72+
*/
73+
static icu::message2::SelectorFactory* CreateSelectorFactory();
74+
};
75+
76+
}

0 commit comments

Comments
 (0)