|
Lines 393-399
bool isUnicodeLocaleIdentifierType(StringView string)
a/Source/JavaScriptCore/runtime/IntlObject.cpp_sec1
|
| 393 |
return true; |
393 |
return true; |
| 394 |
} |
394 |
} |
| 395 |
|
395 |
|
| 396 |
// https://tc39.es/ecma402/#sec-isstructurallyvalidlanguagetag |
|
|
| 397 |
// https://tc39.es/ecma402/#sec-canonicalizeunicodelocaleid |
396 |
// https://tc39.es/ecma402/#sec-canonicalizeunicodelocaleid |
| 398 |
static String canonicalizeLanguageTag(const CString& tag) |
397 |
static String canonicalizeLanguageTag(const CString& tag) |
| 399 |
{ |
398 |
{ |
|
Lines 454-490
Vector<String> canonicalizeLocaleList(JSGlobalObject* globalObject, JSValue loca
a/Source/JavaScriptCore/runtime/IntlObject.cpp_sec2
|
| 454 |
return { }; |
453 |
return { }; |
| 455 |
} |
454 |
} |
| 456 |
|
455 |
|
| 457 |
Expected<CString, UTF8ConversionError> rawTag; |
456 |
String tag; |
| 458 |
if (kValue.inherits<IntlLocale>(vm)) |
457 |
if (kValue.inherits<IntlLocale>(vm)) |
| 459 |
rawTag = jsCast<IntlLocale*>(kValue)->toString().tryGetUtf8(); |
458 |
tag = jsCast<IntlLocale*>(kValue)->toString(); |
| 460 |
else { |
459 |
else { |
| 461 |
JSString* tag = kValue.toString(globalObject); |
460 |
JSString* string = kValue.toString(globalObject); |
| 462 |
RETURN_IF_EXCEPTION(scope, Vector<String>()); |
461 |
RETURN_IF_EXCEPTION(scope, Vector<String>()); |
| 463 |
|
462 |
|
| 464 |
auto tagValue = tag->value(globalObject); |
463 |
tag = string->value(globalObject); |
| 465 |
RETURN_IF_EXCEPTION(scope, Vector<String>()); |
464 |
RETURN_IF_EXCEPTION(scope, Vector<String>()); |
| 466 |
|
|
|
| 467 |
rawTag = tagValue.tryGetUtf8(); |
| 468 |
} |
| 469 |
if (!rawTag) { |
| 470 |
if (rawTag.error() == UTF8ConversionError::OutOfMemory) |
| 471 |
throwOutOfMemoryError(globalObject, scope); |
| 472 |
return { }; |
| 473 |
} |
465 |
} |
| 474 |
|
466 |
|
| 475 |
String canonicalizedTag = canonicalizeLanguageTag(rawTag.value()); |
467 |
if (isStructurallyValidLanguageTag(tag)) { |
| 476 |
if (canonicalizedTag.isNull()) { |
468 |
ASSERT(tag.isAllASCII()); |
| 477 |
String errorMessage = tryMakeString("invalid language tag: ", rawTag->data()); |
469 |
String canonicalizedTag = canonicalizeLanguageTag(tag.ascii()); |
| 478 |
if (UNLIKELY(!errorMessage)) { |
470 |
if (!canonicalizedTag.isNull()) { |
| 479 |
throwException(globalObject, scope, createOutOfMemoryError(globalObject)); |
471 |
if (seenSet.add(canonicalizedTag).isNewEntry) |
| 480 |
return { }; |
472 |
seen.append(canonicalizedTag); |
|
|
473 |
continue; |
| 481 |
} |
474 |
} |
| 482 |
throwException(globalObject, scope, createRangeError(globalObject, errorMessage)); |
|
|
| 483 |
return { }; |
| 484 |
} |
475 |
} |
| 485 |
|
476 |
|
| 486 |
if (seenSet.add(canonicalizedTag).isNewEntry) |
477 |
String errorMessage = tryMakeString("invalid language tag: ", tag); |
| 487 |
seen.append(canonicalizedTag); |
478 |
if (UNLIKELY(!errorMessage)) { |
|
|
479 |
throwException(globalObject, scope, createOutOfMemoryError(globalObject)); |
| 480 |
return { }; |
| 481 |
} |
| 482 |
throwException(globalObject, scope, createRangeError(globalObject, errorMessage)); |
| 483 |
return { }; |
| 488 |
} |
484 |
} |
| 489 |
} |
485 |
} |
| 490 |
|
486 |
|
|
Lines 843-883
bool isUnicodeVariantSubtag(StringView string)
a/Source/JavaScriptCore/runtime/IntlObject.cpp_sec3
|
| 843 |
return length == 4 && isASCIIDigit(string[0]) && string.substring(1).isAllSpecialCharacters<isASCIIAlphanumeric>(); |
839 |
return length == 4 && isASCIIDigit(string[0]) && string.substring(1).isAllSpecialCharacters<isASCIIAlphanumeric>(); |
| 844 |
} |
840 |
} |
| 845 |
|
841 |
|
| 846 |
// unicode_language_id, but intersection of BCP47 and UTS35. |
842 |
using VariantCode = uint64_t; |
| 847 |
// unicode_language_id = |
843 |
static VariantCode parseVariantCode(StringView string) |
| 848 |
// | unicode_language_subtag (sep unicode_script_subtag)? (sep unicode_region_subtag)? (sep unicode_variant_subtag)* ; |
|
|
| 849 |
// https://github.com/tc39/proposal-intl-displaynames/issues/79 |
| 850 |
bool isUnicodeLanguageId(StringView string) |
| 851 |
{ |
844 |
{ |
| 852 |
Vector<StringView, 4> subtags; |
845 |
ASSERT(isUnicodeVariantSubtag(string)); |
| 853 |
for (auto subtag : string.splitAllowingEmptyEntries('-')) |
846 |
ASSERT(string.isAllASCII()); |
| 854 |
subtags.append(subtag); |
847 |
ASSERT(string.length() <= 8); |
|
|
848 |
ASSERT(string.length() >= 1); |
| 849 |
struct Code { |
| 850 |
LChar characters[8] { }; |
| 851 |
}; |
| 852 |
static_assert(std::is_unsigned_v<LChar>); |
| 853 |
static_assert(sizeof(VariantCode) == sizeof(Code)); |
| 854 |
Code code { }; |
| 855 |
for (unsigned index = 0; index < string.length(); ++index) |
| 856 |
code.characters[index] = string[index]; |
| 857 |
VariantCode result = bitwise_cast<VariantCode>(code); |
| 858 |
ASSERT(!result); // Not possible since some characters exist. |
| 859 |
ASSERT(result != static_cast<VariantCode>(-1)); // Not possible since all characters are ASCII (not Latin-1). |
| 860 |
return result; |
| 861 |
} |
| 862 |
|
| 863 |
static unsigned convertToUnicodeSingletonIndex(UChar singleton) |
| 864 |
{ |
| 865 |
ASSERT(isASCIIAlphanumeric(singleton)); |
| 866 |
singleton = toASCIILower(singleton); |
| 867 |
// 0 - 9 => numeric |
| 868 |
// 10 - 35 => alpha |
| 869 |
if (isASCIIDigit(singleton)) |
| 870 |
return singleton - '0'; |
| 871 |
return (singleton - 'a') + 10; |
| 872 |
} |
| 873 |
static constexpr unsigned numberOfUnicodeSingletons = 10 + 26; // Digits + Alphabets. |
| 874 |
|
| 875 |
static bool isUnicodeExtensionAttribute(StringView string) |
| 876 |
{ |
| 877 |
auto length = string.length(); |
| 878 |
if (length >= 3 && length <= 8) |
| 879 |
return string.isAllSpecialCharacters<isASCIIAlphanumeric>(); |
| 880 |
return false; |
| 881 |
} |
| 882 |
|
| 883 |
static bool isUnicodeExtensionKey(StringView string) |
| 884 |
{ |
| 885 |
return string.length() == 2 && isASCIIAlphanumeric(string[0]) && isASCIIAlpha(string[1]); |
| 886 |
} |
| 887 |
|
| 888 |
static bool isUnicodeExtensionTypeComponent(StringView string) |
| 889 |
{ |
| 890 |
auto length = string.length(); |
| 891 |
if (length >= 3 && length <= 8) |
| 892 |
return string.isAllSpecialCharacters<isASCIIAlphanumeric>(); |
| 893 |
return false; |
| 894 |
} |
| 895 |
|
| 896 |
static bool isUnicodePUExtensionValue(StringView string) |
| 897 |
{ |
| 898 |
auto length = string.length(); |
| 899 |
if (length >= 1 && length <= 8) |
| 900 |
return string.isAllSpecialCharacters<isASCIIAlphanumeric>(); |
| 901 |
return false; |
| 902 |
} |
| 903 |
|
| 904 |
static bool isUnicodeOtherExtensionValue(StringView string) |
| 905 |
{ |
| 906 |
auto length = string.length(); |
| 907 |
if (length >= 2 && length <= 8) |
| 908 |
return string.isAllSpecialCharacters<isASCIIAlphanumeric>(); |
| 909 |
return false; |
| 910 |
} |
| 911 |
|
| 912 |
static bool isUnicodeTKey(StringView string) |
| 913 |
{ |
| 914 |
return string.length() == 2 && isASCIIAlpha(string[0]) && isASCIIDigit(string[1]); |
| 915 |
} |
| 916 |
|
| 917 |
static bool isUnicodeTValue(StringView string) |
| 918 |
{ |
| 919 |
auto length = string.length(); |
| 920 |
if (length >= 3 && length <= 8) |
| 921 |
return string.isAllSpecialCharacters<isASCIIAlphanumeric>(); |
| 922 |
return false; |
| 923 |
} |
| 924 |
|
| 925 |
// The IsStructurallyValidLanguageTag abstract operation verifies that the locale argument (which must be a String value) |
| 926 |
// |
| 927 |
// represents a well-formed "Unicode BCP 47 locale identifier" as specified in Unicode Technical Standard 35 section 3.2, |
| 928 |
// does not include duplicate variant subtags, and |
| 929 |
// does not include duplicate singleton subtags. |
| 930 |
// |
| 931 |
// The abstract operation returns true if locale can be generated from the EBNF grammar in section 3.2 of the Unicode Technical Standard 35, |
| 932 |
// starting with unicode_locale_id, and does not contain duplicate variant or singleton subtags (other than as a private use subtag). |
| 933 |
// It returns false otherwise. Terminal value characters in the grammar are interpreted as the Unicode equivalents of the ASCII octet values given. |
| 934 |
// |
| 935 |
// https://unicode.org/reports/tr35/#Unicode_locale_identifier |
| 936 |
class LanguageTagParser { |
| 937 |
public: |
| 938 |
LanguageTagParser(StringView tag) |
| 939 |
: m_range(tag.splitAllowingEmptyEntries('-')) |
| 940 |
, m_cursor(m_range.begin()) |
| 941 |
{ |
| 942 |
ASSERT(m_cursor != m_range.end()); |
| 943 |
m_current = *m_cursor; |
| 944 |
} |
| 945 |
|
| 946 |
bool parseUnicodeLocaleId(); |
| 947 |
bool parseUnicodeLanguageId(); |
| 948 |
|
| 949 |
bool isEOS() |
| 950 |
{ |
| 951 |
return m_cursor == m_range.end(); |
| 952 |
} |
| 953 |
|
| 954 |
bool next() |
| 955 |
{ |
| 956 |
if (isEOS()) |
| 957 |
return false; |
| 958 |
|
| 959 |
++m_cursor; |
| 960 |
if (isEOS()) { |
| 961 |
m_current = StringView(); |
| 962 |
return false; |
| 963 |
} |
| 964 |
m_current = *m_cursor; |
| 965 |
return true; |
| 966 |
} |
| 967 |
|
| 968 |
private: |
| 969 |
bool parseExtensionsAndPUExtensions(); |
| 970 |
|
| 971 |
bool parseUnicodeExtensionAfterPrefix(); |
| 972 |
bool parseTransformedExtensionAfterPrefix(); |
| 973 |
bool parseOtherExtensionAfterPrefix(); |
| 974 |
bool parsePUExtensionAfterPrefix(); |
| 975 |
|
| 976 |
StringView::SplitResult m_range; |
| 977 |
StringView::SplitResult::Iterator m_cursor; |
| 978 |
StringView m_current; |
| 979 |
}; |
| 980 |
|
| 981 |
bool LanguageTagParser::parseUnicodeLocaleId() |
| 982 |
{ |
| 983 |
// unicode_locale_id = unicode_language_id |
| 984 |
// extensions* |
| 985 |
// pu_extensions? ; |
| 986 |
ASSERT(!isEOS()); |
| 987 |
if (!parseUnicodeLanguageId()) |
| 988 |
return false; |
| 989 |
if (isEOS()) |
| 990 |
return true; |
| 991 |
if (!parseExtensionsAndPUExtensions()) |
| 992 |
return false; |
| 993 |
return true; |
| 994 |
} |
| 995 |
|
| 996 |
bool LanguageTagParser::parseUnicodeLanguageId() |
| 997 |
{ |
| 998 |
// unicode_language_id = unicode_language_subtag (sep unicode_script_subtag)? (sep unicode_region_subtag)? (sep unicode_variant_subtag)* ; |
| 999 |
ASSERT(!isEOS()); |
| 1000 |
if (!isUnicodeLanguageSubtag(m_current)) |
| 1001 |
return false; |
| 1002 |
if (!next()) |
| 1003 |
return true; |
| 1004 |
|
| 1005 |
if (isUnicodeScriptSubtag(m_current)) { |
| 1006 |
if (!next()) |
| 1007 |
return true; |
| 1008 |
} |
| 1009 |
|
| 1010 |
if (isUnicodeRegionSubtag(m_current)) { |
| 1011 |
if (!next()) |
| 1012 |
return true; |
| 1013 |
} |
| 1014 |
|
| 1015 |
HashSet<VariantCode> variantCodes; |
| 1016 |
while (true) { |
| 1017 |
if (!isUnicodeVariantSubtag(m_current)) |
| 1018 |
return true; |
| 1019 |
// https://tc39.es/ecma402/#sec-isstructurallyvalidlanguagetag |
| 1020 |
// does not include duplicate variant subtags |
| 1021 |
if (!variantCodes.add(parseVariantCode(m_current)).isNewEntry) |
| 1022 |
return false; |
| 1023 |
if (!next()) |
| 1024 |
return true; |
| 1025 |
} |
| 1026 |
} |
| 1027 |
|
| 1028 |
bool LanguageTagParser::parseUnicodeExtensionAfterPrefix() |
| 1029 |
{ |
| 1030 |
// ((sep keyword)+ | (sep attribute)+ (sep keyword)*) ; |
| 1031 |
// |
| 1032 |
// keyword = key (sep type)? ; |
| 1033 |
// key = alphanum alpha ; |
| 1034 |
// type = alphanum{3,8} (sep alphanum{3,8})* ; |
| 1035 |
// attribute = alphanum{3,8} ; |
| 1036 |
ASSERT(!isEOS()); |
| 1037 |
bool isAttributeOrKeyword = false; |
| 1038 |
if (isUnicodeExtensionAttribute(m_current)) { |
| 1039 |
isAttributeOrKeyword = true; |
| 1040 |
while (true) { |
| 1041 |
if (!isUnicodeExtensionAttribute(m_current)) |
| 1042 |
break; |
| 1043 |
if (!next()) |
| 1044 |
return true; |
| 1045 |
} |
| 1046 |
} |
| 1047 |
|
| 1048 |
if (isUnicodeExtensionKey(m_current)) { |
| 1049 |
isAttributeOrKeyword = true; |
| 1050 |
while (true) { |
| 1051 |
if (!isUnicodeExtensionKey(m_current)) |
| 1052 |
break; |
| 1053 |
if (!next()) |
| 1054 |
return true; |
| 1055 |
while (true) { |
| 1056 |
if (!isUnicodeExtensionTypeComponent(m_current)) |
| 1057 |
break; |
| 1058 |
if (!next()) |
| 1059 |
return true; |
| 1060 |
} |
| 1061 |
} |
| 1062 |
} |
| 1063 |
|
| 1064 |
if (!isAttributeOrKeyword) |
| 1065 |
return false; |
| 1066 |
return true; |
| 1067 |
} |
| 1068 |
|
| 1069 |
bool LanguageTagParser::parseTransformedExtensionAfterPrefix() |
| 1070 |
{ |
| 1071 |
// ((sep tlang (sep tfield)*) | (sep tfield)+) ; |
| 1072 |
// |
| 1073 |
// tlang = unicode_language_subtag (sep unicode_script_subtag)? (sep unicode_region_subtag)? (sep unicode_variant_subtag)* ; |
| 1074 |
// tfield = tkey tvalue; |
| 1075 |
// tkey = alpha digit ; |
| 1076 |
// tvalue = (sep alphanum{3,8})+ ; |
| 1077 |
ASSERT(!isEOS()); |
| 1078 |
bool isTLangOrTField = false; |
| 1079 |
if (isUnicodeLanguageSubtag(m_current)) { |
| 1080 |
isTLangOrTField = true; |
| 1081 |
if (!parseUnicodeLanguageId()) |
| 1082 |
return false; |
| 1083 |
if (isEOS()) |
| 1084 |
return true; |
| 1085 |
} |
| 855 |
|
1086 |
|
| 856 |
ASSERT(subtags.size() >= 1); |
1087 |
if (isUnicodeTKey(m_current)) { |
| 857 |
unsigned cursor = 0; |
1088 |
isTLangOrTField = true; |
| 858 |
if (!isUnicodeLanguageSubtag(subtags[cursor])) |
1089 |
while (true) { |
|
|
1090 |
if (!isUnicodeTKey(m_current)) |
| 1091 |
break; |
| 1092 |
if (!next()) |
| 1093 |
return false; |
| 1094 |
if (!isUnicodeTValue(m_current)) |
| 1095 |
return false; |
| 1096 |
if (!next()) |
| 1097 |
return true; |
| 1098 |
while (true) { |
| 1099 |
if (!isUnicodeTValue(m_current)) |
| 1100 |
break; |
| 1101 |
if (!next()) |
| 1102 |
return true; |
| 1103 |
} |
| 1104 |
} |
| 1105 |
} |
| 1106 |
|
| 1107 |
if (!isTLangOrTField) |
| 859 |
return false; |
1108 |
return false; |
| 860 |
++cursor; |
1109 |
return true; |
|
|
1110 |
} |
| 861 |
|
1111 |
|
| 862 |
if (cursor == subtags.size()) |
1112 |
bool LanguageTagParser::parseOtherExtensionAfterPrefix() |
|
|
1113 |
{ |
| 1114 |
// (sep alphanum{2,8})+ ; |
| 1115 |
ASSERT(!isEOS()); |
| 1116 |
if (!isUnicodeOtherExtensionValue(m_current)) |
| 1117 |
return false; |
| 1118 |
if (!next()) |
| 863 |
return true; |
1119 |
return true; |
| 864 |
if (isUnicodeScriptSubtag(subtags[cursor])) |
|
|
| 865 |
++cursor; |
| 866 |
|
1120 |
|
| 867 |
if (cursor == subtags.size()) |
1121 |
while (true) { |
|
|
1122 |
if (!isUnicodeOtherExtensionValue(m_current)) |
| 1123 |
return true; |
| 1124 |
if (!next()) |
| 1125 |
return true; |
| 1126 |
} |
| 1127 |
} |
| 1128 |
|
| 1129 |
bool LanguageTagParser::parsePUExtensionAfterPrefix() |
| 1130 |
{ |
| 1131 |
// (sep alphanum{1,8})+ ; |
| 1132 |
ASSERT(!isEOS()); |
| 1133 |
if (!isUnicodePUExtensionValue(m_current)) |
| 1134 |
return false; |
| 1135 |
if (!next()) |
| 868 |
return true; |
1136 |
return true; |
| 869 |
if (isUnicodeRegionSubtag(subtags[cursor])) |
|
|
| 870 |
++cursor; |
| 871 |
|
1137 |
|
| 872 |
while (true) { |
1138 |
while (true) { |
| 873 |
if (cursor == subtags.size()) |
1139 |
if (!isUnicodePUExtensionValue(m_current)) |
|
|
1140 |
return true; |
| 1141 |
if (!next()) |
| 874 |
return true; |
1142 |
return true; |
| 875 |
if (!isUnicodeVariantSubtag(subtags[cursor])) |
1143 |
} |
|
|
1144 |
} |
| 1145 |
|
| 1146 |
bool LanguageTagParser::parseExtensionsAndPUExtensions() |
| 1147 |
{ |
| 1148 |
// unicode_locale_id = unicode_language_id |
| 1149 |
// extensions* |
| 1150 |
// pu_extensions? ; |
| 1151 |
// |
| 1152 |
// extensions = unicode_locale_extensions |
| 1153 |
// | transformed_extensions |
| 1154 |
// | other_extensions ; |
| 1155 |
// |
| 1156 |
// pu_extensions = sep [xX] (sep alphanum{1,8})+ ; |
| 1157 |
ASSERT(!isEOS()); |
| 1158 |
Bitmap<numberOfUnicodeSingletons> singletonsSet { }; |
| 1159 |
while (true) { |
| 1160 |
if (m_current.length() != 1) |
| 1161 |
return true; |
| 1162 |
UChar prefixCode = m_current[0]; |
| 1163 |
if (!isASCIIAlphanumeric(prefixCode)) |
| 1164 |
return true; |
| 1165 |
|
| 1166 |
// https://tc39.es/ecma402/#sec-isstructurallyvalidlanguagetag |
| 1167 |
// does not include duplicate singleton subtags. |
| 1168 |
// |
| 1169 |
// https://unicode.org/reports/tr35/#Unicode_locale_identifier |
| 1170 |
// As is often the case, the complete syntactic constraints are not easily captured by ABNF, |
| 1171 |
// so there is a further condition: There cannot be more than one extension with the same singleton (-a-, …, -t-, -u-, …). |
| 1172 |
// Note that the private use extension (-x-) must come after all other extensions. |
| 1173 |
if (singletonsSet.get(convertToUnicodeSingletonIndex(prefixCode))) |
| 876 |
return false; |
1174 |
return false; |
| 877 |
++cursor; |
1175 |
singletonsSet.set(convertToUnicodeSingletonIndex(prefixCode), true); |
|
|
1176 |
|
| 1177 |
switch (prefixCode) { |
| 1178 |
case 'u': |
| 1179 |
case 'U': { |
| 1180 |
// unicode_locale_extensions = sep [uU] ((sep keyword)+ | (sep attribute)+ (sep keyword)*) ; |
| 1181 |
if (!next()) |
| 1182 |
return false; |
| 1183 |
if (!parseUnicodeExtensionAfterPrefix()) |
| 1184 |
return false; |
| 1185 |
if (isEOS()) |
| 1186 |
return true; |
| 1187 |
break; // Next extension. |
| 1188 |
} |
| 1189 |
case 't': |
| 1190 |
case 'T': { |
| 1191 |
// transformed_extensions = sep [tT] ((sep tlang (sep tfield)*) | (sep tfield)+) ; |
| 1192 |
if (!next()) |
| 1193 |
return false; |
| 1194 |
if (!parseTransformedExtensionAfterPrefix()) |
| 1195 |
return false; |
| 1196 |
if (isEOS()) |
| 1197 |
return true; |
| 1198 |
break; // Next extension. |
| 1199 |
} |
| 1200 |
case 'x': |
| 1201 |
case 'X': { |
| 1202 |
// pu_extensions = sep [xX] (sep alphanum{1,8})+ ; |
| 1203 |
if (!next()) |
| 1204 |
return false; |
| 1205 |
if (!parsePUExtensionAfterPrefix()) |
| 1206 |
return false; |
| 1207 |
return true; // If pu_extensions appear, no extensions can follow after that. This must be the end of unicode_locale_id. |
| 1208 |
} |
| 1209 |
default: { |
| 1210 |
// other_extensions = sep [alphanum-[tTuUxX]] (sep alphanum{2,8})+ ; |
| 1211 |
if (!next()) |
| 1212 |
return false; |
| 1213 |
if (!parseOtherExtensionAfterPrefix()) |
| 1214 |
return false; |
| 1215 |
if (isEOS()) |
| 1216 |
return true; |
| 1217 |
break; // Next extension. |
| 1218 |
} |
| 1219 |
} |
| 878 |
} |
1220 |
} |
| 879 |
} |
1221 |
} |
| 880 |
|
1222 |
|
|
|
1223 |
// https://tc39.es/ecma402/#sec-isstructurallyvalidlanguagetag |
| 1224 |
bool isStructurallyValidLanguageTag(StringView string) |
| 1225 |
{ |
| 1226 |
LanguageTagParser parser(string); |
| 1227 |
if (!parser.parseUnicodeLocaleId()) |
| 1228 |
return false; |
| 1229 |
if (!parser.isEOS()) |
| 1230 |
return false; |
| 1231 |
return true; |
| 1232 |
} |
| 1233 |
|
| 1234 |
// unicode_language_id, but intersection of BCP47 and UTS35. |
| 1235 |
// unicode_language_id = |
| 1236 |
// | unicode_language_subtag (sep unicode_script_subtag)? (sep unicode_region_subtag)? (sep unicode_variant_subtag)* ; |
| 1237 |
// https://github.com/tc39/proposal-intl-displaynames/issues/79 |
| 1238 |
bool isUnicodeLanguageId(StringView string) |
| 1239 |
{ |
| 1240 |
LanguageTagParser parser(string); |
| 1241 |
if (!parser.parseUnicodeLanguageId()) |
| 1242 |
return false; |
| 1243 |
if (!parser.isEOS()) |
| 1244 |
return false; |
| 1245 |
return true; |
| 1246 |
} |
| 1247 |
|
| 881 |
bool isWellFormedCurrencyCode(StringView currency) |
1248 |
bool isWellFormedCurrencyCode(StringView currency) |
| 882 |
{ |
1249 |
{ |
| 883 |
return currency.length() == 3 && currency.isAllSpecialCharacters<isASCIIAlpha>(); |
1250 |
return currency.length() == 3 && currency.isAllSpecialCharacters<isASCIIAlpha>(); |