DisplayNames.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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. 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. void DisplayNames::set_language_display(StringView language_display)
  73. {
  74. if (language_display == "dialect"sv)
  75. m_language_display = LanguageDisplay::Dialect;
  76. else if (language_display == "standard"sv)
  77. m_language_display = LanguageDisplay::Standard;
  78. else
  79. VERIFY_NOT_REACHED();
  80. }
  81. StringView DisplayNames::language_display_string() const
  82. {
  83. VERIFY(m_language_display.has_value());
  84. switch (*m_language_display) {
  85. case LanguageDisplay::Dialect:
  86. return "dialect"sv;
  87. case LanguageDisplay::Standard:
  88. return "standard"sv;
  89. default:
  90. VERIFY_NOT_REACHED();
  91. }
  92. }
  93. // 12.5.1 CanonicalCodeForDisplayNames ( type, code ), https://tc39.es/ecma402/#sec-canonicalcodefordisplaynames
  94. ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::Type type, StringView code)
  95. {
  96. // 1. If type is "language", then
  97. if (type == DisplayNames::Type::Language) {
  98. // a. If code does not match the unicode_language_id production, throw a RangeError exception.
  99. if (!::Locale::parse_unicode_language_id(code).has_value())
  100. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "language"sv);
  101. // b. If IsStructurallyValidLanguageTag(code) is false, throw a RangeError exception.
  102. auto locale_id = is_structurally_valid_language_tag(code);
  103. if (!locale_id.has_value())
  104. return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, code);
  105. // c. Return ! CanonicalizeUnicodeLocaleId(code).
  106. auto canonicalized_tag = JS::Intl::canonicalize_unicode_locale_id(*locale_id);
  107. return PrimitiveString::create(vm, move(canonicalized_tag));
  108. }
  109. // 2. If type is "region", then
  110. if (type == DisplayNames::Type::Region) {
  111. // a. If code does not match the unicode_region_subtag production, throw a RangeError exception.
  112. if (!::Locale::is_unicode_region_subtag(code))
  113. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "region"sv);
  114. // b. Return the ASCII-uppercase of code.
  115. return PrimitiveString::create(vm, code.to_uppercase_string());
  116. }
  117. // 3. If type is "script", then
  118. if (type == DisplayNames::Type::Script) {
  119. // a. If code does not match the unicode_script_subtag production, throw a RangeError exception.
  120. if (!::Locale::is_unicode_script_subtag(code))
  121. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "script"sv);
  122. // 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).
  123. VERIFY(code.length() == 4);
  124. VERIFY(all_of(code, is_ascii_alpha));
  125. // c. Let first be the ASCII-uppercase of the substring of code from 0 to 1.
  126. // d. Let rest be the ASCII-lowercase of the substring of code from 1.
  127. // e. Return the string-concatenation of first and rest.
  128. return PrimitiveString::create(vm, code.to_titlecase_string());
  129. }
  130. // 4. If type is "calendar", then
  131. if (type == DisplayNames::Type::Calendar) {
  132. // a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
  133. if (!::Locale::is_type_identifier(code))
  134. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
  135. // 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.
  136. if (code.contains('_'))
  137. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
  138. // c. Return the ASCII-lowercase of code.
  139. return PrimitiveString::create(vm, code.to_lowercase_string());
  140. }
  141. // 5. If type is "dateTimeField", then
  142. if (type == DisplayNames::Type::DateTimeField) {
  143. // a. If the result of IsValidDateTimeFieldCode(code) is false, throw a RangeError exception.
  144. if (!is_valid_date_time_field_code(code))
  145. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "dateTimeField"sv);
  146. // b. Return code.
  147. return PrimitiveString::create(vm, code);
  148. }
  149. // 6. Assert: type is "currency".
  150. VERIFY(type == DisplayNames::Type::Currency);
  151. // 7. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception.
  152. if (!is_well_formed_currency_code(code))
  153. return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "currency"sv);
  154. // 8. Return the ASCII-uppercase of code.
  155. return PrimitiveString::create(vm, code.to_uppercase_string());
  156. }
  157. // 12.5.2 IsValidDateTimeFieldCode ( field ), https://tc39.es/ecma402/#sec-isvaliddatetimefieldcode
  158. bool is_valid_date_time_field_code(StringView field)
  159. {
  160. // 1. If field is listed in the Code column of Table 9, return true.
  161. // 2. Return false.
  162. 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);
  163. }
  164. }