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(®);
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(®);
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