diff options
Diffstat (limited to 'intl/unicharutil/util/ICUUtils.cpp')
-rw-r--r-- | intl/unicharutil/util/ICUUtils.cpp | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/intl/unicharutil/util/ICUUtils.cpp b/intl/unicharutil/util/ICUUtils.cpp new file mode 100644 index 000000000..2bb33d854 --- /dev/null +++ b/intl/unicharutil/util/ICUUtils.cpp @@ -0,0 +1,284 @@ +/* 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 +#ifdef ENABLE_INTL_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<nsIToolkitChromeRegistry> 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<float>::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<float>::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 /* ENABLE_INTL_API */ +#endif /* MOZILLA_INTERNAL_API */ + |