michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: #ifdef ENABLE_INTL_API michael@0: michael@0: #include "ICUUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIToolkitChromeRegistry.h" michael@0: #include "nsStringGlue.h" michael@0: #include "unicode/uloc.h" michael@0: #include "unicode/unum.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: /** michael@0: * This pref just controls whether we format the number with grouping separator michael@0: * characters when the internal value is set or updated. It does not stop the michael@0: * user from typing in a number and using grouping separators. michael@0: */ michael@0: static bool gLocaleNumberGroupingEnabled; michael@0: static const char LOCALE_NUMBER_GROUPING_PREF_STR[] = "dom.forms.number.grouping"; michael@0: michael@0: static bool michael@0: LocaleNumberGroupingIsEnabled() michael@0: { michael@0: static bool sInitialized = false; michael@0: michael@0: if (!sInitialized) { michael@0: /* check and register ourselves with the pref */ michael@0: Preferences::AddBoolVarCache(&gLocaleNumberGroupingEnabled, michael@0: LOCALE_NUMBER_GROUPING_PREF_STR, michael@0: false); michael@0: sInitialized = true; michael@0: } michael@0: michael@0: return gLocaleNumberGroupingEnabled; michael@0: } michael@0: michael@0: void michael@0: ICUUtils::LanguageTagIterForContent::GetNext(nsACString& aBCP47LangTag) michael@0: { michael@0: if (mCurrentFallbackIndex < 0) { michael@0: mCurrentFallbackIndex = 0; michael@0: // Try the language specified by a 'lang'/'xml:lang' attribute on mContent michael@0: // or any ancestor, if such an attribute is specified: michael@0: nsAutoString lang; michael@0: mContent->GetLang(lang); michael@0: if (!lang.IsEmpty()) { michael@0: aBCP47LangTag = NS_ConvertUTF16toUTF8(lang); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (mCurrentFallbackIndex < 1) { michael@0: mCurrentFallbackIndex = 1; michael@0: // Else try the language specified by any Content-Language HTTP header or michael@0: // pragma directive: michael@0: nsIDocument* doc = mContent->OwnerDoc(); michael@0: nsAutoString lang; michael@0: doc->GetContentLanguage(lang); michael@0: if (!lang.IsEmpty()) { michael@0: aBCP47LangTag = NS_ConvertUTF16toUTF8(lang); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (mCurrentFallbackIndex < 2) { michael@0: mCurrentFallbackIndex = 2; michael@0: // Else try the user-agent's locale: michael@0: nsCOMPtr cr = michael@0: mozilla::services::GetToolkitChromeRegistryService(); michael@0: nsAutoCString uaLangTag; michael@0: if (cr) { michael@0: cr->GetSelectedLocale(NS_LITERAL_CSTRING("global"), uaLangTag); michael@0: } michael@0: if (!uaLangTag.IsEmpty()) { michael@0: aBCP47LangTag = uaLangTag; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // TODO: Probably not worth it, but maybe have a fourth fallback to using michael@0: // the OS locale? michael@0: michael@0: aBCP47LangTag.Truncate(); // Signal iterator exhausted michael@0: } michael@0: michael@0: /* static */ bool michael@0: ICUUtils::LocalizeNumber(double aValue, michael@0: LanguageTagIterForContent& aLangTags, michael@0: nsAString& aLocalizedValue) michael@0: { michael@0: MOZ_ASSERT(aLangTags.IsAtStart(), "Don't call Next() before passing"); michael@0: michael@0: static const int32_t kBufferSize = 256; michael@0: michael@0: UChar buffer[kBufferSize]; michael@0: michael@0: nsAutoCString langTag; michael@0: aLangTags.GetNext(langTag); michael@0: while (!langTag.IsEmpty()) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: AutoCloseUNumberFormat format(unum_open(UNUM_DECIMAL, nullptr, 0, michael@0: langTag.get(), nullptr, &status)); michael@0: unum_setAttribute(format, UNUM_GROUPING_USED, michael@0: LocaleNumberGroupingIsEnabled()); michael@0: // ICU default is a maximum of 3 significant fractional digits. We don't michael@0: // want that limit, so we set it to the maximum that a double can represent michael@0: // (14-16 decimal fractional digits). michael@0: unum_setAttribute(format, UNUM_MAX_FRACTION_DIGITS, 16); michael@0: int32_t length = unum_formatDouble(format, aValue, buffer, kBufferSize, michael@0: nullptr, &status); michael@0: NS_ASSERTION(length < kBufferSize && michael@0: status != U_BUFFER_OVERFLOW_ERROR && michael@0: status != U_STRING_NOT_TERMINATED_WARNING, michael@0: "Need a bigger buffer?!"); michael@0: if (U_SUCCESS(status)) { michael@0: ICUUtils::AssignUCharArrayToString(buffer, length, aLocalizedValue); michael@0: return true; michael@0: } michael@0: aLangTags.GetNext(langTag); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /* static */ double michael@0: ICUUtils::ParseNumber(nsAString& aValue, michael@0: LanguageTagIterForContent& aLangTags) michael@0: { michael@0: MOZ_ASSERT(aLangTags.IsAtStart(), "Don't call Next() before passing"); michael@0: michael@0: if (aValue.IsEmpty()) { michael@0: return std::numeric_limits::quiet_NaN(); michael@0: } michael@0: michael@0: uint32_t length = aValue.Length(); michael@0: michael@0: nsAutoCString langTag; michael@0: aLangTags.GetNext(langTag); michael@0: while (!langTag.IsEmpty()) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: AutoCloseUNumberFormat format(unum_open(UNUM_DECIMAL, nullptr, 0, michael@0: langTag.get(), nullptr, &status)); michael@0: int32_t parsePos = 0; michael@0: static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2, michael@0: "Unexpected character size - the following cast is unsafe"); michael@0: double val = unum_parseDouble(format, michael@0: (const UChar*)PromiseFlatString(aValue).get(), michael@0: length, &parsePos, &status); michael@0: if (U_SUCCESS(status) && parsePos == (int32_t)length) { michael@0: return val; michael@0: } michael@0: aLangTags.GetNext(langTag); michael@0: } michael@0: return std::numeric_limits::quiet_NaN(); michael@0: } michael@0: michael@0: /* static */ void michael@0: ICUUtils::AssignUCharArrayToString(UChar* aICUString, michael@0: int32_t aLength, michael@0: nsAString& aMozString) michael@0: { michael@0: // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can michael@0: // cast here. michael@0: michael@0: static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2, michael@0: "Unexpected character size - the following cast is unsafe"); michael@0: michael@0: aMozString.Assign((const nsAString::char_type*)aICUString, aLength); michael@0: michael@0: NS_ASSERTION((int32_t)aMozString.Length() == aLength, "Conversion failed"); michael@0: } michael@0: michael@0: #if 0 michael@0: /* static */ Locale michael@0: ICUUtils::BCP47CodeToLocale(const nsAString& aBCP47Code) michael@0: { michael@0: MOZ_ASSERT(!aBCP47Code.IsEmpty(), "Don't pass an empty BCP 47 code"); michael@0: michael@0: Locale locale; michael@0: locale.setToBogus(); michael@0: michael@0: // BCP47 codes are guaranteed to be ASCII, so lossy conversion is okay michael@0: NS_LossyConvertUTF16toASCII bcp47code(aBCP47Code); michael@0: michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: int32_t needed; michael@0: michael@0: char localeID[256]; michael@0: needed = uloc_forLanguageTag(bcp47code.get(), localeID, michael@0: PR_ARRAY_SIZE(localeID) - 1, nullptr, michael@0: &status); michael@0: MOZ_ASSERT(needed < int32_t(PR_ARRAY_SIZE(localeID)) - 1, michael@0: "Need a bigger buffer"); michael@0: if (needed <= 0 || U_FAILURE(status)) { michael@0: return locale; michael@0: } michael@0: michael@0: char lang[64]; michael@0: needed = uloc_getLanguage(localeID, lang, PR_ARRAY_SIZE(lang) - 1, michael@0: &status); michael@0: MOZ_ASSERT(needed < int32_t(PR_ARRAY_SIZE(lang)) - 1, michael@0: "Need a bigger buffer"); michael@0: if (needed <= 0 || U_FAILURE(status)) { michael@0: return locale; michael@0: } michael@0: michael@0: char country[64]; michael@0: needed = uloc_getCountry(localeID, country, PR_ARRAY_SIZE(country) - 1, michael@0: &status); michael@0: MOZ_ASSERT(needed < int32_t(PR_ARRAY_SIZE(country)) - 1, michael@0: "Need a bigger buffer"); michael@0: if (needed > 0 && U_SUCCESS(status)) { michael@0: locale = Locale(lang, country); michael@0: } michael@0: michael@0: if (locale.isBogus()) { michael@0: // Using the country resulted in a bogus Locale, so try with only the lang michael@0: locale = Locale(lang); michael@0: } michael@0: michael@0: return locale; michael@0: } michael@0: michael@0: /* static */ void michael@0: ICUUtils::ToMozString(UnicodeString& aICUString, nsAString& aMozString) michael@0: { michael@0: // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can michael@0: // cast here. michael@0: michael@0: static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2, michael@0: "Unexpected character size - the following cast is unsafe"); michael@0: michael@0: const nsAString::char_type* buf = michael@0: (const nsAString::char_type*)aICUString.getTerminatedBuffer(); michael@0: aMozString.Assign(buf); michael@0: michael@0: NS_ASSERTION(aMozString.Length() == (uint32_t)aICUString.length(), michael@0: "Conversion failed"); michael@0: } michael@0: michael@0: /* static */ void michael@0: ICUUtils::ToICUString(nsAString& aMozString, UnicodeString& aICUString) michael@0: { michael@0: // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can michael@0: // cast here. michael@0: michael@0: static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2, michael@0: "Unexpected character size - the following cast is unsafe"); michael@0: michael@0: aICUString.setTo((UChar*)PromiseFlatString(aMozString).get(), michael@0: aMozString.Length()); michael@0: michael@0: NS_ASSERTION(aMozString.Length() == (uint32_t)aICUString.length(), michael@0: "Conversion failed"); michael@0: } michael@0: #endif michael@0: michael@0: #endif /* ENABLE_INTL_API */ michael@0: #endif /* MOZILLA_INTERNAL_API */ michael@0: