DisplayNames.cpp 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. /*
  2. * Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibJS/Runtime/GlobalObject.h>
  7. #include <LibJS/Runtime/Intl/AbstractOperations.h>
  8. #include <LibJS/Runtime/Intl/DisplayNames.h>
  9. namespace JS::Intl {
  10. JS_DEFINE_ALLOCATOR(DisplayNames);
  11. // 12 DisplayNames Objects, https://tc39.es/ecma402/#intl-displaynames-objects
  12. DisplayNames::DisplayNames(Object& prototype)
  13. : Object(ConstructWithPrototypeTag::Tag, prototype)
  14. {
  15. }
  16. void DisplayNames::set_type(StringView type)
  17. {
  18. if (type == "language"sv)
  19. m_type = Type::Language;
  20. else if (type == "region"sv)
  21. m_type = Type::Region;
  22. else if (type == "script"sv)
  23. m_type = Type::Script;
  24. else if (type == "currency"sv)
  25. m_type = Type::Currency;
  26. else if (type == "calendar"sv)
  27. m_type = Type::Calendar;
  28. else if (type == "dateTimeField"sv)
  29. m_type = Type::DateTimeField;
  30. else
  31. VERIFY_NOT_REACHED();
  32. }
  33. StringView DisplayNames::type_string() const
  34. {
  35. switch (m_type) {
  36. case Type::Language:
  37. return "language"sv;
  38. case Type::Region:
  39. return "region"sv;
  40. case Type::Script:
  41. return "script"sv;
  42. case Type::Currency:
  43. return "currency"sv;
  44. case Type::Calendar:
  45. return "calendar"sv;
  46. case Type::DateTimeField:
  47. return "dateTimeField"sv;
  48. default:
  49. VERIFY_NOT_REACHED();
  50. }
  51. }
  52. void DisplayNames::set_fallback(StringView fallback)
  53. {
  54. if (fallback == "none"sv)
  55. m_fallback = Fallback::None;
  56. else if (fallback == "code"sv)
  57. m_fallback = Fallback::Code;
  58. else
  59. VERIFY_NOT_REACHED();
  60. }
  61. StringView DisplayNames::fallback_string() const
  62. {
  63. switch (m_fallback) {
  64. case Fallback::None:
  65. return "none"sv;
  66. case Fallback::Code:
  67. return "code"sv;
  68. default:
  69. VERIFY_NOT_REACHED();
  70. }
  71. }
  72. // 12.5.1 CanonicalCodeForDisplayNames ( type, code ), https://tc39.es/ecma402/#sec-canonicalcodefordisplaynames
  73. ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::Type type, StringView code)
  74. {
  75. // 1. If type is "language", then
  76. if (type == DisplayNames::Type::Language) {
  77. // a. If code does not match the unicode_language_id production, throw a RangeError exception.
  78. if (!::Locale::parse_unicode_language_id(code).has_value())
  79. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "language"sv);
  80. // b. If IsStructurallyValidLanguageTag(code) is false, throw a RangeError exception.
  81. if (!is_structurally_valid_language_tag(code))
  82. return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, code);
  83. // c. Return ! CanonicalizeUnicodeLocaleId(code).
  84. auto canonicalized_tag = canonicalize_unicode_locale_id(code);
  85. return PrimitiveString::create(vm, move(canonicalized_tag));
  86. }
  87. // 2. If type is "region", then
  88. if (type == DisplayNames::Type::Region) {
  89. // a. If code does not match the unicode_region_subtag production, throw a RangeError exception.
  90. if (!::Locale::is_unicode_region_subtag(code))
  91. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "region"sv);
  92. // b. Return the ASCII-uppercase of code.
  93. return PrimitiveString::create(vm, code.to_uppercase_string());
  94. }
  95. // 3. If type is "script", then
  96. if (type == DisplayNames::Type::Script) {
  97. // a. If code does not match the unicode_script_subtag production, throw a RangeError exception.
  98. if (!::Locale::is_unicode_script_subtag(code))
  99. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "script"sv);
  100. // Assert: The length of code is 4, and every code unit of code represents an ASCII letter (0x0041 through 0x005A and 0x0061 through 0x007A, both inclusive).
  101. VERIFY(code.length() == 4);
  102. VERIFY(all_of(code, is_ascii_alpha));
  103. // c. Let first be the ASCII-uppercase of the substring of code from 0 to 1.
  104. // d. Let rest be the ASCII-lowercase of the substring of code from 1.
  105. // e. Return the string-concatenation of first and rest.
  106. return PrimitiveString::create(vm, code.to_titlecase_string());
  107. }
  108. // 4. If type is "calendar", then
  109. if (type == DisplayNames::Type::Calendar) {
  110. // a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
  111. if (!::Locale::is_type_identifier(code))
  112. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
  113. // b. If code uses any of the backwards compatibility syntax described in Unicode Technical Standard #35 LDML § 3.3 BCP 47 Conformance, throw a RangeError exception.
  114. if (code.contains('_'))
  115. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
  116. // c. Return the ASCII-lowercase of code.
  117. return PrimitiveString::create(vm, code.to_lowercase_string());
  118. }
  119. // 5. If type is "dateTimeField", then
  120. if (type == DisplayNames::Type::DateTimeField) {
  121. // a. If the result of IsValidDateTimeFieldCode(code) is false, throw a RangeError exception.
  122. if (!is_valid_date_time_field_code(code))
  123. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "dateTimeField"sv);
  124. // b. Return code.
  125. return PrimitiveString::create(vm, code);
  126. }
  127. // 6. Assert: type is "currency".
  128. VERIFY(type == DisplayNames::Type::Currency);
  129. // 7. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception.
  130. if (!is_well_formed_currency_code(code))
  131. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "currency"sv);
  132. // 8. Return the ASCII-uppercase of code.
  133. return PrimitiveString::create(vm, code.to_uppercase_string());
  134. }
  135. // 12.5.2 IsValidDateTimeFieldCode ( field ), https://tc39.es/ecma402/#sec-isvaliddatetimefieldcode
  136. bool is_valid_date_time_field_code(StringView field)
  137. {
  138. // 1. If field is listed in the Code column of Table 9, return true.
  139. // 2. Return false.
  140. return field.is_one_of("era"sv, "year"sv, "quarter"sv, "month"sv, "weekOfYear"sv, "weekday"sv, "day"sv, "dayPeriod"sv, "hour"sv, "minute"sv, "second"sv, "timeZoneName"sv);
  141. }
  142. }