1 // Copyright 2017 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include "xfa/fgas/crt/cfgas_stringformatter.h"
8
9 #include <iterator>
10
11 #include "build/build_config.h"
12 #include "core/fpdfapi/page/cpdf_pagemodule.h"
13 #include "core/fxcrt/cfx_datetime.h"
14 #include "testing/fx_string_testhelpers.h"
15 #include "testing/fxgc_unittest.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "testing/scoped_set_tz.h"
19 #include "v8/include/cppgc/persistent.h"
20 #include "xfa/fxfa/parser/cxfa_localemgr.h"
21
22 using ::testing::ElementsAre;
23
24 class CFGASStringFormatterTest : public FXGCUnitTest {
25 public:
CFGASStringFormatterTest()26 CFGASStringFormatterTest() : scoped_tz_("UTC") { CPDF_PageModule::Create(); }
27
~CFGASStringFormatterTest()28 ~CFGASStringFormatterTest() override { CPDF_PageModule::Destroy(); }
29
Mgr(const WideString & locale)30 CXFA_LocaleMgr* Mgr(const WideString& locale) {
31 return cppgc::MakeGarbageCollected<CXFA_LocaleMgr>(
32 heap()->GetAllocationHandle(), heap(), nullptr, locale);
33 }
34
35 private:
36 ScopedSetTZ scoped_tz_;
37 };
38
39 // TODO(dsinclair): Looks like the formatter/parser does not handle the various
40 // 'g' flags.
TEST_F(CFGASStringFormatterTest,DateFormat)41 TEST_F(CFGASStringFormatterTest, DateFormat) {
42 struct {
43 const wchar_t* locale;
44 const wchar_t* input;
45 const wchar_t* pattern;
46 const wchar_t* output;
47 } kTests[] = {
48 {L"en", L"2002-10-25", L"MMMM DD, YYYY", L"October 25, 2002"},
49 // Note, this is in the doc as 5 but it's wrong and should be 3 by the
50 // example in the Picture Clause Reference section.
51 {L"en", L"20040722", L"'Week of the month is' w",
52 L"Week of the month is 3"},
53 {L"en", L"20040722", L"e 'days after Sunday'", L"4 days after Sunday"},
54 {L"en", L"20040722", L"YYYY-'W'WW-e", L"2004-W30-4"},
55 {L"en", L"20040722", L"E 'days after Saturday'",
56 L"5 days after Saturday"},
57 {L"en", L"2000-01-01", L"EEE, 'the' D 'of' MMMM, YYYY",
58 L"Sat, the 1 of January, 2000"},
59 {L"en", L"2000-01-01", L"EEEE, 'the' D 'of' MMMM, YYYY",
60 L"Saturday, the 1 of January, 2000"},
61 {L"en", L"19991202", L"MM/D/YY", L"12/2/99"},
62 {L"en", L"19990110", L"MMM D, YYYY", L"Jan 10, 1999"},
63 {L"en", L"19990202", L"J", L"33"},
64 {L"en", L"19990202", L"JJJ", L"033"},
65 {L"en", L"19991231", L"J", L"365"},
66 {L"en", L"20001231", L"J", L"366"},
67 {L"en", L"19990501", L"J", L"121"},
68 {L"en", L"19990901", L"J", L"244"},
69 {L"en", L"19990228", L"J", L"59"},
70 {L"en", L"20000229", L"J", L"60"},
71 {L"en", L"21000501", L"J", L"121"},
72 {L"en", L"19990102", L"M", L"1"},
73 {L"en", L"19990102", L"MMM", L"Jan"},
74 {L"en", L"19990102", L"YYYY G", L"1999 AD"},
75 // Week 01 of the year is the week containing Jan 04.
76 // {L"en", L"19990102", L"WW", L"00"}, -- Returns 01 incorrectly
77 // {L"en", L"19990104", L"WW", L"01"}, -- Returns 02 incorrectly
78 // The ?*+ should format as whitespace.
79 // {L"en", L"19990104", L"YYYY?*+MM", L"1999 01"},
80 // {L"en", L"1999-07-16", L"date{DD/MM/YY} '('date{MMM DD, YYYY}')'",
81 // L"16/07/99 (Jul 16, 1999)"},
82 {L"de_CH", L"20041030", L"D. MMMM YYYY", L"30. Oktober 2004"},
83 {L"fr_CA", L"20041030", L"D MMMM YYYY", L"30 octobre 2004"},
84 {L"en", L"2002-10-25", L"date(fr){DD MMMM, YYYY}", L"25 octobre, 2002"},
85 {L"en", L"2002-10-25", L"date(es){EEEE, D 'de' MMMM 'de' YYYY}",
86 L"viernes, 25 de octubre de 2002"},
87 // {L"en", L"2002-20-25", L"date.long(fr)()", L"25 octobre, 2002"},
88 // {L"ja", L"2003-11-03", L"gY/M/D", L"H15/11/3"},
89 // {L"ja", L"1989-01-08", L"ggY-M-D", L"\u5e731-1-8"},
90 // {L"ja", L"1989-11-03", L"gggYY/MM/DD", L"\u5e73\u621089/11/03"},
91 };
92 // Note, none of the full width date symbols are listed here
93 // as they are not supported. In theory there are the full width versions
94 // of DDD, DDDD, MMM, MMMM, E, e, gg, YYY, YYYYY.
95
96 size_t i = 0;
97 for (const auto& test : kTests) {
98 WideString result;
99 CFGAS_StringFormatter fmt(test.pattern);
100 EXPECT_TRUE(fmt.FormatDateTime(Mgr(test.locale), test.input,
101 CFGAS_StringFormatter::DateTimeType::kDate,
102 &result));
103 EXPECT_EQ(test.output, result) << " TEST: " << i;
104 ++i;
105 }
106 }
107
TEST_F(CFGASStringFormatterTest,TimeFormat)108 TEST_F(CFGASStringFormatterTest, TimeFormat) {
109 static const struct {
110 const wchar_t* locale;
111 const wchar_t* input;
112 const wchar_t* pattern;
113 const wchar_t* output;
114 } kTests[] = {
115 {L"en", L"01:01:11", L"h:M A", L"1:1 AM"},
116 {L"en", L"13:01:11", L"h:M A", L"1:1 PM"},
117 {L"en", L"01:01:11", L"hh:MM:SS A", L"01:01:11 AM"},
118 {L"en", L"13:01:11", L"hh:MM:SS A", L"01:01:11 PM"},
119 {L"en", L"01:01:11", L"hh:MM:SS A Z", L"01:01:11 AM GMT-02:00"},
120 {L"en", L"01:01:11", L"hh:MM:SS A z", L"01:01:11 AM -02:00"},
121 // {L"en", L"01:01:11", L"hh:MM:SS A zz", L"01:01:11 AM GMT"},
122 // Should change ?*+ into ' ' when formatting.
123 // {L"en", L"01:01:11", L"hh:MM:SS?*+A", L"01:01:11 AM"},
124 {L"en", L"12:01:01", L"k:MM:SS", L"12:01:01"},
125 {L"en", L"14:01:01", L"k:MM:SS", L"2:01:01"},
126 {L"en", L"12:01:11", L"kk:MM", L"12:01"},
127 {L"en", L"14:01:11", L"kk:MM", L"02:01"},
128 {L"en", L"12:01:11 +04:30", L"kk:MM", L"05:31"},
129 {L"en", L"12:01:11", L"kk:MM A", L"12:01 PM"},
130 {L"en", L"00:01:01", L"H:M:S", L"0:1:1"},
131 {L"en", L"13:02:11", L"H:M:S", L"13:2:11"},
132 {L"en", L"00:01:11.001", L"HH:M:S.FFF", L"00:1:11.001"},
133 {L"en", L"13:02:11", L"HH:M", L"13:2"},
134 {L"en", L"00:01:11", L"K:M", L"24:1"},
135 {L"en", L"00:02:11", L"KK:M", L"24:2"},
136 {L"en", L"11:11:11", L"HH:MM:SS 'o''clock' A Z",
137 L"11:11:11 o'clock AM GMT-02:00"},
138 {L"en", L"14:30:59", L"h:MM A", L"2:30 PM"},
139 {L"en", L"14:30:59", L"HH:MM:SS A Z", L"14:30:59 PM GMT-02:00"}};
140 // Note, none of the full width time symbols are listed here
141 // as they are not supported. In theory there are the full
142 // width versions of kkk, kkkk, HHH, HHHH, KKK, KKKK, MMM, MMMM,
143 // SSS, SSSS plus 2 more that the spec apparently forgot to
144 // list the symbol.
145
146 // The z modifier only appends if the TZ is outside of +0
147 {
148 ScopedSetTZ scoped_tz("UTC+2");
149 size_t i = 0;
150 for (const auto& test : kTests) {
151 WideString result;
152 CFGAS_StringFormatter fmt(test.pattern);
153 EXPECT_TRUE(fmt.FormatDateTime(Mgr(test.locale), test.input,
154 CFGAS_StringFormatter::DateTimeType::kTime,
155 &result));
156 EXPECT_EQ(test.output, result) << " TEST: " << i;
157 }
158 ++i;
159 }
160 }
161
TEST_F(CFGASStringFormatterTest,DateTimeFormat)162 TEST_F(CFGASStringFormatterTest, DateTimeFormat) {
163 static const struct {
164 const wchar_t* locale;
165 const wchar_t* input;
166 const wchar_t* pattern;
167 const wchar_t* output;
168 } kTests[] = {
169 {L"en", L"1999-07-16T10:30Z",
170 L"'At' time{HH:MM Z} 'on' date{MMM DD, YYYY}",
171 L"At 10:30 GMT on Jul 16, 1999"},
172 {L"en", L"1999-07-16T10:30", L"'At' time{HH:MM} 'on' date{MMM DD, YYYY}",
173 L"At 10:30 on Jul 16, 1999"},
174 {L"en", L"1999-07-16T10:30Z",
175 L"time{'At' HH:MM Z} date{'on' MMM DD, YYYY}",
176 L"At 10:30 GMT on Jul 16, 1999"},
177 {L"en", L"1999-07-16T10:30Z",
178 L"time{'At 'HH:MM Z}date{' on 'MMM DD, YYYY}",
179 L"At 10:30 GMT on Jul 16, 1999"},
180 {L"en", L"9111T1111:", L"MMM D, YYYYTh:MM:SS A",
181 L"Jan 1, 9111 11:11:00 AM"}};
182
183 size_t i = 0;
184 for (const auto test : kTests) {
185 WideString result;
186 CFGAS_StringFormatter fmt(test.pattern);
187 EXPECT_TRUE(fmt.FormatDateTime(
188 Mgr(test.locale), test.input,
189 CFGAS_StringFormatter::DateTimeType::kDateTime, &result));
190 EXPECT_EQ(test.output, result) << " TEST: " << i;
191 ++i;
192 }
193 }
194
TEST_F(CFGASStringFormatterTest,TimeDateFormat)195 TEST_F(CFGASStringFormatterTest, TimeDateFormat) {
196 static const struct {
197 const wchar_t* locale;
198 const wchar_t* input;
199 const wchar_t* pattern;
200 const wchar_t* output;
201 } kTests[] = {
202 {L"en", L"1999-07-16T10:30Z",
203 L"'At' time{HH:MM Z} 'on' date{MMM DD, YYYY}",
204 L"At 10:30 GMT on Jul 16, 1999"},
205 {L"en", L"1999-07-16T10:30", L"'At' time{HH:MM} 'on' date{MMM DD, YYYY}",
206 L"At 10:30 on Jul 16, 1999"},
207 {L"en", L"1999-07-16T10:30Z",
208 L"time{'At' HH:MM Z} date{'on' MMM DD, YYYY}",
209 L"At 10:30 GMT on Jul 16, 1999"},
210 {L"en", L"1999-07-16T10:30Z",
211 L"time{'At 'HH:MM Z}date{' on 'MMM DD, YYYY}",
212 L"At 10:30 GMT on Jul 16, 1999"}};
213
214 size_t i = 0;
215 for (const auto& test : kTests) {
216 WideString result;
217 CFGAS_StringFormatter fmt(test.pattern);
218 EXPECT_TRUE(fmt.FormatDateTime(
219 Mgr(test.locale), test.input,
220 CFGAS_StringFormatter::DateTimeType::kTimeDate, &result));
221 EXPECT_EQ(test.output, result) << " TEST: " << i;
222 ++i;
223 }
224 }
225
TEST_F(CFGASStringFormatterTest,DateParse)226 TEST_F(CFGASStringFormatterTest, DateParse) {
227 static const struct {
228 const wchar_t* locale;
229 const wchar_t* input;
230 const wchar_t* pattern;
231 CFX_DateTime output;
232 } kTests[] = {
233 {L"en", L"12/2/99", L"MM/D/YY", CFX_DateTime(1999, 12, 2, 0, 0, 0, 0)},
234 {L"en", L"2/2/99", L"M/D/YY", CFX_DateTime(1999, 2, 2, 0, 0, 0, 0)},
235 {L"en", L"2/2/10", L"M/D/YY", CFX_DateTime(2010, 2, 2, 0, 0, 0, 0)},
236 {L"en", L"Jan 10, 1999", L"MMM D, YYYY",
237 CFX_DateTime(1999, 1, 10, 0, 0, 0, 0)},
238 {L"en", L"Jan 10, 1999 AD", L"MMM D, YYYY G",
239 CFX_DateTime(1999, 1, 10, 0, 0, 0, 0)},
240 // TODO(dsinclair): Should this be -2 instead of 2?
241 {L"en", L"Jan 10, 0002 BC", L"MMM D, YYYY G",
242 CFX_DateTime(2, 1, 10, 0, 0, 0, 0)},
243 {L"en", L"October 25, 2002", L"MMMM DD, YYYY",
244 CFX_DateTime(2002, 10, 25, 0, 0, 0, 0)},
245 // TODO(dsinclair): The J and JJJ are ignored during parsing when they
246 // could be turned back into a date.
247 {L"en", L"1999-33", L"YYYY-J", CFX_DateTime(1999, 1, 1, 0, 0, 0, 0)},
248 {L"en", L"1999-033", L"YYYY-JJJ", CFX_DateTime(1999, 1, 1, 0, 0, 0, 0)},
249 {L"de_CH", L"30. Oktober 2004", L"D. MMMM YYYY",
250 CFX_DateTime(2004, 10, 30, 0, 0, 0, 0)},
251 {L"fr_CA", L"30 octobre 2004", L"D MMMM YYYY",
252 CFX_DateTime(2004, 10, 30, 0, 0, 0, 0)},
253 {L"en", L"Saturday, the 1 of January, 2000",
254 L"EEEE, 'the' D 'of' MMMM, YYYY", CFX_DateTime(2000, 1, 1, 0, 0, 0, 0)},
255 {L"en", L"Sat, the 1 of January, 2000", L"EEE, 'the' D 'of' MMMM, YYYY",
256 CFX_DateTime(2000, 1, 1, 0, 0, 0, 0)},
257 {L"en", L"7, the 1 of January, 2000", // 7 == Saturday as 1 == Sunday
258 L"E, 'the' D 'of' MMMM, YYYY", CFX_DateTime(2000, 1, 1, 0, 0, 0, 0)},
259 {L"en", L"6, the 1 of January, 2000", // 6 == Saturday as 1 == Monday
260 L"e, 'the' D 'of' MMMM, YYYY", CFX_DateTime(2000, 1, 1, 0, 0, 0, 0)},
261 {L"en", L"2004-07-22 Week of the month is 3",
262 L"YYYY-MM-DD 'Week of the month is' w",
263 CFX_DateTime(2004, 7, 22, 0, 0, 0, 0)},
264 {L"en", L"2004-07-22 Week of the year is 03",
265 L"YYYY-MM-DD 'Week of the year is' WW",
266 CFX_DateTime(2004, 7, 22, 0, 0, 0, 0)}
267 // {L"ja", L"H15/11/3", L"gY/M/D", CFX_DateTime(2003, 11, 3, 0, 0, 0, 0)},
268 // {L"ja", L"\u5e731-1-8", L"ggY-M-D", CFX_DateTime(1989, 1, 8, 0, 0, 0,
269 // 0)}, {L"ja", L"\u5e73\u621089/11/03", L"gggYY/MM/DD",
270 // CFX_DateTime(1989, 11, 3, 0, 0, 0, 0)},
271 // {L"ja", L"u337b99/01/08", L"\u0067\u0067YY/MM/DD",
272 // CFX_DateTime(1999, 1, 8, 0, 0, 0, 0)}
273 };
274 // Note, none of the full width date symbols are listed here as they are
275 // not supported. In theory there are the full width versions of DDD,
276 // DDDD, MMM, MMMM, E, e, gg, YYY, YYYYY.
277
278 size_t i = 0;
279 for (const auto& test : kTests) {
280 CFX_DateTime result;
281 CFGAS_StringFormatter fmt(test.pattern);
282 EXPECT_TRUE(fmt.ParseDateTime(Mgr(test.locale), test.input,
283 CFGAS_StringFormatter::DateTimeType::kDate,
284 &result));
285 EXPECT_EQ(test.output, result) << " TEST: " << i;
286 ++i;
287 }
288 }
289
290 // TODO(dsinclair): GetDateTimeFormat is broken and doesn't allow just returning
291 // a parsed Time. It will assume it's a Date. The method needs to be re-written.
292 // TEST_F(CFGASStringFormatterTest, TimeParse) {
293 // struct {
294 // const wchar_t* locale;
295 // const wchar_t* input;
296 // const wchar_t* pattern;
297 // CFX_DateTime output;
298 // } tests[] = {
299 // {L"en", L"18:00", L"HH:MM", CFX_DateTime(0, 0, 0, 18, 0, 0, 0)},
300 // {L"en", L"12.59 Uhr", L"H.MM 'Uhr'", CFX_DateTime(0, 0, 0, 12, 59, 0,
301 // 0)}, {L"en", L"1:05:10 PM PST", L"h:MM:SS A Z",
302 // CFX_DateTime(0, 0, 0, 17, 05, 10, 0)}};
303 // // Note, none of the full width date symbols are listed here as they are
304 // // not supported. In theory there are the full width versions of kkk,
305 // // kkkk, HHH, HHHH, KKK, KKKK, MMM, MMMM, SSS, SSSS plus 2 more that the
306 // // spec apparently forgot to list the symbol.
307
308 // for (size_t i = 0; i < std::size(tests); ++i) {
309 // CFX_DateTime result;
310 // EXPECT_TRUE(fmt(tests[i].locale)
311 // ->ParseDateTime(tests[i].input, tests[i].pattern,
312 // CFGAS_StringFormatter::DateTimeType::kTime,
313 // &result));
314 // EXPECT_EQ(tests[i].output, result) << " TEST: " << i;
315 // }
316 // }
317
TEST_F(CFGASStringFormatterTest,SplitFormatString)318 TEST_F(CFGASStringFormatterTest, SplitFormatString) {
319 std::vector<WideString> results = CFGAS_StringFormatter::SplitOnBars(L"");
320 EXPECT_EQ(1UL, results.size());
321 EXPECT_TRUE(results[0].IsEmpty());
322
323 results = CFGAS_StringFormatter::SplitOnBars(L"|");
324 EXPECT_EQ(2UL, results.size());
325 EXPECT_TRUE(results[0].IsEmpty());
326 EXPECT_TRUE(results[1].IsEmpty());
327
328 results = CFGAS_StringFormatter::SplitOnBars(
329 L"null{'No|data'} | null{} | text{999*9999} | text{999*999*9999}");
330 EXPECT_THAT(results,
331 ElementsAre(L"null{'No|data'} ", L" null{} ", L" text{999*9999} ",
332 L" text{999*999*9999}"));
333 }
334
TEST_F(CFGASStringFormatterTest,NumParse)335 TEST_F(CFGASStringFormatterTest, NumParse) {
336 struct TestCase {
337 const wchar_t* locale;
338 const wchar_t* input;
339 const wchar_t* pattern;
340 const wchar_t* output;
341 };
342
343 static const TestCase tests[] = {
344 // {L"en", L"€100.00", L"num(en_GB){$z,zz9.99}", L"100"},
345 // {L"en", L"1050", L"99V99", L"10.50"},
346 // {L"en", L"3125", L"99V99", L"31.25"},
347 {L"en", L"12.345e3", L"99.999E", L"12345.000000"},
348 {L"en", L"12.345e+3", L"99.999E", L"12345.000000"},
349 {L"en", L"12.345E-2", L"99.999E", L"0.123450"},
350 // TODO(dsinclair): Returns 0.000?
351 // {L"en", L"12e-2", L"99E", L"0.12"},
352 {L"en", L"150", L"z999", L"150"},
353 {L"en", L"150.50$", L"zzz.zz$", L"150.50"},
354 {L"en", L"0150", L"z999", L"0150"},
355 {L"en", L"123CR", L"999cr", L"-123"},
356 {L"en", L"123", L"999cr", L"123"},
357 {L"en", L"123CR", L"999CR", L"-123"},
358 {L"en", L"123 ", L"999CR", L"123"},
359 {L"en", L"123DB", L"999db", L"-123"},
360 {L"en", L"123", L"999db", L"123"},
361 {L"en", L"123DB", L"999DB", L"-123"},
362 {L"en", L"123 ", L"999DB", L"123"},
363 {L"en", L"123.5CR", L"999.9cr", L"-123.5"},
364 {L"en", L"123.5", L"999.9cr", L"123.5"},
365 {L"en", L"123.5CR", L"999.9CR", L"-123.5"},
366 // {L"en", L"123.5 ", L"999.9CR", L"123.5"},
367 {L"en", L"123.5DB", L"999.9db", L"-123.5"},
368 {L"en", L"123.5", L"999.9db", L"123.5"},
369 {L"en", L"123.5DB", L"999.9DB", L"-123.5"},
370 // {L"en", L"123.5 ", L"999.9DB", L"123.5"},
371 {L"en", L"10.50", L"z,zz9.99", L"10.50"},
372 {L"en", L"3,125.00", L"z,zz9.99", L"3125.00"},
373 {L"en", L"$1,234.00", L"$z,zz9.99DB", L"1234.00"},
374 // TODO(dsinclair): Comes out as 1234 instead of -1234.
375 // {L"en", L"$,1234.00DB", L"$z,zz9.99DB", L"-1234.00"},
376 {L"en", L"1.234", L"zz9.zzz", L"1.234"},
377 {L"en", L"1 text", L"num{z 'text'}", L"1"},
378 {L"en", L"1.234 text", L"z.zzz 'text'", L"1.234"},
379 {L"en", L" 1.234", L"ZZ9.ZZZ", L"1.234"},
380 {L"en", L"12.345", L"zz9.zzz", L"12.345"},
381 {L"en", L" 12.345", L"ZZ9.ZZZ", L"12.345"},
382 {L"en", L"123.456", L"zz9.zzz", L"123.456"},
383 {L"en", L"123.456", L"ZZ9.ZZZ", L"123.456"},
384 {L"en", L"123.456-", L"ZZ9.ZZZS", L"-123.456"},
385 {L"en", L"123.456+", L"ZZ9.ZZZS", L"123.456"},
386 {L"en", L"123.456 ", L"ZZ9.ZZZS", L"123.456"},
387 {L"en", L"123.456-", L"ZZ9.ZZZS", L"-123.456"},
388 {L"en", L"123.456+", L"ZZ9.ZZZS", L"123.456"},
389 {L"en", L"123", L"zz9.zzz", L"123"},
390 {L"en", L"123.", L"ZZ9.ZZZ", L"123."},
391 {L"en", L"123.", L"zz9.zzz", L"123."},
392 {L"en", L"123.", L"ZZ9.ZZZ", L"123."},
393 {L"en", L"123.0", L"zz9.zzz", L"123.0"},
394 {L"en", L"123.0", L"ZZ9.ZZZ", L"123.0"},
395 {L"en", L"123.000", L"zz9.zzz", L"123.000"},
396 {L"en", L"123.000", L"ZZ9.ZZZ", L"123.000"},
397 {L"en", L"12,345.67", L"zzz,zz9.88888888", L"12345.67"},
398 {L"en", L"12,345.0000", L"zzz,zz9.88888888", L"12345.0000"},
399 {L"en", L"12,345.6789", L"zzz,zz9.8", L"12345.6789"},
400 {L"en", L"12,345.", L"zzz,zz9.8", L"12345."},
401 {L"en", L"123,456.000", L"zzz,zz9.8888", L"123456.000"},
402 {L"en", L"123,456.0", L"zzz,zz9.8888", L"123456.0"},
403 {L"en", L"123,456", L"zzz,zz9.8888", L"123456"},
404 {L"en", L"123,456", L"ZZZ,ZZ9.88", L"123456"},
405 {L"en", L"12,345.67", L"zzz,zz9.88888888", L"12345.67"},
406 {L"en", L"12,345.0000", L"zzz,zz9.88888888", L"12345.0000"},
407 {L"en", L"12,345.6789", L"zzz,zz9.8", L"12345.6789"},
408 {L"en", L"12,345.", L"zzz,zz9.8", L"12345."},
409 // TODO(dsinclair): Parses to 0
410 // {L"en", L"12%", L"zz9.%%", L".12"},
411 {L"en", L"1,234.50%", L"zzz,zz9.99%%", L"12.345"},
412 // {L"en", L"-00123", L"S999v99", L"-1.23"},
413 {L"en", L" 001.23", L"S999V99", L"001.23"},
414 // {L"en", L" 123.00", L"S999V99", L"123"},
415 {L"en", L" 12.30", L"SZZ9.99", L"12.30"},
416 {L"en", L"- 12.30", L"SZ99.99", L"-12.30"},
417 {L"en", L"123.00", L"szz9.99", L"123.00"},
418 {L"en", L"-123.00", L"szz9.99", L"-123.00"},
419 // {L"en", L"$ 1,234.00 ", L"$ZZ,ZZ9.99CR", L"1234"},
420 // {L"en", L"$ 1,234.00CR", L"$ZZ,ZZ9.99CR", L"-1234"},
421 // {L"en", L"$1,23400", L"$z,zz9.99DB", L"1234"},
422 {L"en", L"$1,234.00DB", L"$z,zz9.99DB", L"-1234.00"},
423 {L"en",
424 L"1\xA0"
425 L"234",
426 L"num(fr){z,zzz}", L"1234"},
427 // TODO(dsinclair): Parses to blank
428 // {L"en", L"1,234%", L"num.percent{}", L"12.34"},
429 // {L"en", L"1\xA0" L"234%%", L"num(fr).percent{}", L"12.34"},
430 // TODO(dsinclair): Parses to blank
431 // {L"en", L"1,234%", L"num{9,999%%}", L"12.34"},
432 {L"fr",
433 L"123\xA0"
434 L"456",
435 L"zzz,zzz", L"123456"},
436 {L"en", L"12%", L"zz%", L"0.12"},
437 {L"en", L"(123", L"(zzz", L"-123"},
438 {L"en", L"123)", L"zzz)", L"-123"},
439 {L"en", L"(123)", L"(zzz)", L"-123"},
440 {L"en", L"123 ", L"zzz)", L"123"},
441 {L"en", L" 123", L"(zzz", L"123"},
442 {L"en", L" 123 ", L"(zzz)", L"123"},
443 {L"en", L"123.5(", L"zzz.z(", L"-123.5"},
444 {L"en", L"123.5)", L"zzz.z)", L"-123.5"},
445 {L"en", L"123.5 ", L"zzz.z)", L"123.5"},
446 {L"en", L"123.5 ", L"zzz.z(", L"123.5"},
447 {L"en", L"123.545,4", L"zzz.zzz,z", L"123.5454"},
448 // https://crbug.com/938724
449 {L"en", L"1", L" num.().().}", L"1"},
450 };
451
452 static const TestCase failures[] = {
453 // https://crbug.com/pdfium/1260
454 {L"en", L"..", L"VC", L"."},
455
456 // https://crbug.com/938626
457 {L"en", L"PDF", L"num( ", L"."},
458
459 // https://crbug.com/945836
460 {L"en", L"9.E99999999999", L"EdEE.E999", L""},
461
462 // https://crbug.com/947188
463 {L"en", L"-3.E98998998 ", L" 35EEEE.EE98", L""},
464 };
465
466 for (const auto& test : tests) {
467 WideString result;
468 CFGAS_StringFormatter fmt(test.pattern);
469 EXPECT_TRUE(fmt.ParseNum(Mgr(test.locale), test.input, &result))
470 << " TEST: " << test.input << ", " << test.pattern;
471 EXPECT_EQ(test.output, result)
472 << " TEST: " << test.input << ", " << test.pattern;
473 }
474
475 for (const auto& test : failures) {
476 WideString result;
477 CFGAS_StringFormatter fmt(test.pattern);
478 EXPECT_FALSE(fmt.ParseNum(Mgr(test.locale), test.input, &result))
479 << " TEST: " << test.input << ", " << test.pattern;
480 }
481 }
482
TEST_F(CFGASStringFormatterTest,NumFormat)483 TEST_F(CFGASStringFormatterTest, NumFormat) {
484 struct TestCase {
485 const wchar_t* locale;
486 const wchar_t* input;
487 const wchar_t* pattern;
488 const wchar_t* output;
489 };
490
491 static const TestCase tests[] = {
492 {L"en", L"1.234", L"zz9.zzz", L"1.234"},
493 {L"en", L"1", L"num{z 'text'}", L"1 text"},
494 {L"en", L"1", L"num{'text' z}", L"text 1"},
495 {L"en", L"1.234", L"ZZ9.ZZZ", L" 1.234"},
496 {L"en", L"12.345", L"zz9.zzz", L"12.345"},
497 {L"en", L"12.345", L"ZZ9.ZZZ", L" 12.345"},
498 {L"en", L"123.456", L"zz9.zzz", L"123.456"},
499 {L"en", L"123.456", L"ZZ9.ZZZ", L"123.456"},
500 {L"en", L"123", L"zz9.zzz", L"123"},
501 {L"en", L"123", L"ZZ9.ZZZ", L"123.000"},
502 {L"en", L"123.", L"zz9.zzz", L"123."},
503 {L"en", L"123.", L"ZZ9.ZZZ", L"123.000"},
504 {L"en", L"123.0", L"zz9.zzz", L"123"},
505 {L"en", L"123.0", L"ZZ9.ZZZ", L"123.000"},
506 {L"en", L"123.000", L"zz9.zzz", L"123"},
507 {L"en", L"123.000", L"ZZ9.ZZZ", L"123.000"},
508 // {L"en", L"12345.67", L"zzz,zz9.88888888", L"12,345.67"},
509 // {L"en", L"12345.0000", L"zzz,zz9.88888888", L"12,345.0000"},
510 // {L"en", L"12345.6789", L"zzz,zz9.8", L"12,345.6789"},
511 // {L"en", L"12345.", L"zzz,zz9.8", L"12,345"},
512 // {L"en", L"123456.000", L"zzz,zz9.8888", L"123,456.000"},
513 // {L"en", L"123456.0", L"zzz,zz9.8888", L"123,456.0"},
514 {L"en", L"123456", L"zzz,zz9.8888", L"123,456"},
515 {L"en", L"123456", L"ZZZ,ZZ9.88", L"123,456"},
516 // {L"en", L"12345.67", L"zzz,zz9.88888888", L"12,345.67"},
517 // {L"en", L"12345.0000", L"zzz,zz9.88888888", L"12,345.0000"},
518 // {L"en", L"12345.6789", L"zzz,zz9.8", L"12,345.6789"},
519 // {L"en", L"12345.", L"zzz,zz9.8", L"12,345"},
520 // {L"en", L"12%%", L"zz9.%%", L"12%%"},
521 // {L"en", L"1,234.5%%", L"zzz,zz9.99%%", L"1,234.50%%"},
522 {L"en", L"-1.23", L"S999v99", L"-00123"},
523 {L"en", L"1.23", L"S999V99", L" 001.23"},
524 {L"en", L"123", L"S999V99", L" 123.00"},
525 {L"en", L"12.3", L"SZZ9.99", L" 12.30"},
526 {L"en", L"-12.3", L"SZ99.99", L"- 12.30"},
527 {L"en", L"123", L"szz9.99", L"123.00"},
528 {L"en", L"-123", L"szz9.99", L"-123.00"},
529 // {L"en", L"1234", L"$ZZ,ZZ9.99CR", L"$ 1,234.00 "},
530 // {L"en", L"-1234", L"$ZZ,ZZ9.99CR", L"$ 1,234.00CR"},
531 // {L"en", L"1234", L"$z,zz9.99DB", L"$1,234.00"},
532 {L"en", L"-1234", L"$z,zz9.99DB", L"$1,234.00DB"},
533 {L"en", L"12345", L"99.999E", L"12.345E+3"},
534 {L"en", L"12345", L"99999E", L"12345E+0"},
535 {L"en", L".12345", L"99.999E", L"12.345E-2"},
536 {L"en", L"12345", L"99,999", L"12,345"},
537 {L"en", L"1234", L"num(fr){z,zzz}",
538 L"1\xA0"
539 L"234"},
540 {L"en", L"12.34", L"num.percent{}", L"1,234%"},
541 {L"en", L"12.34", L"num(fr).percent{}",
542 L"1\xA0"
543 L"234%"},
544 // {L"en", L"12.34", L"num{9,999%%}", L"1,234%"},
545 {L"en", L"-123", L"zzzCR", L"123CR"},
546 {L"en", L"123", L"zzzCR", L"123 "},
547 {L"en", L"-123", L"zzzcr", L"123CR"},
548 {L"en", L"123", L"zzzcr", L"123"},
549 {L"en", L"123", L"zzz$", L"123$"},
550 {L"en", L"-123.5", L"zzz.zCR", L"123.5CR"},
551 {L"en", L"123.5", L"zzz.zCR", L"123.5 "},
552 {L"en", L"-123.5", L"zzz.zcr", L"123.5CR"},
553 {L"en", L"123.5", L"zzz.zcr", L"123.5"},
554
555 {L"en", L"-123.5", L"999.9db", L"123.5db"},
556 {L"en", L"123.5", L"999.9db", L"123.5"},
557 {L"en", L"-123.5", L"999.9DB", L"123.5DB"},
558 {L"en", L"123.5", L"999.9DB", L"123.5 "},
559
560 {L"en", L"-123", L"(zzz", L"(123"},
561 // {L"en", L"-123", L"zzz)", L"123)"},
562 {L"en", L"-123", L"(zzz)", L"(123)"},
563 {L"en", L"123", L"zzz)", L"123 "},
564 {L"en", L"123", L"(zzz", L" 123"},
565 {L"en", L"123", L"(zzz)", L" 123 "},
566 {L"en", L"-123.5", L"zzz.z(", L"123.5("},
567 // {L"en", L"-123.5", L"zzz.z)", L"123.5)"},
568 {L"en", L"123.5", L"zzz.z)", L"123.5 "},
569 {L"en", L"123.5", L"zzz.z(", L"123.5 "},
570 // https://crbug.com/pdfium/1233
571 {L"en", L"1", L"r9", L"r1"},
572 {L"en", L"1", L"R9", L"R1"},
573 {L"en", L"1", L"b9", L"b1"},
574 {L"en", L"1", L"B9", L"B1"},
575 {L"en", L"1", L"9.c", L"1"},
576 {L"en", L"1", L"9.C", L"1"},
577 {L"en", L"1", L"9.d", L"1"},
578 {L"en", L"1", L"9.D", L"1"},
579 // https://crbug.com/pdfium/1244
580 {L"en", L"1", L"E", L"1"},
581 {L"en", L"0", L"E", L"0"},
582 {L"en", L"-1", L"E", L"-1"},
583 {L"en", L"900000000000000000000", L"E", L"900,000,000,000,000,000,000"},
584 // TODO(tsepez): next one seems wrong
585 // {L"en", L".000000000000000000009", L"E", L"9"},
586 // https://crbug.com/938724
587 {L"en", L"1", L"| num.().().", L"1"},
588 // https://crbug.com/942449
589 {L"en", L"1", L"9.", L"1"},
590 };
591
592 static const TestCase failures[] = {
593 // https://crbug.com/pdfium/1271
594 {L"en", L"1", L"num.{E", L""},
595 };
596
597 for (const auto& test : tests) {
598 WideString result;
599 CFGAS_StringFormatter fmt(test.pattern);
600 EXPECT_TRUE(fmt.FormatNum(Mgr(test.locale), test.input, &result))
601 << " TEST: " << test.input << ", " << test.pattern;
602 EXPECT_EQ(test.output, result)
603 << " TEST: " << test.input << ", " << test.pattern;
604 }
605
606 for (const auto& test : failures) {
607 WideString result;
608 CFGAS_StringFormatter fmt(test.pattern);
609 EXPECT_FALSE(fmt.FormatNum(Mgr(test.locale), test.input, &result))
610 << " TEST: " << test.input << ", " << test.pattern;
611 }
612 }
613
TEST_F(CFGASStringFormatterTest,TextParse)614 TEST_F(CFGASStringFormatterTest, TextParse) {
615 static const struct {
616 const wchar_t* input;
617 const wchar_t* pattern;
618 const wchar_t* output;
619 } kTests[] = {// TODO(dsinclair) Missing support for the global modifiers:
620 // ? - wildcard
621 // * - zero or more whitespace
622 // + - one or more whitespace
623 // {L"en", L"555-1212", L"text(th_TH){999*9999}", L"5551212"},
624 {L"ABC-1234-5", L"AAA-9999-X", L"ABC12345"},
625 {L"ABC-1234-D", L"AAA-9999-X", L"ABC1234D"},
626 {L"A1C-1234-D", L"OOO-9999-X", L"A1C1234D"},
627 {L"A1C-1234-D", L"000-9999-X", L"A1C1234D"},
628 {L"A1C-1234-D text", L"000-9999-X 'text'", L"A1C1234D"}};
629
630 size_t i = 0;
631 for (const auto& test : kTests) {
632 WideString result;
633 CFGAS_StringFormatter fmt(test.pattern);
634 EXPECT_TRUE(fmt.ParseText(test.input, &result));
635 EXPECT_EQ(test.output, result) << " TEST: " << i;
636 }
637 }
638
TEST_F(CFGASStringFormatterTest,InvalidTextParse)639 TEST_F(CFGASStringFormatterTest, InvalidTextParse) {
640 // Input does not match mask.
641 WideString result;
642 CFGAS_StringFormatter fmt(L"AAA-9999-X");
643 EXPECT_FALSE(fmt.ParseText(L"123-4567-8", &result));
644 }
645
TEST_F(CFGASStringFormatterTest,TextFormat)646 TEST_F(CFGASStringFormatterTest, TextFormat) {
647 static const struct {
648 const wchar_t* locale;
649 const wchar_t* input;
650 const wchar_t* pattern;
651 const wchar_t* output;
652 } kTests[] = {
653 {L"en", L"K1S5K2", L"A9A 9A9", L"K1S 5K2"},
654 {L"en", L"K1S5K2", L"text(fr){A9A 9A9}", L"K1S 5K2"},
655 {L"en", L"6135551212", L"'+1 ('9\u002399') '999-9999",
656 L"+1 (6#13) 555-1212"},
657 {L"en", L"6135551212", L"999.999.9999", L"613.555.1212"},
658 {L"en", L"6135551212", L"999\u0023999\u002A9999", L"613#555*1212"},
659 {L"en", L"K1#5K2", L"00X OO9", L"K1# 5K2"},
660 };
661
662 size_t i = 0;
663 for (const auto& test : kTests) {
664 WideString result;
665 CFGAS_StringFormatter fmt(test.pattern);
666 EXPECT_TRUE(fmt.FormatText(test.input, &result));
667 EXPECT_EQ(test.output, result) << " TEST: " << i;
668 ++i;
669 }
670 }
671
TEST_F(CFGASStringFormatterTest,NullParse)672 TEST_F(CFGASStringFormatterTest, NullParse) {
673 static const struct {
674 const wchar_t* input;
675 const wchar_t* pattern;
676 } kTests[] = {
677 {L"", L"null{}"},
678 {L"No data", L"null{'No data'}"},
679 };
680 size_t i = 0;
681 for (const auto& test : kTests) {
682 CFGAS_StringFormatter fmt(test.pattern);
683 EXPECT_TRUE(fmt.ParseNull(test.input)) << " TEST: " << i;
684 ++i;
685 }
686 }
687
TEST_F(CFGASStringFormatterTest,NullFormat)688 TEST_F(CFGASStringFormatterTest, NullFormat) {
689 static const struct {
690 const wchar_t* pattern;
691 const wchar_t* output;
692 } kTests[] = {{L"null{'n/a'}", L"n/a"}, {L"null{}", L""}};
693
694 size_t i = 0;
695 for (const auto& test : kTests) {
696 WideString result;
697 CFGAS_StringFormatter fmt(test.pattern);
698 EXPECT_TRUE(fmt.FormatNull(&result));
699 EXPECT_EQ(test.output, result) << " TEST: " << i;
700 ++i;
701 }
702 }
703
TEST_F(CFGASStringFormatterTest,ZeroParse)704 TEST_F(CFGASStringFormatterTest, ZeroParse) {
705 static const struct {
706 const wchar_t* input;
707 const wchar_t* pattern;
708 } kTests[] = {{L"", L"zero{}"}, {L"9", L"zero{9}"}, {L"a", L"zero{'a'}"}};
709
710 size_t i = 0;
711 for (const auto& test : kTests) {
712 CFGAS_StringFormatter fmt(test.pattern);
713 EXPECT_TRUE(fmt.ParseZero(test.input)) << " TEST: " << i;
714 ++i;
715 }
716 }
717
TEST_F(CFGASStringFormatterTest,ZeroFormat)718 TEST_F(CFGASStringFormatterTest, ZeroFormat) {
719 static const struct {
720 const wchar_t* input;
721 const wchar_t* pattern;
722 const wchar_t* output;
723 } kTests[] = {// TODO(dsinclair): The zero format can take a number specifier
724 // which we don't take into account.
725 // {L"", L"zero {9}", L""},
726 // {L"0", L"zero {9}", L"0"},
727 // {L"0.0", L"zero{9}", L"0"},
728 {L"0", L"zero{}", L""}};
729
730 size_t i = 0;
731 for (const auto& test : kTests) {
732 WideString result;
733 CFGAS_StringFormatter fmt(test.pattern);
734 EXPECT_TRUE(fmt.FormatZero(&result));
735 EXPECT_EQ(test.output, result) << " TEST: " << i;
736 ++i;
737 }
738 }
739
TEST_F(CFGASStringFormatterTest,GetCategory)740 TEST_F(CFGASStringFormatterTest, GetCategory) {
741 EXPECT_EQ(CFGAS_StringFormatter::Category::kUnknown,
742 CFGAS_StringFormatter(L"'just text'").GetCategory());
743 EXPECT_EQ(CFGAS_StringFormatter::Category::kNull,
744 CFGAS_StringFormatter(L"null{}").GetCategory());
745 EXPECT_EQ(CFGAS_StringFormatter::Category::kZero,
746 CFGAS_StringFormatter(L"zero{}").GetCategory());
747 EXPECT_EQ(CFGAS_StringFormatter::Category::kNum,
748 CFGAS_StringFormatter(L"num{}").GetCategory());
749 EXPECT_EQ(CFGAS_StringFormatter::Category::kText,
750 CFGAS_StringFormatter(L"text{}").GetCategory());
751 EXPECT_EQ(CFGAS_StringFormatter::Category::kDateTime,
752 CFGAS_StringFormatter(L"datetime{}").GetCategory());
753 EXPECT_EQ(CFGAS_StringFormatter::Category::kTime,
754 CFGAS_StringFormatter(L"time{}").GetCategory());
755 EXPECT_EQ(CFGAS_StringFormatter::Category::kDate,
756 CFGAS_StringFormatter(L"date{}").GetCategory());
757 EXPECT_EQ(CFGAS_StringFormatter::Category::kDateTime,
758 CFGAS_StringFormatter(L"time{} date{}").GetCategory());
759 EXPECT_EQ(CFGAS_StringFormatter::Category::kDateTime,
760 CFGAS_StringFormatter(L"date{} time{}").GetCategory());
761 EXPECT_EQ(CFGAS_StringFormatter::Category::kNum,
762 CFGAS_StringFormatter(L"num(en_GB){}").GetCategory());
763 EXPECT_EQ(CFGAS_StringFormatter::Category::kDate,
764 CFGAS_StringFormatter(L"date.long{}").GetCategory());
765 }
766