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