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