Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / 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 "platform/text/PlatformLocale.h"
33
34 #include "platform/text/DateTimeFormat.h"
35 #include "public/platform/Platform.h"
36 #include "wtf/MainThread.h"
37 #include "wtf/text/StringBuilder.h"
38
39 namespace blink {
40
41 class DateTimeStringBuilder : private DateTimeFormat::TokenHandler {
42     WTF_MAKE_NONCOPYABLE(DateTimeStringBuilder);
43 public:
44     // The argument objects must be alive until this object dies.
45     DateTimeStringBuilder(Locale&, const DateComponents&);
46
47     bool build(const String&);
48     String toString();
49
50 private:
51     // DateTimeFormat::TokenHandler functions.
52     virtual void visitField(DateTimeFormat::FieldType, int) override final;
53     virtual void visitLiteral(const String&) override final;
54
55     String zeroPadString(const String&, size_t width);
56     void appendNumber(int number, size_t width);
57
58     StringBuilder m_builder;
59     Locale& m_localizer;
60     const DateComponents& m_date;
61 };
62
63 DateTimeStringBuilder::DateTimeStringBuilder(Locale& localizer, const DateComponents& date)
64     : m_localizer(localizer)
65     , m_date(date)
66 {
67 }
68
69 bool DateTimeStringBuilder::build(const String& formatString)
70 {
71     m_builder.reserveCapacity(formatString.length());
72     return DateTimeFormat::parse(formatString, *this);
73 }
74
75 String DateTimeStringBuilder::zeroPadString(const String& string, size_t width)
76 {
77     if (string.length() >= width)
78         return string;
79     StringBuilder zeroPaddedStringBuilder;
80     zeroPaddedStringBuilder.reserveCapacity(width);
81     for (size_t i = string.length(); i < width; ++i)
82         zeroPaddedStringBuilder.append('0');
83     zeroPaddedStringBuilder.append(string);
84     return zeroPaddedStringBuilder.toString();
85 }
86
87 void DateTimeStringBuilder::appendNumber(int number, size_t width)
88 {
89     String zeroPaddedNumberString = zeroPadString(String::number(number), width);
90     m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedNumberString));
91 }
92
93 void DateTimeStringBuilder::visitField(DateTimeFormat::FieldType fieldType, int numberOfPatternCharacters)
94 {
95     switch (fieldType) {
96     case DateTimeFormat::FieldTypeYear:
97         // Always use padding width of 4 so it matches DateTimeEditElement.
98         appendNumber(m_date.fullYear(), 4);
99         return;
100     case DateTimeFormat::FieldTypeMonth:
101         if (numberOfPatternCharacters == 3) {
102             m_builder.append(m_localizer.shortMonthLabels()[m_date.month()]);
103         } else if (numberOfPatternCharacters == 4) {
104             m_builder.append(m_localizer.monthLabels()[m_date.month()]);
105         } else {
106             // Always use padding width of 2 so it matches DateTimeEditElement.
107             appendNumber(m_date.month() + 1, 2);
108         }
109         return;
110     case DateTimeFormat::FieldTypeMonthStandAlone:
111         if (numberOfPatternCharacters == 3) {
112             m_builder.append(m_localizer.shortStandAloneMonthLabels()[m_date.month()]);
113         } else if (numberOfPatternCharacters == 4) {
114             m_builder.append(m_localizer.standAloneMonthLabels()[m_date.month()]);
115         } else {
116             // Always use padding width of 2 so it matches DateTimeEditElement.
117             appendNumber(m_date.month() + 1, 2);
118         }
119         return;
120     case DateTimeFormat::FieldTypeDayOfMonth:
121         // Always use padding width of 2 so it matches DateTimeEditElement.
122         appendNumber(m_date.monthDay(), 2);
123         return;
124     case DateTimeFormat::FieldTypeWeekOfYear:
125         // Always use padding width of 2 so it matches DateTimeEditElement.
126         appendNumber(m_date.week(), 2);
127         return;
128     case DateTimeFormat::FieldTypePeriod:
129         m_builder.append(m_localizer.timeAMPMLabels()[(m_date.hour() >= 12 ? 1 : 0)]);
130         return;
131     case DateTimeFormat::FieldTypeHour12: {
132         int hour12 = m_date.hour() % 12;
133         if (!hour12)
134             hour12 = 12;
135         appendNumber(hour12, numberOfPatternCharacters);
136         return;
137     }
138     case DateTimeFormat::FieldTypeHour23:
139         appendNumber(m_date.hour(), numberOfPatternCharacters);
140         return;
141     case DateTimeFormat::FieldTypeHour11:
142         appendNumber(m_date.hour() % 12, numberOfPatternCharacters);
143         return;
144     case DateTimeFormat::FieldTypeHour24: {
145         int hour24 = m_date.hour();
146         if (!hour24)
147             hour24 = 24;
148         appendNumber(hour24, numberOfPatternCharacters);
149         return;
150     }
151     case DateTimeFormat::FieldTypeMinute:
152         appendNumber(m_date.minute(), numberOfPatternCharacters);
153         return;
154     case DateTimeFormat::FieldTypeSecond:
155         if (!m_date.millisecond()) {
156             appendNumber(m_date.second(), numberOfPatternCharacters);
157         } else {
158             double second = m_date.second() + m_date.millisecond() / 1000.0;
159             String zeroPaddedSecondString = zeroPadString(String::format("%.03f", second), numberOfPatternCharacters + 4);
160             m_builder.append(m_localizer.convertToLocalizedNumber(zeroPaddedSecondString));
161         }
162         return;
163     default:
164         return;
165     }
166 }
167
168 void DateTimeStringBuilder::visitLiteral(const String& text)
169 {
170     ASSERT(text.length());
171     m_builder.append(text);
172 }
173
174 String DateTimeStringBuilder::toString()
175 {
176     return m_builder.toString();
177 }
178
179 Locale& Locale::defaultLocale()
180 {
181     static Locale* locale = Locale::create(defaultLanguage()).leakPtr();
182     ASSERT(isMainThread());
183     return *locale;
184 }
185
186 Locale::~Locale()
187 {
188 }
189
190 String Locale::queryString(WebLocalizedString::Name name)
191 {
192     // FIXME: Returns a string locazlied for this locale.
193     return Platform::current()->queryLocalizedString(name);
194 }
195
196 String Locale::queryString(WebLocalizedString::Name name, const String& parameter)
197 {
198     // FIXME: Returns a string locazlied for this locale.
199     return Platform::current()->queryLocalizedString(name, parameter);
200 }
201
202 String Locale::queryString(WebLocalizedString::Name name, const String& parameter1, const String& parameter2)
203 {
204     // FIXME: Returns a string locazlied for this locale.
205     return Platform::current()->queryLocalizedString(name, parameter1, parameter2);
206 }
207
208 String Locale::validationMessageTooLongText(unsigned valueLength, int maxLength)
209 {
210     return queryString(WebLocalizedString::ValidationTooLong, convertToLocalizedNumber(String::number(valueLength)), convertToLocalizedNumber(String::number(maxLength)));
211 }
212
213 String Locale::validationMessageTooShortText(unsigned valueLength, int minLength)
214 {
215     return queryString(WebLocalizedString::ValidationTooShort, convertToLocalizedNumber(String::number(valueLength)), convertToLocalizedNumber(String::number(minLength)));
216 }
217
218 String Locale::weekFormatInLDML()
219 {
220     String templ = queryString(WebLocalizedString::WeekFormatTemplate);
221     // Converts a string like "Week $2, $1" to an LDML date format pattern like
222     // "'Week 'ww', 'yyyy".
223     StringBuilder builder;
224     unsigned literalStart = 0;
225     unsigned length = templ.length();
226     for (unsigned i = 0; i + 1 < length; ++i) {
227         if (templ[i] == '$' && (templ[i + 1] == '1' || templ[i + 1] == '2')) {
228             if (literalStart < i)
229                 DateTimeFormat::quoteAndAppendLiteral(templ.substring(literalStart, i - literalStart), builder);
230             builder.append(templ[++i] == '1' ? "yyyy" : "ww");
231             literalStart = i + 1;
232         }
233     }
234     if (literalStart < length)
235         DateTimeFormat::quoteAndAppendLiteral(templ.substring(literalStart, length - literalStart), builder);
236     return builder.toString();
237 }
238
239 void Locale::setLocaleData(const Vector<String, DecimalSymbolsSize>& symbols, const String& positivePrefix, const String& positiveSuffix, const String& negativePrefix, const String& negativeSuffix)
240 {
241     for (size_t i = 0; i < symbols.size(); ++i) {
242         ASSERT(!symbols[i].isEmpty());
243         m_decimalSymbols[i] = symbols[i];
244     }
245     m_positivePrefix = positivePrefix;
246     m_positiveSuffix = positiveSuffix;
247     m_negativePrefix = negativePrefix;
248     m_negativeSuffix = negativeSuffix;
249     ASSERT(!m_positivePrefix.isEmpty() || !m_positiveSuffix.isEmpty() || !m_negativePrefix.isEmpty() || !m_negativeSuffix.isEmpty());
250     m_hasLocaleData = true;
251 }
252
253 String Locale::convertToLocalizedNumber(const String& input)
254 {
255     initializeLocaleData();
256     if (!m_hasLocaleData || input.isEmpty())
257         return input;
258
259     unsigned i = 0;
260     bool isNegative = false;
261     StringBuilder builder;
262     builder.reserveCapacity(input.length());
263
264     if (input[0] == '-') {
265         ++i;
266         isNegative = true;
267         builder.append(m_negativePrefix);
268     } else {
269         builder.append(m_positivePrefix);
270     }
271
272     for (; i < input.length(); ++i) {
273         switch (input[i]) {
274         case '0':
275         case '1':
276         case '2':
277         case '3':
278         case '4':
279         case '5':
280         case '6':
281         case '7':
282         case '8':
283         case '9':
284             builder.append(m_decimalSymbols[input[i] - '0']);
285             break;
286         case '.':
287             builder.append(m_decimalSymbols[DecimalSeparatorIndex]);
288             break;
289         default:
290             ASSERT_NOT_REACHED();
291         }
292     }
293
294     builder.append(isNegative ? m_negativeSuffix : m_positiveSuffix);
295
296     return builder.toString();
297 }
298
299 static bool matches(const String& text, unsigned position, const String& part)
300 {
301     if (part.isEmpty())
302         return true;
303     if (position + part.length() > text.length())
304         return false;
305     for (unsigned i = 0; i < part.length(); ++i) {
306         if (text[position + i] != part[i])
307             return false;
308     }
309     return true;
310 }
311
312 bool Locale::detectSignAndGetDigitRange(const String& input, bool& isNegative, unsigned& startIndex, unsigned& endIndex)
313 {
314     startIndex = 0;
315     endIndex = input.length();
316     if (m_negativePrefix.isEmpty() && m_negativeSuffix.isEmpty()) {
317         if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
318             isNegative = false;
319             startIndex = m_positivePrefix.length();
320             endIndex -= m_positiveSuffix.length();
321         } else {
322             isNegative = true;
323         }
324     } else {
325         if (input.startsWith(m_negativePrefix) && input.endsWith(m_negativeSuffix)) {
326             isNegative = true;
327             startIndex = m_negativePrefix.length();
328             endIndex -= m_negativeSuffix.length();
329         } else {
330             isNegative = false;
331             if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
332                 startIndex = m_positivePrefix.length();
333                 endIndex -= m_positiveSuffix.length();
334             } else {
335                 return false;
336             }
337         }
338     }
339     return true;
340 }
341
342 unsigned Locale::matchedDecimalSymbolIndex(const String& input, unsigned& position)
343 {
344     for (unsigned symbolIndex = 0; symbolIndex < DecimalSymbolsSize; ++symbolIndex) {
345         if (m_decimalSymbols[symbolIndex].length() && matches(input, position, m_decimalSymbols[symbolIndex])) {
346             position += m_decimalSymbols[symbolIndex].length();
347             return symbolIndex;
348         }
349     }
350     return DecimalSymbolsSize;
351 }
352
353 String Locale::convertFromLocalizedNumber(const String& localized)
354 {
355     initializeLocaleData();
356     String input = localized.removeCharacters(isASCIISpace);
357     if (!m_hasLocaleData || input.isEmpty())
358         return input;
359
360     bool isNegative;
361     unsigned startIndex;
362     unsigned endIndex;
363     if (!detectSignAndGetDigitRange(input, isNegative, startIndex, endIndex))
364         return input;
365
366     StringBuilder builder;
367     builder.reserveCapacity(input.length());
368     if (isNegative)
369         builder.append('-');
370     for (unsigned i = startIndex; i < endIndex;) {
371         unsigned symbolIndex = matchedDecimalSymbolIndex(input, i);
372         if (symbolIndex >= DecimalSymbolsSize)
373             return input;
374         if (symbolIndex == DecimalSeparatorIndex)
375             builder.append('.');
376         else if (symbolIndex == GroupSeparatorIndex)
377             return input;
378         else
379             builder.append(static_cast<UChar>('0' + symbolIndex));
380     }
381     return builder.toString();
382 }
383
384 #if ENABLE(INPUT_MULTIPLE_FIELDS_UI)
385 String Locale::localizedDecimalSeparator()
386 {
387     initializeLocaleData();
388     return m_decimalSymbols[DecimalSeparatorIndex];
389 }
390 #endif
391
392 String Locale::formatDateTime(const DateComponents& date, FormatType formatType)
393 {
394     if (date.type() == DateComponents::Invalid)
395         return String();
396
397     DateTimeStringBuilder builder(*this, date);
398     switch (date.type()) {
399     case DateComponents::Time:
400         builder.build(formatType == FormatTypeShort ? shortTimeFormat() : timeFormat());
401         break;
402     case DateComponents::Date:
403         builder.build(dateFormat());
404         break;
405     case DateComponents::Month:
406         builder.build(formatType == FormatTypeShort ? shortMonthFormat() : monthFormat());
407         break;
408     case DateComponents::Week:
409         builder.build(weekFormatInLDML());
410         break;
411     case DateComponents::DateTime:
412     case DateComponents::DateTimeLocal:
413         builder.build(formatType == FormatTypeShort ? dateTimeFormatWithoutSeconds() : dateTimeFormatWithSeconds());
414         break;
415     case DateComponents::Invalid:
416         ASSERT_NOT_REACHED();
417         break;
418     }
419     return builder.toString();
420 }
421
422 } // namespace blink