1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/intl/unicharutil/util/ICUUtils.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,265 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#ifdef MOZILLA_INTERNAL_API 1.9 +#ifdef ENABLE_INTL_API 1.10 + 1.11 +#include "ICUUtils.h" 1.12 +#include "mozilla/Preferences.h" 1.13 +#include "nsIContent.h" 1.14 +#include "nsIDocument.h" 1.15 +#include "nsIToolkitChromeRegistry.h" 1.16 +#include "nsStringGlue.h" 1.17 +#include "unicode/uloc.h" 1.18 +#include "unicode/unum.h" 1.19 + 1.20 +using namespace mozilla; 1.21 + 1.22 +/** 1.23 + * This pref just controls whether we format the number with grouping separator 1.24 + * characters when the internal value is set or updated. It does not stop the 1.25 + * user from typing in a number and using grouping separators. 1.26 + */ 1.27 +static bool gLocaleNumberGroupingEnabled; 1.28 +static const char LOCALE_NUMBER_GROUPING_PREF_STR[] = "dom.forms.number.grouping"; 1.29 + 1.30 +static bool 1.31 +LocaleNumberGroupingIsEnabled() 1.32 +{ 1.33 + static bool sInitialized = false; 1.34 + 1.35 + if (!sInitialized) { 1.36 + /* check and register ourselves with the pref */ 1.37 + Preferences::AddBoolVarCache(&gLocaleNumberGroupingEnabled, 1.38 + LOCALE_NUMBER_GROUPING_PREF_STR, 1.39 + false); 1.40 + sInitialized = true; 1.41 + } 1.42 + 1.43 + return gLocaleNumberGroupingEnabled; 1.44 +} 1.45 + 1.46 +void 1.47 +ICUUtils::LanguageTagIterForContent::GetNext(nsACString& aBCP47LangTag) 1.48 +{ 1.49 + if (mCurrentFallbackIndex < 0) { 1.50 + mCurrentFallbackIndex = 0; 1.51 + // Try the language specified by a 'lang'/'xml:lang' attribute on mContent 1.52 + // or any ancestor, if such an attribute is specified: 1.53 + nsAutoString lang; 1.54 + mContent->GetLang(lang); 1.55 + if (!lang.IsEmpty()) { 1.56 + aBCP47LangTag = NS_ConvertUTF16toUTF8(lang); 1.57 + return; 1.58 + } 1.59 + } 1.60 + 1.61 + if (mCurrentFallbackIndex < 1) { 1.62 + mCurrentFallbackIndex = 1; 1.63 + // Else try the language specified by any Content-Language HTTP header or 1.64 + // pragma directive: 1.65 + nsIDocument* doc = mContent->OwnerDoc(); 1.66 + nsAutoString lang; 1.67 + doc->GetContentLanguage(lang); 1.68 + if (!lang.IsEmpty()) { 1.69 + aBCP47LangTag = NS_ConvertUTF16toUTF8(lang); 1.70 + return; 1.71 + } 1.72 + } 1.73 + 1.74 + if (mCurrentFallbackIndex < 2) { 1.75 + mCurrentFallbackIndex = 2; 1.76 + // Else try the user-agent's locale: 1.77 + nsCOMPtr<nsIToolkitChromeRegistry> cr = 1.78 + mozilla::services::GetToolkitChromeRegistryService(); 1.79 + nsAutoCString uaLangTag; 1.80 + if (cr) { 1.81 + cr->GetSelectedLocale(NS_LITERAL_CSTRING("global"), uaLangTag); 1.82 + } 1.83 + if (!uaLangTag.IsEmpty()) { 1.84 + aBCP47LangTag = uaLangTag; 1.85 + return; 1.86 + } 1.87 + } 1.88 + 1.89 + // TODO: Probably not worth it, but maybe have a fourth fallback to using 1.90 + // the OS locale? 1.91 + 1.92 + aBCP47LangTag.Truncate(); // Signal iterator exhausted 1.93 +} 1.94 + 1.95 +/* static */ bool 1.96 +ICUUtils::LocalizeNumber(double aValue, 1.97 + LanguageTagIterForContent& aLangTags, 1.98 + nsAString& aLocalizedValue) 1.99 +{ 1.100 + MOZ_ASSERT(aLangTags.IsAtStart(), "Don't call Next() before passing"); 1.101 + 1.102 + static const int32_t kBufferSize = 256; 1.103 + 1.104 + UChar buffer[kBufferSize]; 1.105 + 1.106 + nsAutoCString langTag; 1.107 + aLangTags.GetNext(langTag); 1.108 + while (!langTag.IsEmpty()) { 1.109 + UErrorCode status = U_ZERO_ERROR; 1.110 + AutoCloseUNumberFormat format(unum_open(UNUM_DECIMAL, nullptr, 0, 1.111 + langTag.get(), nullptr, &status)); 1.112 + unum_setAttribute(format, UNUM_GROUPING_USED, 1.113 + LocaleNumberGroupingIsEnabled()); 1.114 + // ICU default is a maximum of 3 significant fractional digits. We don't 1.115 + // want that limit, so we set it to the maximum that a double can represent 1.116 + // (14-16 decimal fractional digits). 1.117 + unum_setAttribute(format, UNUM_MAX_FRACTION_DIGITS, 16); 1.118 + int32_t length = unum_formatDouble(format, aValue, buffer, kBufferSize, 1.119 + nullptr, &status); 1.120 + NS_ASSERTION(length < kBufferSize && 1.121 + status != U_BUFFER_OVERFLOW_ERROR && 1.122 + status != U_STRING_NOT_TERMINATED_WARNING, 1.123 + "Need a bigger buffer?!"); 1.124 + if (U_SUCCESS(status)) { 1.125 + ICUUtils::AssignUCharArrayToString(buffer, length, aLocalizedValue); 1.126 + return true; 1.127 + } 1.128 + aLangTags.GetNext(langTag); 1.129 + } 1.130 + return false; 1.131 +} 1.132 + 1.133 +/* static */ double 1.134 +ICUUtils::ParseNumber(nsAString& aValue, 1.135 + LanguageTagIterForContent& aLangTags) 1.136 +{ 1.137 + MOZ_ASSERT(aLangTags.IsAtStart(), "Don't call Next() before passing"); 1.138 + 1.139 + if (aValue.IsEmpty()) { 1.140 + return std::numeric_limits<float>::quiet_NaN(); 1.141 + } 1.142 + 1.143 + uint32_t length = aValue.Length(); 1.144 + 1.145 + nsAutoCString langTag; 1.146 + aLangTags.GetNext(langTag); 1.147 + while (!langTag.IsEmpty()) { 1.148 + UErrorCode status = U_ZERO_ERROR; 1.149 + AutoCloseUNumberFormat format(unum_open(UNUM_DECIMAL, nullptr, 0, 1.150 + langTag.get(), nullptr, &status)); 1.151 + int32_t parsePos = 0; 1.152 + static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2, 1.153 + "Unexpected character size - the following cast is unsafe"); 1.154 + double val = unum_parseDouble(format, 1.155 + (const UChar*)PromiseFlatString(aValue).get(), 1.156 + length, &parsePos, &status); 1.157 + if (U_SUCCESS(status) && parsePos == (int32_t)length) { 1.158 + return val; 1.159 + } 1.160 + aLangTags.GetNext(langTag); 1.161 + } 1.162 + return std::numeric_limits<float>::quiet_NaN(); 1.163 +} 1.164 + 1.165 +/* static */ void 1.166 +ICUUtils::AssignUCharArrayToString(UChar* aICUString, 1.167 + int32_t aLength, 1.168 + nsAString& aMozString) 1.169 +{ 1.170 + // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can 1.171 + // cast here. 1.172 + 1.173 + static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2, 1.174 + "Unexpected character size - the following cast is unsafe"); 1.175 + 1.176 + aMozString.Assign((const nsAString::char_type*)aICUString, aLength); 1.177 + 1.178 + NS_ASSERTION((int32_t)aMozString.Length() == aLength, "Conversion failed"); 1.179 +} 1.180 + 1.181 +#if 0 1.182 +/* static */ Locale 1.183 +ICUUtils::BCP47CodeToLocale(const nsAString& aBCP47Code) 1.184 +{ 1.185 + MOZ_ASSERT(!aBCP47Code.IsEmpty(), "Don't pass an empty BCP 47 code"); 1.186 + 1.187 + Locale locale; 1.188 + locale.setToBogus(); 1.189 + 1.190 + // BCP47 codes are guaranteed to be ASCII, so lossy conversion is okay 1.191 + NS_LossyConvertUTF16toASCII bcp47code(aBCP47Code); 1.192 + 1.193 + UErrorCode status = U_ZERO_ERROR; 1.194 + int32_t needed; 1.195 + 1.196 + char localeID[256]; 1.197 + needed = uloc_forLanguageTag(bcp47code.get(), localeID, 1.198 + PR_ARRAY_SIZE(localeID) - 1, nullptr, 1.199 + &status); 1.200 + MOZ_ASSERT(needed < int32_t(PR_ARRAY_SIZE(localeID)) - 1, 1.201 + "Need a bigger buffer"); 1.202 + if (needed <= 0 || U_FAILURE(status)) { 1.203 + return locale; 1.204 + } 1.205 + 1.206 + char lang[64]; 1.207 + needed = uloc_getLanguage(localeID, lang, PR_ARRAY_SIZE(lang) - 1, 1.208 + &status); 1.209 + MOZ_ASSERT(needed < int32_t(PR_ARRAY_SIZE(lang)) - 1, 1.210 + "Need a bigger buffer"); 1.211 + if (needed <= 0 || U_FAILURE(status)) { 1.212 + return locale; 1.213 + } 1.214 + 1.215 + char country[64]; 1.216 + needed = uloc_getCountry(localeID, country, PR_ARRAY_SIZE(country) - 1, 1.217 + &status); 1.218 + MOZ_ASSERT(needed < int32_t(PR_ARRAY_SIZE(country)) - 1, 1.219 + "Need a bigger buffer"); 1.220 + if (needed > 0 && U_SUCCESS(status)) { 1.221 + locale = Locale(lang, country); 1.222 + } 1.223 + 1.224 + if (locale.isBogus()) { 1.225 + // Using the country resulted in a bogus Locale, so try with only the lang 1.226 + locale = Locale(lang); 1.227 + } 1.228 + 1.229 + return locale; 1.230 +} 1.231 + 1.232 +/* static */ void 1.233 +ICUUtils::ToMozString(UnicodeString& aICUString, nsAString& aMozString) 1.234 +{ 1.235 + // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can 1.236 + // cast here. 1.237 + 1.238 + static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2, 1.239 + "Unexpected character size - the following cast is unsafe"); 1.240 + 1.241 + const nsAString::char_type* buf = 1.242 + (const nsAString::char_type*)aICUString.getTerminatedBuffer(); 1.243 + aMozString.Assign(buf); 1.244 + 1.245 + NS_ASSERTION(aMozString.Length() == (uint32_t)aICUString.length(), 1.246 + "Conversion failed"); 1.247 +} 1.248 + 1.249 +/* static */ void 1.250 +ICUUtils::ToICUString(nsAString& aMozString, UnicodeString& aICUString) 1.251 +{ 1.252 + // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can 1.253 + // cast here. 1.254 + 1.255 + static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2, 1.256 + "Unexpected character size - the following cast is unsafe"); 1.257 + 1.258 + aICUString.setTo((UChar*)PromiseFlatString(aMozString).get(), 1.259 + aMozString.Length()); 1.260 + 1.261 + NS_ASSERTION(aMozString.Length() == (uint32_t)aICUString.length(), 1.262 + "Conversion failed"); 1.263 +} 1.264 +#endif 1.265 + 1.266 +#endif /* ENABLE_INTL_API */ 1.267 +#endif /* MOZILLA_INTERNAL_API */ 1.268 +