普通文本  |  215行  |  8.05 KB

// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif  // V8_INTL_SUPPORT

#include "src/objects/js-relative-time-format.h"

#include <map>
#include <memory>
#include <string>

#include "src/heap/factory.h"
#include "src/isolate.h"
#include "src/objects-inl.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-relative-time-format-inl.h"
#include "src/objects/managed.h"
#include "unicode/numfmt.h"
#include "unicode/reldatefmt.h"

namespace v8 {
namespace internal {

namespace {
UDateRelativeDateTimeFormatterStyle getIcuStyle(
    JSRelativeTimeFormat::Style style) {
  switch (style) {
    case JSRelativeTimeFormat::Style::LONG:
      return UDAT_STYLE_LONG;
    case JSRelativeTimeFormat::Style::SHORT:
      return UDAT_STYLE_SHORT;
    case JSRelativeTimeFormat::Style::NARROW:
      return UDAT_STYLE_NARROW;
    case JSRelativeTimeFormat::Style::COUNT:
      UNREACHABLE();
  }
}
}  // namespace

JSRelativeTimeFormat::Style JSRelativeTimeFormat::getStyle(const char* str) {
  if (strcmp(str, "long") == 0) return JSRelativeTimeFormat::Style::LONG;
  if (strcmp(str, "short") == 0) return JSRelativeTimeFormat::Style::SHORT;
  if (strcmp(str, "narrow") == 0) return JSRelativeTimeFormat::Style::NARROW;
  UNREACHABLE();
}

JSRelativeTimeFormat::Numeric JSRelativeTimeFormat::getNumeric(
    const char* str) {
  if (strcmp(str, "auto") == 0) return JSRelativeTimeFormat::Numeric::AUTO;
  if (strcmp(str, "always") == 0) return JSRelativeTimeFormat::Numeric::ALWAYS;
  UNREACHABLE();
}

MaybeHandle<JSRelativeTimeFormat>
JSRelativeTimeFormat::InitializeRelativeTimeFormat(
    Isolate* isolate, Handle<JSRelativeTimeFormat> relative_time_format_holder,
    Handle<Object> input_locales, Handle<Object> input_options) {
  Factory* factory = isolate->factory();
  relative_time_format_holder->set_flags(0);
  // 4. If options is undefined, then
  Handle<JSReceiver> options;
  if (input_options->IsUndefined(isolate)) {
    // a. Let options be ObjectCreate(null).
    options = isolate->factory()->NewJSObjectWithNullProto();
    // 5. Else
  } else {
    // a. Let options be ? ToObject(options).
    ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
                               Object::ToObject(isolate, input_options),
                               JSRelativeTimeFormat);
  }

  // 10. Let r be ResolveLocale(%RelativeTimeFormat%.[[AvailableLocales]],
  //                            requestedLocales, opt,
  //                            %RelativeTimeFormat%.[[RelevantExtensionKeys]],
  //                            localeData).
  Handle<JSObject> r;
  ASSIGN_RETURN_ON_EXCEPTION(isolate, r,
                             Intl::ResolveLocale(isolate, "relativetimeformat",
                                                 input_locales, options),
                             JSRelativeTimeFormat);
  Handle<Object> locale_obj =
      JSObject::GetDataProperty(r, factory->locale_string());
  Handle<String> locale;
  ASSIGN_RETURN_ON_EXCEPTION(isolate, locale,
                             Object::ToString(isolate, locale_obj),
                             JSRelativeTimeFormat);

  // 11. Let locale be r.[[Locale]].
  // 12. Set relativeTimeFormat.[[Locale]] to locale.
  relative_time_format_holder->set_locale(*locale);

  // 14. Let s be ? GetOption(options, "style", "string",
  //                          «"long", "short", "narrow"», "long").
  std::unique_ptr<char[]> style_str = nullptr;
  std::vector<const char*> style_values = {"long", "short", "narrow"};
  Maybe<bool> maybe_found_style =
      Intl::GetStringOption(isolate, options, "style", style_values,
                            "Intl.RelativeTimeFormat", &style_str);
  Style style_enum = Style::LONG;
  MAYBE_RETURN(maybe_found_style, MaybeHandle<JSRelativeTimeFormat>());
  if (maybe_found_style.FromJust()) {
    DCHECK_NOT_NULL(style_str.get());
    style_enum = getStyle(style_str.get());
  }

  // 15. Set relativeTimeFormat.[[Style]] to s.
  relative_time_format_holder->set_style(style_enum);

  // 16. Let numeric be ? GetOption(options, "numeric", "string",
  //                                «"always", "auto"», "always").
  std::unique_ptr<char[]> numeric_str = nullptr;
  std::vector<const char*> numeric_values = {"always", "auto"};
  Maybe<bool> maybe_found_numeric =
      Intl::GetStringOption(isolate, options, "numeric", numeric_values,
                            "Intl.RelativeTimeFormat", &numeric_str);
  Numeric numeric_enum = Numeric::ALWAYS;
  MAYBE_RETURN(maybe_found_numeric, MaybeHandle<JSRelativeTimeFormat>());
  if (maybe_found_numeric.FromJust()) {
    DCHECK_NOT_NULL(numeric_str.get());
    numeric_enum = getNumeric(numeric_str.get());
  }

  // 17. Set relativeTimeFormat.[[Numeric]] to numeric.
  relative_time_format_holder->set_numeric(numeric_enum);

  std::unique_ptr<char[]> locale_name = locale->ToCString();
  icu::Locale icu_locale(locale_name.get());
  UErrorCode status = U_ZERO_ERROR;

  // 25. Let relativeTimeFormat.[[NumberFormat]] be
  //     ? Construct(%NumberFormat%, « nfLocale, nfOptions »).
  icu::NumberFormat* number_format =
      icu::NumberFormat::createInstance(icu_locale, UNUM_DECIMAL, status);
  if (U_FAILURE(status)) {
    delete number_format;
    FATAL("Failed to create ICU number format, are ICU data files missing?");
  }
  CHECK_NOT_NULL(number_format);

  // Change UDISPCTX_CAPITALIZATION_NONE to other values if
  // ECMA402 later include option to change capitalization.
  // Ref: https://github.com/tc39/proposal-intl-relative-time/issues/11
  icu::RelativeDateTimeFormatter* icu_formatter =
      new icu::RelativeDateTimeFormatter(icu_locale, number_format,
                                         getIcuStyle(style_enum),
                                         UDISPCTX_CAPITALIZATION_NONE, status);
  if (U_FAILURE(status)) {
    delete icu_formatter;
    FATAL(
        "Failed to create ICU relative date time formatter, are ICU data files "
        "missing?");
  }
  CHECK_NOT_NULL(icu_formatter);

  Handle<Managed<icu::RelativeDateTimeFormatter>> managed_formatter =
      Managed<icu::RelativeDateTimeFormatter>::FromRawPtr(isolate, 0,
                                                          icu_formatter);

  // 30. Set relativeTimeFormat.[[InitializedRelativeTimeFormat]] to true.
  relative_time_format_holder->set_formatter(*managed_formatter);
  // 31. Return relativeTimeFormat.
  return relative_time_format_holder;
}

Handle<JSObject> JSRelativeTimeFormat::ResolvedOptions(
    Isolate* isolate, Handle<JSRelativeTimeFormat> format_holder) {
  Factory* factory = isolate->factory();
  Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
  Handle<String> locale(format_holder->locale(), isolate);
  JSObject::AddProperty(isolate, result, factory->locale_string(), locale,
                        NONE);
  JSObject::AddProperty(isolate, result, factory->style_string(),
                        format_holder->StyleAsString(), NONE);
  JSObject::AddProperty(isolate, result, factory->numeric_string(),
                        format_holder->NumericAsString(), NONE);
  return result;
}

icu::RelativeDateTimeFormatter* JSRelativeTimeFormat::UnpackFormatter(
    Handle<JSRelativeTimeFormat> holder) {
  return Managed<icu::RelativeDateTimeFormatter>::cast(holder->formatter())
      ->raw();
}

Handle<String> JSRelativeTimeFormat::StyleAsString() const {
  switch (style()) {
    case Style::LONG:
      return GetReadOnlyRoots().long_string_handle();
    case Style::SHORT:
      return GetReadOnlyRoots().short_string_handle();
    case Style::NARROW:
      return GetReadOnlyRoots().narrow_string_handle();
    case Style::COUNT:
      UNREACHABLE();
  }
}

Handle<String> JSRelativeTimeFormat::NumericAsString() const {
  switch (numeric()) {
    case Numeric::ALWAYS:
      return GetReadOnlyRoots().always_string_handle();
    case Numeric::AUTO:
      return GetReadOnlyRoots().auto_string_handle();
    case Numeric::COUNT:
      UNREACHABLE();
  }
}

}  // namespace internal
}  // namespace v8