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