Line data Source code
1 : /* This Source Code Form is subject to the terms of the Mozilla Public
2 : * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
4 :
5 : #ifdef MOZILLA_INTERNAL_API
6 : #ifdef ENABLE_INTL_API
7 :
8 : #include "ICUUtils.h"
9 : #include "mozilla/Preferences.h"
10 : #include "mozilla/intl/LocaleService.h"
11 : #include "nsIContent.h"
12 : #include "nsIDocument.h"
13 : #include "nsStringGlue.h"
14 : #include "unicode/uloc.h"
15 : #include "unicode/unum.h"
16 :
17 : using namespace mozilla;
18 : using mozilla::intl::LocaleService;
19 :
20 : /**
21 : * This pref just controls whether we format the number with grouping separator
22 : * characters when the internal value is set or updated. It does not stop the
23 : * user from typing in a number and using grouping separators.
24 : */
25 : static bool gLocaleNumberGroupingEnabled;
26 : static const char LOCALE_NUMBER_GROUPING_PREF_STR[] = "dom.forms.number.grouping";
27 :
28 : static bool
29 0 : LocaleNumberGroupingIsEnabled()
30 : {
31 : static bool sInitialized = false;
32 :
33 0 : if (!sInitialized) {
34 : /* check and register ourselves with the pref */
35 : Preferences::AddBoolVarCache(&gLocaleNumberGroupingEnabled,
36 : LOCALE_NUMBER_GROUPING_PREF_STR,
37 0 : false);
38 0 : sInitialized = true;
39 : }
40 :
41 0 : return gLocaleNumberGroupingEnabled;
42 : }
43 :
44 : void
45 0 : ICUUtils::LanguageTagIterForContent::GetNext(nsACString& aBCP47LangTag)
46 : {
47 0 : if (mCurrentFallbackIndex < 0) {
48 0 : mCurrentFallbackIndex = 0;
49 : // Try the language specified by a 'lang'/'xml:lang' attribute on mContent
50 : // or any ancestor, if such an attribute is specified:
51 0 : nsAutoString lang;
52 0 : mContent->GetLang(lang);
53 0 : if (!lang.IsEmpty()) {
54 0 : aBCP47LangTag = NS_ConvertUTF16toUTF8(lang);
55 0 : return;
56 : }
57 : }
58 :
59 0 : if (mCurrentFallbackIndex < 1) {
60 0 : mCurrentFallbackIndex = 1;
61 : // Else try the language specified by any Content-Language HTTP header or
62 : // pragma directive:
63 0 : nsIDocument* doc = mContent->OwnerDoc();
64 0 : nsAutoString lang;
65 0 : doc->GetContentLanguage(lang);
66 0 : if (!lang.IsEmpty()) {
67 0 : aBCP47LangTag = NS_ConvertUTF16toUTF8(lang);
68 0 : return;
69 : }
70 : }
71 :
72 0 : if (mCurrentFallbackIndex < 2) {
73 0 : mCurrentFallbackIndex = 2;
74 : // Else take the app's locale:
75 :
76 0 : nsAutoCString appLocale;
77 0 : LocaleService::GetInstance()->GetAppLocaleAsBCP47(aBCP47LangTag);
78 0 : return;
79 : }
80 :
81 : // TODO: Probably not worth it, but maybe have a fourth fallback to using
82 : // the OS locale?
83 :
84 0 : aBCP47LangTag.Truncate(); // Signal iterator exhausted
85 : }
86 :
87 : /* static */ bool
88 0 : ICUUtils::LocalizeNumber(double aValue,
89 : LanguageTagIterForContent& aLangTags,
90 : nsAString& aLocalizedValue)
91 : {
92 0 : MOZ_ASSERT(aLangTags.IsAtStart(), "Don't call Next() before passing");
93 :
94 : static const int32_t kBufferSize = 256;
95 :
96 : UChar buffer[kBufferSize];
97 :
98 0 : nsAutoCString langTag;
99 0 : aLangTags.GetNext(langTag);
100 0 : while (!langTag.IsEmpty()) {
101 0 : UErrorCode status = U_ZERO_ERROR;
102 0 : AutoCloseUNumberFormat format(unum_open(UNUM_DECIMAL, nullptr, 0,
103 0 : langTag.get(), nullptr, &status));
104 0 : unum_setAttribute(format, UNUM_GROUPING_USED,
105 0 : LocaleNumberGroupingIsEnabled());
106 : // ICU default is a maximum of 3 significant fractional digits. We don't
107 : // want that limit, so we set it to the maximum that a double can represent
108 : // (14-16 decimal fractional digits).
109 0 : unum_setAttribute(format, UNUM_MAX_FRACTION_DIGITS, 16);
110 0 : int32_t length = unum_formatDouble(format, aValue, buffer, kBufferSize,
111 0 : nullptr, &status);
112 0 : NS_ASSERTION(length < kBufferSize &&
113 : status != U_BUFFER_OVERFLOW_ERROR &&
114 : status != U_STRING_NOT_TERMINATED_WARNING,
115 : "Need a bigger buffer?!");
116 0 : if (U_SUCCESS(status)) {
117 0 : ICUUtils::AssignUCharArrayToString(buffer, length, aLocalizedValue);
118 0 : return true;
119 : }
120 0 : aLangTags.GetNext(langTag);
121 : }
122 0 : return false;
123 : }
124 :
125 : /* static */ double
126 0 : ICUUtils::ParseNumber(nsAString& aValue,
127 : LanguageTagIterForContent& aLangTags)
128 : {
129 0 : MOZ_ASSERT(aLangTags.IsAtStart(), "Don't call Next() before passing");
130 :
131 0 : if (aValue.IsEmpty()) {
132 0 : return std::numeric_limits<float>::quiet_NaN();
133 : }
134 :
135 0 : uint32_t length = aValue.Length();
136 :
137 0 : nsAutoCString langTag;
138 0 : aLangTags.GetNext(langTag);
139 0 : while (!langTag.IsEmpty()) {
140 0 : UErrorCode status = U_ZERO_ERROR;
141 0 : AutoCloseUNumberFormat format(unum_open(UNUM_DECIMAL, nullptr, 0,
142 0 : langTag.get(), nullptr, &status));
143 0 : int32_t parsePos = 0;
144 : static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2,
145 : "Unexpected character size - the following cast is unsafe");
146 0 : double val = unum_parseDouble(format,
147 0 : (const UChar*)PromiseFlatString(aValue).get(),
148 0 : length, &parsePos, &status);
149 0 : if (U_SUCCESS(status) && parsePos == (int32_t)length) {
150 0 : return val;
151 : }
152 0 : aLangTags.GetNext(langTag);
153 : }
154 0 : return std::numeric_limits<float>::quiet_NaN();
155 : }
156 :
157 : /* static */ void
158 0 : ICUUtils::AssignUCharArrayToString(UChar* aICUString,
159 : int32_t aLength,
160 : nsAString& aMozString)
161 : {
162 : // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can
163 : // cast here.
164 :
165 : static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2,
166 : "Unexpected character size - the following cast is unsafe");
167 :
168 0 : aMozString.Assign((const nsAString::char_type*)aICUString, aLength);
169 :
170 0 : NS_ASSERTION((int32_t)aMozString.Length() == aLength, "Conversion failed");
171 0 : }
172 :
173 : /* static */ nsresult
174 0 : ICUUtils::UErrorToNsResult(const UErrorCode aErrorCode)
175 : {
176 0 : if (U_SUCCESS(aErrorCode)) {
177 0 : return NS_OK;
178 : }
179 :
180 0 : switch(aErrorCode) {
181 : case U_ILLEGAL_ARGUMENT_ERROR:
182 0 : return NS_ERROR_INVALID_ARG;
183 :
184 : case U_MEMORY_ALLOCATION_ERROR:
185 0 : return NS_ERROR_OUT_OF_MEMORY;
186 :
187 : default:
188 0 : return NS_ERROR_FAILURE;
189 : }
190 : }
191 :
192 : #if 0
193 : /* static */ Locale
194 : ICUUtils::BCP47CodeToLocale(const nsAString& aBCP47Code)
195 : {
196 : MOZ_ASSERT(!aBCP47Code.IsEmpty(), "Don't pass an empty BCP 47 code");
197 :
198 : Locale locale;
199 : locale.setToBogus();
200 :
201 : // BCP47 codes are guaranteed to be ASCII, so lossy conversion is okay
202 : NS_LossyConvertUTF16toASCII bcp47code(aBCP47Code);
203 :
204 : UErrorCode status = U_ZERO_ERROR;
205 : int32_t needed;
206 :
207 : char localeID[256];
208 : needed = uloc_forLanguageTag(bcp47code.get(), localeID,
209 : PR_ARRAY_SIZE(localeID) - 1, nullptr,
210 : &status);
211 : MOZ_ASSERT(needed < int32_t(PR_ARRAY_SIZE(localeID)) - 1,
212 : "Need a bigger buffer");
213 : if (needed <= 0 || U_FAILURE(status)) {
214 : return locale;
215 : }
216 :
217 : char lang[64];
218 : needed = uloc_getLanguage(localeID, lang, PR_ARRAY_SIZE(lang) - 1,
219 : &status);
220 : MOZ_ASSERT(needed < int32_t(PR_ARRAY_SIZE(lang)) - 1,
221 : "Need a bigger buffer");
222 : if (needed <= 0 || U_FAILURE(status)) {
223 : return locale;
224 : }
225 :
226 : char country[64];
227 : needed = uloc_getCountry(localeID, country, PR_ARRAY_SIZE(country) - 1,
228 : &status);
229 : MOZ_ASSERT(needed < int32_t(PR_ARRAY_SIZE(country)) - 1,
230 : "Need a bigger buffer");
231 : if (needed > 0 && U_SUCCESS(status)) {
232 : locale = Locale(lang, country);
233 : }
234 :
235 : if (locale.isBogus()) {
236 : // Using the country resulted in a bogus Locale, so try with only the lang
237 : locale = Locale(lang);
238 : }
239 :
240 : return locale;
241 : }
242 :
243 : /* static */ void
244 : ICUUtils::ToMozString(UnicodeString& aICUString, nsAString& aMozString)
245 : {
246 : // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can
247 : // cast here.
248 :
249 : static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2,
250 : "Unexpected character size - the following cast is unsafe");
251 :
252 : const nsAString::char_type* buf =
253 : (const nsAString::char_type*)aICUString.getTerminatedBuffer();
254 : aMozString.Assign(buf);
255 :
256 : NS_ASSERTION(aMozString.Length() == (uint32_t)aICUString.length(),
257 : "Conversion failed");
258 : }
259 :
260 : /* static */ void
261 : ICUUtils::ToICUString(nsAString& aMozString, UnicodeString& aICUString)
262 : {
263 : // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can
264 : // cast here.
265 :
266 : static_assert(sizeof(UChar) == 2 && sizeof(nsAString::char_type) == 2,
267 : "Unexpected character size - the following cast is unsafe");
268 :
269 : aICUString.setTo((UChar*)PromiseFlatString(aMozString).get(),
270 : aMozString.Length());
271 :
272 : NS_ASSERTION(aMozString.Length() == (uint32_t)aICUString.length(),
273 : "Conversion failed");
274 : }
275 : #endif
276 :
277 : #endif /* ENABLE_INTL_API */
278 : #endif /* MOZILLA_INTERNAL_API */
279 :
|