Line data Source code
1 : // Copyright 2018 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #ifndef V8_INTL_SUPPORT
6 : #error Internationalization is expected to be enabled.
7 : #endif // V8_INTL_SUPPORT
8 :
9 : #include "src/objects/js-number-format.h"
10 :
11 : #include <set>
12 : #include <string>
13 :
14 : #include "src/isolate.h"
15 : #include "src/objects-inl.h"
16 : #include "src/objects/intl-objects.h"
17 : #include "src/objects/js-number-format-inl.h"
18 : #include "unicode/decimfmt.h"
19 : #include "unicode/locid.h"
20 : #include "unicode/numfmt.h"
21 : #include "unicode/uloc.h"
22 :
23 : namespace v8 {
24 : namespace internal {
25 :
26 : namespace {
27 :
28 1367 : UNumberFormatStyle ToNumberFormatStyle(
29 : JSNumberFormat::CurrencyDisplay currency_display) {
30 1367 : switch (currency_display) {
31 : case JSNumberFormat::CurrencyDisplay::SYMBOL:
32 : return UNUM_CURRENCY;
33 : case JSNumberFormat::CurrencyDisplay::CODE:
34 27 : return UNUM_CURRENCY_ISO;
35 : case JSNumberFormat::CurrencyDisplay::NAME:
36 54 : return UNUM_CURRENCY_PLURAL;
37 : case JSNumberFormat::CurrencyDisplay::COUNT:
38 0 : UNREACHABLE();
39 : }
40 0 : }
41 :
42 : // ecma-402/#sec-currencydigits
43 : // The currency is expected to an all upper case string value.
44 117 : int CurrencyDigits(const icu::UnicodeString& currency) {
45 117 : UErrorCode status = U_ZERO_ERROR;
46 : uint32_t fraction_digits = ucurr_getDefaultFractionDigits(
47 117 : reinterpret_cast<const UChar*>(currency.getBuffer()), &status);
48 : // For missing currency codes, default to the most common, 2
49 117 : return U_SUCCESS(status) ? fraction_digits : 2;
50 : }
51 :
52 : bool IsAToZ(char ch) { return IsInRange(AsciiAlphaToLower(ch), 'a', 'z'); }
53 :
54 : // ecma402/#sec-iswellformedcurrencycode
55 126 : bool IsWellFormedCurrencyCode(const std::string& currency) {
56 : // Verifies that the input is a well-formed ISO 4217 currency code.
57 : // ecma402/#sec-currency-codes
58 : // 2. If the number of elements in normalized is not 3, return false.
59 126 : if (currency.length() != 3) return false;
60 : // 1. Let normalized be the result of mapping currency to upper case as
61 : // described in 6.1.
62 : //
63 : // 3. If normalized contains any character that is not in
64 : // the range "A" to "Z" (U+0041 to U+005A), return false.
65 : //
66 : // 4. Return true.
67 : // Don't uppercase to test. It could convert invalid code into a valid one.
68 : // For example \u00DFP (Eszett+P) becomes SSP.
69 504 : return (IsAToZ(currency[0]) && IsAToZ(currency[1]) && IsAToZ(currency[2]));
70 : }
71 :
72 : } // anonymous namespace
73 :
74 : // static
75 : // ecma402 #sec-intl.numberformat.prototype.resolvedoptions
76 729 : Handle<JSObject> JSNumberFormat::ResolvedOptions(
77 : Isolate* isolate, Handle<JSNumberFormat> number_format_holder) {
78 : Factory* factory = isolate->factory();
79 :
80 : // 4. Let options be ! ObjectCreate(%ObjectPrototype%).
81 729 : Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
82 :
83 : icu::NumberFormat* number_format =
84 1458 : number_format_holder->icu_number_format()->raw();
85 729 : CHECK_NOT_NULL(number_format);
86 : icu::DecimalFormat* decimal_format =
87 : static_cast<icu::DecimalFormat*>(number_format);
88 729 : CHECK_NOT_NULL(decimal_format);
89 :
90 : Handle<String> locale =
91 1458 : Handle<String>(number_format_holder->locale(), isolate);
92 :
93 729 : std::unique_ptr<char[]> locale_str = locale->ToCString();
94 2187 : icu::Locale icu_locale = Intl::CreateICULocale(locale_str.get());
95 :
96 729 : std::string numbering_system = Intl::GetNumberingSystem(icu_locale);
97 :
98 : // 5. For each row of Table 4, except the header row, in table order, do
99 : // Table 4: Resolved Options of NumberFormat Instances
100 : // Internal Slot Property
101 : // [[Locale]] "locale"
102 : // [[NumberingSystem]] "numberingSystem"
103 : // [[Style]] "style"
104 : // [[Currency]] "currency"
105 : // [[CurrencyDisplay]] "currencyDisplay"
106 : // [[MinimumIntegerDigits]] "minimumIntegerDigits"
107 : // [[MinimumFractionDigits]] "minimumFractionDigits"
108 : // [[MaximumFractionDigits]] "maximumFractionDigits"
109 : // [[MinimumSignificantDigits]] "minimumSignificantDigits"
110 : // [[MaximumSignificantDigits]] "maximumSignificantDigits"
111 : // [[UseGrouping]] "useGrouping"
112 1458 : CHECK(JSReceiver::CreateDataProperty(
113 : isolate, options, factory->locale_string(), locale, kDontThrow)
114 : .FromJust());
115 729 : if (!numbering_system.empty()) {
116 2187 : CHECK(JSReceiver::CreateDataProperty(
117 : isolate, options, factory->numberingSystem_string(),
118 : factory->NewStringFromAsciiChecked(numbering_system.c_str()),
119 : kDontThrow)
120 : .FromJust());
121 : }
122 1458 : CHECK(JSReceiver::CreateDataProperty(
123 : isolate, options, factory->style_string(),
124 : number_format_holder->StyleAsString(), kDontThrow)
125 : .FromJust());
126 729 : if (number_format_holder->style() == Style::CURRENCY) {
127 9 : icu::UnicodeString currency(number_format->getCurrency());
128 : DCHECK(!currency.isEmpty());
129 36 : CHECK(JSReceiver::CreateDataProperty(
130 : isolate, options, factory->currency_string(),
131 : factory
132 : ->NewStringFromTwoByte(Vector<const uint16_t>(
133 : reinterpret_cast<const uint16_t*>(currency.getBuffer()),
134 : currency.length()))
135 : .ToHandleChecked(),
136 : kDontThrow)
137 : .FromJust());
138 :
139 27 : CHECK(JSReceiver::CreateDataProperty(
140 : isolate, options, factory->currencyDisplay_string(),
141 : number_format_holder->CurrencyDisplayAsString(), kDontThrow)
142 9 : .FromJust());
143 : }
144 2187 : CHECK(JSReceiver::CreateDataProperty(
145 : isolate, options, factory->minimumIntegerDigits_string(),
146 : factory->NewNumberFromInt(number_format->getMinimumIntegerDigits()),
147 : kDontThrow)
148 : .FromJust());
149 2187 : CHECK(
150 : JSReceiver::CreateDataProperty(
151 : isolate, options, factory->minimumFractionDigits_string(),
152 : factory->NewNumberFromInt(number_format->getMinimumFractionDigits()),
153 : kDontThrow)
154 : .FromJust());
155 2187 : CHECK(
156 : JSReceiver::CreateDataProperty(
157 : isolate, options, factory->maximumFractionDigits_string(),
158 : factory->NewNumberFromInt(number_format->getMaximumFractionDigits()),
159 : kDontThrow)
160 : .FromJust());
161 729 : if (decimal_format->areSignificantDigitsUsed()) {
162 27 : CHECK(JSReceiver::CreateDataProperty(
163 : isolate, options, factory->minimumSignificantDigits_string(),
164 : factory->NewNumberFromInt(
165 : decimal_format->getMinimumSignificantDigits()),
166 : kDontThrow)
167 : .FromJust());
168 27 : CHECK(JSReceiver::CreateDataProperty(
169 : isolate, options, factory->maximumSignificantDigits_string(),
170 : factory->NewNumberFromInt(
171 : decimal_format->getMaximumSignificantDigits()),
172 : kDontThrow)
173 : .FromJust());
174 : }
175 2187 : CHECK(JSReceiver::CreateDataProperty(
176 : isolate, options, factory->useGrouping_string(),
177 : factory->ToBoolean((number_format->isGroupingUsed() == TRUE)),
178 : kDontThrow)
179 : .FromJust());
180 1458 : return options;
181 : }
182 :
183 : // ecma402/#sec-unwrapnumberformat
184 1494 : MaybeHandle<JSNumberFormat> JSNumberFormat::UnwrapNumberFormat(
185 : Isolate* isolate, Handle<JSReceiver> format_holder) {
186 : // old code copy from NumberFormat::Unwrap that has no spec comment and
187 : // compiled but fail unit tests.
188 : Handle<Context> native_context =
189 2988 : Handle<Context>(isolate->context()->native_context(), isolate);
190 : Handle<JSFunction> constructor = Handle<JSFunction>(
191 2988 : JSFunction::cast(native_context->intl_number_format_function()), isolate);
192 : Handle<Object> object;
193 4482 : ASSIGN_RETURN_ON_EXCEPTION(
194 : isolate, object,
195 : Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
196 : format_holder->IsJSNumberFormat()),
197 : JSNumberFormat);
198 : // 4. If ... or nf does not have an [[InitializedNumberFormat]] internal slot,
199 : // then
200 2988 : if (!object->IsJSNumberFormat()) {
201 : // a. Throw a TypeError exception.
202 90 : THROW_NEW_ERROR(isolate,
203 : NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
204 : isolate->factory()->NewStringFromAsciiChecked(
205 : "UnwrapNumberFormat")),
206 : JSNumberFormat);
207 : }
208 : // 5. Return nf.
209 1449 : return Handle<JSNumberFormat>::cast(object);
210 : }
211 :
212 : // static
213 1439 : MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
214 : Isolate* isolate, Handle<JSNumberFormat> number_format,
215 : Handle<Object> locales, Handle<Object> options_obj) {
216 : // set the flags to 0 ASAP.
217 : number_format->set_flags(0);
218 : Factory* factory = isolate->factory();
219 :
220 : // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
221 : Maybe<std::vector<std::string>> maybe_requested_locales =
222 1439 : Intl::CanonicalizeLocaleList(isolate, locales);
223 1439 : MAYBE_RETURN(maybe_requested_locales, Handle<JSNumberFormat>());
224 : std::vector<std::string> requested_locales =
225 1376 : maybe_requested_locales.FromJust();
226 :
227 : // 2. If options is undefined, then
228 2752 : if (options_obj->IsUndefined(isolate)) {
229 : // 2. a. Let options be ObjectCreate(null).
230 809 : options_obj = isolate->factory()->NewJSObjectWithNullProto();
231 : } else {
232 : // 3. Else
233 : // 3. a. Let options be ? ToObject(options).
234 1134 : ASSIGN_RETURN_ON_EXCEPTION(
235 : isolate, options_obj,
236 : Object::ToObject(isolate, options_obj, "Intl.NumberFormat"),
237 : JSNumberFormat);
238 : }
239 :
240 : // At this point, options_obj can either be a JSObject or a JSProxy only.
241 1367 : Handle<JSReceiver> options = Handle<JSReceiver>::cast(options_obj);
242 :
243 : // 4. Let opt be a new Record.
244 : // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", «
245 : // "lookup", "best fit" », "best fit").
246 : // 6. Set opt.[[localeMatcher]] to matcher.
247 : Maybe<Intl::MatcherOption> maybe_locale_matcher =
248 1367 : Intl::GetLocaleMatcher(isolate, options, "Intl.NumberFormat");
249 1367 : MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSNumberFormat>());
250 : Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
251 :
252 : // 7. Let localeData be %NumberFormat%.[[LocaleData]].
253 : // 8. Let r be ResolveLocale(%NumberFormat%.[[AvailableLocales]],
254 : // requestedLocales, opt, %NumberFormat%.[[RelevantExtensionKeys]],
255 : // localeData).
256 2734 : std::set<std::string> relevant_extension_keys{"nu"};
257 : Intl::ResolvedLocale r =
258 : Intl::ResolveLocale(isolate, JSNumberFormat::GetAvailableLocales(),
259 4101 : requested_locales, matcher, relevant_extension_keys);
260 :
261 : // 9. Set numberFormat.[[Locale]] to r.[[locale]].
262 : Handle<String> locale_str =
263 1367 : isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
264 1367 : number_format->set_locale(*locale_str);
265 :
266 : // 11. Let dataLocale be r.[[dataLocale]].
267 : //
268 : // 12. Let style be ? GetOption(options, "style", "string", « "decimal",
269 : // "percent", "currency" », "decimal").
270 : const char* service = "Intl.NumberFormat";
271 : Maybe<Style> maybe_style = Intl::GetStringOption<Style>(
272 : isolate, options, "style", service, {"decimal", "percent", "currency"},
273 4101 : {Style::DECIMAL, Style::PERCENT, Style::CURRENCY}, Style::DECIMAL);
274 1367 : MAYBE_RETURN(maybe_style, MaybeHandle<JSNumberFormat>());
275 : Style style = maybe_style.FromJust();
276 :
277 : // 13. Set numberFormat.[[Style]] to style.
278 1367 : number_format->set_style(style);
279 :
280 : // 14. Let currency be ? GetOption(options, "currency", "string", undefined,
281 : // undefined).
282 1367 : std::unique_ptr<char[]> currency_cstr;
283 : const std::vector<const char*> empty_values = {};
284 : Maybe<bool> found_currency = Intl::GetStringOption(
285 2734 : isolate, options, "currency", empty_values, service, ¤cy_cstr);
286 1367 : MAYBE_RETURN(found_currency, MaybeHandle<JSNumberFormat>());
287 :
288 : std::string currency;
289 : // 15. If currency is not undefined, then
290 1367 : if (found_currency.FromJust()) {
291 : DCHECK_NOT_NULL(currency_cstr.get());
292 : currency = currency_cstr.get();
293 : // 15. a. If the result of IsWellFormedCurrencyCode(currency) is false,
294 : // throw a RangeError exception.
295 126 : if (!IsWellFormedCurrencyCode(currency)) {
296 0 : THROW_NEW_ERROR(
297 : isolate,
298 : NewRangeError(MessageTemplate::kInvalidCurrencyCode,
299 : factory->NewStringFromAsciiChecked(currency.c_str())),
300 : JSNumberFormat);
301 : }
302 : }
303 :
304 : // 16. If style is "currency" and currency is undefined, throw a TypeError
305 : // exception.
306 1484 : if (style == Style::CURRENCY && !found_currency.FromJust()) {
307 0 : THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kCurrencyCode),
308 : JSNumberFormat);
309 : }
310 : // 17. If style is "currency", then
311 : int c_digits = 0;
312 1367 : icu::UnicodeString currency_ustr;
313 1367 : if (style == Style::CURRENCY) {
314 : // a. Let currency be the result of converting currency to upper case as
315 : // specified in 6.1
316 117 : std::transform(currency.begin(), currency.end(), currency.begin(), toupper);
317 : // c. Let cDigits be CurrencyDigits(currency).
318 234 : currency_ustr = currency.c_str();
319 117 : c_digits = CurrencyDigits(currency_ustr);
320 : }
321 :
322 : // 18. Let currencyDisplay be ? GetOption(options, "currencyDisplay",
323 : // "string", « "code", "symbol", "name" », "symbol").
324 : Maybe<CurrencyDisplay> maybe_currencyDisplay =
325 : Intl::GetStringOption<CurrencyDisplay>(
326 : isolate, options, "currencyDisplay", service,
327 : {"code", "symbol", "name"},
328 : {CurrencyDisplay::CODE, CurrencyDisplay::SYMBOL,
329 : CurrencyDisplay::NAME},
330 4101 : CurrencyDisplay::SYMBOL);
331 1367 : MAYBE_RETURN(maybe_currencyDisplay, MaybeHandle<JSNumberFormat>());
332 : CurrencyDisplay currency_display = maybe_currencyDisplay.FromJust();
333 1367 : UNumberFormatStyle format_style = ToNumberFormatStyle(currency_display);
334 :
335 1367 : UErrorCode status = U_ZERO_ERROR;
336 : std::unique_ptr<icu::NumberFormat> icu_number_format;
337 1367 : if (style == Style::DECIMAL) {
338 : icu_number_format.reset(
339 1214 : icu::NumberFormat::createInstance(r.icu_locale, status));
340 153 : } else if (style == Style::PERCENT) {
341 : icu_number_format.reset(
342 36 : icu::NumberFormat::createPercentInstance(r.icu_locale, status));
343 : } else {
344 : DCHECK_EQ(style, Style::CURRENCY);
345 : icu_number_format.reset(
346 117 : icu::NumberFormat::createInstance(r.icu_locale, format_style, status));
347 : }
348 :
349 1367 : if (U_FAILURE(status) || icu_number_format.get() == nullptr) {
350 0 : status = U_ZERO_ERROR;
351 : // Remove extensions and try again.
352 0 : icu::Locale no_extension_locale(r.icu_locale.getBaseName());
353 : icu_number_format.reset(
354 0 : icu::NumberFormat::createInstance(no_extension_locale, status));
355 :
356 0 : if (U_FAILURE(status) || icu_number_format.get() == nullptr) {
357 0 : FATAL("Failed to create ICU number_format, are ICU data files missing?");
358 0 : }
359 : }
360 : DCHECK(U_SUCCESS(status));
361 1367 : CHECK_NOT_NULL(icu_number_format.get());
362 1367 : if (style == Style::CURRENCY) {
363 : // 19. If style is "currency", set numberFormat.[[CurrencyDisplay]] to
364 : // currencyDisplay.
365 117 : number_format->set_currency_display(currency_display);
366 :
367 : // 17.b. Set numberFormat.[[Currency]] to currency.
368 234 : if (!currency_ustr.isEmpty()) {
369 117 : status = U_ZERO_ERROR;
370 234 : icu_number_format->setCurrency(currency_ustr.getBuffer(), status);
371 234 : CHECK(U_SUCCESS(status));
372 : }
373 : }
374 :
375 : // 20. If style is "currency", then
376 : int mnfd_default, mxfd_default;
377 1367 : if (style == Style::CURRENCY) {
378 : // a. Let mnfdDefault be cDigits.
379 : // b. Let mxfdDefault be cDigits.
380 : mnfd_default = c_digits;
381 : mxfd_default = c_digits;
382 : } else {
383 : // 21. Else,
384 : // a. Let mnfdDefault be 0.
385 : mnfd_default = 0;
386 : // b. If style is "percent", then
387 1250 : if (style == Style::PERCENT) {
388 : // i. Let mxfdDefault be 0.
389 : mxfd_default = 0;
390 : } else {
391 : // c. Else,
392 : // i. Let mxfdDefault be 3.
393 : mxfd_default = 3;
394 : }
395 : }
396 : // 22. Perform ? SetNumberFormatDigitOptions(numberFormat, options,
397 : // mnfdDefault, mxfdDefault).
398 : icu::DecimalFormat* icu_decimal_format =
399 : static_cast<icu::DecimalFormat*>(icu_number_format.get());
400 : Maybe<bool> maybe_set_number_for_digit_options =
401 : Intl::SetNumberFormatDigitOptions(isolate, icu_decimal_format, options,
402 1367 : mnfd_default, mxfd_default);
403 1367 : MAYBE_RETURN(maybe_set_number_for_digit_options, Handle<JSNumberFormat>());
404 :
405 : // 23. Let useGrouping be ? GetOption(options, "useGrouping", "boolean",
406 : // undefined, true).
407 1259 : bool use_grouping = true;
408 : Maybe<bool> found_use_grouping = Intl::GetBoolOption(
409 1259 : isolate, options, "useGrouping", service, &use_grouping);
410 1259 : MAYBE_RETURN(found_use_grouping, MaybeHandle<JSNumberFormat>());
411 : // 24. Set numberFormat.[[UseGrouping]] to useGrouping.
412 1259 : icu_number_format->setGroupingUsed(use_grouping ? TRUE : FALSE);
413 :
414 : // 25. Let dataLocaleData be localeData.[[<dataLocale>]].
415 : //
416 : // 26. Let patterns be dataLocaleData.[[patterns]].
417 : //
418 : // 27. Assert: patterns is a record (see 11.3.3).
419 : //
420 : // 28. Let stylePatterns be patterns.[[<style>]].
421 : //
422 : // 29. Set numberFormat.[[PositivePattern]] to
423 : // stylePatterns.[[positivePattern]].
424 : //
425 : // 30. Set numberFormat.[[NegativePattern]] to
426 : // stylePatterns.[[negativePattern]].
427 :
428 : Handle<Managed<icu::NumberFormat>> managed_number_format =
429 : Managed<icu::NumberFormat>::FromUniquePtr(isolate, 0,
430 2518 : std::move(icu_number_format));
431 1259 : number_format->set_icu_number_format(*managed_number_format);
432 2518 : number_format->set_bound_format(*factory->undefined_value());
433 :
434 : // 31. Return numberFormat.
435 1259 : return number_format;
436 : }
437 :
438 729 : Handle<String> JSNumberFormat::StyleAsString() const {
439 729 : switch (style()) {
440 : case Style::DECIMAL:
441 1440 : return GetReadOnlyRoots().decimal_string_handle();
442 : case Style::PERCENT:
443 0 : return GetReadOnlyRoots().percent_string_handle();
444 : case Style::CURRENCY:
445 18 : return GetReadOnlyRoots().currency_string_handle();
446 : case Style::COUNT:
447 0 : UNREACHABLE();
448 : }
449 0 : }
450 :
451 9 : Handle<String> JSNumberFormat::CurrencyDisplayAsString() const {
452 9 : switch (currency_display()) {
453 : case CurrencyDisplay::CODE:
454 0 : return GetReadOnlyRoots().code_string_handle();
455 : case CurrencyDisplay::SYMBOL:
456 0 : return GetReadOnlyRoots().symbol_string_handle();
457 : case CurrencyDisplay::NAME:
458 18 : return GetReadOnlyRoots().name_string_handle();
459 : case CurrencyDisplay::COUNT:
460 0 : UNREACHABLE();
461 : }
462 0 : }
463 :
464 1998 : MaybeHandle<String> JSNumberFormat::FormatNumber(
465 : Isolate* isolate, const icu::NumberFormat& number_format, double number) {
466 : icu::UnicodeString result;
467 1998 : number_format.format(number, result);
468 :
469 : return isolate->factory()->NewStringFromTwoByte(Vector<const uint16_t>(
470 3996 : reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length()));
471 : }
472 :
473 : namespace {
474 :
475 3137 : bool cmp_NumberFormatSpan(const NumberFormatSpan& a,
476 : const NumberFormatSpan& b) {
477 : // Regions that start earlier should be encountered earlier.
478 3137 : if (a.begin_pos < b.begin_pos) return true;
479 2957 : if (a.begin_pos > b.begin_pos) return false;
480 : // For regions that start in the same place, regions that last longer should
481 : // be encountered earlier.
482 1508 : if (a.end_pos < b.end_pos) return false;
483 983 : if (a.end_pos > b.end_pos) return true;
484 : // For regions that are exactly the same, one of them must be the "literal"
485 : // backdrop we added, which has a field_id of -1, so consider higher field_ids
486 : // to be later.
487 933 : return a.field_id < b.field_id;
488 : }
489 :
490 : // The list comes from third_party/icu/source/i18n/unicode/unum.h.
491 : // They're mapped to NumberFormat part types mentioned throughout
492 : // https://tc39.github.io/ecma402/#sec-partitionnumberpattern .
493 1476 : Handle<String> IcuNumberFieldIdToNumberType(int32_t field_id, double number,
494 : Isolate* isolate) {
495 1476 : switch (static_cast<UNumberFormatFields>(field_id)) {
496 : case UNUM_INTEGER_FIELD:
497 819 : if (std::isfinite(number)) return isolate->factory()->integer_string();
498 27 : if (std::isnan(number)) return isolate->factory()->nan_string();
499 : return isolate->factory()->infinity_string();
500 : case UNUM_FRACTION_FIELD:
501 : return isolate->factory()->fraction_string();
502 : case UNUM_DECIMAL_SEPARATOR_FIELD:
503 : return isolate->factory()->decimal_string();
504 : case UNUM_GROUPING_SEPARATOR_FIELD:
505 : return isolate->factory()->group_string();
506 : case UNUM_CURRENCY_FIELD:
507 : return isolate->factory()->currency_string();
508 : case UNUM_PERCENT_FIELD:
509 : return isolate->factory()->percentSign_string();
510 : case UNUM_SIGN_FIELD:
511 : return number < 0 ? isolate->factory()->minusSign_string()
512 108 : : isolate->factory()->plusSign_string();
513 :
514 : case UNUM_EXPONENT_SYMBOL_FIELD:
515 : case UNUM_EXPONENT_SIGN_FIELD:
516 : case UNUM_EXPONENT_FIELD:
517 : // We should never get these because we're not using any scientific
518 : // formatter.
519 0 : UNREACHABLE();
520 : return Handle<String>();
521 :
522 : case UNUM_PERMILL_FIELD:
523 : // We're not creating any permill formatter, and it's not even clear how
524 : // that would be possible with the ICU API.
525 0 : UNREACHABLE();
526 : return Handle<String>();
527 :
528 : default:
529 0 : UNREACHABLE();
530 : return Handle<String>();
531 : }
532 : }
533 : } // namespace
534 :
535 : // Flattens a list of possibly-overlapping "regions" to a list of
536 : // non-overlapping "parts". At least one of the input regions must span the
537 : // entire space of possible indexes. The regions parameter will sorted in-place
538 : // according to some criteria; this is done for performance to avoid copying the
539 : // input.
540 714 : std::vector<NumberFormatSpan> FlattenRegionsToParts(
541 4340 : std::vector<NumberFormatSpan>* regions) {
542 : // The intention of this algorithm is that it's used to translate ICU "fields"
543 : // to JavaScript "parts" of a formatted string. Each ICU field and JavaScript
544 : // part has an integer field_id, which corresponds to something like "grouping
545 : // separator", "fraction", or "percent sign", and has a begin and end
546 : // position. Here's a diagram of:
547 :
548 : // var nf = new Intl.NumberFormat(['de'], {style:'currency',currency:'EUR'});
549 : // nf.formatToParts(123456.78);
550 :
551 : // : 6
552 : // input regions: 0000000211 7
553 : // ('-' means -1): ------------
554 : // formatted string: "123.456,78 €"
555 : // output parts: 0006000211-7
556 :
557 : // To illustrate the requirements of this algorithm, here's a contrived and
558 : // convoluted example of inputs and expected outputs:
559 :
560 : // : 4
561 : // : 22 33 3
562 : // : 11111 22
563 : // input regions: 0000000 111
564 : // : ------------
565 : // formatted string: "abcdefghijkl"
566 : // output parts: 0221340--231
567 : // (The characters in the formatted string are irrelevant to this function.)
568 :
569 : // We arrange the overlapping input regions like a mountain range where
570 : // smaller regions are "on top" of larger regions, and we output a birds-eye
571 : // view of the mountains, so that smaller regions take priority over larger
572 : // regions.
573 714 : std::sort(regions->begin(), regions->end(), cmp_NumberFormatSpan);
574 : std::vector<size_t> overlapping_region_index_stack;
575 : // At least one item in regions must be a region spanning the entire string.
576 : // Due to the sorting above, the first item in the vector will be one of them.
577 1428 : overlapping_region_index_stack.push_back(0);
578 714 : NumberFormatSpan top_region = regions->at(0);
579 : size_t region_iterator = 1;
580 : int32_t entire_size = top_region.end_pos;
581 :
582 : std::vector<NumberFormatSpan> out_parts;
583 :
584 : // The "climber" is a cursor that advances from left to right climbing "up"
585 : // and "down" the mountains. Whenever the climber moves to the right, that
586 : // represents an item of output.
587 : int32_t climber = 0;
588 3598 : while (climber < entire_size) {
589 : int32_t next_region_begin_pos;
590 2170 : if (region_iterator < regions->size()) {
591 1456 : next_region_begin_pos = regions->at(region_iterator).begin_pos;
592 : } else {
593 : // finish off the rest of the input by proceeding to the end.
594 : next_region_begin_pos = entire_size;
595 : }
596 :
597 2170 : if (climber < next_region_begin_pos) {
598 1717 : while (top_region.end_pos < next_region_begin_pos) {
599 286 : if (climber < top_region.end_pos) {
600 : // step down
601 : out_parts.push_back(NumberFormatSpan(top_region.field_id, climber,
602 239 : top_region.end_pos));
603 : climber = top_region.end_pos;
604 : } else {
605 : // drop down
606 : }
607 : overlapping_region_index_stack.pop_back();
608 572 : top_region = regions->at(overlapping_region_index_stack.back());
609 : }
610 1431 : if (climber < next_region_begin_pos) {
611 : // cross a plateau/mesa/valley
612 : out_parts.push_back(NumberFormatSpan(top_region.field_id, climber,
613 1431 : next_region_begin_pos));
614 : climber = next_region_begin_pos;
615 : }
616 : }
617 2170 : if (region_iterator < regions->size()) {
618 2912 : overlapping_region_index_stack.push_back(region_iterator++);
619 2912 : top_region = regions->at(overlapping_region_index_stack.back());
620 : }
621 : }
622 714 : return out_parts;
623 : }
624 :
625 684 : Maybe<int> JSNumberFormat::FormatToParts(Isolate* isolate,
626 : Handle<JSArray> result,
627 : int start_index,
628 : const icu::NumberFormat& fmt,
629 : double number, Handle<String> unit) {
630 : icu::UnicodeString formatted;
631 1368 : icu::FieldPositionIterator fp_iter;
632 684 : UErrorCode status = U_ZERO_ERROR;
633 684 : fmt.format(number, formatted, &fp_iter, status);
634 684 : if (U_FAILURE(status)) {
635 0 : THROW_NEW_ERROR_RETURN_VALUE(
636 : isolate, NewTypeError(MessageTemplate::kIcuError), Nothing<int>());
637 : }
638 :
639 : int32_t length = formatted.length();
640 : int index = start_index;
641 684 : if (length == 0) return Just(index);
642 :
643 : std::vector<NumberFormatSpan> regions;
644 : // Add a "literal" backdrop for the entire string. This will be used if no
645 : // other region covers some part of the formatted string. It's possible
646 : // there's another field with exactly the same begin and end as this backdrop,
647 : // in which case the backdrop's field_id of -1 will give it lower priority.
648 684 : regions.push_back(NumberFormatSpan(-1, 0, formatted.length()));
649 :
650 : {
651 : icu::FieldPosition fp;
652 2025 : while (fp_iter.next(fp)) {
653 : regions.push_back(NumberFormatSpan(fp.getField(), fp.getBeginIndex(),
654 2682 : fp.getEndIndex()));
655 684 : }
656 : }
657 :
658 684 : std::vector<NumberFormatSpan> parts = FlattenRegionsToParts(®ions);
659 :
660 2898 : for (auto it = parts.begin(); it < parts.end(); it++) {
661 1530 : NumberFormatSpan part = *it;
662 : Handle<String> field_type_string =
663 : part.field_id == -1
664 : ? isolate->factory()->literal_string()
665 1584 : : IcuNumberFieldIdToNumberType(part.field_id, number, isolate);
666 : Handle<String> substring;
667 3060 : ASSIGN_RETURN_ON_EXCEPTION_VALUE(
668 : isolate, substring,
669 : Intl::ToString(isolate, formatted, part.begin_pos, part.end_pos),
670 : Nothing<int>());
671 1530 : if (unit.is_null()) {
672 1080 : Intl::AddElement(isolate, result, index, field_type_string, substring);
673 : } else {
674 : Intl::AddElement(isolate, result, index, field_type_string, substring,
675 450 : isolate->factory()->unit_string(), unit);
676 : }
677 1530 : ++index;
678 : }
679 684 : JSObject::ValidateElements(*result);
680 684 : return Just(index);
681 : }
682 :
683 270 : MaybeHandle<JSArray> JSNumberFormat::FormatToParts(
684 : Isolate* isolate, Handle<JSNumberFormat> number_format, double number) {
685 : Factory* factory = isolate->factory();
686 540 : icu::NumberFormat* fmt = number_format->icu_number_format()->raw();
687 270 : CHECK_NOT_NULL(fmt);
688 :
689 270 : Handle<JSArray> result = factory->NewJSArray(0);
690 :
691 : Maybe<int> maybe_format_to_parts = JSNumberFormat::FormatToParts(
692 270 : isolate, result, 0, *fmt, number, Handle<String>());
693 270 : MAYBE_RETURN(maybe_format_to_parts, Handle<JSArray>());
694 :
695 270 : return result;
696 : }
697 :
698 1498 : std::set<std::string> JSNumberFormat::GetAvailableLocales() {
699 1498 : int32_t num_locales = 0;
700 : const icu::Locale* icu_available_locales =
701 1498 : icu::NumberFormat::getAvailableLocales(num_locales);
702 1498 : return Intl::BuildLocaleSet(icu_available_locales, num_locales);
703 : }
704 :
705 : } // namespace internal
706 183867 : } // namespace v8
|