1 // © 2017 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3
4 #include "unicode/utypes.h"
5
6 #if !UCONFIG_NO_FORMATTING
7
8 #include "putilimp.h"
9 #include "unicode/dcfmtsym.h"
10 #include "numbertest.h"
11 #include "number_utils.h"
12
13 using namespace icu::number::impl;
14
15 class DefaultSymbolProvider : public SymbolProvider {
16 DecimalFormatSymbols fSymbols;
17
18 public:
DefaultSymbolProvider(UErrorCode & status)19 DefaultSymbolProvider(UErrorCode &status) : fSymbols(Locale("ar_SA"), status) {}
20
getSymbol(AffixPatternType type) const21 UnicodeString getSymbol(AffixPatternType type) const U_OVERRIDE {
22 switch (type) {
23 case TYPE_MINUS_SIGN:
24 return u"−";
25 case TYPE_PLUS_SIGN:
26 return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPlusSignSymbol);
27 case TYPE_APPROXIMATELY_SIGN:
28 return u"≃";
29 case TYPE_PERCENT:
30 return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPercentSymbol);
31 case TYPE_PERMILLE:
32 return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPerMillSymbol);
33 case TYPE_CURRENCY_SINGLE:
34 return u"$";
35 case TYPE_CURRENCY_DOUBLE:
36 return u"XXX";
37 case TYPE_CURRENCY_TRIPLE:
38 return u"long name";
39 case TYPE_CURRENCY_QUAD:
40 return u"\uFFFD";
41 case TYPE_CURRENCY_QUINT:
42 // TODO: Add support for narrow currency symbols here.
43 return u"\uFFFD";
44 case TYPE_CURRENCY_OVERFLOW:
45 return u"\uFFFD";
46 default:
47 UPRV_UNREACHABLE_EXIT;
48 }
49 }
50 };
51
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)52 void AffixUtilsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
53 if (exec) {
54 logln("TestSuite AffixUtilsTest: ");
55 }
56 TESTCASE_AUTO_BEGIN;
57 TESTCASE_AUTO(testEscape);
58 TESTCASE_AUTO(testUnescape);
59 TESTCASE_AUTO(testContainsReplaceType);
60 TESTCASE_AUTO(testInvalid);
61 TESTCASE_AUTO(testUnescapeWithSymbolProvider);
62 TESTCASE_AUTO_END;
63 }
64
testEscape()65 void AffixUtilsTest::testEscape() {
66 static const char16_t *cases[][2] = {{u"", u""},
67 {u"abc", u"abc"},
68 {u"-", u"'-'"},
69 {u"-!", u"'-'!"},
70 {u"−", u"−"},
71 {u"---", u"'---'"},
72 {u"-%-", u"'-%-'"},
73 {u"'", u"''"},
74 {u"-'", u"'-'''"},
75 {u"-'-", u"'-''-'"},
76 {u"a-'-", u"a'-''-'"}};
77
78 for (auto &cas : cases) {
79 UnicodeString input(cas[0]);
80 UnicodeString expected(cas[1]);
81 UnicodeString result = AffixUtils::escape(input);
82 assertEquals(input, expected, result);
83 }
84 }
85
testUnescape()86 void AffixUtilsTest::testUnescape() {
87 static struct TestCase {
88 const char16_t *input;
89 bool currency;
90 int32_t expectedLength;
91 const char16_t *output;
92 } cases[] = {{u"", false, 0, u""},
93 {u"abc", false, 3, u"abc"},
94 {u"-", false, 1, u"−"},
95 {u"-!", false, 2, u"−!"},
96 {u"+", false, 1, u"\u061C+"},
97 {u"+!", false, 2, u"\u061C+!"},
98 {u"~", false, 1, u"≃"},
99 {u"‰", false, 1, u"؉"},
100 {u"‰!", false, 2, u"؉!"},
101 {u"-x", false, 2, u"−x"},
102 {u"'-'x", false, 2, u"-x"},
103 {u"'--''-'-x", false, 6, u"--'-−x"},
104 {u"''", false, 1, u"'"},
105 {u"''''", false, 2, u"''"},
106 {u"''''''", false, 3, u"'''"},
107 {u"''x''", false, 3, u"'x'"},
108 {u"¤", true, 1, u"$"},
109 {u"¤¤", true, 2, u"XXX"},
110 {u"¤¤¤", true, 3, u"long name"},
111 {u"¤¤¤¤", true, 4, u"\uFFFD"},
112 {u"¤¤¤¤¤", true, 5, u"\uFFFD"},
113 {u"¤¤¤¤¤¤", true, 6, u"\uFFFD"},
114 {u"¤¤¤a¤¤¤¤", true, 8, u"long namea\uFFFD"},
115 {u"a¤¤¤¤b¤¤¤¤¤c", true, 12, u"a\uFFFDb\uFFFDc"},
116 {u"¤!", true, 2, u"$!"},
117 {u"¤¤!", true, 3, u"XXX!"},
118 {u"¤¤¤!", true, 4, u"long name!"},
119 {u"-¤¤", true, 3, u"−XXX"},
120 {u"¤¤-", true, 3, u"XXX−"},
121 {u"'¤'", false, 1, u"¤"},
122 {u"%", false, 1, u"٪\u061C"},
123 {u"'%'", false, 1, u"%"},
124 {u"¤'-'%", true, 3, u"$-٪\u061C"},
125 {u"#0#@#*#;#", false, 9, u"#0#@#*#;#"}};
126
127 UErrorCode status = U_ZERO_ERROR;
128 DefaultSymbolProvider defaultProvider(status);
129 assertSuccess("Constructing DefaultSymbolProvider", status);
130
131 for (TestCase cas : cases) {
132 UnicodeString input(cas.input);
133 UnicodeString output(cas.output);
134
135 assertEquals(input, cas.currency, AffixUtils::hasCurrencySymbols(input, status));
136 assertSuccess("Spot 1", status);
137 assertEquals(input, cas.expectedLength, AffixUtils::estimateLength(input, status));
138 assertSuccess("Spot 2", status);
139
140 UnicodeString actual = unescapeWithDefaults(defaultProvider, input, status);
141 assertSuccess("Spot 3", status);
142 assertEquals(input, output, actual);
143
144 int32_t ulength = AffixUtils::unescapedCodePointCount(input, defaultProvider, status);
145 assertSuccess("Spot 4", status);
146 assertEquals(input, output.countChar32(), ulength);
147 }
148 }
149
testContainsReplaceType()150 void AffixUtilsTest::testContainsReplaceType() {
151 static struct TestCase {
152 const char16_t *input;
153 bool hasMinusSign;
154 const char16_t *output;
155 } cases[] = {{u"", false, u""},
156 {u"-", true, u"+"},
157 {u"-a", true, u"+a"},
158 {u"a-", true, u"a+"},
159 {u"a-b", true, u"a+b"},
160 {u"--", true, u"++"},
161 {u"x", false, u"x"}};
162
163 UErrorCode status = U_ZERO_ERROR;
164 for (TestCase cas : cases) {
165 UnicodeString input(cas.input);
166 bool hasMinusSign = cas.hasMinusSign;
167 UnicodeString output(cas.output);
168
169 assertEquals(
170 input, hasMinusSign, AffixUtils::containsType(input, TYPE_MINUS_SIGN, status));
171 assertSuccess("Spot 1", status);
172 assertEquals(
173 input, output, AffixUtils::replaceType(input, TYPE_MINUS_SIGN, u'+', status));
174 assertSuccess("Spot 2", status);
175 }
176 }
177
testInvalid()178 void AffixUtilsTest::testInvalid() {
179 static const char16_t *invalidExamples[] = {
180 u"'", u"x'", u"'x", u"'x''", u"''x'"};
181
182 UErrorCode status = U_ZERO_ERROR;
183 DefaultSymbolProvider defaultProvider(status);
184 assertSuccess("Constructing DefaultSymbolProvider", status);
185
186 for (const char16_t *strPtr : invalidExamples) {
187 UnicodeString str(strPtr);
188
189 status = U_ZERO_ERROR;
190 AffixUtils::hasCurrencySymbols(str, status);
191 assertEquals("Should set error code spot 1", status, U_ILLEGAL_ARGUMENT_ERROR);
192
193 status = U_ZERO_ERROR;
194 AffixUtils::estimateLength(str, status);
195 assertEquals("Should set error code spot 2", status, U_ILLEGAL_ARGUMENT_ERROR);
196
197 status = U_ZERO_ERROR;
198 unescapeWithDefaults(defaultProvider, str, status);
199 assertEquals("Should set error code spot 3", status, U_ILLEGAL_ARGUMENT_ERROR);
200 }
201 }
202
203 class NumericSymbolProvider : public SymbolProvider {
204 public:
getSymbol(AffixPatternType type) const205 virtual UnicodeString getSymbol(AffixPatternType type) const override {
206 return Int64ToUnicodeString(type < 0 ? -type : type);
207 }
208 };
209
testUnescapeWithSymbolProvider()210 void AffixUtilsTest::testUnescapeWithSymbolProvider() {
211 static const char16_t* cases[][2] = {
212 {u"", u""},
213 {u"-", u"1"},
214 {u"'-'", u"-"},
215 {u"- + ~ % ‰ ¤ ¤¤ ¤¤¤ ¤¤¤¤ ¤¤¤¤¤", u"1 2 3 4 5 6 7 8 9 10"},
216 {u"'¤¤¤¤¤¤'", u"¤¤¤¤¤¤"},
217 {u"¤¤¤¤¤¤", u"\uFFFD"}
218 };
219
220 NumericSymbolProvider provider;
221
222 UErrorCode status = U_ZERO_ERROR;
223 FormattedStringBuilder sb;
224 for (auto& cas : cases) {
225 UnicodeString input(cas[0]);
226 UnicodeString expected(cas[1]);
227 sb.clear();
228 AffixUtils::unescape(input, sb, 0, provider, kUndefinedField, status);
229 assertSuccess("Spot 1", status);
230 assertEquals(input, expected, sb.toUnicodeString());
231 assertEquals(input, expected, sb.toTempUnicodeString());
232 }
233
234 // Test insertion position
235 sb.clear();
236 sb.append(u"abcdefg", kUndefinedField, status);
237 assertSuccess("Spot 2", status);
238 AffixUtils::unescape(u"-+~", sb, 4, provider, kUndefinedField, status);
239 assertSuccess("Spot 3", status);
240 assertEquals(u"Symbol provider into middle", u"abcd123efg", sb.toUnicodeString());
241 }
242
unescapeWithDefaults(const SymbolProvider & defaultProvider,UnicodeString input,UErrorCode & status)243 UnicodeString AffixUtilsTest::unescapeWithDefaults(const SymbolProvider &defaultProvider,
244 UnicodeString input, UErrorCode &status) {
245 FormattedStringBuilder nsb;
246 int32_t length = AffixUtils::unescape(input, nsb, 0, defaultProvider, kUndefinedField, status);
247 assertEquals("Return value of unescape", nsb.length(), length);
248 return nsb.toUnicodeString();
249 }
250
251 #endif /* #if !UCONFIG_NO_FORMATTING */
252