Unreviewed, rolling out r234489.
[WebKit-https.git] / Source / WebCore / platform / text / PlatformLocale.cpp
1 /*
2  * Copyright (C) 2011,2012 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "PlatformLocale.h"
33
34 #include "DateTimeFormat.h"
35 #include "LocalizedStrings.h"
36 #include <wtf/text/StringBuilder.h>
37
38 namespace WebCore {
39
40 #if ENABLE(DATE_AND_TIME_INPUT_TYPES)
41
42 class DateTimeStringBuilder : private DateTimeFormat::TokenHandler {
43     WTF_MAKE_NONCOPYABLE(DateTimeStringBuilder);
44
45 public:
46     // The argument objects must be alive until this object dies.
47     DateTimeStringBuilder(Locale&, const DateComponents&);
48
49     bool build(const String&);
50     String toString();
51
52 private:
53     // DateTimeFormat::TokenHandler functions.
54     void visitField(DateTimeFormat::FieldType, int) final;
55     void visitLiteral(const String&) final;
56
57     String zeroPadString(const String&, size_t width);
58     void appendNumber(int number, size_t width);
59
60     StringBuilder m_builder;
61     Locale& m_localizer;
62     const DateComponents& m_date;
63 };
64
65 DateTimeStringBuilder::DateTimeStringBuilder(Locale& localizer, const DateComponents& date)
66     : m_localizer(localizer)
67     , m_date(date)
68 {
69 }
70
71 bool DateTimeStringBuilder::build(const String& formatString)
72 {
73     m_builder.reserveCapacity(formatString.length());
74     return DateTimeFormat::parse(formatString, *this);
75 }
76
77 String DateTimeStringBuilder::zeroPadString(const String& string, size_t width)
78 {
79     if (string.length() >= width)
80         return string;
81     StringBuilder zeroPaddedStringBuilder;
82     zeroPaddedStringBuilder.reserveCapacity(width);
83     for (size_t i = string.length(); i < width; ++i)
84         zeroPaddedStringBuilder.append('0');
85     zeroPaddedStringBuilder.append(string);
86     return zeroPaddedStringBuilder.toString();
87 }
88
89 void DateTimeStringBuilder::appendNumber(int number, size_t width)
90 {
91     String zeroPaddedNumberString = zeroPadString(String::number(number), width);
92     m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedNumberString));
93 }
94
95 void DateTimeStringBuilder::visitField(DateTimeFormat::FieldType fieldType, int numberOfPatternCharacters)
96 {
97     switch (fieldType) {
98     case DateTimeFormat::FieldTypeYear:
99         // Always use padding width of 4 so it matches DateTimeEditElement.
100         appendNumber(m_date.fullYear(), 4);
101         return;
102     case DateTimeFormat::FieldTypeMonth:
103         if (numberOfPatternCharacters == 3)
104             m_builder.append(m_localizer.shortMonthLabels()[m_date.month()]);
105         else if (numberOfPatternCharacters == 4)
106             m_builder.append(m_localizer.monthLabels()[m_date.month()]);
107         else {
108             // Always use padding width of 2 so it matches DateTimeEditElement.
109             appendNumber(m_date.month() + 1, 2);
110         }
111         return;
112     case DateTimeFormat::FieldTypeMonthStandAlone:
113         if (numberOfPatternCharacters == 3)
114             m_builder.append(m_localizer.shortStandAloneMonthLabels()[m_date.month()]);
115         else if (numberOfPatternCharacters == 4)
116             m_builder.append(m_localizer.standAloneMonthLabels()[m_date.month()]);
117         else {
118             // Always use padding width of 2 so it matches DateTimeEditElement.
119             appendNumber(m_date.month() + 1, 2);
120         }
121         return;
122     case DateTimeFormat::FieldTypeDayOfMonth:
123         // Always use padding width of 2 so it matches DateTimeEditElement.
124         appendNumber(m_date.monthDay(), 2);
125         return;
126     case DateTimeFormat::FieldTypeWeekOfYear:
127         // Always use padding width of 2 so it matches DateTimeEditElement.
128         appendNumber(m_date.week(), 2);
129         return;
130     case DateTimeFormat::FieldTypePeriod:
131         m_builder.append(m_localizer.timeAMPMLabels()[(m_date.hour() >= 12 ? 1 : 0)]);
132         return;
133     case DateTimeFormat::FieldTypeHour12: {
134         int hour12 = m_date.hour() % 12;
135         if (!hour12)
136             hour12 = 12;
137         appendNumber(hour12, numberOfPatternCharacters);
138         return;
139     }
140     case DateTimeFormat::FieldTypeHour23:
141         appendNumber(m_date.hour(), numberOfPatternCharacters);
142         return;
143     case DateTimeFormat::FieldTypeHour11:
144         appendNumber(m_date.hour() % 12, numberOfPatternCharacters);
145         return;
146     case DateTimeFormat::FieldTypeHour24: {
147         int hour24 = m_date.hour();
148         if (!hour24)
149             hour24 = 24;
150         appendNumber(hour24, numberOfPatternCharacters);
151         return;
152     }
153     case DateTimeFormat::FieldTypeMinute:
154         appendNumber(m_date.minute(), numberOfPatternCharacters);
155         return;
156     case DateTimeFormat::FieldTypeSecond:
157         if (!m_date.millisecond())
158             appendNumber(m_date.second(), numberOfPatternCharacters);
159         else {
160             double second = m_date.second() + m_date.millisecond() / 1000.0;
161             String zeroPaddedSecondString = zeroPadString(String::format("%.03f", second), numberOfPatternCharacters + 4);
162             m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedSecondString));
163         }
164         return;
165     default:
166         return;
167     }
168 }
169
170 void DateTimeStringBuilder::visitLiteral(const String& text)
171 {
172     ASSERT(text.length());
173     m_builder.append(text);
174 }
175
176 String DateTimeStringBuilder::toString()
177 {
178     return m_builder.toString();
179 }
180
181 #endif
182
183 Locale::~Locale() = default;
184
185 void Locale::setLocaleData(const Vector<String, DecimalSymbolsSize>& symbols, const String& positivePrefix, const String& positiveSuffix, const String& negativePrefix, const String& negativeSuffix)
186 {
187     for (size_t i = 0; i < symbols.size(); ++i) {
188         ASSERT(!symbols[i].isEmpty());
189         m_decimalSymbols[i] = symbols[i];
190     }
191     m_positivePrefix = positivePrefix;
192     m_positiveSuffix = positiveSuffix;
193     m_negativePrefix = negativePrefix;
194     m_negativeSuffix = negativeSuffix;
195     ASSERT(!m_positivePrefix.isEmpty() || !m_positiveSuffix.isEmpty() || !m_negativePrefix.isEmpty() || !m_negativeSuffix.isEmpty());
196     m_hasLocaleData = true;
197 }
198
199 String Locale::convertToLocalizedNumber(const String& input)
200 {
201     initializeLocaleData();
202     if (!m_hasLocaleData || input.isEmpty())
203         return input;
204
205     unsigned i = 0;
206     bool isNegative = false;
207     StringBuilder builder;
208     builder.reserveCapacity(input.length());
209
210     if (input[0] == '-') {
211         ++i;
212         isNegative = true;
213         builder.append(m_negativePrefix);
214     } else
215         builder.append(m_positivePrefix);
216
217     for (; i < input.length(); ++i) {
218         switch (input[i]) {
219         case '0':
220         case '1':
221         case '2':
222         case '3':
223         case '4':
224         case '5':
225         case '6':
226         case '7':
227         case '8':
228         case '9':
229             builder.append(m_decimalSymbols[input[i] - '0']);
230             break;
231         case '.':
232             builder.append(m_decimalSymbols[DecimalSeparatorIndex]);
233             break;
234         default:
235             ASSERT_NOT_REACHED();
236         }
237     }
238
239     builder.append(isNegative ? m_negativeSuffix : m_positiveSuffix);
240
241     return builder.toString();
242 }
243
244 static bool matches(const String& text, unsigned position, const String& part)
245 {
246     if (part.isEmpty())
247         return true;
248     if (position + part.length() > text.length())
249         return false;
250     for (unsigned i = 0; i < part.length(); ++i) {
251         if (text[position + i] != part[i])
252             return false;
253     }
254     return true;
255 }
256
257 bool Locale::detectSignAndGetDigitRange(const String& input, bool& isNegative, unsigned& startIndex, unsigned& endIndex)
258 {
259     startIndex = 0;
260     endIndex = input.length();
261     if (m_negativePrefix.isEmpty() && m_negativeSuffix.isEmpty()) {
262         if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
263             isNegative = false;
264             startIndex = m_positivePrefix.length();
265             endIndex -= m_positiveSuffix.length();
266         } else
267             isNegative = true;
268     } else {
269         if (input.startsWith(m_negativePrefix) && input.endsWith(m_negativeSuffix)) {
270             isNegative = true;
271             startIndex = m_negativePrefix.length();
272             endIndex -= m_negativeSuffix.length();
273         } else {
274             isNegative = false;
275             if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
276                 startIndex = m_positivePrefix.length();
277                 endIndex -= m_positiveSuffix.length();
278             } else
279                 return false;
280         }
281     }
282     return true;
283 }
284
285 unsigned Locale::matchedDecimalSymbolIndex(const String& input, unsigned& position)
286 {
287     for (unsigned symbolIndex = 0; symbolIndex < DecimalSymbolsSize; ++symbolIndex) {
288         if (m_decimalSymbols[symbolIndex].length() && matches(input, position, m_decimalSymbols[symbolIndex])) {
289             position += m_decimalSymbols[symbolIndex].length();
290             return symbolIndex;
291         }
292     }
293     return DecimalSymbolsSize;
294 }
295
296 String Locale::convertFromLocalizedNumber(const String& localized)
297 {
298     initializeLocaleData();
299     String input = localized.stripWhiteSpace();
300     if (!m_hasLocaleData || input.isEmpty())
301         return input;
302
303     bool isNegative;
304     unsigned startIndex;
305     unsigned endIndex;
306     if (!detectSignAndGetDigitRange(input, isNegative, startIndex, endIndex))
307         return input;
308
309     StringBuilder builder;
310     builder.reserveCapacity(input.length());
311     if (isNegative)
312         builder.append('-');
313     for (unsigned i = startIndex; i < endIndex;) {
314         unsigned symbolIndex = matchedDecimalSymbolIndex(input, i);
315         if (symbolIndex >= DecimalSymbolsSize)
316             return input;
317         if (symbolIndex == DecimalSeparatorIndex)
318             builder.append('.');
319         else if (symbolIndex == GroupSeparatorIndex)
320             return input;
321         else
322             builder.append(static_cast<UChar>('0' + symbolIndex));
323     }
324     return builder.toString();
325 }
326
327 #if ENABLE(DATE_AND_TIME_INPUT_TYPES)
328
329 #if !PLATFORM(IOS)
330 String Locale::formatDateTime(const DateComponents& date, FormatType formatType)
331 {
332     if (date.type() == DateComponents::Invalid)
333         return String();
334 #if !ENABLE(INPUT_TYPE_WEEK)
335     if (date.type() == DateComponents::Week)
336         return String();
337 #endif
338
339     DateTimeStringBuilder builder(*this, date);
340     switch (date.type()) {
341     case DateComponents::Time:
342         builder.build(formatType == FormatTypeShort ? shortTimeFormat() : timeFormat());
343         break;
344     case DateComponents::Date:
345         builder.build(dateFormat());
346         break;
347     case DateComponents::Month:
348         builder.build(formatType == FormatTypeShort ? shortMonthFormat() : monthFormat());
349         break;
350     case DateComponents::Week:    
351 #if ENABLE(INPUT_TYPE_WEEK)
352         builder.build(weekFormatInLDML());
353         break;
354 #endif
355     case DateComponents::DateTime:
356     case DateComponents::DateTimeLocal:
357         builder.build(formatType == FormatTypeShort ? dateTimeFormatWithoutSeconds() : dateTimeFormatWithSeconds());
358         break;
359     case DateComponents::Invalid:
360         ASSERT_NOT_REACHED();
361         break;
362     }
363     return builder.toString();
364 }
365 #endif // !PLATFORM(IOS)
366
367 #endif
368
369 }