/********************************************************************
* COPYRIGHT:
- * Copyright (c) 1997-2010, International Business Machines Corporation and
+ * Copyright (c) 1997-2016, International Business Machines Corporation and
* others. All Rights Reserved.
********************************************************************
* File TMSGFMT.CPP
#if !UCONFIG_NO_FORMATTING
#include "tmsgfmt.h"
+#include "cmemory.h"
#include "unicode/format.h"
#include "unicode/decimfmt.h"
+#include "unicode/localpointer.h"
#include "unicode/locid.h"
#include "unicode/msgfmt.h"
#include "unicode/numfmt.h"
#include "unicode/choicfmt.h"
+#include "unicode/messagepattern.h"
#include "unicode/selfmt.h"
#include "unicode/gregocal.h"
#include <stdio.h>
-#define E_WITH_ACUTE ((char)0x00E9)
-static const char E_ACCENTED[]={E_WITH_ACUTE,0};
-
void
TestMessageFormat::runIndexedTest(int32_t index, UBool exec,
const char* &name, char* /*par*/) {
- switch (index) {
- TESTCASE(0,testBug1);
- TESTCASE(1,testBug2);
- TESTCASE(2,sample);
- TESTCASE(3,PatternTest);
- TESTCASE(4,testStaticFormat);
- TESTCASE(5,testSimpleFormat);
- TESTCASE(6,testMsgFormatChoice);
- TESTCASE(7,testCopyConstructor);
- TESTCASE(8,testAssignment);
- TESTCASE(9,testClone);
- TESTCASE(10,testEquals);
- TESTCASE(11,testNotEquals);
- TESTCASE(12,testSetLocale);
- TESTCASE(13,testFormat);
- TESTCASE(14,testParse);
- TESTCASE(15,testAdopt);
- TESTCASE(16,testCopyConstructor2);
- TESTCASE(17,TestUnlimitedArgsAndSubformats);
- TESTCASE(18,TestRBNF);
- TESTCASE(19,TestTurkishCasing);
- TESTCASE(20,testAutoQuoteApostrophe);
- TESTCASE(21,testMsgFormatPlural);
- TESTCASE(22,testCoverage);
- TESTCASE(23,testMsgFormatSelect);
- default: name = ""; break;
- }
+ TESTCASE_AUTO_BEGIN;
+ TESTCASE_AUTO(testBug1);
+ TESTCASE_AUTO(testBug2);
+ TESTCASE_AUTO(sample);
+ TESTCASE_AUTO(PatternTest);
+ TESTCASE_AUTO(testStaticFormat);
+ TESTCASE_AUTO(testSimpleFormat);
+ TESTCASE_AUTO(testMsgFormatChoice);
+ TESTCASE_AUTO(testCopyConstructor);
+ TESTCASE_AUTO(testAssignment);
+ TESTCASE_AUTO(testClone);
+ TESTCASE_AUTO(testEquals);
+ TESTCASE_AUTO(testNotEquals);
+ TESTCASE_AUTO(testSetLocale);
+ TESTCASE_AUTO(testFormat);
+ TESTCASE_AUTO(testParse);
+ TESTCASE_AUTO(testAdopt);
+ TESTCASE_AUTO(testCopyConstructor2);
+ TESTCASE_AUTO(TestUnlimitedArgsAndSubformats);
+ TESTCASE_AUTO(TestRBNF);
+ TESTCASE_AUTO(TestTurkishCasing);
+ TESTCASE_AUTO(testAutoQuoteApostrophe);
+ TESTCASE_AUTO(testMsgFormatPlural);
+ TESTCASE_AUTO(testMsgFormatSelect);
+ TESTCASE_AUTO(testApostropheInPluralAndSelect);
+ TESTCASE_AUTO(TestApostropheMode);
+ TESTCASE_AUTO(TestCompatibleApostrophe);
+ TESTCASE_AUTO(testCoverage);
+ TESTCASE_AUTO(testGetFormatNames);
+ TESTCASE_AUTO(TestTrimArgumentName);
+ TESTCASE_AUTO(TestSelectOrdinal);
+ TESTCASE_AUTO(TestDecimals);
+ TESTCASE_AUTO(TestArgIsPrefixOfAnother);
+ TESTCASE_AUTO_END;
}
void TestMessageFormat::testBug3()
logln("The input pattern : " + pattern);
MessageFormat *fmt = new MessageFormat(pattern, status);
if (U_FAILURE(status)) {
- errln("MessageFormat pattern creation failed.");
+ dataerrln("MessageFormat pattern creation failed. - %s", u_errorName(status));
return;
}
logln("The output pattern is : " + fmt->toPattern(result));
}
#if 0
-#if defined(_DEBUG) && U_IOSTREAM_SOURCE!=0
+#if defined(_DEBUG) && U_IOSTREAM_SOURCE >= 199711
//----------------------------------------------------
// console I/O
//----------------------------------------------------
-#if U_IOSTREAM_SOURCE >= 199711
-# include <iostream>
- std::ostream& operator<<(std::ostream& stream, const Formattable& obj);
-#elif U_IOSTREAM_SOURCE >= 198506
-# include <iostream.h>
- ostream& operator<<(ostream& stream, const Formattable& obj);
-#endif
+#include <iostream>
+std::ostream& operator<<(std::ostream& stream, const Formattable& obj);
#include "unicode/datefmt.h"
#include <stdlib.h>
}
return stream;
}
-#endif /* defined(_DEBUG) && U_IOSTREAM_SOURCE!=0 */
+#endif /* defined(_DEBUG) && U_IOSTREAM_SOURCE >= 199711 */
#endif
void TestMessageFormat::PatternTest()
"'{1,number,#,##}' {1,number,#,##}",
};
- UnicodeString testResultPatterns[] = {
+ // ICU 4.8 returns the original pattern (testCases),
+ // rather than toPattern() reconstituting a new, equivalent pattern string (testResultPatterns).
+ /*UnicodeString testResultPatterns[] = {
"Quotes '', '{', a {0} '{'0}",
"Quotes '', '{', a {0,number} '{'0}",
"'{'1,number,#,##} {1,number,'#'#,##}",
"'{'1,date,full}, {1,date,full},",
"'{'3,date,full}, {3,date,full},",
"'{'1,number,#,##} {1,number,#,##}"
- };
+ };*/
UnicodeString testResultStrings[] = {
- "Quotes ', {, a 1 {0}",
- "Quotes ', {, a 1 {0}",
- "{1,number,#,##} #34,56",
- "There are 3,456 files on Disk at 1/12/70 5:46 AM.",
+ "Quotes ', {, 'a' 1 {0}",
+ "Quotes ', {, 'a' 1 {0}",
+ "{1,number,'#',##} #34,56",
+ "There are 3,456 files on Disk at 1/12/70, 5:46 AM.",
"On Disk, there are 3,456 files, with $1.00.",
"{1,number,percent}, 345,600%,",
"{1,date,full}, Wednesday, December 31, 1969,",
logln(((UnicodeString)"MessageFormat for ") + testCases[i] + " creation failed.\n");
continue;
}
- if (form->toPattern(buffer) != testResultPatterns[i]) {
+ // ICU 4.8 returns the original pattern (testCases),
+ // rather than toPattern() reconstituting a new, equivalent pattern string (testResultPatterns).
+ if (form->toPattern(buffer) != testCases[i]) {
+ // Note: An alternative test would be to build MessagePattern objects for
+ // both the input and output patterns and compare them, taking SKIP_SYNTAX etc.
+ // into account.
+ // (Too much trouble...)
errln(UnicodeString("TestMessageFormat::PatternTest failed test #2, i = ") + i);
//form->toPattern(buffer);
errln(((UnicodeString)" Orig: ") + testCases[i]);
- errln(((UnicodeString)" Exp: ") + testResultPatterns[i]);
+ errln(((UnicodeString)" Exp: ") + testCases[i]);
errln(((UnicodeString)" Got: ") + buffer);
}
logln(UnicodeString(" Result: ") + result );
logln(UnicodeString(" Expected: ") + testResultStrings[i] );
}
-
+
//it_out << "Result: " << result);
#if 0
}
const UnicodeString expected(
- "At 12:20:00 on 08.08.1997, there was a disturbance in the Force on planet 7.", "");
+ "At 12:20:00 on 8.08.1997, there was a disturbance in the Force on planet 7.", "");
if (result != expected) {
errln("TestTurkishCasing failed on test");
errln( UnicodeString(" Result: ") + result );
UnicodeString t2("{argument, plural, one{C''est # fichier} other {Ce sont # fichiers}} dans la liste.");
UnicodeString t3("There {0, plural, one{is # zavod}few{are {0, number,###.0} zavoda} other{are # zavodov}} in the directory.");
UnicodeString t4("There {argument, plural, one{is # zavod}few{are {argument, number,###.0} zavoda} other{are #zavodov}} in the directory.");
- UnicodeString t5("{0, plural, one {{0, number,C''''est #,##0.0# fichier}} other {Ce sont # fichiers}} dans la liste.");
+ UnicodeString t5("{0, plural, one {{0, number,C''est #,##0.0# fichier}} other {Ce sont # fichiers}} dans la liste.");
MessageFormat* mfNum = new MessageFormat(t1, Locale("fr"), err);
if (U_FAILURE(err)) {
dataerrln("TestMessageFormat::testMsgFormatPlural #1 - argumentIndex - %s", u_errorName(err));
UnicodeString argNameResult;
mfAlpha->format(argName, &testArgs1, 1, argNameResult, err);
if (U_FAILURE(err)) {
- errln("TestMessageFormat::testMsgFormatPlural #1 - argumentName");
+ dataerrln("TestMessageFormat::testMsgFormatPlural #1 - argumentName - %s", u_errorName(err));
logln(UnicodeString("TestMessageFormat::testMsgFormatPlural #1 with error code ")+(int32_t)err);
delete mfNum;
return;
delete mfNum;
delete mfAlpha;
- MessageFormat* mfNum2 = new MessageFormat(t3, Locale("ru"), err);
+ MessageFormat* mfNum2 = new MessageFormat(t3, Locale("uk"), err);
numResult1.remove();
Formattable testArgs2((int32_t)4);
mfNum2->format(&testArgs2, 1, numResult1, ignore, err);
- MessageFormat* mfAlpha2 = new MessageFormat(t4, Locale("ru"), err);
+ MessageFormat* mfAlpha2 = new MessageFormat(t4, Locale("uk"), err);
argNameResult.remove();
mfAlpha2->format(argName, &testArgs2, 1, argNameResult, err);
errln("TestMessageFormat::test nested PluralFormat with argumentName");
}
if ( argNameResult!= UnicodeString("C'est 0,0 fichier dans la liste.")) {
- errln(UnicodeString("TestMessageFormat::test nested named PluralFormat."));
+ errln(UnicodeString("TestMessageFormat::test nested named PluralFormat: ") + argNameResult);
logln(UnicodeString("The unexpected nested named PluralFormat."));
}
delete msgFmt;
}
+void TestMessageFormat::testApostropheInPluralAndSelect() {
+ UErrorCode errorCode = U_ZERO_ERROR;
+ MessageFormat msgFmt(UNICODE_STRING_SIMPLE(
+ "abc_{0,plural,other{#'#'#'{'#''}}_def_{1,select,other{sel'}'ect''}}_xyz"),
+ Locale::getEnglish(),
+ errorCode);
+ if (U_FAILURE(errorCode)) {
+ errln("MessageFormat constructor failed - %s\n", u_errorName(errorCode));
+ return;
+ }
+ UnicodeString expected = UNICODE_STRING_SIMPLE("abc_3#3{3'_def_sel}ect'_xyz");
+ Formattable args[] = { (int32_t)3, UNICODE_STRING_SIMPLE("x") };
+ internalFormat(
+ &msgFmt, args, 2, expected,
+ "MessageFormat with apostrophes in plural/select arguments failed:\n");
+}
+
void TestMessageFormat::internalFormat(MessageFormat* msgFmt ,
Formattable* args , int32_t numOfArgs ,
- UnicodeString expected ,char* errMsg)
+ UnicodeString expected, const char* errMsg)
{
UnicodeString result;
FieldPosition ignore(FieldPosition::DONT_CARE);
//Format with passed arguments
msgFmt->format( args , numOfArgs , result, ignore, status);
if (U_FAILURE(status)) {
- dataerrln( "%serror while formatting with ErrorCode as %s" ,errMsg, u_errorName(status) );
+ dataerrln( "%s error while formatting with ErrorCode as %s" ,errMsg, u_errorName(status) );
}
//Compare expected with obtained result
if ( result!= expected ) {
//Create the MessageFormat with simple SelectFormat
MessageFormat* msgFmt = new MessageFormat(pattern, locale, status);
if (U_FAILURE(status)) {
- dataerrln( "%serror while constructing with ErrorCode as %s" ,errMsg, u_errorName(status) );
+ dataerrln( "%s error while constructing with ErrorCode as %s" ,errMsg, u_errorName(status) );
logln(UnicodeString("TestMessageFormat::testMsgFormatSelect #1 with error code ")+(int32_t)status);
return NULL;
}
//Nested patterns with plural, number ,choice ,select format etc.
//Select Format with embedded number format
UnicodeString t4("{0} est {1, select, female {{2,number,integer} all\\u00E9e} other {all\\u00E9}} \\u00E0 Paris.");
+ err = U_ZERO_ERROR;
//Create the MessageFormat with Select Format with embedded number format (nested pattern)
MessageFormat* msgFmt4 = internalCreate(t4.unescape(), Locale("fr"),err,(char*)"From TestMessageFormat::TestSelectFormat create t4");
if (!U_FAILURE(err)) {
}
delete msgFmt4;
- err = U_ZERO_ERROR;
//Plural format with embedded select format
UnicodeString t5("{0} {1, plural, one {est {2, select, female {all\\u00E9e} other {all\\u00E9}}} other {sont {2, select, female {all\\u00E9es} other {all\\u00E9s}}}} \\u00E0 Paris.");
err = U_ZERO_ERROR;
{
Formattable( UDate(8.71068e+011), Formattable::kIsDate )
};
- const int32_t ft_cnt = sizeof(ftarray) / sizeof(Formattable);
+ const int32_t ft_cnt = UPRV_LENGTHOF(ftarray);
Formattable ft_arr( ftarray, ft_cnt );
Formattable* fmt = new Formattable(UDate(8.71068e+011), Formattable::kIsDate);
err = U_ZERO_ERROR;
MessageFormat msg( formatStr, err);
MessageFormat msgCmp( formatStr, err);
+ if (U_FAILURE(err)) {
+ dataerrln("Unable to instantiate MessageFormat - %s", u_errorName(err));
+ return;
+ }
int32_t count, countCmp;
const Format** formats = msg.getFormats(count);
const Format** formatsCmp = msgCmp.getFormats(countCmp);
}
assertEquals("msgCmp.toPattern()", formatStr, msgCmp.toPattern(patCmp.remove()));
- assertEquals("msg.toPattern()", formatStr, msg.toPattern(patAct.remove()));
+ // ICU 4.8 does not support toPattern() when there are custom formats (from setFormat() etc.).
+ // assertEquals("msg.toPattern()", formatStr, msg.toPattern(patAct.remove()));
+ msg.toPattern(patCmp.remove());
+ if (!patCmp.isBogus()) {
+ errln("msg.setFormat().toPattern() succeeds.");
+ }
for (i = 0; i < countAct; i++) {
a = formatsAct[i];
delete[] formatsToAdopt;
assertEquals("msgCmp.toPattern()", formatStr, msgCmp.toPattern(patCmp.remove()));
- assertEquals("msg.toPattern()", formatStr, msg.toPattern(patAct.remove()));
+ // ICU 4.8 does not support toPattern() when there are custom formats (from setFormat() etc.).
+ // assertEquals("msg.toPattern()", formatStr, msg.toPattern(patAct.remove()));
formatsAct = msg.getFormats(countAct);
if (!formatsAct || (countAct <=0) || (countAct != countCmp)) {
delete[] formatsToAdopt; // array itself not needed in this case;
assertEquals("msgCmp.toPattern()", formatStr, msgCmp.toPattern(patCmp.remove()));
- assertEquals("msg.toPattern()", formatStr, msg.toPattern(patAct.remove()));
+ // ICU 4.8 does not support toPattern() when there are custom formats (from setFormat() etc.).
+ // assertEquals("msg.toPattern()", formatStr, msg.toPattern(patAct.remove()));
formatsAct = msg.getFormats(countAct);
if (!formatsAct || (countAct <=0) || (countAct != countCmp)) {
const Formattable fargs( d, Formattable::kIsDate );
MessageFormat* fmt1 = new MessageFormat( formatStr, status );
- MessageFormat* fmt2 = new MessageFormat( *fmt1 );
- MessageFormat* fmt3;
- MessageFormat* fmt4;
+ MessageFormat* fmt2 = NULL;
+ MessageFormat* fmt3 = NULL;
+ MessageFormat* fmt4 = NULL;
- if (fmt1 == NULL) it_err("testCopyConstructor2: (fmt1 != NULL)");
+ if (fmt1 == NULL) {
+ it_err("testCopyConstructor2: (fmt1 != NULL)");
+ goto cleanup;
+ }
+ fmt2 = new MessageFormat( *fmt1 );
result = fmt1->format( &fargs, 1, resultStr, fp, status );
- if (fmt2 == NULL) it_err("testCopyConstructor2: (fmt2 != NULL)");
+ if (fmt2 == NULL) {
+ it_err("testCopyConstructor2: (fmt2 != NULL)");
+ goto cleanup;
+ }
fmt3 = (MessageFormat*) fmt1->clone();
fmt4 = (MessageFormat*) fmt2->clone();
- if (fmt3 == NULL) it_err("testCopyConstructor2: (fmt3 != NULL)");
- if (fmt4 == NULL) it_err("testCopyConstructor2: (fmt4 != NULL)");
+ if (fmt3 == NULL) {
+ it_err("testCopyConstructor2: (fmt3 != NULL)");
+ goto cleanup;
+ }
+ if (fmt4 == NULL) {
+ it_err("testCopyConstructor2: (fmt4 != NULL)");
+ goto cleanup;
+ }
result = fmt1->format( &fargs, 1, resultStr, fp, status );
result = fmt2->format( &fargs, 1, resultStr, fp, status );
result = fmt3->format( &fargs, 1, resultStr, fp, status );
result = fmt4->format( &fargs, 1, resultStr, fp, status );
+
+cleanup:
delete fmt1;
delete fmt2;
delete fmt3;
Formattable("of course"),
Formattable("Horace"),
};
- const int32_t ARGS_LENGTH = sizeof(ARGS) / sizeof(ARGS[0]);
+ const int32_t ARGS_LENGTH = UPRV_LENGTHOF(ARGS);
Formattable ARGS_OBJ(ARGS, ARGS_LENGTH);
UnicodeString expected =
// 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]);
+ int32_t values_count = UPRV_LENGTHOF(values);
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
+ "This is the {0,ordinal} file to search.",
"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]);
+ int32_t formats_count = UPRV_LENGTHOF(formats);
Formattable args[1];
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;
+ 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
"'", "''",
"'} '{'}'", "'} '{'}''",
"'} {{{''", "'} {{{'''",
};
- int32_t pattern_count = sizeof(patterns)/sizeof(patterns[0]);
+ int32_t pattern_count = UPRV_LENGTHOF(patterns);
for (int i = 0; i < pattern_count; i += 2) {
UErrorCode status = U_ZERO_ERROR;
}
}
- msgfmt->adoptFormat("adopt", &cf, status);
+ // 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;
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 fail to setFormat instead of passing.");
+ 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 fail to get format names enumeration instead of passing.");
+ 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();
+}
+
+void TestMessageFormat::TestArgIsPrefixOfAnother() {
+ IcuTestErrorCode errorCode(*this, "TestArgIsPrefixOfAnother");
+ // Ticket #11952
+ MessageFormat mf1("{0,select,a{A}ab{AB}abc{ABC}other{?}}", Locale::getEnglish(), errorCode);
+ Formattable args[3];
+ FieldPosition ignore;
+ UnicodeString result;
+ args[0].setString("a");
+ assertEquals("a", "A", mf1.format(args, 1, result, ignore, errorCode));
+ args[0].setString("ab");
+ assertEquals("ab", "AB", mf1.format(args, 1, result.remove(), ignore, errorCode));
+ args[0].setString("abc");
+ assertEquals("abc", "ABC", mf1.format(args, 1, result.remove(), ignore, errorCode));
+
+ // Ticket #12172
+ MessageFormat mf2("{a} {aa} {aaa}", Locale::getEnglish(), errorCode);
+ UnicodeString argNames[3] = { "a", "aa", "aaa" };
+ args[0].setString("A");
+ args[1].setString("AB");
+ args[2].setString("ABC");
+ assertEquals("a aa aaa", "A AB ABC", mf2.format(argNames, args, 3, result.remove(), errorCode));
+
+ // Ticket #12172
+ MessageFormat mf3("{aa} {aaa}", Locale::getEnglish(), errorCode);
+ assertEquals("aa aaa", "AB ABC", mf3.format(argNames + 1, args + 1, 2, result.remove(), errorCode));
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */