Rename ICULocale to LocaleICU, part 2
[WebKit-https.git] / Source / WebCore / platform / text / LocaleICU.cpp
1 /*
2  * Copyright (C) 2011 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 "LocaleICU.h"
33
34 #include "LocalizedStrings.h"
35 #include <limits>
36 #include <wtf/DateMath.h>
37 #include <wtf/PassOwnPtr.h>
38 #include <wtf/text/StringBuilder.h>
39
40 using namespace icu;
41 using namespace std;
42
43 namespace WebCore {
44
45 LocaleICU::LocaleICU(const char* locale)
46     : m_locale(locale)
47     , m_numberFormat(0)
48     , m_shortDateFormat(0)
49     , m_didCreateDecimalFormat(false)
50     , m_didCreateShortDateFormat(false)
51 #if ENABLE(CALENDAR_PICKER)
52     , m_firstDayOfWeek(0)
53 #endif
54 {
55 }
56
57 LocaleICU::~LocaleICU()
58 {
59     unum_close(m_numberFormat);
60     udat_close(m_shortDateFormat);
61 }
62
63 PassOwnPtr<LocaleICU> LocaleICU::create(const char* localeString)
64 {
65     return adoptPtr(new LocaleICU(localeString));
66 }
67
68 PassOwnPtr<LocaleICU> LocaleICU::createForCurrentLocale()
69 {
70     return adoptPtr(new LocaleICU(0));
71 }
72
73 LocaleICU* LocaleICU::currentLocale()
74 {
75     static LocaleICU* currentLocale = LocaleICU::createForCurrentLocale().leakPtr();
76     return currentLocale;
77 }
78
79 void LocaleICU::setDecimalSymbol(unsigned index, UNumberFormatSymbol symbol)
80 {
81     UErrorCode status = U_ZERO_ERROR;
82     int32_t bufferLength = unum_getSymbol(m_numberFormat, symbol, 0, 0, &status);
83     ASSERT(U_SUCCESS(status) || status == U_BUFFER_OVERFLOW_ERROR);
84     if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)
85         return;
86     Vector<UChar> buffer(bufferLength);
87     status = U_ZERO_ERROR;
88     unum_getSymbol(m_numberFormat, symbol, buffer.data(), bufferLength, &status);
89     if (U_FAILURE(status))
90         return;
91     m_decimalSymbols[index] = String::adopt(buffer);
92 }
93
94 void LocaleICU::setDecimalTextAttribute(String& destination, UNumberFormatTextAttribute tag)
95 {
96     UErrorCode status = U_ZERO_ERROR;
97     int32_t bufferLength = unum_getTextAttribute(m_numberFormat, tag, 0, 0, &status);
98     ASSERT(U_SUCCESS(status) || status == U_BUFFER_OVERFLOW_ERROR);
99     if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR)
100         return;
101     Vector<UChar> buffer(bufferLength);
102     status = U_ZERO_ERROR;
103     unum_getTextAttribute(m_numberFormat, tag, buffer.data(), bufferLength, &status);
104     ASSERT(U_SUCCESS(status));
105     if (U_FAILURE(status))
106         return;
107     destination = String::adopt(buffer);
108 }
109
110 void LocaleICU::initializeDecimalFormat()
111 {
112     if (m_didCreateDecimalFormat)
113         return;
114     m_didCreateDecimalFormat = true;
115     UErrorCode status = U_ZERO_ERROR;
116     m_numberFormat = unum_open(UNUM_DECIMAL, 0, 0, m_locale.data(), 0, &status);
117     if (!U_SUCCESS(status))
118         return;
119
120     setDecimalSymbol(0, UNUM_ZERO_DIGIT_SYMBOL);
121     setDecimalSymbol(1, UNUM_ONE_DIGIT_SYMBOL);
122     setDecimalSymbol(2, UNUM_TWO_DIGIT_SYMBOL);
123     setDecimalSymbol(3, UNUM_THREE_DIGIT_SYMBOL);
124     setDecimalSymbol(4, UNUM_FOUR_DIGIT_SYMBOL);
125     setDecimalSymbol(5, UNUM_FIVE_DIGIT_SYMBOL);
126     setDecimalSymbol(6, UNUM_SIX_DIGIT_SYMBOL);
127     setDecimalSymbol(7, UNUM_SEVEN_DIGIT_SYMBOL);
128     setDecimalSymbol(8, UNUM_EIGHT_DIGIT_SYMBOL);
129     setDecimalSymbol(9, UNUM_NINE_DIGIT_SYMBOL);
130     setDecimalSymbol(DecimalSeparatorIndex, UNUM_DECIMAL_SEPARATOR_SYMBOL);
131     setDecimalSymbol(GroupSeparatorIndex, UNUM_GROUPING_SEPARATOR_SYMBOL);
132     setDecimalTextAttribute(m_positivePrefix, UNUM_POSITIVE_PREFIX);
133     setDecimalTextAttribute(m_positiveSuffix, UNUM_POSITIVE_SUFFIX);
134     setDecimalTextAttribute(m_negativePrefix, UNUM_NEGATIVE_PREFIX);
135     setDecimalTextAttribute(m_negativeSuffix, UNUM_NEGATIVE_SUFFIX);
136     ASSERT(!m_positivePrefix.isEmpty() || !m_positiveSuffix.isEmpty() || !m_negativePrefix.isEmpty() || !m_negativeSuffix.isEmpty());
137 }
138
139 String LocaleICU::convertToLocalizedNumber(const String& input)
140 {
141     initializeDecimalFormat();
142     if (!m_numberFormat || input.isEmpty())
143         return input;
144
145     unsigned i = 0;
146     bool isNegative = false;
147     UnicodeString ustring;
148     StringBuilder builder;
149     builder.reserveCapacity(input.length());
150
151     if (input[0] == '-') {
152         ++i;
153         isNegative = true;
154         builder.append(m_negativePrefix);
155     } else
156         builder.append(m_positivePrefix);
157
158     for (; i < input.length(); ++i) {
159         switch (input[i]) {
160         case '0':
161         case '1':
162         case '2':
163         case '3':
164         case '4':
165         case '5':
166         case '6':
167         case '7':
168         case '8':
169         case '9':
170             builder.append(m_decimalSymbols[input[i] - '0']);
171             break;
172         case '.':
173             builder.append(m_decimalSymbols[DecimalSeparatorIndex]);
174             break;
175         default:
176             ASSERT_NOT_REACHED();
177         }
178     }
179
180     builder.append(isNegative ? m_negativeSuffix : m_positiveSuffix);
181
182     return builder.toString();
183 }
184
185 static bool matches(const String& text, unsigned position, const String& part)
186 {
187     if (part.isEmpty())
188         return true;
189     if (position + part.length() > text.length())
190         return false;
191     for (unsigned i = 0; i < part.length(); ++i) {
192         if (text[position + i] != part[i])
193             return false;
194     }
195     return true;
196 }
197
198 bool LocaleICU::detectSignAndGetDigitRange(const String& input, bool& isNegative, unsigned& startIndex, unsigned& endIndex)
199 {
200     startIndex = 0;
201     endIndex = input.length();
202     if (m_negativePrefix.isEmpty() && m_negativeSuffix.isEmpty()) {
203         if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
204             isNegative = false;
205             startIndex = m_positivePrefix.length();
206             endIndex -= m_positiveSuffix.length();
207         } else
208             isNegative = true;
209     } else {
210         if (input.startsWith(m_negativePrefix) && input.endsWith(m_negativeSuffix)) {
211             isNegative = true;
212             startIndex = m_negativePrefix.length();
213             endIndex -= m_negativeSuffix.length();
214         } else {
215             isNegative = false;
216             if (input.startsWith(m_positivePrefix) && input.endsWith(m_positiveSuffix)) {
217                 startIndex = m_positivePrefix.length();
218                 endIndex -= m_positiveSuffix.length();
219             } else
220                 return false;
221         }
222     }
223     return true;
224 }
225
226 unsigned LocaleICU::matchedDecimalSymbolIndex(const String& input, unsigned& position)
227 {
228     for (unsigned symbolIndex = 0; symbolIndex < DecimalSymbolsSize; ++symbolIndex) {
229         if (m_decimalSymbols[symbolIndex].length() && matches(input, position, m_decimalSymbols[symbolIndex])) {
230             position += m_decimalSymbols[symbolIndex].length();
231             return symbolIndex;
232         }
233     }
234     return DecimalSymbolsSize;
235 }
236
237 String LocaleICU::convertFromLocalizedNumber(const String& localized)
238 {
239     initializeDecimalFormat();
240     String input = localized.stripWhiteSpace();
241     if (!m_numberFormat || input.isEmpty())
242         return input;
243
244     bool isNegative;
245     unsigned startIndex;
246     unsigned endIndex;
247     if (!detectSignAndGetDigitRange(input, isNegative, startIndex, endIndex)) {
248         // Input is broken. Returning an invalid number string.
249         return "*";
250     }
251
252     StringBuilder builder;
253     builder.reserveCapacity(input.length());
254     if (isNegative)
255         builder.append("-");
256     for (unsigned i = startIndex; i < endIndex;) {
257         unsigned symbolIndex = matchedDecimalSymbolIndex(input, i);
258         if (symbolIndex >= DecimalSymbolsSize)
259             return "*";
260         if (symbolIndex == DecimalSeparatorIndex)
261             builder.append('.');
262         else if (symbolIndex == GroupSeparatorIndex) {
263             // Ignore group separators.
264
265         } else
266             builder.append(static_cast<UChar>('0' + symbolIndex));
267     }
268     return builder.toString();
269 }
270
271 bool LocaleICU::initializeShortDateFormat()
272 {
273     if (m_didCreateShortDateFormat)
274         return m_shortDateFormat;
275     const UChar gmtTimezone[3] = {'G', 'M', 'T'};
276     UErrorCode status = U_ZERO_ERROR;
277     m_shortDateFormat = udat_open(UDAT_NONE, UDAT_SHORT, m_locale.data(), gmtTimezone, WTF_ARRAY_LENGTH(gmtTimezone), 0, -1, &status);
278     m_didCreateShortDateFormat = true;
279     return m_shortDateFormat;
280 }
281
282 double LocaleICU::parseLocalizedDate(const String& input)
283 {
284     if (!initializeShortDateFormat())
285         return numeric_limits<double>::quiet_NaN();
286     if (input.length() > static_cast<unsigned>(numeric_limits<int32_t>::max()))
287         return numeric_limits<double>::quiet_NaN();
288     int32_t inputLength = static_cast<int32_t>(input.length());
289     UErrorCode status = U_ZERO_ERROR;
290     int32_t parsePosition = 0;
291     UDate date = udat_parse(m_shortDateFormat, input.characters(), inputLength, &parsePosition, &status);
292     if (parsePosition != inputLength || U_FAILURE(status))
293         return numeric_limits<double>::quiet_NaN();
294     // UDate, which is an alias of double, is compatible with our expectation.
295     return date;
296 }
297
298 String LocaleICU::formatLocalizedDate(const DateComponents& dateComponents)
299 {
300     if (!initializeShortDateFormat())
301         return String();
302     double input = dateComponents.millisecondsSinceEpoch();
303     UErrorCode status = U_ZERO_ERROR;
304     int32_t length = udat_format(m_shortDateFormat, input, 0, 0, 0, &status);
305     if (status != U_BUFFER_OVERFLOW_ERROR)
306         return String();
307     Vector<UChar> buffer(length);
308     status = U_ZERO_ERROR;
309     udat_format(m_shortDateFormat, input, buffer.data(), length, 0, &status);
310     if (U_FAILURE(status))
311         return String();
312     return String::adopt(buffer);
313 }
314
315 #if ENABLE(CALENDAR_PICKER)
316 static inline bool isICUYearSymbol(UChar letter)
317 {
318     return letter == 'y' || letter == 'Y';
319 }
320
321 static inline bool isICUMonthSymbol(UChar letter)
322 {
323     return letter == 'M';
324 }
325
326 static inline bool isICUDayInMonthSymbol(UChar letter)
327 {
328     return letter == 'd';
329 }
330
331 // Specification of the input:
332 // http://icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details
333 static String localizeFormat(const Vector<UChar>& buffer)
334 {
335     StringBuilder builder;
336     UChar lastChar = 0;
337     bool inQuote = false;
338     for (unsigned i = 0; i < buffer.size(); ++i) {
339         if (inQuote) {
340             if (buffer[i] == '\'') {
341                 inQuote = false;
342                 lastChar = 0;
343                 ASSERT(i);
344                 if (buffer[i - 1] == '\'')
345                     builder.append('\'');
346             } else
347                 builder.append(buffer[i]);
348         } else {
349             if (isASCIIAlpha(lastChar) && lastChar == buffer[i])
350                 continue;
351             lastChar = buffer[i];
352             if (isICUYearSymbol(lastChar)) {
353                 String text = dateFormatYearText();
354                 builder.append(text.isEmpty() ? "Year" : text);
355             } else if (isICUMonthSymbol(lastChar)) {
356                 String text = dateFormatMonthText();
357                 builder.append(text.isEmpty() ? "Month" : text);
358             } else if (isICUDayInMonthSymbol(lastChar)) {
359                 String text = dateFormatDayInMonthText();
360                 builder.append(text.isEmpty() ? "Day" : text);
361             } else if (lastChar == '\'')
362                 inQuote = true;
363             else
364                 builder.append(lastChar);
365         }
366     }
367     return builder.toString();
368 }
369
370 void LocaleICU::initializeLocalizedDateFormatText()
371 {
372     if (!m_localizedDateFormatText.isNull())
373         return;
374     m_localizedDateFormatText = String("");
375     if (!initializeShortDateFormat())
376         return;
377     UErrorCode status = U_ZERO_ERROR;
378     int32_t length = udat_toPattern(m_shortDateFormat, TRUE, 0, 0, &status);
379     if (status != U_BUFFER_OVERFLOW_ERROR)
380         return;
381     Vector<UChar> buffer(length);
382     status = U_ZERO_ERROR;
383     udat_toPattern(m_shortDateFormat, TRUE, buffer.data(), length, &status);
384     if (U_FAILURE(status))
385         return;
386     m_localizedDateFormatText = localizeFormat(buffer);
387 }
388
389 String LocaleICU::localizedDateFormatText()
390 {
391     initializeLocalizedDateFormatText();
392     return m_localizedDateFormatText;
393 }
394
395 PassOwnPtr<Vector<String> > LocaleICU::createLabelVector(UDateFormatSymbolType type, int32_t startIndex, int32_t size)
396 {
397     if (!m_shortDateFormat)
398         return PassOwnPtr<Vector<String> >();
399     if (udat_countSymbols(m_shortDateFormat, type) != startIndex + size)
400         return PassOwnPtr<Vector<String> >();
401
402     OwnPtr<Vector<String> > labels = adoptPtr(new Vector<String>());
403     labels->reserveCapacity(size);
404     for (int32_t i = 0; i < size; ++i) {
405         UErrorCode status = U_ZERO_ERROR;
406         int32_t length = udat_getSymbols(m_shortDateFormat, type, startIndex + i, 0, 0, &status);
407         if (status != U_BUFFER_OVERFLOW_ERROR)
408             return PassOwnPtr<Vector<String> >();
409         Vector<UChar> buffer(length);
410         status = U_ZERO_ERROR;
411         udat_getSymbols(m_shortDateFormat, type, startIndex + i, buffer.data(), length, &status);
412         if (U_FAILURE(status))
413             return PassOwnPtr<Vector<String> >();
414         labels->append(String::adopt(buffer));
415     }
416     return labels.release();
417 }
418
419 static PassOwnPtr<Vector<String> > createFallbackMonthLabels()
420 {
421     OwnPtr<Vector<String> > labels = adoptPtr(new Vector<String>());
422     labels->reserveCapacity(WTF_ARRAY_LENGTH(WTF::monthFullName));
423     for (unsigned i = 0; i < WTF_ARRAY_LENGTH(WTF::monthFullName); ++i)
424         labels->append(WTF::monthFullName[i]);
425     return labels.release();
426 }
427
428 static PassOwnPtr<Vector<String> > createFallbackWeekDayShortLabels()
429 {
430     OwnPtr<Vector<String> > labels = adoptPtr(new Vector<String>());
431     labels->reserveCapacity(7);
432     labels->append("Sun");
433     labels->append("Mon");
434     labels->append("Tue");
435     labels->append("Wed");
436     labels->append("Thu");
437     labels->append("Fri");
438     labels->append("Sat");
439     return labels.release();
440 }
441
442 void LocaleICU::initializeCalendar()
443 {
444     if (m_monthLabels && m_weekDayShortLabels)
445         return;
446
447     if (!initializeShortDateFormat()) {
448         m_firstDayOfWeek = 0;
449         m_monthLabels = createFallbackMonthLabels();
450         m_weekDayShortLabels = createFallbackWeekDayShortLabels();
451         return;
452     }
453     m_firstDayOfWeek = ucal_getAttribute(udat_getCalendar(m_shortDateFormat), UCAL_FIRST_DAY_OF_WEEK) - UCAL_SUNDAY;
454
455     m_monthLabels = createLabelVector(UDAT_MONTHS, UCAL_JANUARY, 12);
456     if (!m_monthLabels)
457         m_monthLabels = createFallbackMonthLabels();
458
459     m_weekDayShortLabels = createLabelVector(UDAT_SHORT_WEEKDAYS, UCAL_SUNDAY, 7);
460     if (!m_weekDayShortLabels)
461         m_weekDayShortLabels = createFallbackWeekDayShortLabels();
462 }
463
464 const Vector<String>& LocaleICU::monthLabels()
465 {
466     initializeCalendar();
467     return *m_monthLabels;
468 }
469
470 const Vector<String>& LocaleICU::weekDayShortLabels()
471 {
472     initializeCalendar();
473     return *m_weekDayShortLabels;
474 }
475
476 unsigned LocaleICU::firstDayOfWeek()
477 {
478     initializeCalendar();
479     return m_firstDayOfWeek;
480 }
481 #endif
482
483 } // namespace WebCore
484