xref: /aosp_15_r20/external/icu/icu4c/source/test/intltest/messageformat2test_custom.cpp (revision 0e209d3975ff4a8c132096b14b0e9364a753506e)
1 // © 2024 and later: Unicode, Inc. and others.
2 
3 #include "unicode/utypes.h"
4 
5 #if !UCONFIG_NO_FORMATTING
6 
7 #if !UCONFIG_NO_MF2
8 
9 #include "plurrule_impl.h"
10 
11 #include "unicode/listformatter.h"
12 #include "messageformat2test.h"
13 #include "hash.h"
14 #include "intltest.h"
15 
16 
17 using namespace message2;
18 using namespace pluralimpl;
19 
20 /*
21 Tests reflect the syntax specified in
22 
23   https://github.com/unicode-org/message-format-wg/commits/main/spec/message.abnf
24 
25 as of the following commit from 2023-05-09:
26   https://github.com/unicode-org/message-format-wg/commit/194f6efcec5bf396df36a19bd6fa78d1fa2e0867
27 */
28 
29 using namespace data_model;
30 
testPersonFormatter(IcuTestErrorCode & errorCode)31 void TestMessageFormat2::testPersonFormatter(IcuTestErrorCode& errorCode) {
32     CHECK_ERROR(errorCode);
33 
34     MFFunctionRegistry customRegistry(MFFunctionRegistry::Builder(errorCode)
35                                       .adoptFormatter(FunctionName("person"), new PersonNameFormatterFactory(), errorCode)
36                                       .build());
37     UnicodeString name = "name";
38     LocalPointer<Person> person(new Person(UnicodeString("Mr."), UnicodeString("John"), UnicodeString("Doe")));
39     TestCase::Builder testBuilder;
40     testBuilder.setName("testPersonFormatter");
41     testBuilder.setLocale(Locale("en"));
42 
43     TestCase test = testBuilder.setPattern("Hello {$name :person formality=formal}")
44         .setArgument(name, person.getAlias())
45         .setExpected("Hello {$name}")
46         .setExpectedError(U_MF_UNKNOWN_FUNCTION_ERROR)
47         .build();
48     TestUtils::runTestCase(*this, test, errorCode);
49 
50     test = testBuilder.setPattern("Hello {$name :person formality=informal}")
51                                 .setArgument(name, person.getAlias())
52                                 .setExpected("Hello {$name}")
53                                 .setExpectedError(U_MF_UNKNOWN_FUNCTION_ERROR)
54                                 .build();
55     TestUtils::runTestCase(*this, test, errorCode);
56 
57     testBuilder.setFunctionRegistry(&customRegistry);
58 
59     test = testBuilder.setPattern("Hello {$name :person formality=formal}")
60                                 .setArgument(name, person.getAlias())
61                                 .setExpected("Hello Mr. Doe")
62                                 .setExpectSuccess()
63                                 .build();
64     TestUtils::runTestCase(*this, test, errorCode);
65 
66     test = testBuilder.setPattern("Hello {$name :person formality=informal}")
67                                 .setArgument(name, person.getAlias())
68                                 .setExpected("Hello John")
69                                 .setExpectSuccess()
70                                 .build();
71     TestUtils::runTestCase(*this, test, errorCode);
72 
73     test = testBuilder.setPattern("Hello {$name :person formality=formal length=long}")
74                                 .setArgument(name, person.getAlias())
75                                 .setExpected("Hello Mr. John Doe")
76                                 .setExpectSuccess()
77                                 .build();
78     TestUtils::runTestCase(*this, test, errorCode);
79 
80     test = testBuilder.setPattern("Hello {$name :person formality=formal length=medium}")
81                                 .setArgument(name, person.getAlias())
82                                 .setExpected("Hello John Doe")
83                                 .setExpectSuccess()
84                                 .build();
85     TestUtils::runTestCase(*this, test, errorCode);
86 
87     test = testBuilder.setPattern("Hello {$name :person formality=formal length=short}")
88                                 .setArgument(name, person.getAlias())
89                                 .setExpected("Hello Mr. Doe")
90                                 .setExpectSuccess()
91                                 .build();
92     TestUtils::runTestCase(*this, test, errorCode);
93 }
94 
testCustomFunctionsComplexMessage(IcuTestErrorCode & errorCode)95 void TestMessageFormat2::testCustomFunctionsComplexMessage(IcuTestErrorCode& errorCode) {
96     CHECK_ERROR(errorCode);
97 
98     MFFunctionRegistry customRegistry(MFFunctionRegistry::Builder(errorCode)
99                                       .adoptFormatter(FunctionName("person"), new PersonNameFormatterFactory(), errorCode)
100                                       .build());
101     UnicodeString host = "host";
102     UnicodeString hostGender = "hostGender";
103     UnicodeString guest = "guest";
104     UnicodeString guestCount = "guestCount";
105 
106     LocalPointer<Person> jane(new Person(UnicodeString("Ms."), UnicodeString("Jane"), UnicodeString("Doe")));
107     LocalPointer<Person> john(new Person(UnicodeString("Mr."), UnicodeString("John"), UnicodeString("Doe")));
108     LocalPointer<Person> anonymous(new Person(UnicodeString("Mx."), UnicodeString("Anonymous"), UnicodeString("Doe")));
109 
110     if (!jane.isValid() || !john.isValid() || !anonymous.isValid()) {
111        ((UErrorCode&) errorCode) = U_MEMORY_ALLOCATION_ERROR;
112        return;
113    }
114 
115     UnicodeString message = ".local $hostName = {$host :person length=long}\n\
116                 .local $guestName = {$guest :person length=long}\n\
117                 .input {$guestCount :number}\n\
118                 .match {$hostGender :string} {$guestCount :number}\n\
119                  female 0 {{{$hostName} does not give a party.}}\n\
120                  female 1 {{{$hostName} invites {$guestName} to her party.}}\n\
121                  female 2 {{{$hostName} invites {$guestName} and one other person to her party.}}\n\
122                  female * {{{$hostName} invites {$guestCount} people, including {$guestName}, to her party.}}\n\
123                  male 0 {{{$hostName} does not give a party.}}\n\
124                  male 1 {{{$hostName} invites {$guestName} to his party.}}\n\
125                  male 2 {{{$hostName} invites {$guestName} and one other person to his party.}}\n\
126                  male * {{{$hostName} invites {$guestCount} people, including {$guestName}, to his party.}}\n\
127                  * 0 {{{$hostName} does not give a party.}}\n\
128                  * 1 {{{$hostName} invites {$guestName} to their party.}}\n\
129                  * 2 {{{$hostName} invites {$guestName} and one other person to their party.}}\n\
130                  * * {{{$hostName} invites {$guestCount} people, including {$guestName}, to their party.}}";
131 
132 
133     TestCase::Builder testBuilder;
134     testBuilder.setName("testCustomFunctionsComplexMessage");
135     testBuilder.setLocale(Locale("en"));
136     testBuilder.setPattern(message);
137     testBuilder.setFunctionRegistry(&customRegistry);
138 
139     TestCase test = testBuilder.setArgument(host, jane.getAlias())
140         .setArgument(hostGender, "female")
141         .setArgument(guest, john.getAlias())
142         .setArgument(guestCount, (int64_t) 3)
143         .setExpected("Ms. Jane Doe invites 3 people, including Mr. John Doe, to her party.")
144         .setExpectSuccess()
145         .build();
146     TestUtils::runTestCase(*this, test, errorCode);
147 
148     test = testBuilder.setArgument(host, jane.getAlias())
149                                 .setArgument(hostGender, "female")
150                                 .setArgument(guest, john.getAlias())
151                                 .setArgument(guestCount, (int64_t) 2)
152                                 .setExpected("Ms. Jane Doe invites Mr. John Doe and one other person to her party.")
153                                 .setExpectSuccess()
154                                 .build();
155     TestUtils::runTestCase(*this, test, errorCode);
156 
157     test = testBuilder.setArgument(host, jane.getAlias())
158                                 .setArgument(hostGender, "female")
159                                 .setArgument(guest, john.getAlias())
160                                 .setArgument(guestCount, (int64_t) 1)
161                                 .setExpected("Ms. Jane Doe invites Mr. John Doe to her party.")
162                                 .setExpectSuccess()
163                                 .build();
164     TestUtils::runTestCase(*this, test, errorCode);
165 
166     test = testBuilder.setArgument(host, john.getAlias())
167                                 .setArgument(hostGender, "male")
168                                 .setArgument(guest, jane.getAlias())
169                                 .setArgument(guestCount, (int64_t) 3)
170                                 .setExpected("Mr. John Doe invites 3 people, including Ms. Jane Doe, to his party.")
171                                 .setExpectSuccess()
172                                 .build();
173     TestUtils::runTestCase(*this, test, errorCode);
174 
175     test = testBuilder.setArgument(host, anonymous.getAlias())
176                                 .setArgument(hostGender, "unknown")
177                                 .setArgument(guest, jane.getAlias())
178                                 .setArgument(guestCount, (int64_t) 2)
179                                 .setExpected("Mx. Anonymous Doe invites Ms. Jane Doe and one other person to their party.")
180                                 .setExpectSuccess()
181                                 .build();
182     TestUtils::runTestCase(*this, test, errorCode);
183 }
184 
testCustomFunctions()185 void TestMessageFormat2::testCustomFunctions() {
186   IcuTestErrorCode errorCode(*this, "testCustomFunctions");
187 
188   testPersonFormatter(errorCode);
189   testCustomFunctionsComplexMessage(errorCode);
190   testGrammarCasesFormatter(errorCode);
191   testListFormatter(errorCode);
192   testMessageRefFormatter(errorCode);
193 }
194 
195 
196 // -------------- Custom function implementations
197 
createFormatter(const Locale & locale,UErrorCode & errorCode)198 Formatter* PersonNameFormatterFactory::createFormatter(const Locale& locale, UErrorCode& errorCode) {
199     if (U_FAILURE(errorCode)) {
200         return nullptr;
201     }
202 
203     // Locale not used
204     (void) locale;
205 
206     Formatter* result = new PersonNameFormatter();
207     if (result == nullptr) {
208         errorCode = U_MEMORY_ALLOCATION_ERROR;
209     }
210     return result;
211 }
212 
format(FormattedPlaceholder && arg,FunctionOptions && options,UErrorCode & errorCode) const213 message2::FormattedPlaceholder PersonNameFormatter::format(FormattedPlaceholder&& arg, FunctionOptions&& options, UErrorCode& errorCode) const {
214     if (U_FAILURE(errorCode)) {
215         return {};
216     }
217 
218     message2::FormattedPlaceholder errorVal = message2::FormattedPlaceholder("not a person");
219 
220     if (!arg.canFormat() || arg.asFormattable().getType() != UFMT_OBJECT) {
221         return errorVal;
222     }
223     const Formattable& toFormat = arg.asFormattable();
224 
225     FunctionOptionsMap opt = options.getOptions();
226     bool hasFormality = opt.count("formality") > 0 && opt["formality"].getType() == UFMT_STRING;
227     bool hasLength = opt.count("length") > 0 && opt["length"].getType() == UFMT_STRING;
228 
229     bool useFormal = hasFormality && opt["formality"].getString(errorCode) == "formal";
230     UnicodeString length = hasLength ? opt["length"].getString(errorCode) : "short";
231 
232     const FormattableObject* fp = toFormat.getObject(errorCode);
233     U_ASSERT(U_SUCCESS(errorCode));
234 
235     if (fp == nullptr || fp->tag() != u"person") {
236         return errorVal;
237     }
238     const Person* p = static_cast<const Person*>(fp);
239 
240     UnicodeString title = p->title;
241     UnicodeString firstName = p->firstName;
242     UnicodeString lastName = p->lastName;
243 
244     UnicodeString result;
245     if (length == "long") {
246         result += title;
247         result += " ";
248         result += firstName;
249         result += " ";
250         result += lastName;
251     } else if (length == "medium") {
252         if (useFormal) {
253             result += firstName;
254             result += " ";
255             result += lastName;
256         } else {
257             result += title;
258             result += " ";
259             result += firstName;
260         }
261     } else if (useFormal) {
262         // Default to "short" length
263         result += title;
264         result += " ";
265         result += lastName;
266     } else {
267         result += firstName;
268     }
269 
270     return FormattedPlaceholder(arg, FormattedValue(std::move(result)));
271 }
272 
~FormattableProperties()273 FormattableProperties::~FormattableProperties() {}
~Person()274 Person::~Person() {}
275 
276 /*
277   See ICU4J: CustomFormatterGrammarCaseTest.java
278 */
createFormatter(const Locale & locale,UErrorCode & errorCode)279 Formatter* GrammarCasesFormatterFactory::createFormatter(const Locale& locale, UErrorCode& errorCode) {
280     if (U_FAILURE(errorCode)) {
281         return nullptr;
282     }
283 
284     // Locale not used
285     (void) locale;
286 
287     Formatter* result = new GrammarCasesFormatter();
288     if (result == nullptr) {
289         errorCode = U_MEMORY_ALLOCATION_ERROR;
290     }
291     return result;
292 }
293 
294 
getDativeAndGenitive(const UnicodeString & value,UnicodeString & result) const295 /* static */ void GrammarCasesFormatter::getDativeAndGenitive(const UnicodeString& value, UnicodeString& result) const {
296     UnicodeString postfix;
297     if (value.endsWith("ana")) {
298         value.extract(0,  value.length() - 3, postfix);
299         postfix += "nei";
300     }
301     else if (value.endsWith("ca")) {
302         value.extract(0, value.length() - 2, postfix);
303         postfix += "căi";
304     }
305     else if (value.endsWith("ga")) {
306         value.extract(0, value.length() - 2, postfix);
307         postfix += "găi";
308     }
309     else if (value.endsWith("a")) {
310         value.extract(0, value.length() - 1, postfix);
311         postfix += "ei";
312     }
313     else {
314         postfix = "lui " + value;
315     }
316     result += postfix;
317 }
318 
format(FormattedPlaceholder && arg,FunctionOptions && options,UErrorCode & errorCode) const319 message2::FormattedPlaceholder GrammarCasesFormatter::format(FormattedPlaceholder&& arg, FunctionOptions&& options, UErrorCode& errorCode) const {
320     if (U_FAILURE(errorCode)) {
321         return {};
322     }
323 
324     // Argument must be present
325     if (!arg.canFormat()) {
326         errorCode = U_MF_FORMATTING_ERROR;
327         return message2::FormattedPlaceholder("grammarBB");
328     }
329 
330     // Assumes the argument is not-yet-formatted
331     const Formattable& toFormat = arg.asFormattable();
332     UnicodeString result;
333 
334     FunctionOptionsMap opt = options.getOptions();
335     switch (toFormat.getType()) {
336         case UFMT_STRING: {
337             const UnicodeString& in = toFormat.getString(errorCode);
338             bool hasCase = opt.count("case") > 0;
339             bool caseIsString = opt["case"].getType() == UFMT_STRING;
340             if (hasCase && caseIsString && (opt["case"].getString(errorCode) == "dative" || opt["case"].getString(errorCode) == "genitive")) {
341                 getDativeAndGenitive(in, result);
342             } else {
343                 result += in;
344             }
345             U_ASSERT(U_SUCCESS(errorCode));
346             break;
347         }
348         default: {
349             result += toFormat.getString(errorCode);
350             break;
351         }
352     }
353 
354     return message2::FormattedPlaceholder(arg, FormattedValue(std::move(result)));
355 }
356 
testGrammarCasesFormatter(IcuTestErrorCode & errorCode)357 void TestMessageFormat2::testGrammarCasesFormatter(IcuTestErrorCode& errorCode) {
358     CHECK_ERROR(errorCode);
359 
360     MFFunctionRegistry customRegistry = MFFunctionRegistry::Builder(errorCode)
361         .adoptFormatter(FunctionName("grammarBB"), new GrammarCasesFormatterFactory(), errorCode)
362         .build();
363 
364     TestCase::Builder testBuilder;
365     testBuilder.setName("testGrammarCasesFormatter - genitive");
366     testBuilder.setFunctionRegistry(&customRegistry);
367     testBuilder.setLocale(Locale("ro"));
368     testBuilder.setPattern("Cartea {$owner :grammarBB case=genitive}");
369     TestCase test = testBuilder.setArgument("owner", "Maria")
370                                 .setExpected("Cartea Mariei")
371                                 .build();
372     TestUtils::runTestCase(*this, test, errorCode);
373 
374     test = testBuilder.setArgument("owner", "Rodica")
375                                 .setExpected("Cartea Rodicăi")
376                                 .build();
377     TestUtils::runTestCase(*this, test, errorCode);
378 
379     test = testBuilder.setArgument("owner", "Ileana")
380                                 .setExpected("Cartea Ilenei")
381                                 .build();
382     TestUtils::runTestCase(*this, test, errorCode);
383 
384     test = testBuilder.setArgument("owner", "Petre")
385                                 .setExpected("Cartea lui Petre")
386                                 .build();
387     TestUtils::runTestCase(*this, test, errorCode);
388 
389     testBuilder.setName("testGrammarCasesFormatter - nominative");
390     testBuilder.setPattern("M-a sunat {$owner :grammarBB case=nominative}");
391 
392     test = testBuilder.setArgument("owner", "Maria")
393                                 .setExpected("M-a sunat Maria")
394                                 .build();
395     TestUtils::runTestCase(*this, test, errorCode);
396 
397     test = testBuilder.setArgument("owner", "Rodica")
398                                 .setExpected("M-a sunat Rodica")
399                                 .build();
400     TestUtils::runTestCase(*this, test, errorCode);
401 
402     test = testBuilder.setArgument("owner", "Ileana")
403                                 .setExpected("M-a sunat Ileana")
404                                 .build();
405     TestUtils::runTestCase(*this, test, errorCode);
406 
407     test = testBuilder.setArgument("owner", "Petre")
408                                 .setExpected("M-a sunat Petre")
409                                 .build();
410     TestUtils::runTestCase(*this, test, errorCode);
411 }
412 
413 /*
414   See ICU4J: CustomFormatterListTest.java
415 */
createFormatter(const Locale & locale,UErrorCode & errorCode)416 Formatter* ListFormatterFactory::createFormatter(const Locale& locale, UErrorCode& errorCode) {
417     if (U_FAILURE(errorCode)) {
418         return nullptr;
419     }
420 
421     Formatter* result = new ListFormatter(locale);
422     if (result == nullptr) {
423         errorCode = U_MEMORY_ALLOCATION_ERROR;
424     }
425     return result;
426 }
427 
format(FormattedPlaceholder && arg,FunctionOptions && options,UErrorCode & errorCode) const428 message2::FormattedPlaceholder message2::ListFormatter::format(FormattedPlaceholder&& arg, FunctionOptions&& options, UErrorCode& errorCode) const {
429     if (U_FAILURE(errorCode)) {
430         return {};
431     }
432 
433     message2::FormattedPlaceholder errorVal = FormattedPlaceholder("listformat");
434 
435     // Argument must be present
436     if (!arg.canFormat()) {
437         errorCode = U_MF_FORMATTING_ERROR;
438         return errorVal;
439     }
440     // Assumes arg is not-yet-formatted
441     const Formattable& toFormat = arg.asFormattable();
442 
443     FunctionOptionsMap opt = options.getOptions();
444     bool hasType = opt.count("type") > 0 && opt["type"].getType() == UFMT_STRING;
445     UListFormatterType type = UListFormatterType::ULISTFMT_TYPE_AND;
446     if (hasType) {
447         if (opt["type"].getString(errorCode) == "OR") {
448             type = UListFormatterType::ULISTFMT_TYPE_OR;
449         } else if (opt["type"].getString(errorCode) == "UNITS") {
450             type = UListFormatterType::ULISTFMT_TYPE_UNITS;
451         }
452     }
453     bool hasWidth = opt.count("width") > 0 && opt["width"].getType() == UFMT_STRING;
454     UListFormatterWidth width = UListFormatterWidth::ULISTFMT_WIDTH_WIDE;
455     if (hasWidth) {
456         if (opt["width"].getString(errorCode) == "SHORT") {
457             width = UListFormatterWidth::ULISTFMT_WIDTH_SHORT;
458         } else if (opt["width"].getString(errorCode) == "NARROW") {
459             width = UListFormatterWidth::ULISTFMT_WIDTH_NARROW;
460         }
461     }
462     U_ASSERT(U_SUCCESS(errorCode));
463     LocalPointer<icu::ListFormatter> lf(icu::ListFormatter::createInstance(locale, type, width, errorCode));
464     if (U_FAILURE(errorCode)) {
465         return {};
466     }
467 
468     UnicodeString result;
469 
470     switch (toFormat.getType()) {
471         case UFMT_ARRAY: {
472             int32_t n_items;
473             const Formattable* objs = toFormat.getArray(n_items, errorCode);
474             if (U_FAILURE(errorCode)) {
475                 errorCode = U_MF_FORMATTING_ERROR;
476                 return errorVal;
477             }
478             UnicodeString* parts = new UnicodeString[n_items];
479             if (parts == nullptr) {
480                 errorCode = U_MEMORY_ALLOCATION_ERROR;
481                 return {};
482             }
483             for (int32_t i = 0; i < n_items; i++) {
484                 parts[i] = objs[i].getString(errorCode);
485             }
486             U_ASSERT(U_SUCCESS(errorCode));
487             lf->format(parts, n_items, result, errorCode);
488             delete[] parts;
489             break;
490         }
491         default: {
492             result += toFormat.getString(errorCode);
493             U_ASSERT(U_SUCCESS(errorCode));
494             break;
495         }
496     }
497 
498     return FormattedPlaceholder(arg, FormattedValue(std::move(result)));
499 }
500 
testListFormatter(IcuTestErrorCode & errorCode)501 void TestMessageFormat2::testListFormatter(IcuTestErrorCode& errorCode) {
502     if (U_FAILURE(errorCode)) {
503         return;
504     }
505     const message2::Formattable progLanguages[3] = {
506         message2::Formattable("C/C++"),
507         message2::Formattable("Java"),
508         message2::Formattable("Python")
509     };
510 
511     TestCase::Builder testBuilder;
512 
513     MFFunctionRegistry reg = MFFunctionRegistry::Builder(errorCode)
514         .adoptFormatter(FunctionName("listformat"), new ListFormatterFactory(), errorCode)
515         .build();
516     CHECK_ERROR(errorCode);
517 
518     testBuilder.setFunctionRegistry(&reg);
519     testBuilder.setArgument("languages", progLanguages, 3);
520 
521     TestCase test = testBuilder.setName("testListFormatter")
522         .setPattern("I know {$languages :listformat type=AND}!")
523         .setExpected("I know C/C++, Java, and Python!")
524         .build();
525     TestUtils::runTestCase(*this, test, errorCode);
526 
527     test = testBuilder.setName("testListFormatter")
528                       .setPattern("You are allowed to use {$languages :listformat type=OR}!")
529                       .setExpected("You are allowed to use C/C++, Java, or Python!")
530                       .build();
531     TestUtils::runTestCase(*this, test, errorCode);
532 }
533 
534 /*
535   See ICU4J: CustomFormatterMessageRefTest.java
536 */
537 
properties(UErrorCode & errorCode)538 /* static */ Hashtable* message2::ResourceManager::properties(UErrorCode& errorCode) {
539     NULL_ON_ERROR(errorCode);
540 
541     UnicodeString* firefox = new UnicodeString(".match {$gcase :string}  genitive {{Firefoxin}}  * {{Firefox}}");
542     UnicodeString* chrome = new UnicodeString(".match {$gcase :string}  genitive {{Chromen}}  * {{Chrome}}");
543     UnicodeString* safari = new UnicodeString(".match {$gcase :string}  genitive {{Safarin}}  * {{Safari}}");
544 
545     if (firefox != nullptr && chrome != nullptr && safari != nullptr) {
546         Hashtable* result = new Hashtable(uhash_compareUnicodeString, nullptr, errorCode);
547         if (result == nullptr) {
548             return nullptr;
549         }
550         result->setValueDeleter(uprv_deleteUObject);
551         result->put("safari", safari, errorCode);
552         result->put("firefox", firefox, errorCode);
553         result->put("chrome", chrome, errorCode);
554         return result;
555     }
556 
557     // Allocation failed
558     errorCode = U_MEMORY_ALLOCATION_ERROR;
559     if (firefox != nullptr) {
560         delete firefox;
561     }
562     if (chrome != nullptr) {
563         delete chrome;
564     }
565     if (safari != nullptr) {
566         delete safari;
567     }
568     return nullptr;
569 }
570 
createFormatter(const Locale & locale,UErrorCode & errorCode)571 Formatter* ResourceManagerFactory::createFormatter(const Locale& locale, UErrorCode& errorCode) {
572     if (U_FAILURE(errorCode)) {
573         return nullptr;
574     }
575 
576     Formatter* result = new ResourceManager(locale);
577     if (result == nullptr) {
578         errorCode = U_MEMORY_ALLOCATION_ERROR;
579     }
580     return result;
581 }
582 
583 using Arguments = MessageArguments;
584 
localToGlobal(const FunctionOptionsMap & opts,UErrorCode & status)585 static Arguments localToGlobal(const FunctionOptionsMap& opts, UErrorCode& status) {
586     if (U_FAILURE(status)) {
587         return {};
588     }
589     return MessageArguments(opts, status);
590 }
591 
format(FormattedPlaceholder && arg,FunctionOptions && options,UErrorCode & errorCode) const592 message2::FormattedPlaceholder ResourceManager::format(FormattedPlaceholder&& arg, FunctionOptions&& options, UErrorCode& errorCode) const {
593     if (U_FAILURE(errorCode)) {
594         return {};
595     }
596 
597     message2::FormattedPlaceholder errorVal = message2::FormattedPlaceholder("msgref");
598 
599     // Argument must be present
600     if (!arg.canFormat()) {
601         errorCode = U_MF_FORMATTING_ERROR;
602         return errorVal;
603     }
604 
605     // Assumes arg is not-yet-formatted
606     const Formattable& toFormat = arg.asFormattable();
607     UnicodeString in;
608     switch (toFormat.getType()) {
609         case UFMT_STRING: {
610             in = toFormat.getString(errorCode);
611             break;
612         }
613         default: {
614             // Ignore non-strings
615             return errorVal;
616         }
617     }
618     FunctionOptionsMap opt = options.getOptions();
619     bool hasProperties = opt.count("resbundle") > 0 && opt["resbundle"].getType() == UFMT_OBJECT && opt["resbundle"].getObject(errorCode)->tag() == u"properties";
620 
621     // If properties were provided, look up the given string in the properties,
622     // yielding a message
623     if (hasProperties) {
624         const FormattableProperties* properties = reinterpret_cast<const FormattableProperties*>(opt["resbundle"].getObject(errorCode));
625         U_ASSERT(U_SUCCESS(errorCode));
626         UnicodeString* msg = static_cast<UnicodeString*>(properties->properties->get(in));
627         if (msg == nullptr) {
628             // No message given for this key -- error out
629             errorCode = U_MF_FORMATTING_ERROR;
630             return errorVal;
631         }
632 	MessageFormatter::Builder mfBuilder(errorCode);
633         UParseError parseErr;
634         // Any parse/data model errors will be propagated
635 	MessageFormatter mf = mfBuilder.setPattern(*msg, parseErr, errorCode).build(errorCode);
636         Arguments arguments = localToGlobal(opt, errorCode);
637         if (U_FAILURE(errorCode)) {
638             return errorVal;
639         }
640 
641         UErrorCode savedStatus = errorCode;
642         UnicodeString result = mf.formatToString(arguments, errorCode);
643         // Here, we want to ignore errors (this matches the behavior in the ICU4J test).
644         // For example: we want $gcase to default to "$gcase" if the gcase option was
645         // omitted.
646         if (U_FAILURE(errorCode)) {
647             errorCode = savedStatus;
648         }
649         return FormattedPlaceholder(arg, FormattedValue(std::move(result)));
650     } else {
651         // Properties must be provided
652         errorCode = U_MF_FORMATTING_ERROR;
653     }
654     return errorVal;
655 }
656 
657 
testMessageRefFormatter(IcuTestErrorCode & errorCode)658 void TestMessageFormat2::testMessageRefFormatter(IcuTestErrorCode& errorCode) {
659     CHECK_ERROR(errorCode);
660 
661     Hashtable* properties = ResourceManager::properties(errorCode);
662     CHECK_ERROR(errorCode);
663     LocalPointer<FormattableProperties> fProperties(new FormattableProperties(properties));
664     if (!fProperties.isValid()) {
665         ((UErrorCode&) errorCode) = U_MEMORY_ALLOCATION_ERROR;
666         return;
667     }
668     MFFunctionRegistry reg = MFFunctionRegistry::Builder(errorCode)
669         .adoptFormatter(FunctionName("msgRef"), new ResourceManagerFactory(), errorCode)
670         .build();
671     CHECK_ERROR(errorCode);
672 
673     TestCase::Builder testBuilder;
674     testBuilder.setLocale(Locale("ro"));
675     testBuilder.setFunctionRegistry(&reg);
676     testBuilder.setPattern(*((UnicodeString*) properties->get("firefox")));
677     testBuilder.setName("message-ref");
678 
679     TestCase test = testBuilder.setArgument("gcase", "whatever")
680                                 .setExpected("Firefox")
681                                 .build();
682     TestUtils::runTestCase(*this, test, errorCode);
683     test = testBuilder.setArgument("gcase", "genitive")
684                                 .setExpected("Firefoxin")
685                                 .build();
686     TestUtils::runTestCase(*this, test, errorCode);
687 
688     testBuilder.setPattern(*((UnicodeString*) properties->get("chrome")));
689 
690     test = testBuilder.setArgument("gcase", "whatever")
691                                 .setExpected("Chrome")
692                                 .build();
693     TestUtils::runTestCase(*this, test, errorCode);
694     test = testBuilder.setArgument("gcase", "genitive")
695                                 .setExpected("Chromen")
696                                 .build();
697     TestUtils::runTestCase(*this, test, errorCode);
698 
699     testBuilder.setArgument("res", fProperties.getAlias());
700 
701     testBuilder.setPattern("Please start {$browser :msgRef gcase=genitive resbundle=$res}");
702     test = testBuilder.setArgument("browser", "firefox")
703                                 .setExpected("Please start Firefoxin")
704                                 .build();
705     TestUtils::runTestCase(*this, test, errorCode);
706     test = testBuilder.setArgument("browser", "chrome")
707                                 .setExpected("Please start Chromen")
708                                 .build();
709     TestUtils::runTestCase(*this, test, errorCode);
710     test = testBuilder.setArgument("browser", "safari")
711                                 .setExpected("Please start Safarin")
712                                 .build();
713     TestUtils::runTestCase(*this, test, errorCode);
714 
715     testBuilder.setPattern("Please start {$browser :msgRef resbundle=$res}");
716     test = testBuilder.setArgument("browser", "firefox")
717                                 .setExpected("Please start Firefox")
718                                 .build();
719     TestUtils::runTestCase(*this, test, errorCode);
720     test = testBuilder.setArgument("browser", "chrome")
721                                 .setExpected("Please start Chrome")
722                                 .build();
723     TestUtils::runTestCase(*this, test, errorCode);
724     test = testBuilder.setArgument("browser", "safari")
725                                 .setExpected("Please start Safari")
726                                 .build();
727     TestUtils::runTestCase(*this, test, errorCode);
728 }
729 
730 #endif /* #if !UCONFIG_NO_MF2 */
731 
732 #endif /* #if !UCONFIG_NO_FORMATTING */
733