• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2017 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 package ohos.global.icu.dev.test.number;
5 
6 import static org.junit.Assert.assertEquals;
7 import static org.junit.Assert.assertFalse;
8 import static org.junit.Assert.assertTrue;
9 
10 import org.junit.Test;
11 
12 import ohos.global.icu.impl.StringSegment;
13 import ohos.global.icu.impl.number.CustomSymbolCurrency;
14 import ohos.global.icu.impl.number.DecimalFormatProperties;
15 import ohos.global.icu.impl.number.parse.AffixPatternMatcher;
16 import ohos.global.icu.impl.number.parse.AffixTokenMatcherFactory;
17 import ohos.global.icu.impl.number.parse.CombinedCurrencyMatcher;
18 import ohos.global.icu.impl.number.parse.IgnorablesMatcher;
19 import ohos.global.icu.impl.number.parse.MinusSignMatcher;
20 import ohos.global.icu.impl.number.parse.NumberParserImpl;
21 import ohos.global.icu.impl.number.parse.ParsedNumber;
22 import ohos.global.icu.impl.number.parse.ParsingUtils;
23 import ohos.global.icu.impl.number.parse.PercentMatcher;
24 import ohos.global.icu.impl.number.parse.PlusSignMatcher;
25 import ohos.global.icu.impl.number.parse.SeriesMatcher;
26 import ohos.global.icu.text.DecimalFormatSymbols;
27 import ohos.global.icu.util.Currency;
28 import ohos.global.icu.util.ULocale;
29 
30 
31 /**
32  * @author sffc
33  *
34  */
35 
36 public class NumberParserTest {
37     @Test
testBasic()38     public void testBasic() {
39         Object[][] cases = new Object[][] {
40                 // Fields:
41                 // a) Flags:
42                 // --- Bit 0x01 => Test greedy implementation
43                 // --- Bit 0x02 => Test slow implementation
44                 // --- Bit 0x04 => Test strict grouping separators
45                 // b) Input string
46                 // c) Pattern
47                 // d) Expected chars consumed
48                 // e) Expected double result
49                 { 3, "51423", "0", 5, 51423. },
50                 { 3, "51423x", "0", 5, 51423. },
51                 { 3, " 51423", "0", 6, 51423. },
52                 { 3, "51423 ", "0", 5, 51423. },
53                 { 3, "����������", "0", 10, 51423. },
54                 { 3, "����������x", "0", 10, 51423. },
55                 { 3, " ����������", "0", 11, 51423. },
56                 { 3, "���������� ", "0", 10, 51423. },
57                 { 7, "51,423", "#,##,##0", 6, 51423. },
58                 { 7, " 51,423", "#,##,##0", 7, 51423. },
59                 { 7, "51,423 ", "#,##,##0", 6, 51423. },
60                 { 7, "51,423,", "#,##,##0", 6, 51423. },
61                 { 7, "51,423,,", "#,##,##0", 6, 51423. },
62                 { 7, "51,423.5", "#,##,##0", 8, 51423.5 },
63                 { 7, "51,423.5,", "#,##,##0", 8, 51423.5 },
64                 { 7, "51,423.5,,", "#,##,##0", 8, 51423.5 },
65                 { 7, "51,423.5.", "#,##,##0", 8, 51423.5 },
66                 { 7, "51,423.5..", "#,##,##0", 8, 51423.5 },
67                 { 7, "����,������", "#,##,##0", 11, 51423. },
68                 { 7, "��,����,����,������", "#,##,##0", 19, 78951423. },
69                 { 7, "����,������.������", "#,##,##0", 18, 78951.423 },
70                 { 7, "����,������", "#,##,##0", 11, 78000. },
71                 { 7, "����,������.������", "#,##,##0", 18, 78000. },
72                 { 7, "����,������.������", "#,##,##0", 18, 78000.023 },
73                 { 7, "����.������.������", "#,##,##0", 11, 78. },
74                 { 7, "1,", "#,##,##0", 1, 1. },
75                 { 7, "1,,", "#,##,##0", 1, 1. },
76                 { 7, "1.,", "#,##,##0", 2, 1. },
77                 { 3, "1,.", "#,##,##0", 3, 1. },
78                 { 7, "1..", "#,##,##0", 2, 1. },
79                 { 3, ",1", "#,##,##0", 2, 1. },
80                 { 3, "1,1", "#,##,##0", 1, 1. },
81                 { 3, "1,1,", "#,##,##0", 1, 1. },
82                 { 3, "1,1,,", "#,##,##0", 1, 1. },
83                 { 3, "1,1,1", "#,##,##0", 1, 1. },
84                 { 3, "1,1,1,", "#,##,##0", 1, 1. },
85                 { 3, "1,1,1,1", "#,##,##0", 1, 1. },
86                 { 3, "1,1,1,,", "#,##,##0", 1, 1. },
87                 { 3, "-51423", "0", 6, -51423. },
88                 { 3, "51423-", "0", 5, 51423. }, // plus and minus sign by default do NOT match after
89                 { 3, "+51423", "0", 6, 51423. },
90                 { 3, "51423+", "0", 5, 51423. }, // plus and minus sign by default do NOT match after
91                 { 3, "%51423", "0", 6, 51423. },
92                 { 3, "51423%", "0", 6, 51423. },
93                 { 3, "51423%%", "0", 6, 51423. },
94                 { 3, "‰51423", "0", 6, 51423. },
95                 { 3, "51423‰", "0", 6, 51423. },
96                 { 3, "51423‰‰", "0", 6, 51423. },
97                 { 3, "∞", "0", 1, Double.POSITIVE_INFINITY },
98                 { 3, "-∞", "0", 2, Double.NEGATIVE_INFINITY },
99                 { 3, "@@@123  @@", "0", 6, 123. }, // TODO: Should padding be strong instead of weak?
100                 { 3, "@@@123@@  ", "0", 6, 123. }, // TODO: Should padding be strong instead of weak?
101                 { 3, "a51423US dollars", "a0¤¤¤", 16, 51423. },
102                 { 3, "a 51423 US dollars", "a0¤¤¤", 18, 51423. },
103                 { 3, "514.23 USD", "¤0", 10, 514.23 },
104                 { 3, "514.23 GBP", "¤0", 10, 514.23 },
105                 { 3, "a ���������� b", "a0b", 14, 51423. },
106                 { 3, "-a ���������� b", "a0b", 15, -51423. },
107                 { 3, "a -���������� b", "a0b", 15, -51423. },
108                 { 3, "����������", "[0];(0)", 10, 51423. },
109                 { 3, "[����������", "[0];(0)", 11, 51423. },
110                 { 3, "����������]", "[0];(0)", 11, 51423. },
111                 { 3, "[����������]", "[0];(0)", 12, 51423. },
112                 { 3, "(����������", "[0];(0)", 11, -51423. },
113                 { 3, "����������)", "[0];(0)", 11, -51423. },
114                 { 3, "(����������)", "[0];(0)", 12, -51423. },
115                 { 3, "����������", "{0};{0}", 10, 51423. },
116                 { 3, "{����������", "{0};{0}", 11, 51423. },
117                 { 3, "����������}", "{0};{0}", 11, 51423. },
118                 { 3, "{����������}", "{0};{0}", 12, 51423. },
119                 { 1, "a40b", "a0'0b'", 3, 40. }, // greedy code path thinks "40" is the number
120                 { 2, "a40b", "a0'0b'", 4, 4. }, // slow code path finds the suffix "0b"
121                 { 3, "��.������E��", "0", 12, 5142. },
122                 { 3, "��.������E-��", "0", 13, 0.005142 },
123                 { 3, "��.������e-��", "0", 13, 0.005142 },
124                 { 3, "5.142e+3", "0", 8, 5142.0 },
125                 { 3, "5.142\u200Ee+3", "0", 9, 5142.0 },
126                 { 3, "5.142e\u200E+3", "0", 9, 5142.0 },
127                 { 3, "5.142e+\u200E3", "0", 9, 5142.0 },
128                 { 7, "5,142.50 Canadian dollars", "#,##,##0 ¤¤¤", 25, 5142.5 },
129                 { 3, "a$ b5", "a ¤ b0", 5, 5.0 },
130                 { 3, "��1.23", "��0;��0", 6, 1.23 },
131                 { 3, "��1.23", "��0;��0", 6, -1.23 },
132                 { 3, ".00", "0", 3, 0.0 },
133                 { 3, "                              1,234", "a0", 35, 1234. }, // should not hang
134                 { 3, "NaN", "0", 3, Double.NaN },
135                 { 3, "NaN E5", "0", 6, Double.NaN },
136                 { 3, "0", "0", 1, 0.0 } };
137 
138         int parseFlags = ParsingUtils.PARSE_FLAG_IGNORE_CASE
139                 | ParsingUtils.PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES;
140         for (Object[] cas : cases) {
141             int flags = (Integer) cas[0];
142             String inputString = (String) cas[1];
143             String patternString = (String) cas[2];
144             int expectedCharsConsumed = (Integer) cas[3];
145             double expectedResultDouble = (Double) cas[4];
146             NumberParserImpl parser = NumberParserImpl
147                     .createSimpleParser(ULocale.ENGLISH, patternString, parseFlags);
148             String message = "Input <" + inputString + "> Parser " + parser;
149 
150             if (0 != (flags & 0x01)) {
151                 // Test greedy code path
152                 ParsedNumber resultObject = new ParsedNumber();
153                 parser.parse(inputString, true, resultObject);
154                 assertTrue("Greedy Parse failed: " + message, resultObject.success());
155                 assertEquals("Greedy Parse failed: " + message,
156                         expectedCharsConsumed,
157                         resultObject.charEnd);
158                 assertEquals("Greedy Parse failed: " + message,
159                         expectedResultDouble,
160                         resultObject.getNumber().doubleValue(),
161                         0.0);
162             }
163 
164             if (0 != (flags & 0x02)) {
165                 // Test slow code path
166                 ParsedNumber resultObject = new ParsedNumber();
167                 parser.parse(inputString, false, resultObject);
168                 assertTrue("Non-Greedy Parse failed: " + message, resultObject.success());
169                 assertEquals("Non-Greedy Parse failed: " + message,
170                         expectedCharsConsumed,
171                         resultObject.charEnd);
172                 assertEquals("Non-Greedy Parse failed: " + message,
173                         expectedResultDouble,
174                         resultObject.getNumber().doubleValue(),
175                         0.0);
176             }
177 
178             if (0 != (flags & 0x04)) {
179                 // Test with strict separators
180                 parser = NumberParserImpl.createSimpleParser(ULocale.ENGLISH,
181                         patternString,
182                         parseFlags | ParsingUtils.PARSE_FLAG_STRICT_GROUPING_SIZE);
183                 ParsedNumber resultObject = new ParsedNumber();
184                 parser.parse(inputString, true, resultObject);
185                 assertTrue("Strict Parse failed: " + message, resultObject.success());
186                 assertEquals("Strict Parse failed: " + message,
187                         expectedCharsConsumed,
188                         resultObject.charEnd);
189                 assertEquals("Strict Parse failed: " + message,
190                         expectedResultDouble,
191                         resultObject.getNumber().doubleValue(),
192                         0.0);
193             }
194         }
195     }
196 
197     @Test
testLocaleFi()198     public void testLocaleFi() {
199         // This case is interesting because locale fi has NaN starting with 'e', the same as scientific
200         NumberParserImpl parser = NumberParserImpl
201                 .createSimpleParser(new ULocale("fi"), "0", ParsingUtils.PARSE_FLAG_IGNORE_CASE);
202 
203         ParsedNumber resultObject = new ParsedNumber();
204         parser.parse("epäluku", false, resultObject);
205         assertTrue(resultObject.success());
206         assertEquals(Double.NaN, resultObject.getNumber().doubleValue(), 0.0);
207 
208         resultObject = new ParsedNumber();
209         parser.parse("1,2e3", false, resultObject);
210         assertTrue(resultObject.success());
211         assertEquals(1200.0, resultObject.getNumber().doubleValue(), 0.0);
212     }
213 
214     @Test
testSeriesMatcher()215     public void testSeriesMatcher() {
216         DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
217         SeriesMatcher series = new SeriesMatcher();
218         series.addMatcher(PlusSignMatcher.getInstance(symbols, false));
219         series.addMatcher(MinusSignMatcher.getInstance(symbols, false));
220         series.addMatcher(IgnorablesMatcher.getInstance(0));
221         series.addMatcher(PercentMatcher.getInstance(symbols));
222         series.addMatcher(IgnorablesMatcher.getInstance(0));
223         series.freeze();
224 
225         assertFalse(series.smokeTest(new StringSegment("x", false)));
226         assertFalse(series.smokeTest(new StringSegment("-", false)));
227         assertTrue(series.smokeTest(new StringSegment("+", false)));
228 
229         Object[][] cases = new Object[][] {
230                 { "", 0, true },
231                 { " ", 0, false },
232                 { "$", 0, false },
233                 { "+", 0, true },
234                 { " +", 0, false },
235                 { "+-", 0, true },
236                 { "+ -", 0, false },
237                 { "+-  ", 0, true },
238                 { "+-  $", 0, false },
239                 { "+-%", 3, true },
240                 { "  +-  %  ", 0, false },
241                 { "+-  %  ", 7, true },
242                 { "+-%$", 3, false } };
243         for (Object[] cas : cases) {
244             String input = (String) cas[0];
245             int expectedOffset = (Integer) cas[1];
246             boolean expectedMaybeMore = (Boolean) cas[2];
247 
248             StringSegment segment = new StringSegment(input, false);
249             ParsedNumber result = new ParsedNumber();
250             boolean actualMaybeMore = series.match(segment, result);
251             int actualOffset = segment.getOffset();
252 
253             assertEquals("'" + input + "'", expectedOffset, actualOffset);
254             assertEquals("'" + input + "'", expectedMaybeMore, actualMaybeMore);
255         }
256     }
257 
258     @Test
testCombinedCurrencyMatcher()259     public void testCombinedCurrencyMatcher() {
260         AffixTokenMatcherFactory factory = new AffixTokenMatcherFactory();
261         factory.locale = ULocale.US;
262         CustomSymbolCurrency currency = new CustomSymbolCurrency("ICU", "IU$", "ICU");
263         factory.currency = currency;
264         factory.symbols = DecimalFormatSymbols.getInstance(ULocale.US);
265         factory.parseFlags = 0;
266         CombinedCurrencyMatcher matcher = factory.currency();
267         factory.parseFlags = ParsingUtils.PARSE_FLAG_NO_FOREIGN_CURRENCIES;
268         CombinedCurrencyMatcher matcherNoForeignCurrencies = factory.currency();
269 
270         Object[][] cases = new Object[][] {
271                 { "", null, null },
272                 { "FOO", null, null },
273                 { "USD", "USD", null },
274                 { "$", "USD", null },
275                 { "US dollars", "USD", null },
276                 { "eu", null, null },
277                 { "euros", "EUR", null },
278                 { "ICU", "ICU", "ICU" },
279                 { "IU$", "ICU", "ICU" } };
280         for (Object[] cas : cases) {
281             String input = (String) cas[0];
282             String expectedCurrencyCode = (String) cas[1];
283             String expectedNoForeignCurrencyCode = (String) cas[2];
284 
285             {
286                 StringSegment segment = new StringSegment(input, true);
287                 ParsedNumber result = new ParsedNumber();
288                 matcher.match(segment, result);
289                 assertEquals("Parsing " + input,
290                         expectedCurrencyCode,
291                         result.currencyCode);
292                 assertEquals("Whole string on " + input,
293                         expectedCurrencyCode == null ? 0 : input.length(),
294                         result.charEnd);
295             }
296             {
297                 StringSegment segment = new StringSegment(input, true);
298                 ParsedNumber result = new ParsedNumber();
299                 matcherNoForeignCurrencies.match(segment, result);
300                 assertEquals("[no foreign] Parsing " + input,
301                         expectedNoForeignCurrencyCode,
302                         result.currencyCode);
303                 assertEquals("[no foreign] Whole string on " + input,
304                         expectedNoForeignCurrencyCode == null ? 0 : input.length(),
305                         result.charEnd);
306             }
307         }
308     }
309 
310     @Test
testAffixPatternMatcher()311     public void testAffixPatternMatcher() {
312         AffixTokenMatcherFactory factory = new AffixTokenMatcherFactory();
313         factory.currency = Currency.getInstance("EUR");
314         factory.symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
315         factory.ignorables = IgnorablesMatcher.getInstance(0);
316         factory.locale = ULocale.ENGLISH;
317         factory.parseFlags = 0;
318 
319         Object[][] cases = {
320                 { false, "-", 1, "-" },
321                 { false, "+-%", 5, "+-%" },
322                 { true, "+-%", 3, "+-%" },
323                 { false, "ab c", 5, "a    bc" },
324                 { true, "abc", 3, "abc" },
325                 { false, "hello-to+this%very¤long‰string", 59, "hello-to+this%very USD long‰string" } };
326 
327         for (Object[] cas : cases) {
328             boolean exactMatch = (Boolean) cas[0];
329             String affixPattern = (String) cas[1];
330             int expectedMatcherLength = (Integer) cas[2];
331             String sampleParseableString = (String) cas[3];
332             int parseFlags = exactMatch ? ParsingUtils.PARSE_FLAG_EXACT_AFFIX : 0;
333 
334             AffixPatternMatcher matcher = AffixPatternMatcher
335                     .fromAffixPattern(affixPattern, factory, parseFlags);
336 
337             // Check that the matcher has the expected number of children
338             assertEquals(affixPattern + " " + exactMatch, expectedMatcherLength, matcher.length());
339 
340             // Check that the matcher works on a sample string
341             StringSegment segment = new StringSegment(sampleParseableString, true);
342             ParsedNumber result = new ParsedNumber();
343             matcher.match(segment, result);
344             assertEquals(affixPattern + " " + exactMatch,
345                     sampleParseableString.length(),
346                     result.charEnd);
347         }
348     }
349 
350     @Test
testGroupingDisabled()351     public void testGroupingDisabled() {
352         DecimalFormatProperties properties = new DecimalFormatProperties();
353         properties.setGroupingSize(0);
354         DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
355         NumberParserImpl parser = NumberParserImpl
356                 .createParserFromProperties(properties, symbols, false);
357         ParsedNumber result = new ParsedNumber();
358         parser.parse("12,345.678", true, result);
359         assertEquals("Should not parse with grouping separator",
360                 12.0,
361                 result.getNumber().doubleValue(),
362                 0.0);
363     }
364 
365     @Test
testCaseFolding()366     public void testCaseFolding() {
367         Object[][] cases = new Object[][] {
368                 // pattern, input string, case sensitive chars, case insensitive chars
369                 { "0", "JP¥3456", 7, 7 },
370                 { "0", "jp¥3456", 0, 0 }, // not to be accepted, even in case insensitive mode
371                 { "A0", "A5", 2, 2 },
372                 { "A0", "a5", 0, 2 },
373                 { "0", "NaN", 3, 3 },
374                 { "0", "nan", 0, 3 } };
375         for (Object[] cas : cases) {
376             String patternString = (String) cas[0];
377             String inputString = (String) cas[1];
378             int expectedCaseSensitiveChars = (Integer) cas[2];
379             int expectedCaseFoldingChars = (Integer) cas[3];
380 
381             NumberParserImpl caseSensitiveParser = NumberParserImpl
382                     .createSimpleParser(ULocale.ENGLISH, patternString, 0);
383             ParsedNumber result = new ParsedNumber();
384             caseSensitiveParser.parse(inputString, true, result);
385             assertEquals("Case-Sensitive: " + inputString + " on " + patternString,
386                     expectedCaseSensitiveChars,
387                     result.charEnd);
388 
389             NumberParserImpl caseFoldingParser = NumberParserImpl.createSimpleParser(ULocale.ENGLISH,
390                     patternString,
391                     ParsingUtils.PARSE_FLAG_IGNORE_CASE);
392             result = new ParsedNumber();
393             caseFoldingParser.parse(inputString, true, result);
394             assertEquals("Folded: " + inputString + " on " + patternString,
395                     expectedCaseFoldingChars,
396                     result.charEnd);
397         }
398     }
399 
400     @Test
test20360_BidiOverflow()401     public void test20360_BidiOverflow() {
402         StringBuilder inputString = new StringBuilder();
403         inputString.append('-');
404         for (int i=0; i<100000; i++) {
405             inputString.append('\u061C');
406         }
407         inputString.append('5');
408 
409         NumberParserImpl parser = NumberParserImpl.createSimpleParser(ULocale.ENGLISH, "0", 0);
410 
411         ParsedNumber resultObject = new ParsedNumber();
412         parser.parse(inputString.toString(), true, resultObject);
413         assertTrue("Greedy Parse, success", resultObject.success());
414         assertEquals("Greedy Parse, chars consumed", 100002, resultObject.charEnd);
415         assertEquals("Greedy Parse, expected double", -5, resultObject.getNumber().intValue());
416 
417         resultObject.clear();
418         parser.parse(inputString.toString(), false, resultObject);
419         assertFalse("Non-Greedy Parse, success", resultObject.success());
420         assertEquals("Non-Greedy Parse, chars consumed", 1, resultObject.charEnd);
421     }
422 
423     @Test
testInfiniteRecursion()424     public void testInfiniteRecursion() {
425         StringBuilder inputString = new StringBuilder();
426         inputString.append('-');
427         for (int i=0; i<200; i++) {
428             inputString.append('\u061C');
429         }
430         inputString.append('5');
431 
432         NumberParserImpl parser = NumberParserImpl.createSimpleParser(ULocale.ENGLISH, "0", 0);
433 
434         ParsedNumber resultObject = new ParsedNumber();
435         parser.parse(inputString.toString(), false, resultObject);
436         assertFalse("Default recursion limit, success", resultObject.success());
437         assertEquals("Default recursion limit, chars consumed", 1, resultObject.charEnd);
438 
439         parser = NumberParserImpl.createSimpleParser(
440                 ULocale.ENGLISH, "0", ParsingUtils.PARSE_FLAG_ALLOW_INFINITE_RECURSION);
441         resultObject.clear();
442         parser.parse(inputString.toString(), false, resultObject);
443         assertTrue("Unlimited recursion, success", resultObject.success());
444         assertEquals("Unlimited recursion, chars consumed", 202, resultObject.charEnd);
445         assertEquals("Unlimited recursion, expected double", -5, resultObject.getNumber().intValue());
446     }
447 }
448