Coverage Report

Created: 2018-09-25 14:53

/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