ICUUtils.cpp 8.6 KB

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