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