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