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