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