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