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