diff --git a/src/extensions/i18n/i18n-extension.cc b/src/extensions/i18n/i18n-extension.cc index b110b7d80e..ef39d45238 100644 --- a/src/extensions/i18n/i18n-extension.cc +++ b/src/extensions/i18n/i18n-extension.cc @@ -31,7 +31,6 @@ #include "break-iterator.h" #include "collator.h" #include "natives.h" -#include "number-format.h" using v8::internal::I18NNatives; @@ -47,15 +46,6 @@ Extension::Extension() v8::Handle Extension::GetNativeFunction( v8::Handle name) { - // Number format and parse. - if (name->Equals(v8::String::New("NativeJSCreateNumberFormat"))) { - return v8::FunctionTemplate::New(NumberFormat::JSCreateNumberFormat); - } else if (name->Equals(v8::String::New("NativeJSInternalNumberFormat"))) { - return v8::FunctionTemplate::New(NumberFormat::JSInternalFormat); - } else if (name->Equals(v8::String::New("NativeJSInternalNumberParse"))) { - return v8::FunctionTemplate::New(NumberFormat::JSInternalParse); - } - // Collator. if (name->Equals(v8::String::New("NativeJSCreateCollator"))) { return v8::FunctionTemplate::New(Collator::JSCreateCollator); diff --git a/src/extensions/i18n/number-format.cc b/src/extensions/i18n/number-format.cc deleted file mode 100644 index 136471561c..0000000000 --- a/src/extensions/i18n/number-format.cc +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright 2013 the V8 project authors. 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. -// limitations under the License. - -#include "number-format.h" - -#include - -#include "i18n-utils.h" -#include "unicode/curramt.h" -#include "unicode/dcfmtsym.h" -#include "unicode/decimfmt.h" -#include "unicode/locid.h" -#include "unicode/numfmt.h" -#include "unicode/numsys.h" -#include "unicode/uchar.h" -#include "unicode/ucurr.h" -#include "unicode/unum.h" -#include "unicode/uversion.h" - -namespace v8_i18n { - -static icu::DecimalFormat* InitializeNumberFormat(v8::Handle, - v8::Handle, - v8::Handle); -static icu::DecimalFormat* CreateICUNumberFormat(const icu::Locale&, - v8::Handle); -static void SetResolvedSettings(const icu::Locale&, - icu::DecimalFormat*, - v8::Handle); - -icu::DecimalFormat* NumberFormat::UnpackNumberFormat( - v8::Handle obj) { - v8::HandleScope handle_scope; - - // v8::ObjectTemplate doesn't have HasInstance method so we can't check - // if obj is an instance of NumberFormat class. We'll check for a property - // that has to be in the object. The same applies to other services, like - // Collator and DateTimeFormat. - if (obj->HasOwnProperty(v8::String::New("numberFormat"))) { - return static_cast( - obj->GetAlignedPointerFromInternalField(0)); - } - - return NULL; -} - -void NumberFormat::DeleteNumberFormat(v8::Isolate* isolate, - v8::Persistent* object, - void* param) { - // First delete the hidden C++ object. - // Unpacking should never return NULL here. That would only happen if - // this method is used as the weak callback for persistent handles not - // pointing to a date time formatter. - v8::HandleScope handle_scope(isolate); - v8::Local handle = v8::Local::New(isolate, *object); - delete UnpackNumberFormat(handle); - - // Then dispose of the persistent handle to JS object. - object->Dispose(isolate); -} - -void NumberFormat::JSInternalFormat( - const v8::FunctionCallbackInfo& args) { - if (args.Length() != 2 || !args[0]->IsObject() || !args[1]->IsNumber()) { - v8::ThrowException(v8::Exception::Error( - v8::String::New("Formatter and numeric value have to be specified."))); - return; - } - - icu::DecimalFormat* number_format = UnpackNumberFormat(args[0]->ToObject()); - if (!number_format) { - v8::ThrowException(v8::Exception::Error( - v8::String::New("NumberFormat method called on an object " - "that is not a NumberFormat."))); - return; - } - - // ICU will handle actual NaN value properly and return NaN string. - icu::UnicodeString result; - number_format->format(args[1]->NumberValue(), result); - - args.GetReturnValue().Set(v8::String::New( - reinterpret_cast(result.getBuffer()), result.length())); -} - -void NumberFormat::JSInternalParse( - const v8::FunctionCallbackInfo& args) { - if (args.Length() != 2 || !args[0]->IsObject() || !args[1]->IsString()) { - v8::ThrowException(v8::Exception::Error( - v8::String::New("Formatter and string have to be specified."))); - return; - } - - icu::DecimalFormat* number_format = UnpackNumberFormat(args[0]->ToObject()); - if (!number_format) { - v8::ThrowException(v8::Exception::Error( - v8::String::New("NumberFormat method called on an object " - "that is not a NumberFormat."))); - return; - } - - // ICU will handle actual NaN value properly and return NaN string. - icu::UnicodeString string_number; - if (!Utils::V8StringToUnicodeString(args[1]->ToString(), &string_number)) { - string_number = ""; - } - - UErrorCode status = U_ZERO_ERROR; - icu::Formattable result; - // ICU 4.6 doesn't support parseCurrency call. We need to wait for ICU49 - // to be part of Chrome. - // TODO(cira): Include currency parsing code using parseCurrency call. - // We need to check if the formatter parses all currencies or only the - // one it was constructed with (it will impact the API - how to return ISO - // code and the value). - number_format->parse(string_number, result, status); - if (U_FAILURE(status)) { - return; - } - - switch (result.getType()) { - case icu::Formattable::kDouble: - args.GetReturnValue().Set(result.getDouble()); - return; - case icu::Formattable::kLong: - args.GetReturnValue().Set(result.getLong()); - return; - case icu::Formattable::kInt64: - args.GetReturnValue().Set(static_cast(result.getInt64())); - return; - default: - return; - } -} - -void NumberFormat::JSCreateNumberFormat( - const v8::FunctionCallbackInfo& args) { - if (args.Length() != 3 || - !args[0]->IsString() || - !args[1]->IsObject() || - !args[2]->IsObject()) { - v8::ThrowException(v8::Exception::Error( - v8::String::New("Internal error, wrong parameters."))); - return; - } - - v8::Isolate* isolate = args.GetIsolate(); - v8::Local number_format_template = - Utils::GetTemplate(isolate); - - // Create an empty object wrapper. - v8::Local local_object = number_format_template->NewInstance(); - // But the handle shouldn't be empty. - // That can happen if there was a stack overflow when creating the object. - if (local_object.IsEmpty()) { - args.GetReturnValue().Set(local_object); - return; - } - - // Set number formatter as internal field of the resulting JS object. - icu::DecimalFormat* number_format = InitializeNumberFormat( - args[0]->ToString(), args[1]->ToObject(), args[2]->ToObject()); - - if (!number_format) { - v8::ThrowException(v8::Exception::Error(v8::String::New( - "Internal error. Couldn't create ICU number formatter."))); - return; - } else { - local_object->SetAlignedPointerInInternalField(0, number_format); - - v8::TryCatch try_catch; - local_object->Set(v8::String::New("numberFormat"), - v8::String::New("valid")); - if (try_catch.HasCaught()) { - v8::ThrowException(v8::Exception::Error( - v8::String::New("Internal error, couldn't set property."))); - return; - } - } - - v8::Persistent wrapper(isolate, local_object); - // Make object handle weak so we can delete iterator once GC kicks in. - wrapper.MakeWeak(NULL, &DeleteNumberFormat); - args.GetReturnValue().Set(wrapper); - wrapper.ClearAndLeak(); -} - -static icu::DecimalFormat* InitializeNumberFormat( - v8::Handle locale, - v8::Handle options, - v8::Handle resolved) { - // Convert BCP47 into ICU locale format. - UErrorCode status = U_ZERO_ERROR; - icu::Locale icu_locale; - char icu_result[ULOC_FULLNAME_CAPACITY]; - int icu_length = 0; - v8::String::AsciiValue bcp47_locale(locale); - if (bcp47_locale.length() != 0) { - uloc_forLanguageTag(*bcp47_locale, icu_result, ULOC_FULLNAME_CAPACITY, - &icu_length, &status); - if (U_FAILURE(status) || icu_length == 0) { - return NULL; - } - icu_locale = icu::Locale(icu_result); - } - - icu::DecimalFormat* number_format = - CreateICUNumberFormat(icu_locale, options); - if (!number_format) { - // Remove extensions and try again. - icu::Locale no_extension_locale(icu_locale.getBaseName()); - number_format = CreateICUNumberFormat(no_extension_locale, options); - - // Set resolved settings (pattern, numbering system). - SetResolvedSettings(no_extension_locale, number_format, resolved); - } else { - SetResolvedSettings(icu_locale, number_format, resolved); - } - - return number_format; -} - -static icu::DecimalFormat* CreateICUNumberFormat( - const icu::Locale& icu_locale, v8::Handle options) { - // Make formatter from options. Numbering system is added - // to the locale as Unicode extension (if it was specified at all). - UErrorCode status = U_ZERO_ERROR; - icu::DecimalFormat* number_format = NULL; - icu::UnicodeString style; - icu::UnicodeString currency; - if (Utils::ExtractStringSetting(options, "style", &style)) { - if (style == UNICODE_STRING_SIMPLE("currency")) { - Utils::ExtractStringSetting(options, "currency", ¤cy); - - icu::UnicodeString display; - Utils::ExtractStringSetting(options, "currencyDisplay", &display); -#if (U_ICU_VERSION_MAJOR_NUM == 4) && (U_ICU_VERSION_MINOR_NUM <= 6) - icu::NumberFormat::EStyles style; - if (display == UNICODE_STRING_SIMPLE("code")) { - style = icu::NumberFormat::kIsoCurrencyStyle; - } else if (display == UNICODE_STRING_SIMPLE("name")) { - style = icu::NumberFormat::kPluralCurrencyStyle; - } else { - style = icu::NumberFormat::kCurrencyStyle; - } -#else // ICU version is 4.8 or above (we ignore versions below 4.0). - UNumberFormatStyle style; - if (display == UNICODE_STRING_SIMPLE("code")) { - style = UNUM_CURRENCY_ISO; - } else if (display == UNICODE_STRING_SIMPLE("name")) { - style = UNUM_CURRENCY_PLURAL; - } else { - style = UNUM_CURRENCY; - } -#endif - - number_format = static_cast( - icu::NumberFormat::createInstance(icu_locale, style, status)); - } else if (style == UNICODE_STRING_SIMPLE("percent")) { - number_format = static_cast( - icu::NumberFormat::createPercentInstance(icu_locale, status)); - if (U_FAILURE(status)) { - delete number_format; - return NULL; - } - // Make sure 1.1% doesn't go into 2%. - number_format->setMinimumFractionDigits(1); - } else { - // Make a decimal instance by default. - number_format = static_cast( - icu::NumberFormat::createInstance(icu_locale, status)); - } - } - - if (U_FAILURE(status)) { - delete number_format; - return NULL; - } - - // Set all options. - if (!currency.isEmpty()) { - number_format->setCurrency(currency.getBuffer(), status); - } - - int32_t digits; - if (Utils::ExtractIntegerSetting( - options, "minimumIntegerDigits", &digits)) { - number_format->setMinimumIntegerDigits(digits); - } - - if (Utils::ExtractIntegerSetting( - options, "minimumFractionDigits", &digits)) { - number_format->setMinimumFractionDigits(digits); - } - - if (Utils::ExtractIntegerSetting( - options, "maximumFractionDigits", &digits)) { - number_format->setMaximumFractionDigits(digits); - } - - bool significant_digits_used = false; - if (Utils::ExtractIntegerSetting( - options, "minimumSignificantDigits", &digits)) { - number_format->setMinimumSignificantDigits(digits); - significant_digits_used = true; - } - - if (Utils::ExtractIntegerSetting( - options, "maximumSignificantDigits", &digits)) { - number_format->setMaximumSignificantDigits(digits); - significant_digits_used = true; - } - - number_format->setSignificantDigitsUsed(significant_digits_used); - - bool grouping; - if (Utils::ExtractBooleanSetting(options, "useGrouping", &grouping)) { - number_format->setGroupingUsed(grouping); - } - - // Set rounding mode. - number_format->setRoundingMode(icu::DecimalFormat::kRoundHalfUp); - - return number_format; -} - -static void SetResolvedSettings(const icu::Locale& icu_locale, - icu::DecimalFormat* number_format, - v8::Handle resolved) { - icu::UnicodeString pattern; - number_format->toPattern(pattern); - resolved->Set(v8::String::New("pattern"), - v8::String::New(reinterpret_cast( - pattern.getBuffer()), pattern.length())); - - // Set resolved currency code in options.currency if not empty. - icu::UnicodeString currency(number_format->getCurrency()); - if (!currency.isEmpty()) { - resolved->Set(v8::String::New("currency"), - v8::String::New(reinterpret_cast( - currency.getBuffer()), currency.length())); - } - - // Ugly hack. ICU doesn't expose numbering system in any way, so we have - // to assume that for given locale NumberingSystem constructor produces the - // same digits as NumberFormat would. - UErrorCode status = U_ZERO_ERROR; - icu::NumberingSystem* numbering_system = - icu::NumberingSystem::createInstance(icu_locale, status); - if (U_SUCCESS(status)) { - const char* ns = numbering_system->getName(); - resolved->Set(v8::String::New("numberingSystem"), v8::String::New(ns)); - } else { - resolved->Set(v8::String::New("numberingSystem"), v8::Undefined()); - } - delete numbering_system; - - resolved->Set(v8::String::New("useGrouping"), - v8::Boolean::New(number_format->isGroupingUsed())); - - resolved->Set(v8::String::New("minimumIntegerDigits"), - v8::Integer::New(number_format->getMinimumIntegerDigits())); - - resolved->Set(v8::String::New("minimumFractionDigits"), - v8::Integer::New(number_format->getMinimumFractionDigits())); - - resolved->Set(v8::String::New("maximumFractionDigits"), - v8::Integer::New(number_format->getMaximumFractionDigits())); - - if (resolved->HasOwnProperty(v8::String::New("minimumSignificantDigits"))) { - resolved->Set(v8::String::New("minimumSignificantDigits"), v8::Integer::New( - number_format->getMinimumSignificantDigits())); - } - - if (resolved->HasOwnProperty(v8::String::New("maximumSignificantDigits"))) { - resolved->Set(v8::String::New("maximumSignificantDigits"), v8::Integer::New( - number_format->getMaximumSignificantDigits())); - } - - // Set the locale - char result[ULOC_FULLNAME_CAPACITY]; - status = U_ZERO_ERROR; - uloc_toLanguageTag( - icu_locale.getName(), result, ULOC_FULLNAME_CAPACITY, FALSE, &status); - if (U_SUCCESS(status)) { - resolved->Set(v8::String::New("locale"), v8::String::New(result)); - } else { - // This would never happen, since we got the locale from ICU. - resolved->Set(v8::String::New("locale"), v8::String::New("und")); - } -} - -} // namespace v8_i18n diff --git a/src/extensions/i18n/number-format.h b/src/extensions/i18n/number-format.h deleted file mode 100644 index d4dbc4d6f3..0000000000 --- a/src/extensions/i18n/number-format.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2013 the V8 project authors. 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. -// limitations under the License. - -#ifndef V8_EXTENSIONS_I18N_NUMBER_FORMAT_H_ -#define V8_EXTENSIONS_I18N_NUMBER_FORMAT_H_ - -#include "unicode/uversion.h" -#include "v8.h" - -namespace U_ICU_NAMESPACE { -class DecimalFormat; -} - -namespace v8_i18n { - -class NumberFormat { - public: - static void JSCreateNumberFormat( - const v8::FunctionCallbackInfo& args); - - // Helper methods for various bindings. - - // Unpacks date format object from corresponding JavaScript object. - static icu::DecimalFormat* UnpackNumberFormat(v8::Handle obj); - - // Release memory we allocated for the NumberFormat once the JS object that - // holds the pointer gets garbage collected. - static void DeleteNumberFormat(v8::Isolate* isolate, - v8::Persistent* object, - void* param); - - // Formats number and returns corresponding string. - static void JSInternalFormat(const v8::FunctionCallbackInfo& args); - - // Parses a string and returns a number. - static void JSInternalParse(const v8::FunctionCallbackInfo& args); - - private: - NumberFormat(); -}; - -} // namespace v8_i18n - -#endif // V8_EXTENSIONS_I18N_NUMBER_FORMAT_H_ diff --git a/src/extensions/i18n/number-format.js b/src/extensions/i18n/number-format.js index 1cd3db1355..5722a5dc1f 100644 --- a/src/extensions/i18n/number-format.js +++ b/src/extensions/i18n/number-format.js @@ -65,8 +65,6 @@ function getNumberOption(options, property, min, max, fallback) { * Useful for subclassing. */ function initializeNumberFormat(numberFormat, locales, options) { - native function NativeJSCreateNumberFormat(); - if (numberFormat.hasOwnProperty('__initializedIntlObject')) { throw new TypeError('Trying to re-initialize NumberFormat object.'); } @@ -148,9 +146,9 @@ function initializeNumberFormat(numberFormat, locales, options) { if (internalOptions.hasOwnProperty('maximumSignificantDigits')) { defineWEProperty(resolved, 'maximumSignificantDigits', undefined); } - var formatter = NativeJSCreateNumberFormat(requestedLocale, - internalOptions, - resolved); + var formatter = %CreateNumberFormat(requestedLocale, + internalOptions, + resolved); // We can't get information about number or currency style from ICU, so we // assume user request was fulfilled. @@ -269,15 +267,13 @@ function initializeNumberFormat(numberFormat, locales, options) { * NumberFormat. */ function formatNumber(formatter, value) { - native function NativeJSInternalNumberFormat(); - // Spec treats -0 and +0 as 0. var number = Number(value); if (number === -0) { number = 0; } - return NativeJSInternalNumberFormat(formatter.formatter, number); + return %InternalNumberFormat(formatter.formatter, number); } @@ -285,9 +281,7 @@ function formatNumber(formatter, value) { * Returns a Number that represents string value that was passed in. */ function parseNumber(formatter, value) { - native function NativeJSInternalNumberParse(); - - return NativeJSInternalNumberParse(formatter.formatter, String(value)); + return %InternalNumberParse(formatter.formatter, String(value)); } diff --git a/src/i18n.cc b/src/i18n.cc index b2ccfd4985..bd9d9c31a0 100644 --- a/src/i18n.cc +++ b/src/i18n.cc @@ -29,33 +29,83 @@ #include "i18n.h" #include "unicode/calendar.h" +#include "unicode/curramt.h" +#include "unicode/dcfmtsym.h" +#include "unicode/decimfmt.h" #include "unicode/dtfmtsym.h" #include "unicode/dtptngen.h" #include "unicode/locid.h" +#include "unicode/numfmt.h" #include "unicode/numsys.h" #include "unicode/smpdtfmt.h" #include "unicode/timezone.h" +#include "unicode/uchar.h" +#include "unicode/ucurr.h" +#include "unicode/unum.h" +#include "unicode/uversion.h" namespace v8 { namespace internal { namespace { +bool ExtractStringSetting(Isolate* isolate, + Handle options, + const char* key, + icu::UnicodeString* setting) { + MaybeObject* maybe_object = options->GetProperty( + *isolate->factory()->NewStringFromAscii(CStrVector(key))); + Object* object; + if (maybe_object->ToObject(&object) && object->IsString()) { + v8::String::Utf8Value utf8_string( + v8::Utils::ToLocal(Handle(String::cast(object)))); + *setting = icu::UnicodeString::fromUTF8(*utf8_string); + return true; + } + return false; +} + + +bool ExtractIntegerSetting(Isolate* isolate, + Handle options, + const char* key, + int32_t* value) { + MaybeObject* maybe_object = options->GetProperty( + *isolate->factory()->NewStringFromAscii(CStrVector(key))); + Object* object; + if (maybe_object->ToObject(&object) && object->IsNumber()) { + object->ToInt32(value); + return true; + } + return false; +} + + +bool ExtractBooleanSetting(Isolate* isolate, + Handle options, + const char* key, + bool* value) { + MaybeObject* maybe_object = options->GetProperty( + *isolate->factory()->NewStringFromAscii(CStrVector(key))); + Object* object; + if (maybe_object->ToObject(&object) && object->IsBoolean()) { + *value = object->BooleanValue(); + return true; + } + return false; +} + + icu::SimpleDateFormat* CreateICUDateFormat( Isolate* isolate, const icu::Locale& icu_locale, - Handle options) { + Handle options) { // Create time zone as specified by the user. We have to re-create time zone // since calendar takes ownership. icu::TimeZone* tz = NULL; - MaybeObject* maybe_object = options->GetProperty( - *isolate->factory()->NewStringFromAscii(CStrVector("timeZone"))); - Object* timezone; - if (maybe_object->ToObject(&timezone) && timezone->IsString()) { - v8::String::Utf8Value utf8_timezone( - v8::Utils::ToLocal(Handle(String::cast(timezone)))); - icu::UnicodeString u_timezone(icu::UnicodeString::fromUTF8(*utf8_timezone)); - tz = icu::TimeZone::createTimeZone(u_timezone); + icu::UnicodeString timezone; + if (ExtractStringSetting(isolate, options, "timeZone", &timezone)) { + tz = icu::TimeZone::createTimeZone(timezone); } else { tz = icu::TimeZone::createDefault(); } @@ -68,18 +118,13 @@ icu::SimpleDateFormat* CreateICUDateFormat( // Make formatter from skeleton. Calendar and numbering system are added // to the locale as Unicode extension (if they were specified at all). icu::SimpleDateFormat* date_format = NULL; - Object* skeleton; - maybe_object = options->GetProperty( - *isolate->factory()->NewStringFromAscii(CStrVector("skeleton"))); - if (maybe_object->ToObject(&skeleton) && skeleton->IsString()) { - v8::String::Utf8Value utf8_skeleton( - v8::Utils::ToLocal(Handle(String::cast(skeleton)))); - icu::UnicodeString u_skeleton(icu::UnicodeString::fromUTF8(*utf8_skeleton)); + icu::UnicodeString skeleton; + if (ExtractStringSetting(isolate, options, "skeleton", &skeleton)) { icu::DateTimePatternGenerator* generator = icu::DateTimePatternGenerator::createInstance(icu_locale, status); icu::UnicodeString pattern; if (U_SUCCESS(status)) { - pattern = generator->getBestPattern(u_skeleton, status); + pattern = generator->getBestPattern(skeleton, status); delete generator; } @@ -99,10 +144,10 @@ icu::SimpleDateFormat* CreateICUDateFormat( } -void SetResolvedSettings(Isolate* isolate, - const icu::Locale& icu_locale, - icu::SimpleDateFormat* date_format, - Handle resolved) { +void SetResolvedDateSettings(Isolate* isolate, + const icu::Locale& icu_locale, + icu::SimpleDateFormat* date_format, + Handle resolved) { UErrorCode status = U_ZERO_ERROR; icu::UnicodeString pattern; date_format->toPattern(pattern); @@ -217,6 +262,249 @@ Handle GetEternal(Isolate* isolate) { field)); } + +icu::DecimalFormat* CreateICUNumberFormat( + Isolate* isolate, + const icu::Locale& icu_locale, + Handle options) { + // Make formatter from options. Numbering system is added + // to the locale as Unicode extension (if it was specified at all). + UErrorCode status = U_ZERO_ERROR; + icu::DecimalFormat* number_format = NULL; + icu::UnicodeString style; + icu::UnicodeString currency; + if (ExtractStringSetting(isolate, options, "style", &style)) { + if (style == UNICODE_STRING_SIMPLE("currency")) { + icu::UnicodeString display; + ExtractStringSetting(isolate, options, "currency", ¤cy); + ExtractStringSetting(isolate, options, "currencyDisplay", &display); + +#if (U_ICU_VERSION_MAJOR_NUM == 4) && (U_ICU_VERSION_MINOR_NUM <= 6) + icu::NumberFormat::EStyles format_style; + if (display == UNICODE_STRING_SIMPLE("code")) { + format_style = icu::NumberFormat::kIsoCurrencyStyle; + } else if (display == UNICODE_STRING_SIMPLE("name")) { + format_style = icu::NumberFormat::kPluralCurrencyStyle; + } else { + format_style = icu::NumberFormat::kCurrencyStyle; + } +#else // ICU version is 4.8 or above (we ignore versions below 4.0). + UNumberFormatStyle format_style; + if (display == UNICODE_STRING_SIMPLE("code")) { + format_style = UNUM_CURRENCY_ISO; + } else if (display == UNICODE_STRING_SIMPLE("name")) { + format_style = UNUM_CURRENCY_PLURAL; + } else { + format_style = UNUM_CURRENCY; + } +#endif + + number_format = static_cast( + icu::NumberFormat::createInstance(icu_locale, format_style, status)); + } else if (style == UNICODE_STRING_SIMPLE("percent")) { + number_format = static_cast( + icu::NumberFormat::createPercentInstance(icu_locale, status)); + if (U_FAILURE(status)) { + delete number_format; + return NULL; + } + // Make sure 1.1% doesn't go into 2%. + number_format->setMinimumFractionDigits(1); + } else { + // Make a decimal instance by default. + number_format = static_cast( + icu::NumberFormat::createInstance(icu_locale, status)); + } + } + + if (U_FAILURE(status)) { + delete number_format; + return NULL; + } + + // Set all options. + if (!currency.isEmpty()) { + number_format->setCurrency(currency.getBuffer(), status); + } + + int32_t digits; + if (ExtractIntegerSetting( + isolate, options, "minimumIntegerDigits", &digits)) { + number_format->setMinimumIntegerDigits(digits); + } + + if (ExtractIntegerSetting( + isolate, options, "minimumFractionDigits", &digits)) { + number_format->setMinimumFractionDigits(digits); + } + + if (ExtractIntegerSetting( + isolate, options, "maximumFractionDigits", &digits)) { + number_format->setMaximumFractionDigits(digits); + } + + bool significant_digits_used = false; + if (ExtractIntegerSetting( + isolate, options, "minimumSignificantDigits", &digits)) { + number_format->setMinimumSignificantDigits(digits); + significant_digits_used = true; + } + + if (ExtractIntegerSetting( + isolate, options, "maximumSignificantDigits", &digits)) { + number_format->setMaximumSignificantDigits(digits); + significant_digits_used = true; + } + + number_format->setSignificantDigitsUsed(significant_digits_used); + + bool grouping; + if (ExtractBooleanSetting(isolate, options, "useGrouping", &grouping)) { + number_format->setGroupingUsed(grouping); + } + + // Set rounding mode. + number_format->setRoundingMode(icu::DecimalFormat::kRoundHalfUp); + + return number_format; +} + + +void SetResolvedNumberSettings(Isolate* isolate, + const icu::Locale& icu_locale, + icu::DecimalFormat* number_format, + Handle resolved) { + icu::UnicodeString pattern; + number_format->toPattern(pattern); + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii(CStrVector("pattern")), + isolate->factory()->NewStringFromTwoByte( + Vector( + reinterpret_cast(pattern.getBuffer()), + pattern.length())), + NONE, + kNonStrictMode); + + // Set resolved currency code in options.currency if not empty. + icu::UnicodeString currency(number_format->getCurrency()); + if (!currency.isEmpty()) { + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii(CStrVector("currency")), + isolate->factory()->NewStringFromTwoByte( + Vector( + reinterpret_cast(currency.getBuffer()), + currency.length())), + NONE, + kNonStrictMode); + } + + // Ugly hack. ICU doesn't expose numbering system in any way, so we have + // to assume that for given locale NumberingSystem constructor produces the + // same digits as NumberFormat/Calendar would. + UErrorCode status = U_ZERO_ERROR; + icu::NumberingSystem* numbering_system = + icu::NumberingSystem::createInstance(icu_locale, status); + if (U_SUCCESS(status)) { + const char* ns = numbering_system->getName(); + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii(CStrVector("numberingSystem")), + isolate->factory()->NewStringFromAscii(CStrVector(ns)), + NONE, + kNonStrictMode); + } else { + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii(CStrVector("numberingSystem")), + isolate->factory()->undefined_value(), + NONE, + kNonStrictMode); + } + delete numbering_system; + + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii(CStrVector("useGrouping")), + isolate->factory()->ToBoolean(number_format->isGroupingUsed()), + NONE, + kNonStrictMode); + + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii( + CStrVector("minimumIntegerDigits")), + isolate->factory()->NewNumberFromInt( + number_format->getMinimumIntegerDigits()), + NONE, + kNonStrictMode); + + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii( + CStrVector("minimumFractionDigits")), + isolate->factory()->NewNumberFromInt( + number_format->getMinimumFractionDigits()), + NONE, + kNonStrictMode); + + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii( + CStrVector("maximumFractionDigits")), + isolate->factory()->NewNumberFromInt( + number_format->getMaximumFractionDigits()), + NONE, + kNonStrictMode); + + if (resolved->HasLocalProperty(*isolate->factory()->NewStringFromAscii( + CStrVector("minimumSignificantDigits")))) { + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii( + CStrVector("minimumSignificantDigits")), + isolate->factory()->NewNumberFromInt( + number_format->getMinimumSignificantDigits()), + NONE, + kNonStrictMode); + } + + if (resolved->HasLocalProperty(*isolate->factory()->NewStringFromAscii( + CStrVector("maximumSignificantDigits")))) { + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii( + CStrVector("maximumSignificantDigits")), + isolate->factory()->NewNumberFromInt( + number_format->getMaximumSignificantDigits()), + NONE, + kNonStrictMode); + } + + // Set the locale + char result[ULOC_FULLNAME_CAPACITY]; + status = U_ZERO_ERROR; + uloc_toLanguageTag( + icu_locale.getName(), result, ULOC_FULLNAME_CAPACITY, FALSE, &status); + if (U_SUCCESS(status)) { + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii(CStrVector("locale")), + isolate->factory()->NewStringFromAscii(CStrVector(result)), + NONE, + kNonStrictMode); + } else { + // This would never happen, since we got the locale from ICU. + JSObject::SetProperty( + resolved, + isolate->factory()->NewStringFromAscii(CStrVector("locale")), + isolate->factory()->NewStringFromAscii(CStrVector("und")), + NONE, + kNonStrictMode); + } +} + } // namespace @@ -261,9 +549,10 @@ icu::SimpleDateFormat* DateFormat::InitializeDateTimeFormat( date_format = CreateICUDateFormat(isolate, no_extension_locale, options); // Set resolved settings (pattern, numbering system, calendar). - SetResolvedSettings(isolate, no_extension_locale, date_format, resolved); + SetResolvedDateSettings( + isolate, no_extension_locale, date_format, resolved); } else { - SetResolvedSettings(isolate, icu_locale, date_format, resolved); + SetResolvedDateSettings(isolate, icu_locale, date_format, resolved); } return date_format; @@ -294,4 +583,67 @@ void DateFormat::DeleteDateFormat(v8::Isolate* isolate, object->Dispose(isolate); } + +icu::DecimalFormat* NumberFormat::InitializeNumberFormat( + Isolate* isolate, + Handle locale, + Handle options, + Handle resolved) { + // Convert BCP47 into ICU locale format. + UErrorCode status = U_ZERO_ERROR; + icu::Locale icu_locale; + char icu_result[ULOC_FULLNAME_CAPACITY]; + int icu_length = 0; + v8::String::Utf8Value bcp47_locale(v8::Utils::ToLocal(locale)); + if (bcp47_locale.length() != 0) { + uloc_forLanguageTag(*bcp47_locale, icu_result, ULOC_FULLNAME_CAPACITY, + &icu_length, &status); + if (U_FAILURE(status) || icu_length == 0) { + return NULL; + } + icu_locale = icu::Locale(icu_result); + } + + icu::DecimalFormat* number_format = + CreateICUNumberFormat(isolate, icu_locale, options); + if (!number_format) { + // Remove extensions and try again. + icu::Locale no_extension_locale(icu_locale.getBaseName()); + number_format = CreateICUNumberFormat( + isolate, no_extension_locale, options); + + // Set resolved settings (pattern, numbering system). + SetResolvedNumberSettings( + isolate, no_extension_locale, number_format, resolved); + } else { + SetResolvedNumberSettings(isolate, icu_locale, number_format, resolved); + } + + return number_format; +} + + +icu::DecimalFormat* NumberFormat::UnpackNumberFormat( + Isolate* isolate, + Handle obj) { + if (obj->HasLocalProperty(*isolate->factory()->NewStringFromAscii( + CStrVector("numberFormat")))) { + return reinterpret_cast(obj->GetInternalField(0)); + } + + return NULL; +} + + +void NumberFormat::DeleteNumberFormat(v8::Isolate* isolate, + Persistent* object, + void* param) { + // First delete the hidden C++ object. + delete reinterpret_cast(Handle::cast( + v8::Utils::OpenPersistent(object))->GetInternalField(0)); + + // Then dispose of the persistent handle to JS object. + object->Dispose(isolate); +} + } } // namespace v8::internal diff --git a/src/i18n.h b/src/i18n.h index 37c57b135b..b9cbed1bbe 100644 --- a/src/i18n.h +++ b/src/i18n.h @@ -33,6 +33,7 @@ #include "v8.h" namespace U_ICU_NAMESPACE { +class DecimalFormat; class SimpleDateFormat; } @@ -51,6 +52,7 @@ class I18N { I18N(); }; + class DateFormat { public: // Create a formatter for the specificied locale and options. Returns the @@ -74,6 +76,30 @@ class DateFormat { DateFormat(); }; + +class NumberFormat { + public: + // Create a formatter for the specificied locale and options. Returns the + // resolved settings for the locale / options. + static icu::DecimalFormat* InitializeNumberFormat( + Isolate* isolate, + Handle locale, + Handle options, + Handle resolved); + + // Unpacks number format object from corresponding JavaScript object. + static icu::DecimalFormat* UnpackNumberFormat(Isolate* isolate, + Handle obj); + + // Release memory we allocated for the NumberFormat once the JS object that + // holds the pointer gets garbage collected. + static void DeleteNumberFormat(v8::Isolate* isolate, + Persistent* object, + void* param); + private: + NumberFormat(); +}; + } } // namespace v8::internal #endif // V8_I18N_H_ diff --git a/src/runtime.cc b/src/runtime.cc index 80978f4baf..a1c0d750c4 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -71,7 +71,10 @@ #include "unicode/brkiter.h" #include "unicode/calendar.h" #include "unicode/coll.h" +#include "unicode/curramt.h" #include "unicode/datefmt.h" +#include "unicode/dcfmtsym.h" +#include "unicode/decimfmt.h" #include "unicode/dtfmtsym.h" #include "unicode/dtptngen.h" #include "unicode/locid.h" @@ -79,7 +82,10 @@ #include "unicode/numsys.h" #include "unicode/smpdtfmt.h" #include "unicode/timezone.h" +#include "unicode/uchar.h" +#include "unicode/ucurr.h" #include "unicode/uloc.h" +#include "unicode/unum.h" #include "unicode/uversion.h" #endif @@ -13665,6 +13671,121 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_InternalDateParse) { } return *result; } + + +RUNTIME_FUNCTION(MaybeObject*, Runtime_CreateNumberFormat) { + HandleScope scope(isolate); + + ASSERT(args.length() == 3); + + CONVERT_ARG_HANDLE_CHECKED(String, locale, 0); + CONVERT_ARG_HANDLE_CHECKED(JSObject, options, 1); + CONVERT_ARG_HANDLE_CHECKED(JSObject, resolved, 2); + + Handle number_format_template = + I18N::GetTemplate(isolate); + + // Create an empty object wrapper. + bool has_pending_exception = false; + Handle local_object = Execution::InstantiateObject( + number_format_template, &has_pending_exception); + if (has_pending_exception) { + ASSERT(isolate->has_pending_exception()); + return Failure::Exception(); + } + + // Set number formatter as internal field of the resulting JS object. + icu::DecimalFormat* number_format = NumberFormat::InitializeNumberFormat( + isolate, locale, options, resolved); + + if (!number_format) return isolate->ThrowIllegalOperation(); + + local_object->SetInternalField(0, reinterpret_cast(number_format)); + + RETURN_IF_EMPTY_HANDLE(isolate, + JSObject::SetLocalPropertyIgnoreAttributes( + local_object, + isolate->factory()->NewStringFromAscii(CStrVector("numberFormat")), + isolate->factory()->NewStringFromAscii(CStrVector("valid")), + NONE)); + + Persistent wrapper(reinterpret_cast(isolate), + v8::Utils::ToLocal(local_object)); + // Make object handle weak so we can delete the number format once GC kicks + // in. + wrapper.MakeWeak(NULL, &NumberFormat::DeleteNumberFormat); + Handle result = Utils::OpenPersistent(wrapper); + wrapper.ClearAndLeak(); + return *result; +} + + +RUNTIME_FUNCTION(MaybeObject*, Runtime_InternalNumberFormat) { + HandleScope scope(isolate); + + ASSERT(args.length() == 2); + + CONVERT_ARG_HANDLE_CHECKED(JSObject, number_format_holder, 0); + CONVERT_ARG_HANDLE_CHECKED(Object, number, 1); + + bool has_pending_exception = false; + double value = Execution::ToNumber(number, &has_pending_exception)->Number(); + if (has_pending_exception) { + ASSERT(isolate->has_pending_exception()); + return Failure::Exception(); + } + + icu::DecimalFormat* number_format = + NumberFormat::UnpackNumberFormat(isolate, number_format_holder); + if (!number_format) return isolate->ThrowIllegalOperation(); + + icu::UnicodeString result; + number_format->format(value, result); + + return *isolate->factory()->NewStringFromTwoByte( + Vector( + reinterpret_cast(result.getBuffer()), + result.length())); +} + + +RUNTIME_FUNCTION(MaybeObject*, Runtime_InternalNumberParse) { + HandleScope scope(isolate); + + ASSERT(args.length() == 2); + + CONVERT_ARG_HANDLE_CHECKED(JSObject, number_format_holder, 0); + CONVERT_ARG_HANDLE_CHECKED(String, number_string, 1); + + v8::String::Utf8Value utf8_number(v8::Utils::ToLocal(number_string)); + icu::UnicodeString u_number(icu::UnicodeString::fromUTF8(*utf8_number)); + icu::DecimalFormat* number_format = + NumberFormat::UnpackNumberFormat(isolate, number_format_holder); + if (!number_format) return isolate->ThrowIllegalOperation(); + + UErrorCode status = U_ZERO_ERROR; + icu::Formattable result; + // ICU 4.6 doesn't support parseCurrency call. We need to wait for ICU49 + // to be part of Chrome. + // TODO(cira): Include currency parsing code using parseCurrency call. + // We need to check if the formatter parses all currencies or only the + // one it was constructed with (it will impact the API - how to return ISO + // code and the value). + number_format->parse(u_number, result, status); + if (U_FAILURE(status)) return isolate->heap()->undefined_value(); + + switch (result.getType()) { + case icu::Formattable::kDouble: + return *isolate->factory()->NewNumber(result.getDouble()); + case icu::Formattable::kLong: + return *isolate->factory()->NewNumberFromInt(result.getLong()); + case icu::Formattable::kInt64: + return *isolate->factory()->NewNumber( + static_cast(result.getInt64())); + default: + return isolate->heap()->undefined_value(); + } +} #endif // V8_I18N_SUPPORT diff --git a/src/runtime.h b/src/runtime.h index ade7e732c0..9abec62820 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -548,6 +548,11 @@ namespace internal { F(CreateDateTimeFormat, 3, 1) \ F(InternalDateFormat, 2, 1) \ F(InternalDateParse, 2, 1) \ + \ + /* Number format and parse. */ \ + F(CreateNumberFormat, 3, 1) \ + F(InternalNumberFormat, 2, 1) \ + F(InternalNumberParse, 2, 1) \ #else #define RUNTIME_FUNCTION_LIST_I18N_SUPPORT(F) diff --git a/tools/gyp/v8.gyp b/tools/gyp/v8.gyp index cb9b85cd76..14efabdc2c 100644 --- a/tools/gyp/v8.gyp +++ b/tools/gyp/v8.gyp @@ -833,8 +833,6 @@ '../../src/extensions/i18n/i18n-extension.h', '../../src/extensions/i18n/i18n-utils.cc', '../../src/extensions/i18n/i18n-utils.h', - '../../src/extensions/i18n/number-format.cc', - '../../src/extensions/i18n/number-format.h', ], 'dependencies': [ '<(DEPTH)/third_party/icu/icu.gyp:icui18n',