+// test RBNF extensions to message format
+void TestMessageFormat::TestRBNF(void) {
+ // WARNING: this depends on the RBNF formats for en_US
+ Locale locale("en", "US", "");
+
+ UErrorCode ec = U_ZERO_ERROR;
+
+ UnicodeString values[] = {
+ // decimal values do not format completely for ordinal or duration, and
+ // do not always parse, so do not include them
+ "0", "1", "12", "100", "123", "1001", "123,456", "-17",
+ };
+ int32_t values_count = sizeof(values)/sizeof(values[0]);
+
+ UnicodeString formats[] = {
+ "There are {0,spellout} files to search.",
+ "There are {0,spellout,%simplified} files to search.",
+ "The bogus spellout {0,spellout,%BOGUS} files behaves like the default.",
+ "This is the {0,ordinal} file to search.", // TODO fix bug, ordinal does not parse
+ "Searching this file will take {0,duration} to complete.",
+ "Searching this file will take {0,duration,%with-words} to complete.",
+ };
+ int32_t formats_count = sizeof(formats)/sizeof(formats[0]);
+
+ Formattable args[1];
+
+ NumberFormat* numFmt = NumberFormat::createInstance(locale, ec);
+ if (U_FAILURE(ec)) {
+ dataerrln("Error calling NumberFormat::createInstance()");
+ return;
+ }
+
+ for (int i = 0; i < formats_count; ++i) {
+ MessageFormat* fmt = new MessageFormat(formats[i], locale, ec);
+ logln((UnicodeString)"Testing format pattern: '" + formats[i] + "'");
+
+ for (int j = 0; j < values_count; ++j) {
+ ec = U_ZERO_ERROR;
+ numFmt->parse(values[j], args[0], ec);
+ if (U_FAILURE(ec)) {
+ errln((UnicodeString)"Failed to parse test argument " + values[j]);
+ } else {
+ FieldPosition fp(0);
+ UnicodeString result;
+ fmt->format(args, 1, result, fp, ec);
+ logln((UnicodeString)"value: " + toString(args[0]) + " --> " + result + UnicodeString(" ec: ") + u_errorName(ec));
+
+ if (i != 3) { // TODO: fix this, for now skip ordinal parsing (format string at index 3)
+ int32_t count = 0;
+ Formattable* parseResult = fmt->parse(result, count, ec);
+ if (count != 1) {
+ errln((UnicodeString)"parse returned " + count + " args");
+ } else if (parseResult[0] != args[0]) {
+ errln((UnicodeString)"parsed argument " + toString(parseResult[0]) + " != " + toString(args[0]));
+ }
+ delete []parseResult;
+ }
+ }
+ }
+ delete fmt;
+ }
+ delete numFmt;
+}
+
+UnicodeString TestMessageFormat::GetPatternAndSkipSyntax(const MessagePattern& pattern) {
+ UnicodeString us(pattern.getPatternString());
+ int count = pattern.countParts();
+ for (int i = count; i > 0;) {
+ const MessagePattern::Part& part = pattern.getPart(--i);
+ if (part.getType() == UMSGPAT_PART_TYPE_SKIP_SYNTAX) {
+ us.remove(part.getIndex(), part.getLimit() - part.getIndex());
+ }
+ }
+ return us;
+}
+
+void TestMessageFormat::TestApostropheMode() {
+ UErrorCode status = U_ZERO_ERROR;
+ MessagePattern *ado_mp = new MessagePattern(UMSGPAT_APOS_DOUBLE_OPTIONAL, status);
+ MessagePattern *adr_mp = new MessagePattern(UMSGPAT_APOS_DOUBLE_REQUIRED, status);
+ if (ado_mp->getApostropheMode() != UMSGPAT_APOS_DOUBLE_OPTIONAL) {
+ errln("wrong value from ado_mp->getApostropheMode().");
+ }
+ if (adr_mp->getApostropheMode() != UMSGPAT_APOS_DOUBLE_REQUIRED) {
+ errln("wrong value from adr_mp->getApostropheMode().");
+ }
+
+
+ UnicodeString tuples[] = {
+ // Desired output
+ // DOUBLE_OPTIONAL pattern
+ // DOUBLE_REQUIRED pattern (empty=same as DOUBLE_OPTIONAL)
+ "I see {many}", "I see '{many}'", "",
+ "I said {'Wow!'}", "I said '{''Wow!''}'", "",
+ "I dont know", "I dont know", "I don't know",
+ "I don't know", "I don't know", "I don''t know",
+ "I don't know", "I don''t know", "I don''t know"
+ };
+ int32_t tuples_count = UPRV_LENGTHOF(tuples);
+
+ for (int i = 0; i < tuples_count; i += 3) {
+ UnicodeString& desired = tuples[i];
+ UnicodeString& ado_pattern = tuples[i + 1];
+ UErrorCode status = U_ZERO_ERROR;
+ assertEquals("DOUBLE_OPTIONAL failure",
+ desired,
+ GetPatternAndSkipSyntax(ado_mp->parse(ado_pattern, NULL, status)));
+ UnicodeString& adr_pattern = tuples[i + 2].isEmpty() ? ado_pattern : tuples[i + 2];
+ assertEquals("DOUBLE_REQUIRED failure", desired,
+ GetPatternAndSkipSyntax(adr_mp->parse(adr_pattern, NULL, status)));
+ }
+ delete adr_mp;
+ delete ado_mp;
+}
+
+
+// Compare behavior of DOUBLE_OPTIONAL (new default) and DOUBLE_REQUIRED JDK-compatibility mode.
+void TestMessageFormat::TestCompatibleApostrophe() {
+ // Message with choice argument which does not contain another argument.
+ // The JDK performs only one apostrophe-quoting pass on this pattern.
+ UnicodeString pattern = "ab{0,choice,0#1'2''3'''4''''.}yz";
+
+ UErrorCode ec = U_ZERO_ERROR;
+ MessageFormat compMsg("", Locale::getUS(), ec);
+ compMsg.applyPattern(pattern, UMSGPAT_APOS_DOUBLE_REQUIRED, NULL, ec);
+ if (compMsg.getApostropheMode() != UMSGPAT_APOS_DOUBLE_REQUIRED) {
+ errln("wrong value from compMsg.getApostropheMode().");
+ }
+
+ MessageFormat icuMsg("", Locale::getUS(), ec);
+ icuMsg.applyPattern(pattern, UMSGPAT_APOS_DOUBLE_OPTIONAL, NULL, ec);
+ if (icuMsg.getApostropheMode() != UMSGPAT_APOS_DOUBLE_OPTIONAL) {
+ errln("wrong value from icuMsg.getApostropheMode().");
+ }
+
+ Formattable zero0[] = { (int32_t)0 };
+ FieldPosition fieldpos(0);
+ UnicodeString buffer1, buffer2;
+ assertEquals("incompatible ICU MessageFormat compatibility-apostrophe behavior",
+ "ab12'3'4''.yz",
+ compMsg.format(zero0, 1, buffer1, fieldpos, ec));
+ assertEquals("unexpected ICU MessageFormat double-apostrophe-optional behavior",
+ "ab1'2'3''4''.yz",
+ icuMsg.format(zero0, 1, buffer2, fieldpos, ec));
+
+ // Message with choice argument which contains a nested simple argument.
+ // The DOUBLE_REQUIRED version performs two apostrophe-quoting passes.
+ buffer1.remove();
+ buffer2.remove();
+ pattern = "ab{0,choice,0#1'2''3'''4''''.{0,number,'#x'}}yz";
+ compMsg.applyPattern(pattern, ec);
+ icuMsg.applyPattern(pattern, ec);
+ if (U_FAILURE(ec)) {
+ dataerrln("Unable to applyPattern - %s", u_errorName(ec));
+ } else {
+ assertEquals("incompatible ICU MessageFormat compatibility-apostrophe behavior",
+ "ab1234'.0xyz",
+ compMsg.format(zero0, 1, buffer1, fieldpos, ec));
+ assertEquals("unexpected ICU MessageFormat double-apostrophe-optional behavior",
+ "ab1'2'3''4''.#x0yz",
+ icuMsg.format(zero0, 1, buffer2, fieldpos, ec));
+ }
+
+ // This part is copied over from Java tests but cannot be properly tested here
+ // because we do not have a live reference implementation with JDK behavior.
+ // The JDK ChoiceFormat itself always performs one apostrophe-quoting pass.
+ /*
+ ChoiceFormat choice = new ChoiceFormat("0#1'2''3'''4''''.");
+ assertEquals("unexpected JDK ChoiceFormat apostrophe behavior",
+ "12'3'4''.",
+ choice.format(0));
+ choice.applyPattern("0#1'2''3'''4''''.{0,number,'#x'}");
+ assertEquals("unexpected JDK ChoiceFormat apostrophe behavior",
+ "12'3'4''.{0,number,#x}",
+ choice.format(0));
+ */
+}
+
+void TestMessageFormat::testAutoQuoteApostrophe(void) {
+ const char* patterns[] = { // pattern, expected pattern
+ "'", "''",
+ "''", "''",
+ "'{", "'{'",
+ "' {", "'' {",
+ "'a", "''a",
+ "'{'a", "'{'a",
+ "'{a'", "'{a'",
+ "'{}", "'{}'",
+ "{'", "{'",
+ "{'a", "{'a",
+ "{'a{}'a}'a", "{'a{}'a}''a",
+ "'}'", "'}'",
+ "'} '{'}'", "'} '{'}''",
+ "'} {{{''", "'} {{{'''",
+ };
+ int32_t pattern_count = sizeof(patterns)/sizeof(patterns[0]);
+
+ for (int i = 0; i < pattern_count; i += 2) {
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString result = MessageFormat::autoQuoteApostrophe(patterns[i], status);
+ UnicodeString target(patterns[i+1]);
+ if (target != result) {
+ const int BUF2_LEN = 64;
+ char buf[256];
+ char buf2[BUF2_LEN];
+ int32_t len = result.extract(0, result.length(), buf2, BUF2_LEN);
+ if (len >= BUF2_LEN) {
+ buf2[BUF2_LEN-1] = 0;
+ }
+ sprintf(buf, "[%2d] test \"%s\": target (\"%s\") != result (\"%s\")\n", i/2, patterns[i], patterns[i+1], buf2);
+ errln(buf);
+ }
+ }
+}
+
+void TestMessageFormat::testCoverage(void) {
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString testformat("{argument, plural, one{C''est # fichier} other {Ce sont # fichiers}} dans la liste.");
+ MessageFormat *msgfmt = new MessageFormat(testformat, Locale("fr"), status);
+ if (msgfmt == NULL || U_FAILURE(status)) {
+ dataerrln("FAIL: Unable to create MessageFormat.: %s", u_errorName(status));
+ return;
+ }
+ if (!msgfmt->usesNamedArguments()) {
+ errln("FAIL: Unable to detect usage of named arguments.");
+ }
+ const double limit[] = {0.0, 1.0, 2.0};
+ const UnicodeString formats[] = {"0.0<=Arg<1.0",
+ "1.0<=Arg<2.0",
+ "2.0<-Arg"};
+ ChoiceFormat cf(limit, formats, 3);
+
+ msgfmt->setFormat("set", cf, status);
+
+ StringEnumeration *en = msgfmt->getFormatNames(status);
+ if (en == NULL || U_FAILURE(status)) {
+ errln("FAIL: Unable to get format names enumeration.");
+ } else {
+ int32_t count = 0;
+ en->reset(status);
+ count = en->count(status);
+ if (U_FAILURE(status)) {
+ errln("FAIL: Unable to get format name enumeration count.");
+ } else {
+ for (int32_t i = 0; i < count; i++) {
+ en->snext(status);
+ if (U_FAILURE(status)) {
+ errln("FAIL: Error enumerating through names.");
+ break;
+ }
+ }
+ }
+ }
+
+ // adoptFormat() takes ownership of the input Format object.
+ // We need to clone the stack-allocated cf so that we do not attempt to delete cf.
+ Format *cfClone = cf.clone();
+ msgfmt->adoptFormat("adopt", cfClone, status);
+
+ delete en;
+ delete msgfmt;
+
+ msgfmt = new MessageFormat("'", status);
+ if (msgfmt == NULL || U_FAILURE(status)) {
+ errln("FAIL: Unable to create MessageFormat.");
+ return;
+ }
+ if (msgfmt->usesNamedArguments()) {
+ errln("FAIL: Unable to detect usage of named arguments.");
+ }
+
+ // Starting with ICU 4.8, we support setFormat(name, ...) and getFormatNames()
+ // on a MessageFormat without named arguments.
+ msgfmt->setFormat("formatName", cf, status);
+ if (U_FAILURE(status)) {
+ errln("FAIL: Should work to setFormat(name, ...) regardless of pattern.");
+ }
+ status = U_ZERO_ERROR;
+ en = msgfmt->getFormatNames(status);
+ if (U_FAILURE(status)) {
+ errln("FAIL: Should work to get format names enumeration regardless of pattern.");
+ }
+
+ delete en;
+ delete msgfmt;
+}
+
+void TestMessageFormat::testGetFormatNames() {
+ IcuTestErrorCode errorCode(*this, "testGetFormatNames");
+ MessageFormat msgfmt("Hello, {alice,number} {oops,date,full} {zip,spellout} World.", Locale::getRoot(), errorCode);
+ if(errorCode.logDataIfFailureAndReset("MessageFormat() failed")) {
+ return;
+ }
+ LocalPointer<StringEnumeration> names(msgfmt.getFormatNames(errorCode));
+ if(errorCode.logIfFailureAndReset("msgfmt.getFormatNames() failed")) {
+ return;
+ }
+ const UnicodeString *name;
+ name = names->snext(errorCode);
+ if (name == NULL || errorCode.isFailure()) {
+ errln("msgfmt.getFormatNames()[0] failed: %s", errorCode.errorName());
+ errorCode.reset();
+ return;
+ }
+ if (!assertEquals("msgfmt.getFormatNames()[0]", UNICODE_STRING_SIMPLE("alice"), *name)) {
+ return;
+ }
+ name = names->snext(errorCode);
+ if (name == NULL || errorCode.isFailure()) {
+ errln("msgfmt.getFormatNames()[1] failed: %s", errorCode.errorName());
+ errorCode.reset();
+ return;
+ }
+ if (!assertEquals("msgfmt.getFormatNames()[1]", UNICODE_STRING_SIMPLE("oops"), *name)) {
+ return;
+ }
+ name = names->snext(errorCode);
+ if (name == NULL || errorCode.isFailure()) {
+ errln("msgfmt.getFormatNames()[2] failed: %s", errorCode.errorName());
+ errorCode.reset();
+ return;
+ }
+ if (!assertEquals("msgfmt.getFormatNames()[2]", UNICODE_STRING_SIMPLE("zip"), *name)) {
+ return;
+ }
+ name = names->snext(errorCode);
+ if (name != NULL) {
+ errln(UnicodeString("msgfmt.getFormatNames()[3] should be NULL but is: ") + *name);
+ return;
+ }
+}
+
+void TestMessageFormat::TestTrimArgumentName() {
+ // ICU 4.8 allows and ignores white space around argument names and numbers.
+ IcuTestErrorCode errorCode(*this, "TestTrimArgumentName");
+ MessageFormat m("a { 0 , number , '#,#'#.0 } z", Locale::getEnglish(), errorCode);
+ if (errorCode.logDataIfFailureAndReset("Unable to instantiate MessageFormat")) {
+ return;
+ }
+ Formattable args[1] = { (int32_t)2 };
+ FieldPosition ignore(0);
+ UnicodeString result;
+ assertEquals("trim-numbered-arg format() failed", "a #,#2.0 z",
+ m.format(args, 1, result, ignore, errorCode));
+
+ m.applyPattern("x { _oOo_ , number , integer } y", errorCode);
+ UnicodeString argName = UNICODE_STRING_SIMPLE("_oOo_");
+ args[0].setLong(3);
+ result.remove();
+ assertEquals("trim-named-arg format() failed", "x 3 y",
+ m.format(&argName, args, 1, result, errorCode));
+}
+
+void TestMessageFormat::TestSelectOrdinal() {
+ IcuTestErrorCode errorCode(*this, "TestSelectOrdinal");
+ // Test plural & ordinal together,
+ // to make sure that we get the correct cached PluralSelector for each.
+ MessageFormat m(
+ "{0,plural,one{1 file}other{# files}}, "
+ "{0,selectordinal,one{#st file}two{#nd file}few{#rd file}other{#th file}}",
+ Locale::getEnglish(), errorCode);
+ if (errorCode.logDataIfFailureAndReset("Unable to instantiate MessageFormat")) {
+ return;
+ }
+ Formattable args[1] = { (int32_t)21 };
+ FieldPosition ignore(0);
+ UnicodeString result;
+ assertEquals("plural-and-ordinal format(21) failed", "21 files, 21st file",
+ m.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0].setLong(2);
+ assertEquals("plural-and-ordinal format(2) failed", "2 files, 2nd file",
+ m.format(args, 1, result.remove(), ignore, errorCode), TRUE);
+
+ args[0].setLong(1);
+ assertEquals("plural-and-ordinal format(1) failed", "1 file, 1st file",
+ m.format(args, 1, result.remove(), ignore, errorCode), TRUE);
+
+ args[0].setLong(3);
+ assertEquals("plural-and-ordinal format(3) failed", "3 files, 3rd file",
+ m.format(args, 1, result.remove(), ignore, errorCode), TRUE);
+
+ errorCode.logDataIfFailureAndReset("");
+}
+
+void TestMessageFormat::TestDecimals() {
+ IcuTestErrorCode errorCode(*this, "TestDecimals");
+ // Simple number replacement.
+ MessageFormat m(
+ "{0,plural,one{one meter}other{# meters}}",
+ Locale::getEnglish(), errorCode);
+ Formattable args[1] = { (int32_t)1 };
+ FieldPosition ignore;
+ UnicodeString result;
+ assertEquals("simple format(1)", "one meter",
+ m.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (double)1.5;
+ result.remove();
+ assertEquals("simple format(1.5)", "1.5 meters",
+ m.format(args, 1, result, ignore, errorCode), TRUE);
+
+ // Simple but explicit.
+ MessageFormat m0(
+ "{0,plural,one{one meter}other{{0} meters}}",
+ Locale::getEnglish(), errorCode);
+ args[0] = (int32_t)1;
+ result.remove();
+ assertEquals("explicit format(1)", "one meter",
+ m0.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (double)1.5;
+ result.remove();
+ assertEquals("explicit format(1.5)", "1.5 meters",
+ m0.format(args, 1, result, ignore, errorCode), TRUE);
+
+ // With offset and specific simple format with optional decimals.
+ MessageFormat m1(
+ "{0,plural,offset:1 one{another meter}other{{0,number,00.#} meters}}",
+ Locale::getEnglish(), errorCode);
+ args[0] = (int32_t)1;
+ result.remove();
+ assertEquals("offset format(1)", "01 meters",
+ m1.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (int32_t)2;
+ result.remove();
+ assertEquals("offset format(1)", "another meter",
+ m1.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (double)2.5;
+ result.remove();
+ assertEquals("offset format(1)", "02.5 meters",
+ m1.format(args, 1, result, ignore, errorCode), TRUE);
+
+ // With offset and specific simple format with forced decimals.
+ MessageFormat m2(
+ "{0,plural,offset:1 one{another meter}other{{0,number,0.0} meters}}",
+ Locale::getEnglish(), errorCode);
+ args[0] = (int32_t)1;
+ result.remove();
+ assertEquals("offset-decimals format(1)", "1.0 meters",
+ m2.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (int32_t)2;
+ result.remove();
+ assertEquals("offset-decimals format(1)", "2.0 meters",
+ m2.format(args, 1, result, ignore, errorCode), TRUE);
+
+ args[0] = (double)2.5;
+ result.remove();
+ assertEquals("offset-decimals format(1)", "2.5 meters",
+ m2.format(args, 1, result, ignore, errorCode), TRUE);
+ errorCode.reset();
+}
+