blob: 01140e220a9c3c0619c702a9584a844e4c9f4e41 [file] [log] [blame]
/*
* Copyright (C) 2011,2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/platform/text/platform_locale.h"
#include <memory>
#include "base/macros.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/strings/grit/blink_strings.h"
#include "third_party/blink/renderer/platform/text/date_time_format.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
namespace {
Locale* g_default_locale;
}
class DateTimeStringBuilder : private DateTimeFormat::TokenHandler {
public:
// The argument objects must be alive until this object dies.
DateTimeStringBuilder(Locale&, const DateComponents&);
bool Build(const String&);
String ToString();
private:
// DateTimeFormat::TokenHandler functions.
void VisitField(DateTimeFormat::FieldType, int) final;
void VisitLiteral(const String&) final;
String ZeroPadString(const String&, size_t width);
void AppendNumber(int number, size_t width);
StringBuilder builder_;
Locale& localizer_;
const DateComponents& date_;
DISALLOW_COPY_AND_ASSIGN(DateTimeStringBuilder);
};
DateTimeStringBuilder::DateTimeStringBuilder(Locale& localizer,
const DateComponents& date)
: localizer_(localizer), date_(date) {}
bool DateTimeStringBuilder::Build(const String& format_string) {
builder_.ReserveCapacity(format_string.length());
return DateTimeFormat::Parse(format_string, *this);
}
String DateTimeStringBuilder::ZeroPadString(const String& string,
size_t pad_width) {
if (string.length() >= pad_width)
return string;
wtf_size_t width = static_cast<wtf_size_t>(pad_width);
StringBuilder zero_padded_string_builder;
zero_padded_string_builder.ReserveCapacity(width);
for (wtf_size_t i = string.length(); i < width; ++i)
zero_padded_string_builder.Append('0');
zero_padded_string_builder.Append(string);
return zero_padded_string_builder.ToString();
}
void DateTimeStringBuilder::AppendNumber(int number, size_t width) {
String zero_padded_number_string =
ZeroPadString(String::Number(number), width);
builder_.Append(
localizer_.ConvertToLocalizedNumber(zero_padded_number_string));
}
void DateTimeStringBuilder::VisitField(DateTimeFormat::FieldType field_type,
int number_of_pattern_characters) {
switch (field_type) {
case DateTimeFormat::kFieldTypeYear:
// Always use padding width of 4 so it matches DateTimeEditElement.
AppendNumber(date_.FullYear(), 4);
return;
case DateTimeFormat::kFieldTypeMonth:
if (number_of_pattern_characters == 3) {
builder_.Append(localizer_.ShortMonthLabels()[date_.Month()]);
} else if (number_of_pattern_characters == 4) {
builder_.Append(localizer_.MonthLabels()[date_.Month()]);
} else {
// Always use padding width of 2 so it matches DateTimeEditElement.
AppendNumber(date_.Month() + 1, 2);
}
return;
case DateTimeFormat::kFieldTypeMonthStandAlone:
if (number_of_pattern_characters == 3) {
builder_.Append(localizer_.ShortStandAloneMonthLabels()[date_.Month()]);
} else if (number_of_pattern_characters == 4) {
builder_.Append(localizer_.StandAloneMonthLabels()[date_.Month()]);
} else {
// Always use padding width of 2 so it matches DateTimeEditElement.
AppendNumber(date_.Month() + 1, 2);
}
return;
case DateTimeFormat::kFieldTypeDayOfMonth:
// Always use padding width of 2 so it matches DateTimeEditElement.
AppendNumber(date_.MonthDay(), 2);
return;
case DateTimeFormat::kFieldTypeWeekOfYear:
// Always use padding width of 2 so it matches DateTimeEditElement.
AppendNumber(date_.Week(), 2);
return;
case DateTimeFormat::kFieldTypePeriod:
builder_.Append(
localizer_.TimeAMPMLabels()[(date_.Hour() >= 12 ? 1 : 0)]);
return;
case DateTimeFormat::kFieldTypeHour12: {
int hour12 = date_.Hour() % 12;
if (!hour12)
hour12 = 12;
AppendNumber(hour12, number_of_pattern_characters);
return;
}
case DateTimeFormat::kFieldTypeHour23:
AppendNumber(date_.Hour(), number_of_pattern_characters);
return;
case DateTimeFormat::kFieldTypeHour11:
AppendNumber(date_.Hour() % 12, number_of_pattern_characters);
return;
case DateTimeFormat::kFieldTypeHour24: {
int hour24 = date_.Hour();
if (!hour24)
hour24 = 24;
AppendNumber(hour24, number_of_pattern_characters);
return;
}
case DateTimeFormat::kFieldTypeMinute:
AppendNumber(date_.Minute(), number_of_pattern_characters);
return;
case DateTimeFormat::kFieldTypeSecond:
if (!date_.Millisecond()) {
AppendNumber(date_.Second(), number_of_pattern_characters);
} else {
double second = date_.Second() + date_.Millisecond() / 1000.0;
String zero_padded_second_string = ZeroPadString(
String::Format("%.03f", second), number_of_pattern_characters + 4);
builder_.Append(
localizer_.ConvertToLocalizedNumber(zero_padded_second_string));
}
return;
default:
return;
}
}
void DateTimeStringBuilder::VisitLiteral(const String& text) {
DCHECK(text.length());
builder_.Append(text);
}
String DateTimeStringBuilder::ToString() {
return builder_.ToString();
}
Locale& Locale::DefaultLocale() {
DCHECK(IsMainThread());
if (!g_default_locale)
g_default_locale = Locale::Create(DefaultLanguage()).release();
return *g_default_locale;
}
void Locale::ResetDefaultLocale() {
// This is safe because no one owns a Locale object returned by
// DefaultLocale().
delete g_default_locale;
g_default_locale = nullptr;
}
Locale::~Locale() = default;
String Locale::QueryString(int resource_id) {
// FIXME: Returns a string locazlied for this locale.
return Platform::Current()->QueryLocalizedString(resource_id);
}
String Locale::QueryString(int resource_id, const String& parameter) {
// FIXME: Returns a string locazlied for this locale.
return Platform::Current()->QueryLocalizedString(resource_id, parameter);
}
String Locale::QueryString(int resource_id,
const String& parameter1,
const String& parameter2) {
// FIXME: Returns a string locazlied for this locale.
return Platform::Current()->QueryLocalizedString(resource_id, parameter1,
parameter2);
}
String Locale::ValidationMessageTooLongText(unsigned value_length,
int max_length) {
return QueryString(IDS_FORM_VALIDATION_TOO_LONG,
ConvertToLocalizedNumber(String::Number(value_length)),
ConvertToLocalizedNumber(String::Number(max_length)));
}
String Locale::ValidationMessageTooShortText(unsigned value_length,
int min_length) {
if (value_length == 1) {
return QueryString(IDS_FORM_VALIDATION_TOO_SHORT,
ConvertToLocalizedNumber(String::Number(value_length)),
ConvertToLocalizedNumber(String::Number(min_length)));
}
return QueryString(IDS_FORM_VALIDATION_TOO_SHORT_PLURAL,
ConvertToLocalizedNumber(String::Number(value_length)),
ConvertToLocalizedNumber(String::Number(min_length)));
}
String Locale::WeekFormatInLDML() {
String templ = QueryString(IDS_FORM_INPUT_WEEK_TEMPLATE);
// Converts a string like "Week $2, $1" to an LDML date format pattern like
// "'Week 'ww', 'yyyy".
StringBuilder builder;
unsigned literal_start = 0;
unsigned length = templ.length();
for (unsigned i = 0; i + 1 < length; ++i) {
if (templ[i] == '$' && (templ[i + 1] == '1' || templ[i + 1] == '2')) {
if (literal_start < i)
DateTimeFormat::QuoteAndappend(
templ.Substring(literal_start, i - literal_start), builder);
builder.Append(templ[++i] == '1' ? "yyyy" : "ww");
literal_start = i + 1;
}
}
if (literal_start < length)
DateTimeFormat::QuoteAndappend(
templ.Substring(literal_start, length - literal_start), builder);
return builder.ToString();
}
void Locale::SetLocaleData(const Vector<String, kDecimalSymbolsSize>& symbols,
const String& positive_prefix,
const String& positive_suffix,
const String& negative_prefix,
const String& negative_suffix) {
for (wtf_size_t i = 0; i < symbols.size(); ++i) {
DCHECK(!symbols[i].IsEmpty());
decimal_symbols_[i] = symbols[i];
}
positive_prefix_ = positive_prefix;
positive_suffix_ = positive_suffix;
negative_prefix_ = negative_prefix;
negative_suffix_ = negative_suffix;
DCHECK(!positive_prefix_.IsEmpty() || !positive_suffix_.IsEmpty() ||
!negative_prefix_.IsEmpty() || !negative_suffix_.IsEmpty());
has_locale_data_ = true;
StringBuilder builder;
for (size_t i = 0; i < kDecimalSymbolsSize; ++i) {
// We don't accept group separatros.
if (i != kGroupSeparatorIndex)
builder.Append(decimal_symbols_[i]);
}
builder.Append(positive_prefix_);
builder.Append(positive_suffix_);
builder.Append(negative_prefix_);
builder.Append(negative_suffix_);
acceptable_number_characters_ = builder.ToString();
// Check if we can use single character filtering. We can if all symbols are
// 1 character and there's no suffix. Since plus sign is optional, allow
// zero length positive prefix.
uses_single_char_number_filtering_ = false;
if (decimal_symbols_[kDecimalSeparatorIndex].length() == 1 &&
positive_prefix_.length() <= 1 && negative_prefix_.length() == 1 &&
positive_suffix_.length() == 0 && negative_suffix_.length() == 0 &&
!IsRTL()) {
uses_single_char_number_filtering_ = true;
for (wtf_size_t i = 0; i <= 9; ++i) {
if (decimal_symbols_[i].length() != 1) {
uses_single_char_number_filtering_ = false;
break;
}
}
}
}
String Locale::ConvertToLocalizedNumber(const String& input) {
InitializeLocaleData();
if (!has_locale_data_ || input.IsEmpty())
return input;
unsigned i = 0;
bool is_negative = false;
StringBuilder builder;
builder.ReserveCapacity(input.length());
if (input[0] == '-') {
++i;
is_negative = true;
builder.Append(negative_prefix_);
} else {
builder.Append(positive_prefix_);
}
for (; i < input.length(); ++i) {
switch (input[i]) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
builder.Append(decimal_symbols_[input[i] - '0']);
break;
case '.':
builder.Append(decimal_symbols_[kDecimalSeparatorIndex]);
break;
default:
NOTREACHED();
}
}
builder.Append(is_negative ? negative_suffix_ : positive_suffix_);
return builder.ToString();
}
static bool Matches(const String& text, unsigned position, const String& part) {
if (part.IsEmpty())
return true;
if (position + part.length() > text.length())
return false;
for (unsigned i = 0; i < part.length(); ++i) {
if (text[position + i] != part[i])
return false;
}
return true;
}
bool Locale::DetectSignAndGetDigitRange(const String& input,
bool& is_negative,
unsigned& start_index,
unsigned& end_index) {
start_index = 0;
end_index = input.length();
if (negative_prefix_.IsEmpty() && negative_suffix_.IsEmpty()) {
if (input.StartsWith(positive_prefix_) &&
input.EndsWith(positive_suffix_)) {
is_negative = false;
start_index = positive_prefix_.length();
end_index -= positive_suffix_.length();
} else {
is_negative = true;
}
} else {
if (input.StartsWith(negative_prefix_) &&
input.EndsWith(negative_suffix_)) {
is_negative = true;
start_index = negative_prefix_.length();
end_index -= negative_suffix_.length();
} else {
is_negative = false;
if (input.StartsWith(positive_prefix_) &&
input.EndsWith(positive_suffix_)) {
start_index = positive_prefix_.length();
end_index -= positive_suffix_.length();
} else {
return false;
}
}
}
return true;
}
unsigned Locale::MatchedDecimalSymbolIndex(const String& input,
unsigned& position) {
for (unsigned symbol_index = 0; symbol_index < kDecimalSymbolsSize;
++symbol_index) {
if (decimal_symbols_[symbol_index].length() &&
Matches(input, position, decimal_symbols_[symbol_index])) {
position += decimal_symbols_[symbol_index].length();
return symbol_index;
}
}
return kDecimalSymbolsSize;
}
String Locale::ConvertFromLocalizedNumber(const String& localized) {
InitializeLocaleData();
String input = localized.RemoveCharacters(IsASCIISpace);
if (!has_locale_data_ || input.IsEmpty())
return input;
bool is_negative;
unsigned start_index;
unsigned end_index;
if (!DetectSignAndGetDigitRange(input, is_negative, start_index, end_index))
return input;
// Ignore leading '+', but will reject '+'-only string later.
if (!is_negative && end_index - start_index >= 2 && input[start_index] == '+')
++start_index;
StringBuilder builder;
builder.ReserveCapacity(input.length());
if (is_negative)
builder.Append('-');
unsigned num_decimal_separators = 0;
for (unsigned i = start_index; i < end_index;) {
unsigned symbol_index = MatchedDecimalSymbolIndex(input, i);
if (symbol_index >= kDecimalSymbolsSize)
return input;
if (symbol_index == kDecimalSeparatorIndex) {
num_decimal_separators++;
builder.Append('.');
} else if (symbol_index == kGroupSeparatorIndex) {
return input;
} else {
builder.Append(static_cast<UChar>('0' + symbol_index));
}
}
String converted = builder.ToString();
// Ignore trailing '.', but will reject '.'-only string later.
if (converted.length() >= 2 && converted[converted.length() - 1] == '.') {
// Leave it if there are two decimal separators since that's invalid.
if (num_decimal_separators < 2)
converted = converted.Left(converted.length() - 1);
}
return converted;
}
String Locale::StripInvalidNumberCharacters(const String& input,
const String& standard_chars) {
InitializeLocaleData();
StringBuilder builder;
builder.ReserveCapacity(input.length());
for (unsigned i = 0; i < input.length(); ++i) {
UChar ch = input[i];
if (standard_chars.find(ch) != kNotFound)
builder.Append(ch);
else if (acceptable_number_characters_.find(ch) != kNotFound)
builder.Append(ch);
}
return builder.ToString();
}
String Locale::LocalizedDecimalSeparator() {
InitializeLocaleData();
return decimal_symbols_[kDecimalSeparatorIndex];
}
bool Locale::UsesSingleCharNumberFiltering() {
return uses_single_char_number_filtering_;
}
static bool IsE(UChar ch) {
return ch == 'e' || ch == 'E';
}
bool Locale::IsSignPrefix(UChar ch) {
if (ch == '+' || ch == '-')
return true;
if (negative_prefix_.length() == 1 && ch == negative_prefix_[0])
return true;
if (positive_prefix_.length() == 1 && ch == positive_prefix_[0])
return true;
return false;
}
bool Locale::HasTwoSignChars(const String& str) {
// Unretained is safe because callback executes synchronously in Find().
auto pos = str.Find(
WTF::BindRepeating(&Locale::IsSignPrefix, WTF::Unretained(this)));
if (pos == kNotFound)
return false;
// Unretained is safe because callback executes synchronously in Find().
return str.Find(
WTF::BindRepeating(&Locale::IsSignPrefix, WTF::Unretained(this)),
pos + 1) != kNotFound;
}
bool Locale::HasSignNotAfterE(const String& str) {
// Unretained is safe because callback executes synchronously in Find().
auto pos = str.Find(
WTF::BindRepeating(&Locale::IsSignPrefix, WTF::Unretained(this)));
if (pos == kNotFound)
return false;
return pos == 0 || !IsE(str[pos - 1]);
}
bool Locale::IsDigit(UChar ch) {
// Alwoays allow 0 - 9
if (ch >= '0' && ch <= '9')
return true;
// Check each digit otherwise
String ch_str(&ch, 1);
return (ch_str == decimal_symbols_[0] || ch_str == decimal_symbols_[1] ||
ch_str == decimal_symbols_[2] || ch_str == decimal_symbols_[3] ||
ch_str == decimal_symbols_[4] || ch_str == decimal_symbols_[5] ||
ch_str == decimal_symbols_[6] || ch_str == decimal_symbols_[7] ||
ch_str == decimal_symbols_[8] || ch_str == decimal_symbols_[9]);
}
// Is the character a decimal separator?
bool Locale::IsDecimalSeparator(UChar ch) {
if (ch == '.')
return true;
return LocalizedDecimalSeparator() == String(&ch, 1);
}
// Is there a decimal separator in a string?
bool Locale::HasDecimalSeparator(const String& str) {
// Unretained is safe because callback executes synchronously in Find().
return str.Find(WTF::BindRepeating(&Locale::IsDecimalSeparator,
WTF::Unretained(this))) != kNotFound;
}
String Locale::FormatDateTime(const DateComponents& date,
FormatType format_type) {
if (date.GetType() == DateComponents::kInvalid)
return String();
DateTimeStringBuilder builder(*this, date);
switch (date.GetType()) {
case DateComponents::kTime:
builder.Build(format_type == kFormatTypeShort ? ShortTimeFormat()
: TimeFormat());
break;
case DateComponents::kDate:
builder.Build(DateFormat());
break;
case DateComponents::kMonth:
builder.Build(format_type == kFormatTypeShort ? ShortMonthFormat()
: MonthFormat());
break;
case DateComponents::kWeek:
builder.Build(WeekFormatInLDML());
break;
case DateComponents::kDateTimeLocal:
builder.Build(format_type == kFormatTypeShort
? DateTimeFormatWithoutSeconds()
: DateTimeFormatWithSeconds());
break;
case DateComponents::kInvalid:
NOTREACHED();
break;
}
return builder.ToString();
}
} // namespace blink