// © 2017 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING #include "putilimp.h" #include "unicode/dcfmtsym.h" #include "numbertest.h" #include "number_utils.h" using namespace icu::number::impl; class DefaultSymbolProvider : public SymbolProvider { DecimalFormatSymbols fSymbols; public: DefaultSymbolProvider(UErrorCode &status) : fSymbols(Locale("ar_SA"), status) {} UnicodeString getSymbol(AffixPatternType type) const U_OVERRIDE { switch (type) { case TYPE_MINUS_SIGN: return u"−"; case TYPE_PLUS_SIGN: return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPlusSignSymbol); case TYPE_PERCENT: return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPercentSymbol); case TYPE_PERMILLE: return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPerMillSymbol); case TYPE_CURRENCY_SINGLE: return u"$"; case TYPE_CURRENCY_DOUBLE: return u"XXX"; case TYPE_CURRENCY_TRIPLE: return u"long name"; case TYPE_CURRENCY_QUAD: return u"\uFFFD"; case TYPE_CURRENCY_QUINT: // TODO: Add support for narrow currency symbols here. return u"\uFFFD"; case TYPE_CURRENCY_OVERFLOW: return u"\uFFFD"; default: UPRV_UNREACHABLE; } } }; void AffixUtilsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) { if (exec) { logln("TestSuite AffixUtilsTest: "); } TESTCASE_AUTO_BEGIN; TESTCASE_AUTO(testEscape); TESTCASE_AUTO(testUnescape); TESTCASE_AUTO(testContainsReplaceType); TESTCASE_AUTO(testInvalid); TESTCASE_AUTO(testUnescapeWithSymbolProvider); TESTCASE_AUTO_END; } void AffixUtilsTest::testEscape() { static const char16_t *cases[][2] = {{u"", u""}, {u"abc", u"abc"}, {u"-", u"'-'"}, {u"-!", u"'-'!"}, {u"−", u"−"}, {u"---", u"'---'"}, {u"-%-", u"'-%-'"}, {u"'", u"''"}, {u"-'", u"'-'''"}, {u"-'-", u"'-''-'"}, {u"a-'-", u"a'-''-'"}}; for (auto &cas : cases) { UnicodeString input(cas[0]); UnicodeString expected(cas[1]); UnicodeString result = AffixUtils::escape(input); assertEquals(input, expected, result); } } void AffixUtilsTest::testUnescape() { static struct TestCase { const char16_t *input; bool currency; int32_t expectedLength; const char16_t *output; } cases[] = {{u"", false, 0, u""}, {u"abc", false, 3, u"abc"}, {u"-", false, 1, u"−"}, {u"-!", false, 2, u"−!"}, {u"+", false, 1, u"\u061C+"}, {u"+!", false, 2, u"\u061C+!"}, {u"‰", false, 1, u"؉"}, {u"‰!", false, 2, u"؉!"}, {u"-x", false, 2, u"−x"}, {u"'-'x", false, 2, u"-x"}, {u"'--''-'-x", false, 6, u"--'-−x"}, {u"''", false, 1, u"'"}, {u"''''", false, 2, u"''"}, {u"''''''", false, 3, u"'''"}, {u"''x''", false, 3, u"'x'"}, {u"¤", true, 1, u"$"}, {u"¤¤", true, 2, u"XXX"}, {u"¤¤¤", true, 3, u"long name"}, {u"¤¤¤¤", true, 4, u"\uFFFD"}, {u"¤¤¤¤¤", true, 5, u"\uFFFD"}, {u"¤¤¤¤¤¤", true, 6, u"\uFFFD"}, {u"¤¤¤a¤¤¤¤", true, 8, u"long namea\uFFFD"}, {u"a¤¤¤¤b¤¤¤¤¤c", true, 12, u"a\uFFFDb\uFFFDc"}, {u"¤!", true, 2, u"$!"}, {u"¤¤!", true, 3, u"XXX!"}, {u"¤¤¤!", true, 4, u"long name!"}, {u"-¤¤", true, 3, u"−XXX"}, {u"¤¤-", true, 3, u"XXX−"}, {u"'¤'", false, 1, u"¤"}, {u"%", false, 1, u"٪\u061C"}, {u"'%'", false, 1, u"%"}, {u"¤'-'%", true, 3, u"$-٪\u061C"}, {u"#0#@#*#;#", false, 9, u"#0#@#*#;#"}}; UErrorCode status = U_ZERO_ERROR; DefaultSymbolProvider defaultProvider(status); assertSuccess("Constructing DefaultSymbolProvider", status); for (TestCase cas : cases) { UnicodeString input(cas.input); UnicodeString output(cas.output); assertEquals(input, cas.currency, AffixUtils::hasCurrencySymbols(input, status)); assertSuccess("Spot 1", status); assertEquals(input, cas.expectedLength, AffixUtils::estimateLength(input, status)); assertSuccess("Spot 2", status); UnicodeString actual = unescapeWithDefaults(defaultProvider, input, status); assertSuccess("Spot 3", status); assertEquals(input, output, actual); int32_t ulength = AffixUtils::unescapedCodePointCount(input, defaultProvider, status); assertSuccess("Spot 4", status); assertEquals(input, output.countChar32(), ulength); } } void AffixUtilsTest::testContainsReplaceType() { static struct TestCase { const char16_t *input; bool hasMinusSign; const char16_t *output; } cases[] = {{u"", false, u""}, {u"-", true, u"+"}, {u"-a", true, u"+a"}, {u"a-", true, u"a+"}, {u"a-b", true, u"a+b"}, {u"--", true, u"++"}, {u"x", false, u"x"}}; UErrorCode status = U_ZERO_ERROR; for (TestCase cas : cases) { UnicodeString input(cas.input); bool hasMinusSign = cas.hasMinusSign; UnicodeString output(cas.output); assertEquals( input, hasMinusSign, AffixUtils::containsType(input, TYPE_MINUS_SIGN, status)); assertSuccess("Spot 1", status); assertEquals( input, output, AffixUtils::replaceType(input, TYPE_MINUS_SIGN, u'+', status)); assertSuccess("Spot 2", status); } } void AffixUtilsTest::testInvalid() { static const char16_t *invalidExamples[] = { u"'", u"x'", u"'x", u"'x''", u"''x'"}; UErrorCode status = U_ZERO_ERROR; DefaultSymbolProvider defaultProvider(status); assertSuccess("Constructing DefaultSymbolProvider", status); for (const char16_t *strPtr : invalidExamples) { UnicodeString str(strPtr); status = U_ZERO_ERROR; AffixUtils::hasCurrencySymbols(str, status); assertEquals("Should set error code spot 1", status, U_ILLEGAL_ARGUMENT_ERROR); status = U_ZERO_ERROR; AffixUtils::estimateLength(str, status); assertEquals("Should set error code spot 2", status, U_ILLEGAL_ARGUMENT_ERROR); status = U_ZERO_ERROR; unescapeWithDefaults(defaultProvider, str, status); assertEquals("Should set error code spot 3", status, U_ILLEGAL_ARGUMENT_ERROR); } } class NumericSymbolProvider : public SymbolProvider { public: virtual UnicodeString getSymbol(AffixPatternType type) const { return Int64ToUnicodeString(type < 0 ? -type : type); } }; void AffixUtilsTest::testUnescapeWithSymbolProvider() { static const char16_t* cases[][2] = { {u"", u""}, {u"-", u"1"}, {u"'-'", u"-"}, {u"- + % ‰ ¤ ¤¤ ¤¤¤ ¤¤¤¤ ¤¤¤¤¤", u"1 2 3 4 5 6 7 8 9"}, {u"'¤¤¤¤¤¤'", u"¤¤¤¤¤¤"}, {u"¤¤¤¤¤¤", u"\uFFFD"} }; NumericSymbolProvider provider; UErrorCode status = U_ZERO_ERROR; FormattedStringBuilder sb; for (auto& cas : cases) { UnicodeString input(cas[0]); UnicodeString expected(cas[1]); sb.clear(); AffixUtils::unescape(input, sb, 0, provider, kUndefinedField, status); assertSuccess("Spot 1", status); assertEquals(input, expected, sb.toUnicodeString()); assertEquals(input, expected, sb.toTempUnicodeString()); } // Test insertion position sb.clear(); sb.append(u"abcdefg", kUndefinedField, status); assertSuccess("Spot 2", status); AffixUtils::unescape(u"-+%", sb, 4, provider, kUndefinedField, status); assertSuccess("Spot 3", status); assertEquals(u"Symbol provider into middle", u"abcd123efg", sb.toUnicodeString()); } UnicodeString AffixUtilsTest::unescapeWithDefaults(const SymbolProvider &defaultProvider, UnicodeString input, UErrorCode &status) { FormattedStringBuilder nsb; int32_t length = AffixUtils::unescape(input, nsb, 0, defaultProvider, kUndefinedField, status); assertEquals("Return value of unescape", nsb.length(), length); return nsb.toUnicodeString(); } #endif /* #if !UCONFIG_NO_FORMATTING */