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 && !UPRV_INCOMPLETE_CPP11_SUPPORT
7
8#include "putilimp.h"
9#include "unicode/dcfmtsym.h"
10#include "numbertest.h"
11#include "number_utils.h"
12
13using namespace icu::number::impl;
14
15class DefaultSymbolProvider : public SymbolProvider {
16    DecimalFormatSymbols fSymbols;
17
18  public:
19    DefaultSymbolProvider(UErrorCode &status) : fSymbols(Locale("ar_SA"), status) {}
20
21    virtual 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
51void 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
64void 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(UnicodeStringCharSequence(input));
81        assertEquals(input, expected, result);
82    }
83}
84
85void 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(UnicodeStringCharSequence(input), status));
134        assertSuccess("Spot 1", status);
135        assertEquals(input, cas.expectedLength, AffixUtils::estimateLength(UnicodeStringCharSequence(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(UnicodeStringCharSequence(input), defaultProvider, status);
143        assertSuccess("Spot 4", status);
144        assertEquals(input, output.countChar32(), ulength);
145    }
146}
147
148void 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(UnicodeStringCharSequence(input), TYPE_MINUS_SIGN, status));
169        assertSuccess("Spot 1", status);
170        assertEquals(
171                input, output, AffixUtils::replaceType(UnicodeStringCharSequence(input), TYPE_MINUS_SIGN, u'+', status));
172        assertSuccess("Spot 2", status);
173    }
174}
175
176void 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(UnicodeStringCharSequence(str), status);
189        assertEquals("Should set error code spot 1", status, U_ILLEGAL_ARGUMENT_ERROR);
190
191        status = U_ZERO_ERROR;
192        AffixUtils::estimateLength(UnicodeStringCharSequence(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
201class NumericSymbolProvider : public SymbolProvider {
202  public:
203    virtual UnicodeString getSymbol(AffixPatternType type) const {
204        return Int64ToUnicodeString(type < 0 ? -type : type);
205    }
206};
207
208void 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(UnicodeStringCharSequence(input), sb, 0, provider, status);
227        assertSuccess("Spot 1", status);
228        assertEquals(input, expected, sb.toUnicodeString());
229    }
230
231    // Test insertion position
232    sb.clear();
233    sb.append(u"abcdefg", UNUM_FIELD_COUNT, status);
234    assertSuccess("Spot 2", status);
235    AffixUtils::unescape(UnicodeStringCharSequence(UnicodeString(u"-+%")), sb, 4, provider, status);
236    assertSuccess("Spot 3", status);
237    assertEquals(u"Symbol provider into middle", u"abcd123efg", sb.toUnicodeString());
238}
239
240UnicodeString AffixUtilsTest::unescapeWithDefaults(const SymbolProvider &defaultProvider,
241                                                          UnicodeString input, UErrorCode &status) {
242    NumberStringBuilder nsb;
243    int32_t length = AffixUtils::unescape(UnicodeStringCharSequence(input), nsb, 0, defaultProvider, status);
244    assertEquals("Return value of unescape", nsb.length(), length);
245    return nsb.toUnicodeString();
246}
247
248#endif /* #if !UCONFIG_NO_FORMATTING */
249