1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/js_locale.h"
17
18 #include "ecmascript/base/string_helper.h"
19 #include "ecmascript/ecma_macros.h"
20 #include "ecmascript/ecma_vm.h"
21 #include "ecmascript/global_env.h"
22 #include "ecmascript/object_factory.h"
23
24 #if defined(__clang__)
25 #pragma clang diagnostic push
26 #pragma clang diagnostic ignored "-Wshadow"
27 #elif defined(__GNUC__)
28 #pragma GCC diagnostic push
29 #pragma GCC diagnostic ignored "-Wshadow"
30 #endif
31 #include "unicode/localebuilder.h"
32 #if defined(__clang__)
33 #pragma clang diagnostic pop
34 #elif defined(__GNUC__)
35 #pragma GCC diagnostic pop
36 #endif
37
38 #include "unicode/localematcher.h"
39
40 namespace panda::ecmascript {
41 const std::string LATN_STRING = "latn";
42 // 6.2.2 IsStructurallyValidLanguageTag( locale )
IsStructurallyValidLanguageTag(const JSHandle<EcmaString> & tag)43 bool JSLocale::IsStructurallyValidLanguageTag(const JSHandle<EcmaString> &tag)
44 {
45 std::string tagCollection = ConvertToStdString(tag);
46 std::vector<std::string> containers;
47 std::string substring;
48 std::set<std::string> uniqueSubtags;
49 size_t address = 1;
50 for (auto it = tagCollection.begin(); it != tagCollection.end(); it++) {
51 if (*it != '-' && it != tagCollection.end() - 1) {
52 substring += *it;
53 } else {
54 if (it == tagCollection.end() - 1) {
55 substring += *it;
56 }
57 containers.push_back(substring);
58 if (IsVariantSubtag(substring)) {
59 std::transform(substring.begin(), substring.end(), substring.begin(), AsciiAlphaToLower);
60 if (!uniqueSubtags.insert(substring).second) {
61 return false;
62 }
63 }
64 substring.clear();
65 }
66 }
67 bool result = DealwithLanguageTag(containers, address);
68 return result;
69 }
70
ConvertToStdString(const JSHandle<EcmaString> & ecmaStr)71 std::string JSLocale::ConvertToStdString(const JSHandle<EcmaString> &ecmaStr)
72 {
73 return std::string(ConvertToString(*ecmaStr, StringConvertedUsage::LOGICOPERATION));
74 }
75
76 // 6.2.3 CanonicalizeUnicodeLocaleId( locale )
CanonicalizeUnicodeLocaleId(JSThread * thread,const JSHandle<EcmaString> & locale)77 JSHandle<EcmaString> JSLocale::CanonicalizeUnicodeLocaleId(JSThread *thread, const JSHandle<EcmaString> &locale)
78 {
79 [[maybe_unused]] ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
80 if (!IsStructurallyValidLanguageTag(locale)) {
81 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid locale", factory->GetEmptyString());
82 }
83
84 if (EcmaStringAccessor(locale).GetLength() == 0 || EcmaStringAccessor(locale).IsUtf16()) {
85 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid locale", factory->GetEmptyString());
86 }
87
88 std::string localeCStr = ConvertToStdString(locale);
89 std::transform(localeCStr.begin(), localeCStr.end(), localeCStr.begin(), AsciiAlphaToLower);
90 UErrorCode status = U_ZERO_ERROR;
91 icu::Locale formalLocale = icu::Locale::forLanguageTag(localeCStr.c_str(), status);
92 if ((U_FAILURE(status) != 0) || (formalLocale.isBogus() != 0)) {
93 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid locale", factory->GetEmptyString());
94 }
95
96 // Resets the LocaleBuilder to match the locale.
97 // Returns an instance of Locale created from the fields set on this builder.
98 formalLocale = icu::LocaleBuilder().setLocale(formalLocale).build(status);
99 // Canonicalize the locale ID of this object according to CLDR.
100 formalLocale.canonicalize(status);
101 if ((U_FAILURE(status) != 0) || (formalLocale.isBogus() != 0)) {
102 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid locale", factory->GetEmptyString());
103 }
104 JSHandle<EcmaString> languageTag = ToLanguageTag(thread, formalLocale);
105 RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread);
106 return languageTag;
107 }
108
109 // 6.2.4 DefaultLocale ()
DefaultLocale(JSThread * thread)110 JSHandle<EcmaString> JSLocale::DefaultLocale(JSThread *thread)
111 {
112 icu::Locale defaultLocale;
113 auto globalConst = thread->GlobalConstants();
114 if (strcmp(defaultLocale.getName(), "en_US_POSIX") == 0 || strcmp(defaultLocale.getName(), "c") == 0) {
115 return JSHandle<EcmaString>::Cast(globalConst->GetHandledEnUsString());
116 }
117 if (defaultLocale.isBogus() != 0) {
118 return JSHandle<EcmaString>::Cast(globalConst->GetHandledUndString());
119 }
120 return ToLanguageTag(thread, defaultLocale);
121 }
122
123 // 6.4.1 IsValidTimeZoneName ( timeZone )
IsValidTimeZoneName(const icu::TimeZone & tz)124 bool JSLocale::IsValidTimeZoneName(const icu::TimeZone &tz)
125 {
126 UErrorCode status = U_ZERO_ERROR;
127 icu::UnicodeString id;
128 tz.getID(id);
129 icu::UnicodeString canonical;
130 icu::TimeZone::getCanonicalID(id, canonical, status);
131 UBool canonicalFlag = (canonical != icu::UnicodeString("Etc/Unknown", -1, US_INV));
132 return (U_SUCCESS(status) != 0) && (canonicalFlag != 0);
133 }
134
HandleLocaleExtension(size_t & start,size_t & extensionEnd,const std::string result,size_t len)135 void JSLocale::HandleLocaleExtension(size_t &start, size_t &extensionEnd,
136 const std::string result, size_t len)
137 {
138 while (start < len - INTL_INDEX_TWO) {
139 if (result[start] != '-') {
140 start++;
141 continue;
142 }
143 if (result[start + INTL_INDEX_TWO] == '-') {
144 extensionEnd = start;
145 break;
146 }
147 start += INTL_INDEX_THREE;
148 }
149 }
150
HandleLocale(const JSHandle<EcmaString> & localeString)151 JSLocale::ParsedLocale JSLocale::HandleLocale(const JSHandle<EcmaString> &localeString)
152 {
153 std::string result = ConvertToStdString(localeString);
154 size_t len = result.size();
155 ParsedLocale parsedResult;
156
157 // a. The single-character subtag ’x’ as the primary subtag indicates
158 // that the language tag consists solely of subtags whose meaning is
159 // defined by private agreement.
160 // b. Extensions cannot be used in tags that are entirely private use.
161 if (IsPrivateSubTag(result, len)) {
162 parsedResult.base = result;
163 return parsedResult;
164 }
165 // If cannot find "-u-", return the whole string as base.
166 size_t foundExtension = result.find("-u-");
167 if (foundExtension == std::string::npos) {
168 parsedResult.base = result;
169 return parsedResult;
170 }
171 // Let privateIndex be Call(%StringProto_indexOf%, foundLocale, « "-x-" »).
172 size_t privateIndex = result.find("-x-");
173 if (privateIndex != std::string::npos && privateIndex < foundExtension) {
174 parsedResult.base = result;
175 return parsedResult;
176 }
177 const std::string basis = result.substr(INTL_INDEX_ZERO, foundExtension);
178 size_t extensionEnd = len;
179 ASSERT(len > INTL_INDEX_TWO);
180 size_t start = foundExtension + INTL_INDEX_ONE;
181 HandleLocaleExtension(start, extensionEnd, result, len);
182 const std::string end = result.substr(extensionEnd);
183 parsedResult.base = basis + end;
184 parsedResult.extension = result.substr(foundExtension, extensionEnd - foundExtension);
185 return parsedResult;
186 }
187
188 // 9.2.1 CanonicalizeLocaleList ( locales )
CanonicalizeLocaleList(JSThread * thread,const JSHandle<JSTaggedValue> & locales)189 JSHandle<TaggedArray> JSLocale::CanonicalizeLocaleList(JSThread *thread, const JSHandle<JSTaggedValue> &locales)
190 {
191 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
192 // 1. If locales is undefined, then
193 // a. Return a new empty List.
194 if (locales->IsUndefined()) {
195 return factory->EmptyArray();
196 }
197 // 2. Let seen be a new empty List.
198 JSHandle<TaggedArray> localeSeen = factory->NewTaggedArray(1);
199 // 3. If Type(locales) is String or Type(locales) is Object and locales has an [[InitializedLocale]] internal slot,
200 // then
201 // a. Let O be CreateArrayFromList(« locales »).
202 // 4. Else,
203 // a.Let O be ? ToObject(locales).
204 if (locales->IsString()) {
205 JSHandle<EcmaString> tag = JSHandle<EcmaString>::Cast(locales);
206 JSHandle<TaggedArray> temp = factory->NewTaggedArray(1);
207 temp->Set(thread, 0, tag.GetTaggedValue());
208 JSHandle<JSArray> obj = JSArray::CreateArrayFromList(thread, temp);
209 JSHandle<TaggedArray> finalSeen = CanonicalizeHelper<JSArray>(thread, obj, localeSeen);
210 return finalSeen;
211 } else if (locales->IsJSLocale()) {
212 JSHandle<EcmaString> tag = JSLocale::ToString(thread, JSHandle<JSLocale>::Cast(locales));
213 JSHandle<TaggedArray> temp = factory->NewTaggedArray(1);
214 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
215 temp->Set(thread, 0, tag.GetTaggedValue());
216 JSHandle<JSArray> obj = JSArray::CreateArrayFromList(thread, temp);
217 JSHandle<TaggedArray> finalSeen = CanonicalizeHelper<JSArray>(thread, obj, localeSeen);
218 return finalSeen;
219 } else {
220 JSHandle<JSObject> obj = JSTaggedValue::ToObject(thread, locales);
221 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
222 JSHandle<TaggedArray> finalSeen = CanonicalizeHelper<JSObject>(thread, obj, localeSeen);
223 return finalSeen;
224 }
225 return localeSeen;
226 }
227
228 template<typename T>
CanonicalizeHelper(JSThread * thread,JSHandle<T> & obj,JSHandle<TaggedArray> & seen)229 JSHandle<TaggedArray> JSLocale::CanonicalizeHelper(JSThread *thread, JSHandle<T> &obj, JSHandle<TaggedArray> &seen)
230 {
231 OperationResult operationResult = JSTaggedValue::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(obj),
232 thread->GlobalConstants()->GetHandledLengthString());
233 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
234 JSTaggedNumber len = JSTaggedValue::ToLength(thread, operationResult.GetValue());
235 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
236 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
237 // 2. Let seen be a new empty List.
238 uint32_t requestedLocalesLen = len.ToUint32();
239 seen = factory->NewTaggedArray(requestedLocalesLen);
240 // 6. Let k be 0.
241 // 7. Repeat, while k < len
242 JSMutableHandle<JSTaggedValue> pk(thread, JSTaggedValue::Undefined());
243 JSMutableHandle<JSTaggedValue> tag(thread, JSTaggedValue::Undefined());
244 uint32_t index = 0;
245 JSHandle<JSTaggedValue> objTagged = JSHandle<JSTaggedValue>::Cast(obj);
246 for (uint32_t k = 0; k < requestedLocalesLen; k++) {
247 // a. Let Pk be ToString(k).
248 JSHandle<JSTaggedValue> kHandle(thread, JSTaggedValue(k));
249 JSHandle<EcmaString> str = JSTaggedValue::ToString(thread, kHandle);
250 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
251 pk.Update(str.GetTaggedValue());
252 // b. Let kPresent be ? HasProperty(O, Pk).
253 bool kPresent = JSTaggedValue::HasProperty(thread, objTagged, pk);
254 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
255
256 // c. If kPresent is true, then
257 if (kPresent) {
258 // i. Let kValue be ? Get(O, Pk).
259 OperationResult result = JSTaggedValue::GetProperty(thread, objTagged, pk);
260 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
261 JSHandle<JSTaggedValue> kValue = result.GetValue();
262 // ii. If Type(kValue) is not String or Object, throw a TypeError exception.
263 if (!kValue->IsString() && !kValue->IsJSObject()) {
264 THROW_TYPE_ERROR_AND_RETURN(thread, "kValue is not String or Object.", factory->EmptyArray());
265 }
266 // iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
267 // 1. Let tag be kValue.[[Locale]].
268 // iv. Else,
269 // 1. Let tag be ? ToString(kValue).
270 if (kValue->IsJSLocale()) {
271 JSHandle<EcmaString> kValueStr = JSLocale::ToString(thread, JSHandle<JSLocale>::Cast(kValue));
272 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
273 tag.Update(kValueStr.GetTaggedValue());
274 } else {
275 JSHandle<EcmaString> kValueString = JSTaggedValue::ToString(thread, kValue);
276 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
277 JSHandle<EcmaString> canonicalStr = CanonicalizeUnicodeLocaleId(thread, kValueString);
278 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
279 tag.Update(canonicalStr.GetTaggedValue());
280 }
281 // vii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
282 bool isExist = false;
283 uint32_t seenLen = seen->GetLength();
284 for (uint32_t i = 0; i < seenLen; i++) {
285 if (JSTaggedValue::SameValue(seen->Get(thread, i), tag.GetTaggedValue())) {
286 isExist = true;
287 }
288 }
289 if (!isExist) {
290 seen->Set(thread, index++, JSHandle<JSTaggedValue>::Cast(tag));
291 }
292 }
293 // d. Increase k by 1.
294 }
295 // set capacity
296 seen = TaggedArray::SetCapacity(thread, seen, index);
297 // 8. Return seen.
298 return seen;
299 }
300
301 // 9.2.2 BestAvailableLocale ( availableLocales, locale )
BestAvailableLocale(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const std::string & locale)302 std::string JSLocale::BestAvailableLocale(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
303 const std::string &locale)
304 {
305 // 1. Let candidate be locale.
306 std::string localeCandidate = locale;
307 std::string undefined = std::string();
308 // 2. Repeat,
309 uint32_t length = availableLocales->GetLength();
310 JSMutableHandle<EcmaString> item(thread, JSTaggedValue::Undefined());
311 while (true) {
312 // a. If availableLocales contains an element equal to candidate, return candidate.
313 for (uint32_t i = 0; i < length; ++i) {
314 item.Update(availableLocales->Get(thread, i));
315 std::string itemStr = ConvertToStdString(item);
316 if (itemStr == localeCandidate) {
317 return localeCandidate;
318 }
319 }
320 // b. Let pos be the character index of the last occurrence of "-" (U+002D) within candidate.
321 // If that character does not occur, return undefined.
322 size_t pos = localeCandidate.rfind('-');
323 if (pos == std::string::npos) {
324 return undefined;
325 }
326 // c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, decrease pos by 2.
327 if (pos >= INTL_INDEX_TWO && localeCandidate[pos - INTL_INDEX_TWO] == '-') {
328 pos -= INTL_INDEX_TWO;
329 }
330 // d. Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive.
331 localeCandidate = localeCandidate.substr(0, pos);
332 }
333 }
334
335 // 9.2.3 LookupMatcher ( availableLocales, requestedLocales )
LookupMatcher(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales)336 JSHandle<EcmaString> JSLocale::LookupMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
337 const JSHandle<TaggedArray> &requestedLocales)
338 {
339 MatcherResult result = {std::string(), std::string()};
340 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
341 // 1. Let result be a new Record.
342 // 2. For each element locale of requestedLocales in List order, do
343 uint32_t length = requestedLocales->GetLength();
344 JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
345 for (uint32_t i = 0; i < length; ++i) {
346 locale.Update(requestedLocales->Get(thread, i));
347 // 2. a. Let noExtensionsLocale be the String value that is locale
348 // with all Unicode locale extension sequences removed.
349 ParsedLocale parsedResult = HandleLocale(locale);
350 // 2. b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
351 std::string availableLocale = BestAvailableLocale(thread, availableLocales, parsedResult.base);
352 // 2. c. If availableLocale is not undefined, append locale to the end of subset.
353 if (!availableLocale.empty()) {
354 result = {std::string(), std::string()};
355 // 2. c. i. Set result.[[locale]] to availableLocale.
356 result.locale = availableLocale;
357 // 2. c. ii. If locale and noExtensionsLocale are not the same String value, then
358 // 2. c. ii. 1. Let extension be the String value consisting of the first substring of locale that is a
359 // Unicode locale extension sequence.
360 if (!parsedResult.extension.empty()) {
361 result.extension = parsedResult.extension;
362 }
363 // 2. c. ii. 2. Set result.[[extension]] to extension.
364 std::string res = result.locale + result.extension;
365 // 2. c. iii. Return result.
366 return factory->NewFromStdString(res);
367 }
368 }
369
370 // 3. Let defLocale be DefaultLocale();
371 // 4. Set result.[[locale]] to defLocale.
372 // 5. Return result.
373 std::string defLocale = ConvertToStdString(DefaultLocale(thread));
374 result.locale = defLocale;
375 return factory->NewFromStdString(result.locale);
376 }
377
BuildLocaleMatcher(JSThread * thread,uint32_t * availableLength,UErrorCode * status,const JSHandle<TaggedArray> & availableLocales)378 icu::LocaleMatcher BuildLocaleMatcher(JSThread *thread, uint32_t *availableLength, UErrorCode *status,
379 const JSHandle<TaggedArray> &availableLocales)
380 {
381 std::string locale = JSLocale::ConvertToStdString(JSLocale::DefaultLocale(thread));
382 icu::Locale defaultLocale = icu::Locale::forLanguageTag(locale, *status);
383 ASSERT_PRINT(U_SUCCESS(*status), "icu::Locale::forLanguageTag failed");
384 icu::LocaleMatcher::Builder builder;
385 builder.setDefaultLocale(&defaultLocale);
386 uint32_t length = availableLocales->GetLength();
387
388 JSMutableHandle<EcmaString> item(thread, JSTaggedValue::Undefined());
389 for (*availableLength = 0; *availableLength < length; ++(*availableLength)) {
390 item.Update(availableLocales->Get(thread, *availableLength));
391 std::string itemStr = JSLocale::ConvertToStdString(item);
392 icu::Locale localeForLanguageTag = icu::Locale::forLanguageTag(itemStr, *status);
393 if (U_SUCCESS(*status) != 0) {
394 builder.addSupportedLocale(localeForLanguageTag);
395 } else {
396 break;
397 }
398 }
399 *status = U_ZERO_ERROR;
400 return builder.build(*status);
401 }
402
403 // 9.2.4 BestFitMatcher ( availableLocales, requestedLocales )
BestFitMatcher(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales)404 JSHandle<EcmaString> JSLocale::BestFitMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
405 const JSHandle<TaggedArray> &requestedLocales)
406 {
407 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
408 UErrorCode status = U_ZERO_ERROR;
409 uint32_t availableLength = availableLocales->GetLength();
410 icu::LocaleMatcher matcher = BuildLocaleMatcher(thread, &availableLength, &status, availableLocales);
411 ASSERT(U_SUCCESS(status));
412
413 uint32_t requestedLocalesLength = requestedLocales->GetLength();
414 JSIntlIterator iter(requestedLocales, requestedLocalesLength);
415 auto bestFit = matcher.getBestMatch(iter, status)->toLanguageTag<std::string>(status);
416
417 if (U_FAILURE(status) != 0) {
418 return DefaultLocale(thread);
419 }
420
421 for (uint32_t i = 0; i < requestedLocalesLength; ++i) {
422 if (iter[i] == bestFit) {
423 return JSHandle<EcmaString>(thread, requestedLocales->Get(thread, i));
424 }
425 }
426 return factory->NewFromStdString(bestFit);
427 }
428
429 // 9.2.8 LookupSupportedLocales ( availableLocales, requestedLocales )
LookupSupportedLocales(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales)430 JSHandle<TaggedArray> JSLocale::LookupSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
431 const JSHandle<TaggedArray> &requestedLocales)
432 {
433 uint32_t index = 0;
434 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
435 uint32_t length = requestedLocales->GetLength();
436 // 1. Let subset be a new empty List.
437 JSHandle<TaggedArray> subset = factory->NewTaggedArray(length);
438 JSMutableHandle<EcmaString> item(thread, JSTaggedValue::Undefined());
439 // 2. For each element locale of requestedLocales in List order, do
440 // a. Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences
441 // removed.
442 // b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
443 // c. If availableLocale is not undefined, append locale to the end of subset.
444 for (uint32_t i = 0; i < length; ++i) {
445 item.Update(requestedLocales->Get(thread, i));
446 ParsedLocale foundationResult = HandleLocale(item);
447 std::string availableLocale = BestAvailableLocale(thread, availableLocales, foundationResult.base);
448 if (!availableLocale.empty()) {
449 subset->Set(thread, index++, item.GetTaggedValue());
450 }
451 }
452 // 3. Return subset.
453 return TaggedArray::SetCapacity(thread, subset, index);
454 }
455
456 // 9.2.9 BestFitSupportedLocales ( availableLocales, requestedLocales )
BestFitSupportedLocales(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales)457 JSHandle<TaggedArray> JSLocale::BestFitSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
458 const JSHandle<TaggedArray> &requestedLocales)
459 {
460 UErrorCode status = U_ZERO_ERROR;
461 uint32_t requestLength = requestedLocales->GetLength();
462 uint32_t availableLength = availableLocales->GetLength();
463 icu::LocaleMatcher matcher = BuildLocaleMatcher(thread, &availableLength, &status, availableLocales);
464 ASSERT(U_SUCCESS(status));
465
466 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
467 JSHandle<EcmaString> defaultLocale = DefaultLocale(thread);
468 JSHandle<TaggedArray> result = factory->NewTaggedArray(requestLength);
469
470 uint32_t index = 0;
471 JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
472 for (uint32_t i = 0; i < requestLength; ++i) {
473 locale.Update(requestedLocales->Get(thread, i));
474 if (EcmaStringAccessor::StringsAreEqual(*locale, *defaultLocale)) {
475 result->Set(thread, index++, locale.GetTaggedValue());
476 } else {
477 status = U_ZERO_ERROR;
478 std::string localeStr = ConvertToStdString(locale);
479 icu::Locale desired = icu::Locale::forLanguageTag(localeStr, status);
480 auto bestFit = matcher.getBestMatch(desired, status)->toLanguageTag<std::string>(status);
481 if ((U_SUCCESS(status) != 0) &&
482 EcmaStringAccessor::StringsAreEqual(*locale, *(factory->NewFromStdString(bestFit)))) {
483 result->Set(thread, index++, locale.GetTaggedValue());
484 }
485 }
486 }
487 result = TaggedArray::SetCapacity(thread, result, index);
488 return result;
489 }
490
ToLanguageTag(JSThread * thread,const icu::Locale & locale)491 JSHandle<EcmaString> JSLocale::ToLanguageTag(JSThread *thread, const icu::Locale &locale)
492 {
493 UErrorCode status = U_ZERO_ERROR;
494 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
495 auto result = locale.toLanguageTag<std::string>(status);
496 bool flag = (U_FAILURE(status) == 0) ? true : false;
497 if (!flag) {
498 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid locale", factory->GetEmptyString());
499 }
500 size_t findBeginning = result.find("-u-");
501 std::string finalRes;
502 std::string tempRes;
503 if (findBeginning == std::string::npos) {
504 return factory->NewFromStdString(result);
505 }
506 size_t specialBeginning = findBeginning + INTL_INDEX_THREE;
507 size_t specialCount = 0;
508 while ((specialBeginning < result.size()) && (result[specialBeginning] != '-')) {
509 specialCount++;
510 specialBeginning++;
511 }
512 if (findBeginning != std::string::npos) {
513 // It begin with "-u-xx" or with more elements.
514 tempRes = result.substr(0, findBeginning + INTL_INDEX_THREE + specialCount);
515 if (result.size() <= findBeginning + INTL_INDEX_THREE + specialCount) {
516 return factory->NewFromStdString(result);
517 }
518 std::string leftStr = result.substr(findBeginning + INTL_INDEX_THREE + specialCount + INTL_INDEX_ONE);
519 std::istringstream temp(leftStr);
520 std::string buffer;
521 std::vector<std::string> resContainer;
522 while (getline(temp, buffer, '-')) {
523 if (buffer != "true" && buffer != "yes") {
524 resContainer.push_back(buffer);
525 }
526 }
527 for (auto it = resContainer.begin(); it != resContainer.end(); it++) {
528 std::string tag = "-";
529 tag += *it;
530 finalRes += tag;
531 }
532 }
533 if (!finalRes.empty()) {
534 tempRes += finalRes;
535 }
536 result = tempRes;
537 return factory->NewFromStdString(result);
538 }
539
540 // 9.2.10 SupportedLocales ( availableLocales, requestedLocales, options )
SupportedLocales(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales,const JSHandle<JSTaggedValue> & options)541 JSHandle<JSArray> JSLocale::SupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
542 const JSHandle<TaggedArray> &requestedLocales,
543 const JSHandle<JSTaggedValue> &options)
544 {
545 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
546 // 1. If options is not undefined, then
547 // a. Let options be ? ToObject(options).
548 // b. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
549 // 2. Else, let matcher be "best fit".
550 [[maybe_unused]] LocaleMatcherOption matcher = LocaleMatcherOption::BEST_FIT;
551 if (!options->IsUndefined()) {
552 JSHandle<JSObject> obj = JSTaggedValue::ToObject(thread, options);
553 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
554
555 matcher = GetOptionOfString<LocaleMatcherOption>(thread, obj, globalConst->GetHandledLocaleMatcherString(),
556 {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
557 {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
558 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
559 }
560
561 // 3. If matcher is "best fit", then
562 // a. Let supportedLocales be BestFitSupportedLocales(availableLocales, requestedLocales).
563 // 4. Else,
564 // a. Let supportedLocales be LookupSupportedLocales(availableLocales, requestedLocales).
565 JSMutableHandle<TaggedArray> supportedLocales(thread, JSTaggedValue::Undefined());
566 supportedLocales.Update(LookupSupportedLocales(thread, availableLocales, requestedLocales).GetTaggedValue());
567
568 JSHandle<JSArray> subset = JSArray::CreateArrayFromList(thread, supportedLocales);
569 // 5. Return CreateArrayFromList(supportedLocales).
570 return subset;
571 }
572
573 // 9.2.11 GetOption ( options, property, type, values, fallback )
GetOption(JSThread * thread,const JSHandle<JSObject> & options,const JSHandle<JSTaggedValue> & property,OptionType type,const JSHandle<JSTaggedValue> & values,const JSHandle<JSTaggedValue> & fallback)574 JSHandle<JSTaggedValue> JSLocale::GetOption(JSThread *thread, const JSHandle<JSObject> &options,
575 const JSHandle<JSTaggedValue> &property, OptionType type,
576 const JSHandle<JSTaggedValue> &values,
577 const JSHandle<JSTaggedValue> &fallback)
578 {
579 // 1. Let value be ? Get(options, property).
580 JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread, options, property).GetValue();
581 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
582
583 // 2. If value is not undefined, then
584 if (!value->IsUndefined()) {
585 // a. Assert: type is "boolean" or "string".
586 ASSERT_PRINT(type == OptionType::BOOLEAN || type == OptionType::STRING, "type is not boolean or string");
587
588 // b. If type is "boolean", then
589 // i. Let value be ToBoolean(value).
590 if (type == OptionType::BOOLEAN) {
591 value = JSHandle<JSTaggedValue>(thread, JSTaggedValue(value->ToBoolean()));
592 }
593 // c. If type is "string", then
594 // i. Let value be ? ToString(value).
595 if (type == OptionType::STRING) {
596 JSHandle<EcmaString> str = JSTaggedValue::ToString(thread, value);
597 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
598 value = JSHandle<JSTaggedValue>(thread, str.GetTaggedValue());
599 }
600
601 // d. If values is not undefined, then
602 // i. If values does not contain an element equal to value, throw a RangeError exception.
603 if (!values->IsUndefined()) {
604 bool isExist = false;
605 JSHandle<TaggedArray> valuesArray = JSHandle<TaggedArray>::Cast(values);
606 uint32_t length = valuesArray->GetLength();
607 for (uint32_t i = 0; i < length; i++) {
608 if (JSTaggedValue::SameValue(valuesArray->Get(thread, i), value.GetTaggedValue())) {
609 isExist = true;
610 }
611 }
612 if (!isExist) {
613 JSHandle<JSTaggedValue> exception(thread, JSTaggedValue::Exception());
614 THROW_RANGE_ERROR_AND_RETURN(thread, "values does not contain an element equal to value", exception);
615 }
616 }
617 // e. Return value.
618 return value;
619 }
620 // 3. Else, return fallback.
621 return fallback;
622 }
623
GetOptionOfString(JSThread * thread,const JSHandle<JSObject> & options,const JSHandle<JSTaggedValue> & property,const std::vector<std::string> & values,std::string * optionValue)624 bool JSLocale::GetOptionOfString(JSThread *thread, const JSHandle<JSObject> &options,
625 const JSHandle<JSTaggedValue> &property, const std::vector<std::string> &values,
626 std::string *optionValue)
627 {
628 // 1. Let value be ? Get(options, property).
629 OperationResult operationResult = JSObject::GetProperty(thread, options, property);
630 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
631 JSHandle<JSTaggedValue> value = operationResult.GetValue();
632 // 2. If value is not undefined, then
633 if (value->IsUndefined()) {
634 return false;
635 }
636 // c. If type is "string" "string", then
637 // i. Let value be ? ToString(value).
638 JSHandle<EcmaString> valueEStr = JSTaggedValue::ToString(thread, value);
639 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
640 if (EcmaStringAccessor(valueEStr).IsUtf16()) {
641 THROW_RANGE_ERROR_AND_RETURN(thread, "Value out of range for locale options property", false);
642 }
643 *optionValue = JSLocale::ConvertToStdString(valueEStr);
644 if (values.empty()) {
645 return true;
646 }
647 // d. If values is not undefined, then
648 // i. If values does not contain an element equal to value, throw a RangeError exception.
649 for (const auto &item : values) {
650 if (item == *optionValue) {
651 return true;
652 }
653 }
654 THROW_RANGE_ERROR_AND_RETURN(thread, "Value out of range for locale options property", false);
655 }
656
657 // 9.2.12 DefaultNumberOption ( value, minimum, maximum, fallback )
DefaultNumberOption(JSThread * thread,const JSHandle<JSTaggedValue> & value,int minimum,int maximum,int fallback)658 int JSLocale::DefaultNumberOption(JSThread *thread, const JSHandle<JSTaggedValue> &value, int minimum, int maximum,
659 int fallback)
660 {
661 // 1. If value is not undefined, then
662 if (!value->IsUndefined()) {
663 // a. Let value be ? ToNumber(value).
664 JSTaggedNumber number = JSTaggedValue::ToNumber(thread, value);
665 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
666 // b. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
667 double num = JSTaggedValue(number).GetNumber();
668 if (std::isnan(num) || num < minimum || num > maximum) {
669 THROW_RANGE_ERROR_AND_RETURN(thread, "", fallback);
670 }
671 // c. Return floor(value).
672 return std::floor(num);
673 }
674 // 2. Else, return fallback.
675 return fallback;
676 }
677
678 // 9.2.13 GetNumberOption ( options, property, minimum, maximum, fallback )
GetNumberOption(JSThread * thread,const JSHandle<JSObject> & options,const JSHandle<JSTaggedValue> & property,int min,int max,int fallback)679 int JSLocale::GetNumberOption(JSThread *thread, const JSHandle<JSObject> &options,
680 const JSHandle<JSTaggedValue> &property, int min, int max, int fallback)
681 {
682 // 1. Let value be ? Get(options, property).
683 JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread, options, property).GetValue();
684 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
685
686 // 2. Return ? DefaultNumberOption(value, minimum, maximum, fallback).
687 int result = DefaultNumberOption(thread, value, min, max, fallback);
688 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
689 return result;
690 }
691
692 // 9.2.5 UnicodeExtensionValue ( extension, key )
UnicodeExtensionValue(const std::string extension,const std::string key)693 std::string JSLocale::UnicodeExtensionValue(const std::string extension, const std::string key)
694 {
695 // 1. Assert: The number of elements in key is 2.
696 // 2. Let size be the number of elements in extension.
697 ASSERT(key.size() == INTL_INDEX_TWO);
698 size_t size = extension.size();
699 // 3. Let searchValue be the concatenation of "-" , key, and "-".
700 std::string searchValue = "-" + key + "-";
701 // 4. Let pos be Call(%StringProto_indexOf%, extension, « searchValue »).
702 size_t pos = extension.find(searchValue);
703 // 5. If pos ≠ -1, then
704 if (pos != std::string::npos) {
705 // a. Let start be pos + 4.
706 size_t start = pos + INTL_INDEX_FOUR;
707 // b. Let end be start.
708 size_t end = start;
709 // c. Let k be start.
710 size_t k = start;
711 // d. Let done be false.
712 bool done = false;
713 // e. Repeat, while done is false
714 while (!done) {
715 // i. Let e be Call(%StringProto_indexOf%, extension, « "-", k »).
716 size_t e = extension.find("-", k);
717 size_t len;
718 // ii. If e = -1, let len be size - k; else let len be e - k.
719 if (e == std::string::npos) {
720 len = size - k;
721 } else {
722 len = e - k;
723 }
724 // iii. If len = 2, then
725 // 1. Let done be true.
726 if (len == INTL_INDEX_TWO) {
727 done = true;
728 // iv. Else if e = -1, then
729 // 1. Let end be size.
730 // 2. Let done be true.
731 } else if (e == std::string::npos) {
732 end = size;
733 done = true;
734 // v. Else,
735 // 1. Let end be e.
736 // 2. Let k be e + 1.
737 } else {
738 end = e;
739 k = e + INTL_INDEX_ONE;
740 }
741 }
742 // f. Return the String value equal to the substring of extension consisting of the code units at indices.
743 // start (inclusive) through end (exclusive).
744 std::string result = extension.substr(start, end - start);
745 return result;
746 }
747 // 6. Let searchValue be the concatenation of "-" and key.
748 searchValue = "-" + key;
749 // 7. Let pos be Call(%StringProto_indexOf%, extension, « searchValue »).
750 pos = extension.find(searchValue);
751 // 8. If pos ≠ -1 and pos + 3 = size, then
752 // a. Return the empty String.
753 if (pos != std::string::npos && pos + INTL_INDEX_THREE == size) {
754 return "";
755 }
756 // 9. Return undefined.
757 return "undefined";
758 }
759
ResolveLocale(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales,LocaleMatcherOption matcher,const std::set<std::string> & relevantExtensionKeys)760 ResolvedLocale JSLocale::ResolveLocale(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
761 const JSHandle<TaggedArray> &requestedLocales,
762 [[maybe_unused]] LocaleMatcherOption matcher,
763 const std::set<std::string> &relevantExtensionKeys)
764 {
765 std::map<std::string, std::set<std::string>> localeMap = {
766 {"hc", {"h11", "h12", "h23", "h24"}},
767 {"lb", {"strict", "normal", "loose"}},
768 {"kn", {"true", "false"}},
769 {"kf", {"upper", "lower", "false"}}
770 };
771
772 // 1. Let matcher be options.[[localeMatcher]].
773 // 2. If matcher is "lookup" "lookup", then
774 // a. Let r be LookupMatcher(availableLocales, requestedLocales).
775 // 3. Else,
776 // a. Let r be BestFitMatcher(availableLocales, requestedLocales).
777 JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
778 if (availableLocales->GetLength() == 0 && requestedLocales->GetLength() == 0) {
779 locale.Update(DefaultLocale(thread).GetTaggedValue());
780 } else {
781 locale.Update(LookupMatcher(thread, availableLocales, requestedLocales).GetTaggedValue());
782 }
783
784 // 4. Let foundLocale be r.[[locale]].
785 // 5. Let result be a new Record.
786 // 6. Set result.[[dataLocale]] to foundLocale.
787 // 7. Let supportedExtension be "-u".
788 std::string foundLocale = ConvertToStdString(locale);
789 icu::Locale foundLocaleData = BuildICULocale(foundLocale);
790 ResolvedLocale result;
791 result.localeData = foundLocaleData;
792 JSHandle<EcmaString> tag = ToLanguageTag(thread, foundLocaleData);
793 result.locale = ConvertToStdString(tag);
794 std::string supportedExtension = "-u";
795 icu::LocaleBuilder localeBuilder;
796 localeBuilder.setLocale(foundLocaleData).clearExtensions();
797 // 8. For each element key of relevantExtensionKeys in List order, do
798 for (auto &key : relevantExtensionKeys) {
799 auto doubleMatch = foundLocale.find(key);
800 if (doubleMatch == std::string::npos) {
801 continue;
802 }
803 UErrorCode status = U_ZERO_ERROR;
804 std::set<std::string> keyLocaleData;
805 std::unique_ptr<icu::StringEnumeration> wellFormKey(foundLocaleData.createKeywords(status));
806 if (U_FAILURE(status) != 0) {
807 return result;
808 }
809 if (!wellFormKey) {
810 return result;
811 }
812 std::string value;
813
814 // c. Let keyLocaleData be foundLocaleData.[[<key>]].
815 // e. Let value be keyLocaleData[0].
816 if ((key != "ca") && (key != "co") && (key != "nu")) {
817 keyLocaleData = localeMap[key];
818 value = *keyLocaleData.begin();
819 }
820
821 // g. Let supportedExtensionAddition be "".
822 // h. If r has an [[extension]] field, then
823 std::string supportedExtensionAddition;
824 size_t found = foundLocale.find("-u-");
825 if (found != std::string::npos) {
826 std::string extension = foundLocale.substr(found + INTL_INDEX_ONE);
827
828 // i. Let requestedValue be UnicodeExtensionValue(r.[[extension]], key).
829 std::string requestedValue = UnicodeExtensionValue(extension, key);
830 if (key == "kn" && requestedValue.empty()) {
831 requestedValue = "true";
832 }
833
834 // ii. If requestedValue is not undefined, then
835 if (requestedValue != "undefined") {
836 // 1. If requestedValue is not the empty String, then
837 if (!requestedValue.empty()) {
838 // a. If keyLocaleData contains requestedValue, then
839 // i. Let value be requestedValue.
840 // ii. Let supportedExtensionAddition be the concatenation of "-", key, "-", and value.
841 if (key == "ca" || key == "co") {
842 if (key == "co") {
843 bool isValidValue = IsWellCollation(foundLocaleData, requestedValue);
844 if (!isValidValue) {
845 continue;
846 }
847 value = requestedValue;
848 supportedExtensionAddition = "-" + key + "-" + value;
849 localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
850 } else {
851 bool isValidValue = IsWellCalendar(foundLocaleData, requestedValue);
852 if (!isValidValue) {
853 continue;
854 }
855 value = requestedValue;
856 supportedExtensionAddition = "-" + key + "-" + value;
857 localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
858 }
859 } else if (key == "nu") {
860 bool isValidValue = IsWellNumberingSystem(requestedValue);
861 if (!isValidValue) {
862 continue;
863 }
864 value = requestedValue;
865 supportedExtensionAddition = "-" + key + "-" + value;
866 localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
867 } else if (keyLocaleData.find(requestedValue) != keyLocaleData.end()) {
868 value = requestedValue;
869 supportedExtensionAddition = "-" + key + "-" + value;
870 localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
871 }
872 }
873 }
874 }
875 result.extensions.emplace(key, value);
876 supportedExtension += supportedExtensionAddition;
877 }
878 size_t found = foundLocale.find("-u-");
879 if (found != std::string::npos) {
880 foundLocale = foundLocale.substr(0, found);
881 }
882
883 // 9. If the number of elements in supportedExtension is greater than 2, then
884 if (supportedExtension.size() > 2) {
885 // a. Let privateIndex be Call(%StringProto_indexOf%, foundLocale, « "-x-" »).
886 size_t privateIndex = foundLocale.find("-x-");
887 // b. If privateIndex = -1, then
888 // i. Let foundLocale be the concatenation of foundLocale and supportedExtension.
889 if (privateIndex == std::string::npos) {
890 foundLocale = foundLocale + supportedExtension;
891 } else {
892 std::string preExtension = foundLocale.substr(0, privateIndex);
893 std::string postExtension = foundLocale.substr(privateIndex);
894 foundLocale = preExtension + supportedExtension + postExtension;
895 }
896
897 tag = ToLanguageTag(thread, foundLocaleData);
898 if (!IsStructurallyValidLanguageTag(tag)) {
899 result.extensions.erase(result.extensions.begin(), result.extensions.end());
900 result.locale = foundLocale;
901 }
902 tag = CanonicalizeUnicodeLocaleId(thread, tag);
903 foundLocale = ConvertToStdString(tag);
904 }
905
906 // 10. Set result.[[locale]] to foundLocale.
907 result.locale = foundLocale;
908 UErrorCode status = U_ZERO_ERROR;
909 foundLocaleData = localeBuilder.build(status);
910 result.localeData = foundLocaleData;
911
912 // 11. Return result.
913 return result;
914 }
915
BuildICULocale(const std::string & bcp47Locale)916 icu::Locale JSLocale::BuildICULocale(const std::string &bcp47Locale)
917 {
918 UErrorCode status = U_ZERO_ERROR;
919 icu::Locale icuLocale = icu::Locale::forLanguageTag(bcp47Locale, status);
920 ASSERT_PRINT(U_SUCCESS(status), "forLanguageTag failed");
921 ASSERT_PRINT(!icuLocale.isBogus(), "icuLocale is bogus");
922 return icuLocale;
923 }
924
ConstructLocaleList(JSThread * thread,const std::vector<std::string> & icuAvailableLocales)925 JSHandle<TaggedArray> JSLocale::ConstructLocaleList(JSThread *thread,
926 const std::vector<std::string> &icuAvailableLocales)
927 {
928 EcmaVM *ecmaVm = thread->GetEcmaVM();
929 ObjectFactory *factory = ecmaVm->GetFactory();
930 JSHandle<TaggedArray> locales = factory->NewTaggedArray(icuAvailableLocales.size());
931 int32_t index = 0;
932 for (const std::string &locale : icuAvailableLocales) {
933 JSHandle<EcmaString> localeStr = factory->NewFromStdString(locale);
934 locales->Set(thread, index++, localeStr);
935 }
936 return locales;
937 }
938
IcuToString(JSThread * thread,const icu::UnicodeString & string)939 JSHandle<EcmaString> JSLocale::IcuToString(JSThread *thread, const icu::UnicodeString &string)
940 {
941 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
942 return factory->NewFromUtf16(reinterpret_cast<const uint16_t *>(string.getBuffer()), string.length());
943 }
944
IcuToString(JSThread * thread,const icu::UnicodeString & string,int32_t begin,int32_t end)945 JSHandle<EcmaString> JSLocale::IcuToString(JSThread *thread, const icu::UnicodeString &string, int32_t begin,
946 int32_t end)
947 {
948 return IcuToString(thread, string.tempSubStringBetween(begin, end));
949 }
950
GetNumberingSystem(const icu::Locale & icuLocale)951 std::string JSLocale::GetNumberingSystem(const icu::Locale &icuLocale)
952 {
953 UErrorCode status = U_ZERO_ERROR;
954 std::unique_ptr<icu::NumberingSystem> numberingSystem(icu::NumberingSystem::createInstance(icuLocale, status));
955 if (U_SUCCESS(status) != 0) {
956 return numberingSystem->getName();
957 }
958 return LATN_STRING;
959 }
960
IsWellFormedCurrencyCode(const std::string & currency)961 bool JSLocale::IsWellFormedCurrencyCode(const std::string ¤cy)
962 {
963 if (currency.length() != INTL_INDEX_THREE) {
964 return false;
965 }
966 return (IsAToZ(currency[INTL_INDEX_ZERO]) && IsAToZ(currency[INTL_INDEX_ONE]) && IsAToZ(currency[INTL_INDEX_TWO]));
967 }
968
PutElement(JSThread * thread,int index,const JSHandle<JSArray> & array,const JSHandle<JSTaggedValue> & fieldTypeString,const JSHandle<JSTaggedValue> & value)969 JSHandle<JSObject> JSLocale::PutElement(JSThread *thread, int index, const JSHandle<JSArray> &array,
970 const JSHandle<JSTaggedValue> &fieldTypeString,
971 const JSHandle<JSTaggedValue> &value)
972 {
973 auto ecmaVm = thread->GetEcmaVM();
974 ObjectFactory *factory = ecmaVm->GetFactory();
975
976 // Let record be ! ObjectCreate(%ObjectPrototype%).
977 JSHandle<JSObject> record = factory->NewEmptyJSObject();
978
979 auto globalConst = thread->GlobalConstants();
980 // obj.type = field_type_string
981 JSObject::CreateDataPropertyOrThrow(thread, record, globalConst->GetHandledTypeString(), fieldTypeString);
982 // obj.value = value
983 JSObject::CreateDataPropertyOrThrow(thread, record, globalConst->GetHandledValueString(), value);
984
985 JSTaggedValue::SetProperty(thread, JSHandle<JSTaggedValue>::Cast(array), index,
986 JSHandle<JSTaggedValue>::Cast(record), true);
987 return record;
988 }
989
990 // 9.2.11 GetOption ( options, property, type, values, fallback )
GetOptionOfBool(JSThread * thread,const JSHandle<JSObject> & options,const JSHandle<JSTaggedValue> & property,bool fallback,bool * res)991 bool JSLocale::GetOptionOfBool(JSThread *thread, const JSHandle<JSObject> &options,
992 const JSHandle<JSTaggedValue> &property, bool fallback, bool *res)
993 {
994 // 1. Let value be ? Get(options, property).
995 OperationResult operationResult = JSObject::GetProperty(thread, options, property);
996 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
997 JSHandle<JSTaggedValue> value = operationResult.GetValue();
998 *res = fallback;
999 // 2. If value is not undefined, then
1000 if (!value->IsUndefined()) {
1001 // b. Let value be ToBoolean(value).
1002 *res = value->ToBoolean();
1003 return true;
1004 }
1005 // 3. not found
1006 return false;
1007 }
1008
GetNumberFieldType(JSThread * thread,JSTaggedValue x,int32_t fieldId)1009 JSHandle<JSTaggedValue> JSLocale::GetNumberFieldType(JSThread *thread, JSTaggedValue x, int32_t fieldId)
1010 {
1011 ASSERT(x.IsNumber());
1012 double number = 0;
1013 auto globalConst = thread->GlobalConstants();
1014 if (static_cast<UNumberFormatFields>(fieldId) == UNUM_INTEGER_FIELD) {
1015 number = x.GetNumber();
1016 if (std::isfinite(number)) {
1017 return globalConst->GetHandledIntegerString();
1018 }
1019 if (std::isnan(number)) {
1020 return globalConst->GetHandledNanString();
1021 }
1022 return globalConst->GetHandledInfinityString();
1023 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_FRACTION_FIELD) {
1024 return globalConst->GetHandledFractionString();
1025 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_DECIMAL_SEPARATOR_FIELD) {
1026 return globalConst->GetHandledDecimalString();
1027 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) {
1028 return globalConst->GetHandledGroupString();
1029 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_CURRENCY_FIELD) {
1030 return globalConst->GetHandledCurrencyString();
1031 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_PERCENT_FIELD) {
1032 return globalConst->GetHandledPercentSignString();
1033 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_SIGN_FIELD) {
1034 number = x.GetNumber();
1035 return std::signbit(number) ? globalConst->GetHandledMinusSignString()
1036 : globalConst->GetHandledPlusSignString();
1037 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_SYMBOL_FIELD) {
1038 return globalConst->GetHandledExponentSeparatorString();
1039 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_SIGN_FIELD) {
1040 return globalConst->GetHandledExponentMinusSignString();
1041 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_FIELD) {
1042 return globalConst->GetHandledExponentIntegerString();
1043 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_COMPACT_FIELD) {
1044 return globalConst->GetHandledCompactString();
1045 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_MEASURE_UNIT_FIELD) {
1046 return globalConst->GetHandledUnitString();
1047 } else {
1048 UNREACHABLE();
1049 }
1050 }
1051
1052 // 10.1.1 ApplyOptionsToTag( tag, options )
ApplyOptionsToTag(JSThread * thread,const JSHandle<EcmaString> & tag,const JSHandle<JSObject> & options,TagElements & tagElements)1053 bool JSLocale::ApplyOptionsToTag(JSThread *thread, const JSHandle<EcmaString> &tag, const JSHandle<JSObject> &options,
1054 TagElements &tagElements)
1055 {
1056 EcmaVM *ecmaVm = thread->GetEcmaVM();
1057 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
1058 ObjectFactory *factory = ecmaVm->GetFactory();
1059 if (*tag == *(factory->GetEmptyString())) {
1060 return false;
1061 }
1062 // 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
1063 if (!IsStructurallyValidLanguageTag(tag)) {
1064 return false;
1065 }
1066
1067 tagElements.language =
1068 GetOption(thread, options, globalConst->GetHandledLanguageString(), OptionType::STRING,
1069 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
1070 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1071
1072 // 4. If language is not undefined, then
1073 // a. If language does not match the unicode_language_subtag production, throw a RangeError exception.
1074 if (!tagElements.language->IsUndefined()) {
1075 std::string languageStr = ConvertToStdString(JSHandle<EcmaString>::Cast(tagElements.language));
1076 if (languageStr[INTL_INDEX_ZERO] == '\0' ||
1077 IsAlpha(languageStr, INTL_INDEX_FOUR, INTL_INDEX_FOUR)) {
1078 return false;
1079 }
1080 }
1081
1082 // 5. Let script be ? GetOption(options, "script", "string", undefined, undefined).
1083 tagElements.script =
1084 GetOption(thread, options, globalConst->GetHandledScriptString(), OptionType::STRING,
1085 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
1086 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1087
1088 // 6. If script is not undefined, then
1089 // a. If script does not match the unicode_script_subtag production, throw a RangeError exception.
1090 if (!tagElements.script->IsUndefined()) {
1091 std::string scriptStr = JSLocale::ConvertToStdString((JSHandle<EcmaString>::Cast(tagElements.script)));
1092 if (scriptStr[INTL_INDEX_ZERO] == '\0') {
1093 return false;
1094 }
1095 }
1096
1097 // 7. Let region be ? GetOption(options, "region", "string", undefined, undefined).
1098 // 8. If region is not undefined, then
1099 // a. If region does not match the unicode_region_subtag production, throw a RangeError exception.
1100 tagElements.region =
1101 GetOption(thread, options, globalConst->GetHandledRegionString(), OptionType::STRING,
1102 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
1103 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1104
1105 if (!tagElements.region->IsUndefined()) {
1106 std::string regionStr = ConvertToStdString(JSHandle<EcmaString>::Cast(tagElements.region));
1107 if (regionStr[INTL_INDEX_ZERO] == '\0') {
1108 return false;
1109 }
1110 }
1111 return true;
1112 }
1113
BuildOptionsTags(const JSHandle<EcmaString> & tag,icu::LocaleBuilder * builder,JSHandle<JSTaggedValue> language,JSHandle<JSTaggedValue> script,JSHandle<JSTaggedValue> region)1114 bool BuildOptionsTags(const JSHandle<EcmaString> &tag, icu::LocaleBuilder *builder, JSHandle<JSTaggedValue> language,
1115 JSHandle<JSTaggedValue> script, JSHandle<JSTaggedValue> region)
1116 {
1117 std::string tagStr = JSLocale::ConvertToStdString(tag);
1118 int32_t len = static_cast<int32_t>(tagStr.length());
1119 ASSERT(len > 0);
1120 builder->setLanguageTag({ tagStr.c_str(), len });
1121 UErrorCode status = U_ZERO_ERROR;
1122 icu::Locale locale = builder->build(status);
1123 locale.canonicalize(status);
1124 if (U_FAILURE(status) != 0) {
1125 return false;
1126 }
1127 builder->setLocale(locale);
1128
1129 if (!language->IsUndefined()) {
1130 std::string languageStr = JSLocale::ConvertToStdString(JSHandle<EcmaString>::Cast(language));
1131 builder->setLanguage(languageStr);
1132 builder->build(status);
1133 if ((U_FAILURE(status) != 0)) {
1134 return false;
1135 }
1136 }
1137
1138 if (!script->IsUndefined()) {
1139 std::string scriptStr = JSLocale::ConvertToStdString((JSHandle<EcmaString>::Cast(script)));
1140 builder->setScript(scriptStr);
1141 builder->build(status);
1142 if ((U_FAILURE(status) != 0)) {
1143 return false;
1144 }
1145 }
1146
1147 if (!region->IsUndefined()) {
1148 std::string regionStr = JSLocale::ConvertToStdString(JSHandle<EcmaString>::Cast(region));
1149 builder->setRegion(regionStr);
1150 builder->build(status);
1151 if ((U_FAILURE(status) != 0)) {
1152 return false;
1153 }
1154 }
1155 return true;
1156 }
1157
InsertOptions(JSThread * thread,const JSHandle<JSObject> & options,icu::LocaleBuilder * builder)1158 bool InsertOptions(JSThread *thread, const JSHandle<JSObject> &options, icu::LocaleBuilder *builder)
1159 {
1160 const std::vector<std::string> hourCycleValues = {"h11", "h12", "h23", "h24"};
1161 const std::vector<std::string> caseFirstValues = {"upper", "lower", "false"};
1162 const std::vector<std::string> emptyValues = {};
1163 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
1164 std::string strResult;
1165 bool findca =
1166 JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCalendarString(), emptyValues, &strResult);
1167 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1168 if (findca) {
1169 if (!uloc_toLegacyType(uloc_toLegacyKey("ca"), strResult.c_str())) {
1170 return false;
1171 }
1172 builder->setUnicodeLocaleKeyword("ca", strResult.c_str());
1173 }
1174
1175 bool findco =
1176 JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCollationString(), emptyValues, &strResult);
1177 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1178 if (findco) {
1179 if (!uloc_toLegacyType(uloc_toLegacyKey("co"), strResult.c_str())) {
1180 return false;
1181 }
1182 builder->setUnicodeLocaleKeyword("co", strResult.c_str());
1183 }
1184
1185 bool findhc = JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledHourCycleString(),
1186 hourCycleValues, &strResult);
1187 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1188 if (findhc) {
1189 if (!uloc_toLegacyType(uloc_toLegacyKey("hc"), strResult.c_str())) {
1190 return false;
1191 }
1192 builder->setUnicodeLocaleKeyword("hc", strResult.c_str());
1193 }
1194
1195 bool findkf = JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCaseFirstString(),
1196 caseFirstValues, &strResult);
1197 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1198 if (findkf) {
1199 if (!uloc_toLegacyType(uloc_toLegacyKey("kf"), strResult.c_str())) {
1200 return false;
1201 }
1202 builder->setUnicodeLocaleKeyword("kf", strResult.c_str());
1203 }
1204
1205 bool boolResult = false;
1206 bool findkn =
1207 JSLocale::GetOptionOfBool(thread, options, globalConst->GetHandledNumericString(), false, &boolResult);
1208 if (findkn) {
1209 strResult = boolResult ? "true" : "false";
1210 if (!uloc_toLegacyType(uloc_toLegacyKey("kn"), strResult.c_str())) {
1211 return false;
1212 }
1213 builder->setUnicodeLocaleKeyword("kn", strResult.c_str());
1214 }
1215
1216 bool findnu =
1217 JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledNumberingSystemString(), emptyValues,
1218 &strResult);
1219 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1220 if (findnu) {
1221 if (!uloc_toLegacyType(uloc_toLegacyKey("nu"), strResult.c_str())) {
1222 return false;
1223 }
1224 builder->setUnicodeLocaleKeyword("nu", strResult.c_str());
1225 }
1226 return true;
1227 }
1228
InitializeLocale(JSThread * thread,const JSHandle<JSLocale> & locale,const JSHandle<EcmaString> & localeString,const JSHandle<JSObject> & options)1229 JSHandle<JSLocale> JSLocale::InitializeLocale(JSThread *thread, const JSHandle<JSLocale> &locale,
1230 const JSHandle<EcmaString> &localeString,
1231 const JSHandle<JSObject> &options)
1232 {
1233 icu::LocaleBuilder builder;
1234 TagElements tagElements;
1235 if (!ApplyOptionsToTag(thread, localeString, options, tagElements)) {
1236 THROW_RANGE_ERROR_AND_RETURN(thread, "apply option to tag failed", locale);
1237 }
1238
1239 bool res = BuildOptionsTags(localeString, &builder, tagElements.language, tagElements.script, tagElements.region);
1240 if (!res) {
1241 THROW_RANGE_ERROR_AND_RETURN(thread, "apply option to tag failed", locale);
1242 }
1243 bool insertResult = InsertOptions(thread, options, &builder);
1244 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, locale);
1245 UErrorCode status = U_ZERO_ERROR;
1246 icu::Locale icuLocale = builder.build(status);
1247 icuLocale.canonicalize(status);
1248
1249 if (!insertResult || (U_FAILURE(status) != 0)) {
1250 THROW_RANGE_ERROR_AND_RETURN(thread, "insert or build failed", locale);
1251 }
1252 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
1253 factory->NewJSIntlIcuData(locale, icuLocale, JSLocale::FreeIcuLocale);
1254 return locale;
1255 }
1256
DealwithLanguageTag(const std::vector<std::string> & containers,size_t & address)1257 bool JSLocale::DealwithLanguageTag(const std::vector<std::string> &containers, size_t &address)
1258 {
1259 // The abstract operation returns true if locale can be generated from the ABNF grammar in section 2.1 of the RFC,
1260 // starting with Language-Tag, and does not contain duplicate variant or singleton subtags
1261 // If language tag is empty, return false.
1262 if (containers.empty()) {
1263 return false;
1264 }
1265
1266 // a. if the first tag is not language, return false.
1267 if (!IsLanguageSubtag(containers[0])) {
1268 return false;
1269 }
1270
1271 // if the tag include language only, like "zh" or "de", return true;
1272 if (containers.size() == 1) {
1273 return true;
1274 }
1275
1276 // Else, then
1277 // if is unique singleton subtag, script and region tag.
1278 if (IsExtensionSingleton(containers[1])) {
1279 return true;
1280 }
1281
1282 if (IsScriptSubtag(containers[address])) {
1283 address++;
1284 if (containers.size() == address) {
1285 return true;
1286 }
1287 }
1288
1289 if (IsRegionSubtag(containers[address])) {
1290 address++;
1291 }
1292
1293 for (size_t i = address; i < containers.size(); i++) {
1294 if (IsExtensionSingleton(containers[i])) {
1295 return true;
1296 }
1297 if (!IsVariantSubtag(containers[i])) {
1298 return false;
1299 }
1300 }
1301 return true;
1302 }
1303
ConvertValue(const UErrorCode & status,std::string & value,const std::string & key)1304 int ConvertValue(const UErrorCode &status, std::string &value, const std::string &key)
1305 {
1306 if (status == U_ILLEGAL_ARGUMENT_ERROR || value.empty()) {
1307 return 1;
1308 }
1309
1310 if (value == "yes") {
1311 value = "true";
1312 }
1313
1314 if (key == "kf" && value == "true") {
1315 return 2; // 2: in this case normalizedKeyword is empty string
1316 }
1317 return 0;
1318 }
1319
NormalizeKeywordValue(JSThread * thread,const JSHandle<JSLocale> & locale,const std::string & key)1320 JSHandle<EcmaString> JSLocale::NormalizeKeywordValue(JSThread *thread, const JSHandle<JSLocale> &locale,
1321 const std::string &key)
1322 {
1323 icu::Locale *icuLocale = locale->GetIcuLocale();
1324 UErrorCode status = U_ZERO_ERROR;
1325 auto value = icuLocale->getUnicodeKeywordValue<std::string>(key, status);
1326
1327 EcmaVM *ecmaVm = thread->GetEcmaVM();
1328 ObjectFactory *factory = ecmaVm->GetFactory();
1329
1330 int result = ConvertValue(status, value, key);
1331 if (result == 1) {
1332 return JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledUndefinedString());
1333 }
1334 if (result == 2) { // 2: in this case normalizedKeyword is empty string
1335 return factory->GetEmptyString();
1336 }
1337 return factory->NewFromStdString(value);
1338 }
1339
ToString(JSThread * thread,const JSHandle<JSLocale> & locale)1340 JSHandle<EcmaString> JSLocale::ToString(JSThread *thread, const JSHandle<JSLocale> &locale)
1341 {
1342 icu::Locale *icuLocale = locale->GetIcuLocale();
1343 if (icuLocale == nullptr) {
1344 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
1345 return factory->GetEmptyString();
1346 }
1347 JSHandle<EcmaString> result = ToLanguageTag(thread, *icuLocale);
1348 return result;
1349 }
1350
GetAvailableLocales(JSThread * thread,const char * localeKey,const char * localePath)1351 JSHandle<TaggedArray> JSLocale::GetAvailableLocales(JSThread *thread, const char *localeKey, const char *localePath)
1352 {
1353 UErrorCode status = U_ZERO_ERROR;
1354 auto globalConst = thread->GlobalConstants();
1355 JSHandle<EcmaString> specialValue = JSHandle<EcmaString>::Cast(globalConst->GetHandledEnUsPosixString());
1356 std::string specialString = ConvertToStdString(specialValue);
1357 UEnumeration *uenum = uloc_openAvailableByType(ULOC_AVAILABLE_WITH_LEGACY_ALIASES, &status);
1358 std::vector<std::string> allLocales;
1359 const char *loc = nullptr;
1360 for (loc = uenum_next(uenum, nullptr, &status); loc != nullptr; loc = uenum_next(uenum, nullptr, &status)) {
1361 ASSERT(U_SUCCESS(status));
1362 std::string locStr(loc);
1363 std::replace(locStr.begin(), locStr.end(), '_', '-');
1364 if (locStr == specialString) {
1365 locStr = "en-US-u-va-posix";
1366 }
1367
1368 if (localePath != nullptr || localeKey != nullptr) {
1369 icu::Locale locale(locStr.c_str());
1370 bool res = false;
1371 if (!CheckLocales(locale, localeKey, localePath, res)) {
1372 continue;
1373 }
1374 }
1375 bool isScript = false;
1376 allLocales.push_back(locStr);
1377 icu::Locale formalLocale = icu::Locale::createCanonical(locStr.c_str());
1378 std::string scriptStr = formalLocale.getScript();
1379 isScript = scriptStr.empty() ? false : true;
1380 if (isScript) {
1381 std::string languageStr = formalLocale.getLanguage();
1382 std::string countryStr = formalLocale.getCountry();
1383 std::string shortLocale = icu::Locale(languageStr.c_str(), countryStr.c_str()).getName();
1384 std::replace(shortLocale.begin(), shortLocale.end(), '_', '-');
1385 allLocales.push_back(shortLocale);
1386 }
1387 }
1388 uenum_close(uenum);
1389 return ConstructLocaleList(thread, allLocales);
1390 }
1391 } // namespace panda::ecmascript
1392