DisplayNames.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /*
  2. * Copyright (c) 2021-2022, 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. // 12 DisplayNames Objects, https://tc39.es/ecma402/#intl-displaynames-objects
  11. DisplayNames::DisplayNames(Object& prototype)
  12. : Object(ConstructWithPrototypeTag::Tag, prototype)
  13. {
  14. }
  15. void DisplayNames::set_type(StringView type)
  16. {
  17. if (type == "language"sv)
  18. m_type = Type::Language;
  19. else if (type == "region"sv)
  20. m_type = Type::Region;
  21. else if (type == "script"sv)
  22. m_type = Type::Script;
  23. else if (type == "currency"sv)
  24. m_type = Type::Currency;
  25. else if (type == "calendar"sv)
  26. m_type = Type::Calendar;
  27. else if (type == "dateTimeField"sv)
  28. m_type = Type::DateTimeField;
  29. else
  30. VERIFY_NOT_REACHED();
  31. }
  32. StringView DisplayNames::type_string() const
  33. {
  34. switch (m_type) {
  35. case Type::Language:
  36. return "language"sv;
  37. case Type::Region:
  38. return "region"sv;
  39. case Type::Script:
  40. return "script"sv;
  41. case Type::Currency:
  42. return "currency"sv;
  43. case Type::Calendar:
  44. return "calendar"sv;
  45. case Type::DateTimeField:
  46. return "dateTimeField"sv;
  47. default:
  48. VERIFY_NOT_REACHED();
  49. }
  50. }
  51. void DisplayNames::set_fallback(StringView fallback)
  52. {
  53. if (fallback == "none"sv)
  54. m_fallback = Fallback::None;
  55. else if (fallback == "code"sv)
  56. m_fallback = Fallback::Code;
  57. else
  58. VERIFY_NOT_REACHED();
  59. }
  60. StringView DisplayNames::fallback_string() const
  61. {
  62. switch (m_fallback) {
  63. case Fallback::None:
  64. return "none"sv;
  65. case Fallback::Code:
  66. return "code"sv;
  67. default:
  68. VERIFY_NOT_REACHED();
  69. }
  70. }
  71. void DisplayNames::set_language_display(StringView language_display)
  72. {
  73. if (language_display == "dialect"sv)
  74. m_language_display = LanguageDisplay::Dialect;
  75. else if (language_display == "standard"sv)
  76. m_language_display = LanguageDisplay::Standard;
  77. else
  78. VERIFY_NOT_REACHED();
  79. }
  80. StringView DisplayNames::language_display_string() const
  81. {
  82. VERIFY(m_language_display.has_value());
  83. switch (*m_language_display) {
  84. case LanguageDisplay::Dialect:
  85. return "dialect"sv;
  86. case LanguageDisplay::Standard:
  87. return "standard"sv;
  88. default:
  89. VERIFY_NOT_REACHED();
  90. }
  91. }
  92. // 12.5.1 CanonicalCodeForDisplayNames ( type, code ), https://tc39.es/ecma402/#sec-canonicalcodefordisplaynames
  93. ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::Type type, StringView code)
  94. {
  95. // 1. If type is "language", then
  96. if (type == DisplayNames::Type::Language) {
  97. // a. If code does not match the unicode_language_id production, throw a RangeError exception.
  98. if (!::Locale::parse_unicode_language_id(code).has_value())
  99. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "language"sv);
  100. // b. If IsStructurallyValidLanguageTag(code) is false, throw a RangeError exception.
  101. auto locale_id = is_structurally_valid_language_tag(code);
  102. if (!locale_id.has_value())
  103. return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, code);
  104. // c. Return ! CanonicalizeUnicodeLocaleId(code).
  105. auto canonicalized_tag = JS::Intl::canonicalize_unicode_locale_id(*locale_id);
  106. return PrimitiveString::create(vm, move(canonicalized_tag));
  107. }
  108. // 2. If type is "region", then
  109. if (type == DisplayNames::Type::Region) {
  110. // a. If code does not match the unicode_region_subtag production, throw a RangeError exception.
  111. if (!::Locale::is_unicode_region_subtag(code))
  112. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "region"sv);
  113. // b. Return the ASCII-uppercase of code.
  114. return PrimitiveString::create(vm, code.to_uppercase_string());
  115. }
  116. // 3. If type is "script", then
  117. if (type == DisplayNames::Type::Script) {
  118. // a. If code does not match the unicode_script_subtag production, throw a RangeError exception.
  119. if (!::Locale::is_unicode_script_subtag(code))
  120. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "script"sv);
  121. // 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).
  122. VERIFY(code.length() == 4);
  123. VERIFY(all_of(code, is_ascii_alpha));
  124. // c. Let first be the ASCII-uppercase of the substring of code from 0 to 1.
  125. // d. Let rest be the ASCII-lowercase of the substring of code from 1.
  126. // e. Return the string-concatenation of first and rest.
  127. return PrimitiveString::create(vm, code.to_titlecase_string());
  128. }
  129. // 4. If type is "calendar", then
  130. if (type == DisplayNames::Type::Calendar) {
  131. // a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
  132. if (!::Locale::is_type_identifier(code))
  133. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
  134. // 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.
  135. if (code.contains('_'))
  136. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
  137. // c. Return the ASCII-lowercase of code.
  138. return PrimitiveString::create(vm, code.to_lowercase_string());
  139. }
  140. // 5. If type is "dateTimeField", then
  141. if (type == DisplayNames::Type::DateTimeField) {
  142. // a. If the result of IsValidDateTimeFieldCode(code) is false, throw a RangeError exception.
  143. if (!is_valid_date_time_field_code(code))
  144. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "dateTimeField"sv);
  145. // b. Return code.
  146. return PrimitiveString::create(vm, code);
  147. }
  148. // 6. Assert: type is "currency".
  149. VERIFY(type == DisplayNames::Type::Currency);
  150. // 7. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception.
  151. if (!is_well_formed_currency_code(code))
  152. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "currency"sv);
  153. // 8. Return the ASCII-uppercase of code.
  154. return PrimitiveString::create(vm, code.to_uppercase_string());
  155. }
  156. // 12.5.2 IsValidDateTimeFieldCode ( field ), https://tc39.es/ecma402/#sec-isvaliddatetimefieldcode
  157. bool is_valid_date_time_field_code(StringView field)
  158. {
  159. // 1. If field is listed in the Code column of Table 9, return true.
  160. // 2. Return false.
  161. 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);
  162. }
  163. }