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/. */
5 #ifdef MOZILLA_INTERNAL_API
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"
17 using namespace mozilla
;
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.
24 static bool gLocaleNumberGroupingEnabled
;
25 static const char LOCALE_NUMBER_GROUPING_PREF_STR
[] = "dom.forms.number.grouping";
28 LocaleNumberGroupingIsEnabled()
30 static bool sInitialized
= false;
33 /* check and register ourselves with the pref */
34 Preferences::AddBoolVarCache(&gLocaleNumberGroupingEnabled
,
35 LOCALE_NUMBER_GROUPING_PREF_STR
,
40 return gLocaleNumberGroupingEnabled
;
44 ICUUtils::LanguageTagIterForContent::GetNext(nsACString
& aBCP47LangTag
)
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:
51 mContent
->GetLang(lang
);
52 if (!lang
.IsEmpty()) {
53 aBCP47LangTag
= NS_ConvertUTF16toUTF8(lang
);
58 if (mCurrentFallbackIndex
< 1) {
59 mCurrentFallbackIndex
= 1;
60 // Else try the language specified by any Content-Language HTTP header or
62 nsIDocument
* doc
= mContent
->OwnerDoc();
64 doc
->GetContentLanguage(lang
);
65 if (!lang
.IsEmpty()) {
66 aBCP47LangTag
= NS_ConvertUTF16toUTF8(lang
);
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
;
78 cr
->GetSelectedLocale(NS_LITERAL_CSTRING("global"), uaLangTag
);
80 if (!uaLangTag
.IsEmpty()) {
81 aBCP47LangTag
= uaLangTag
;
86 // TODO: Probably not worth it, but maybe have a fourth fallback to using
89 aBCP47LangTag
.Truncate(); // Signal iterator exhausted
93 ICUUtils::LocalizeNumber(double aValue
,
94 LanguageTagIterForContent
& aLangTags
,
95 nsAString
& aLocalizedValue
)
97 MOZ_ASSERT(aLangTags
.IsAtStart(), "Don't call Next() before passing");
99 static const int32_t kBufferSize
= 256;
101 UChar buffer
[kBufferSize
];
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
,
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
);
125 aLangTags
.GetNext(langTag
);
131 ICUUtils::ParseNumber(nsAString
& aValue
,
132 LanguageTagIterForContent
& aLangTags
)
134 MOZ_ASSERT(aLangTags
.IsAtStart(), "Don't call Next() before passing");
136 if (aValue
.IsEmpty()) {
137 return std::numeric_limits
<float>::quiet_NaN();
140 uint32_t length
= aValue
.Length();
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
) {
157 aLangTags
.GetNext(langTag
);
159 return std::numeric_limits
<float>::quiet_NaN();
163 ICUUtils::AssignUCharArrayToString(UChar
* aICUString
,
165 nsAString
& aMozString
)
167 // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can
170 static_assert(sizeof(UChar
) == 2 && sizeof(nsAString::char_type
) == 2,
171 "Unexpected character size - the following cast is unsafe");
173 aMozString
.Assign((const nsAString::char_type
*)aICUString
, aLength
);
175 NS_ASSERTION((int32_t)aMozString
.Length() == aLength
, "Conversion failed");
180 ICUUtils::BCP47CodeToLocale(const nsAString
& aBCP47Code
)
182 MOZ_ASSERT(!aBCP47Code
.IsEmpty(), "Don't pass an empty BCP 47 code");
187 // BCP47 codes are guaranteed to be ASCII, so lossy conversion is okay
188 NS_LossyConvertUTF16toASCII
bcp47code(aBCP47Code
);
190 UErrorCode status
= U_ZERO_ERROR
;
194 needed
= uloc_forLanguageTag(bcp47code
.get(), localeID
,
195 PR_ARRAY_SIZE(localeID
) - 1, nullptr,
197 MOZ_ASSERT(needed
< int32_t(PR_ARRAY_SIZE(localeID
)) - 1,
198 "Need a bigger buffer");
199 if (needed
<= 0 || U_FAILURE(status
)) {
204 needed
= uloc_getLanguage(localeID
, lang
, PR_ARRAY_SIZE(lang
) - 1,
206 MOZ_ASSERT(needed
< int32_t(PR_ARRAY_SIZE(lang
)) - 1,
207 "Need a bigger buffer");
208 if (needed
<= 0 || U_FAILURE(status
)) {
213 needed
= uloc_getCountry(localeID
, country
, PR_ARRAY_SIZE(country
) - 1,
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
);
221 if (locale
.isBogus()) {
222 // Using the country resulted in a bogus Locale, so try with only the lang
223 locale
= Locale(lang
);
230 ICUUtils::ToMozString(UnicodeString
& aICUString
, nsAString
& aMozString
)
232 // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can
235 static_assert(sizeof(UChar
) == 2 && sizeof(nsAString::char_type
) == 2,
236 "Unexpected character size - the following cast is unsafe");
238 const nsAString::char_type
* buf
=
239 (const nsAString::char_type
*)aICUString
.getTerminatedBuffer();
240 aMozString
.Assign(buf
);
242 NS_ASSERTION(aMozString
.Length() == (uint32_t)aICUString
.length(),
243 "Conversion failed");
247 ICUUtils::ToICUString(nsAString
& aMozString
, UnicodeString
& aICUString
)
249 // Both ICU's UnicodeString and Mozilla's nsAString use UTF-16, so we can
252 static_assert(sizeof(UChar
) == 2 && sizeof(nsAString::char_type
) == 2,
253 "Unexpected character size - the following cast is unsafe");
255 aICUString
.setTo((UChar
*)PromiseFlatString(aMozString
).get(),
256 aMozString
.Length());
258 NS_ASSERTION(aMozString
.Length() == (uint32_t)aICUString
.length(),
259 "Conversion failed");
263 #endif /* ENABLE_INTL_API */
264 #endif /* MOZILLA_INTERNAL_API */