• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "base/string_helper.h"
17 #include "ecma_macros.h"
18 #include "ecma_vm.h"
19 #include "global_env.h"
20 #include "js_locale.h"
21 #include "object_factory.h"
22 
23 #include "unicode/localebuilder.h"
24 #include "unicode/localematcher.h"
25 
26 namespace panda::ecmascript {
27 // 6.2.2 IsStructurallyValidLanguageTag( locale )
IsStructurallyValidLanguageTag(const JSHandle<EcmaString> & tag)28 bool JSLocale::IsStructurallyValidLanguageTag(const JSHandle<EcmaString> &tag)
29 {
30     std::string tagCollection = ConvertToStdString(tag);
31     std::vector<std::string> containers;
32     std::string substring;
33     std::set<std::string> uniqueSubtags;
34     size_t address = 1;
35     for (auto it = tagCollection.begin(); it != tagCollection.end(); it++) {
36         if (*it != '-' && it != tagCollection.end() - 1) {
37             substring += *it;
38         } else {
39             if (it == tagCollection.end() - 1) {
40                 substring += *it;
41             }
42             containers.push_back(substring);
43             if (IsVariantSubtag(substring)) {
44                 std::transform(substring.begin(), substring.end(), substring.begin(), AsciiAlphaToLower);
45                 if (!uniqueSubtags.insert(substring).second) {
46                     return false;
47                 }
48             }
49             substring.clear();
50         }
51     }
52     bool result = DealwithLanguageTag(containers, address);
53     return result;
54 }
55 
ConvertToStdString(const JSHandle<EcmaString> & ecmaStr)56 std::string JSLocale::ConvertToStdString(const JSHandle<EcmaString> &ecmaStr)
57 {
58     return std::string(ConvertToString(*ecmaStr, StringConvertedUsage::LOGICOPERATION));
59 }
60 
61 // 6.2.3 CanonicalizeUnicodeLocaleId( locale )
CanonicalizeUnicodeLocaleId(JSThread * thread,const JSHandle<EcmaString> & locale)62 JSHandle<EcmaString> JSLocale::CanonicalizeUnicodeLocaleId(JSThread *thread, const JSHandle<EcmaString> &locale)
63 {
64     [[maybe_unused]] ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
65     if (!IsStructurallyValidLanguageTag(locale)) {
66         THROW_RANGE_ERROR_AND_RETURN(thread, "invalid locale", factory->GetEmptyString());
67     }
68 
69     if (locale->GetLength() == 0 || locale->IsUtf16()) {
70         THROW_RANGE_ERROR_AND_RETURN(thread, "invalid locale", factory->GetEmptyString());
71     }
72 
73     std::string localeCStr = ConvertToStdString(locale);
74     std::transform(localeCStr.begin(), localeCStr.end(), localeCStr.begin(), AsciiAlphaToLower);
75     UErrorCode status = U_ZERO_ERROR;
76     icu::Locale formalLocale = icu::Locale::forLanguageTag(localeCStr.c_str(), status);
77     if ((U_FAILURE(status) != 0) || (formalLocale.isBogus() != 0)) {
78         THROW_RANGE_ERROR_AND_RETURN(thread, "invalid locale", factory->GetEmptyString());
79     }
80 
81     // Resets the LocaleBuilder to match the locale.
82     // Returns an instance of Locale created from the fields set on this builder.
83     formalLocale = icu::LocaleBuilder().setLocale(formalLocale).build(status);
84     // Canonicalize the locale ID of this object according to CLDR.
85     formalLocale.canonicalize(status);
86     if ((U_FAILURE(status) != 0) || (formalLocale.isBogus() != 0)) {
87         THROW_RANGE_ERROR_AND_RETURN(thread, "invalid locale", factory->GetEmptyString());
88     }
89     JSHandle<EcmaString> languageTag = ToLanguageTag(thread, formalLocale);
90     RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread);
91     return languageTag;
92 }
93 
94 // 6.2.4 DefaultLocale ()
DefaultLocale(JSThread * thread)95 JSHandle<EcmaString> JSLocale::DefaultLocale(JSThread *thread)
96 {
97     icu::Locale defaultLocale;
98     auto globalConst = thread->GlobalConstants();
99     if (strcmp(defaultLocale.getName(), "en_US_POSIX") == 0 || strcmp(defaultLocale.getName(), "c") == 0) {
100         return JSHandle<EcmaString>::Cast(globalConst->GetHandledEnUsString());
101     }
102     if (defaultLocale.isBogus() != 0) {
103         return JSHandle<EcmaString>::Cast(globalConst->GetHandledUndString());
104     }
105     return ToLanguageTag(thread, defaultLocale);
106 }
107 
108 // 6.4.1 IsValidTimeZoneName ( timeZone )
IsValidTimeZoneName(const icu::TimeZone & tz)109 bool JSLocale::IsValidTimeZoneName(const icu::TimeZone &tz)
110 {
111     UErrorCode status = U_ZERO_ERROR;
112     icu::UnicodeString id;
113     tz.getID(id);
114     icu::UnicodeString canonical;
115     icu::TimeZone::getCanonicalID(id, canonical, status);
116     UBool canonicalFlag = (canonical != icu::UnicodeString("Etc/Unknown", -1, US_INV));
117     return (U_SUCCESS(status) != 0) && (canonicalFlag != 0);
118 }
119 
HandleLocaleExtension(size_t & start,size_t & extensionEnd,const std::string result,size_t len)120 void JSLocale::HandleLocaleExtension(size_t &start, size_t &extensionEnd,
121                                      const std::string result, size_t len)
122 {
123     bool flag = false;
124     while (start < len - INTL_INDEX_TWO) {
125         if (result[start] != '-') {
126             start++;
127             continue;
128         }
129         if (result[start + INTL_INDEX_TWO] == '-') {
130             extensionEnd = start;
131             flag = true;
132             break;
133         }
134         if (!flag) {
135             start++;
136         }
137         start += INTL_INDEX_TWO;
138     }
139 }
140 
HandleLocale(const JSHandle<EcmaString> & localeString)141 JSLocale::ParsedLocale JSLocale::HandleLocale(const JSHandle<EcmaString> &localeString)
142 {
143     std::string result = ConvertToStdString(localeString);
144     size_t len = result.size();
145     ParsedLocale parsedResult;
146 
147     // a. The single-character subtag ’x’ as the primary subtag indicates
148     //    that the language tag consists solely of subtags whose meaning is
149     //    defined by private agreement.
150     // b. Extensions cannot be used in tags that are entirely private use.
151     if (IsPrivateSubTag(result, len)) {
152         parsedResult.base = result;
153         return parsedResult;
154     }
155     // If cannot find "-u-", return the whole string as base.
156     size_t foundExtension = result.find("-u-");
157     if (foundExtension == std::string::npos) {
158         parsedResult.base = result;
159         return parsedResult;
160     }
161     // Let privateIndex be Call(%StringProto_indexOf%, foundLocale, « "-x-" »).
162     size_t praviteIndex = result.find("-x-");
163     if (praviteIndex != std::string::npos && praviteIndex < foundExtension) {
164         parsedResult.base = result;
165         return parsedResult;
166     }
167     const std::string basis = result.substr(INTL_INDEX_ZERO, foundExtension);
168     size_t extensionEnd = len;
169     ASSERT(len > INTL_INDEX_TWO);
170     size_t start = foundExtension + INTL_INDEX_ONE;
171     HandleLocaleExtension(start, extensionEnd, result, len);
172     const std::string end = result.substr(extensionEnd);
173     parsedResult.base = basis + end;
174     parsedResult.extension = result.substr(foundExtension, extensionEnd - foundExtension);
175     return parsedResult;
176 }
177 
178 // 9.2.1 CanonicalizeLocaleList ( locales )
CanonicalizeLocaleList(JSThread * thread,const JSHandle<JSTaggedValue> & locales)179 JSHandle<TaggedArray> JSLocale::CanonicalizeLocaleList(JSThread *thread, const JSHandle<JSTaggedValue> &locales)
180 {
181     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
182     // 1. If locales is undefined, then
183     //    a. Return a new empty List.
184     if (locales->IsUndefined()) {
185         return factory->EmptyArray();
186     }
187     // 2. Let seen be a new empty List.
188     JSHandle<TaggedArray> localeSeen = factory->NewTaggedArray(1);
189     // 3. If Type(locales) is String or Type(locales) is Object and locales has an [[InitializedLocale]] internal slot,
190     //    then
191     //    a. Let O be CreateArrayFromList(« locales »).
192     // 4. Else,
193     //    a.Let O be ? ToObject(locales).
194     if (locales->IsString()) {
195         JSHandle<EcmaString> tag = JSHandle<EcmaString>::Cast(locales);
196         JSHandle<TaggedArray> temp = factory->NewTaggedArray(1);
197         temp->Set(thread, 0, tag.GetTaggedValue());
198         JSHandle<JSArray> obj = JSArray::CreateArrayFromList(thread, temp);
199         JSHandle<TaggedArray> finalSeen = CanonicalizeHelper<JSArray>(thread, locales, obj, localeSeen);
200         return finalSeen;
201     } else if (locales->IsJSLocale()) {
202         JSHandle<EcmaString> tag = JSLocale::ToString(thread, JSHandle<JSLocale>::Cast(locales));
203         JSHandle<TaggedArray> temp = factory->NewTaggedArray(1);
204         RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
205         temp->Set(thread, 0, tag.GetTaggedValue());
206         JSHandle<JSArray> obj = JSArray::CreateArrayFromList(thread, temp);
207         JSHandle<TaggedArray> finalSeen = CanonicalizeHelper<JSArray>(thread, locales, obj, localeSeen);
208         return finalSeen;
209     } else {
210         JSHandle<JSObject> obj = JSTaggedValue::ToObject(thread, locales);
211         RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
212         JSHandle<TaggedArray> finalSeen = CanonicalizeHelper<JSObject>(thread, locales, obj, localeSeen);
213         return finalSeen;
214     }
215     return localeSeen;
216 }
217 
218 template<typename T>
CanonicalizeHelper(JSThread * thread,const JSHandle<JSTaggedValue> & locales,JSHandle<T> & obj,JSHandle<TaggedArray> & seen)219 JSHandle<TaggedArray> JSLocale::CanonicalizeHelper(JSThread *thread, const JSHandle<JSTaggedValue> &locales,
220                                                    JSHandle<T> &obj, JSHandle<TaggedArray> &seen)
221 {
222     OperationResult operationResult = JSTaggedValue::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(obj),
223                                                                  thread->GlobalConstants()->GetHandledLengthString());
224     RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
225     JSTaggedNumber len = JSTaggedValue::ToLength(thread, operationResult.GetValue());
226     RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
227     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
228     // 2. Let seen be a new empty List.
229     uint32_t requestedLocalesLen = len.ToUint32();
230     seen = factory->NewTaggedArray(requestedLocalesLen);
231     // 6. Let k be 0.
232     // 7. Repeat, while k < len
233     JSMutableHandle<JSTaggedValue> pk(thread, JSTaggedValue::Undefined());
234     JSMutableHandle<JSTaggedValue> tag(thread, JSTaggedValue::Undefined());
235     uint32_t index = 0;
236     JSHandle<JSTaggedValue> objTagged = JSHandle<JSTaggedValue>::Cast(obj);
237     for (uint32_t k = 0; k < requestedLocalesLen; k++) {
238         // a. Let Pk be ToString(k).
239         JSHandle<JSTaggedValue> kHandle(thread, JSTaggedValue(k));
240         JSHandle<EcmaString> str = JSTaggedValue::ToString(thread, kHandle);
241         RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
242         pk.Update(str.GetTaggedValue());
243         // b. Let kPresent be ? HasProperty(O, Pk).
244         bool kPresent = JSTaggedValue::HasProperty(thread, objTagged, pk);
245         RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
246 
247         // c. If kPresent is true, then
248         if (kPresent) {
249             // i. Let kValue be ? Get(O, Pk).
250             OperationResult result = JSTaggedValue::GetProperty(thread, objTagged, pk);
251             RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
252             JSHandle<JSTaggedValue> kValue = result.GetValue();
253             // ii. If Type(kValue) is not String or Object, throw a TypeError exception.
254             if (!kValue->IsString() && !kValue->IsJSObject()) {
255                 THROW_TYPE_ERROR_AND_RETURN(thread, "kValue is not String or Object.", factory->EmptyArray());
256             }
257             // iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
258             //        1. Let tag be kValue.[[Locale]].
259             // iv.  Else,
260             //        1. Let tag be ? ToString(kValue).
261             if (kValue->IsJSLocale()) {
262                 JSHandle<EcmaString> kValueStr = JSLocale::ToString(thread, JSHandle<JSLocale>::Cast(kValue));
263                 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
264                 tag.Update(kValueStr.GetTaggedValue());
265             } else {
266                 JSHandle<EcmaString> kValueString = JSTaggedValue::ToString(thread, kValue);
267                 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
268                 JSHandle<EcmaString> str = CanonicalizeUnicodeLocaleId(thread, kValueString);
269                 RETURN_HANDLE_IF_ABRUPT_COMPLETION(TaggedArray, thread);
270                 tag.Update(str.GetTaggedValue());
271             }
272             // vii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
273             bool isExist = false;
274             uint32_t len = seen->GetLength();
275             for (uint32_t i = 0; i < len; i++) {
276                 if (JSTaggedValue::SameValue(seen->Get(thread, i), tag.GetTaggedValue())) {
277                     isExist = true;
278                 }
279             }
280             if (!isExist) {
281                 seen->Set(thread, index++, JSHandle<JSTaggedValue>::Cast(tag));
282             }
283         }
284         // d. Increase k by 1.
285     }
286     // set capacity
287     seen = TaggedArray::SetCapacity(thread, seen, index);
288     // 8. Return seen.
289     return seen;
290 }
291 
292 // 9.2.2 BestAvailableLocale ( availableLocales, locale )
BestAvailableLocale(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const std::string & locale)293 std::string JSLocale::BestAvailableLocale(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
294                                           const std::string &locale)
295 {
296     // 1. Let candidate be locale.
297     std::string localeCandidate = locale;
298     std::string undefined = std::string();
299     // 2. Repeat,
300     uint32_t length = availableLocales->GetLength();
301     JSMutableHandle<EcmaString> item(thread, JSTaggedValue::Undefined());
302     while (true) {
303         // a. If availableLocales contains an element equal to candidate, return candidate.
304         for (uint32_t i = 0; i < length; ++i) {
305             item.Update(availableLocales->Get(thread, i));
306             std::string itemStr = ConvertToStdString(item);
307             if (itemStr == localeCandidate) {
308                 return localeCandidate;
309             }
310         }
311         // b. Let pos be the character index of the last occurrence of "-" (U+002D) within candidate.
312         //    If that character does not occur, return undefined.
313         size_t pos = localeCandidate.rfind('-');
314         if (pos == std::string::npos) {
315             return undefined;
316         }
317         // c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, decrease pos by 2.
318         if (pos >= INTL_INDEX_TWO && localeCandidate[pos - INTL_INDEX_TWO] == '-') {
319             pos -= INTL_INDEX_TWO;
320         }
321         // d. Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive.
322         localeCandidate = localeCandidate.substr(0, pos);
323     }
324 }
325 
326 // 9.2.3 LookupMatcher ( availableLocales, requestedLocales )
LookupMatcher(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales)327 JSHandle<EcmaString> JSLocale::LookupMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
328                                              const JSHandle<TaggedArray> &requestedLocales)
329 {
330     MatcherResult result = {std::string(), std::string()};
331     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
332     // 1. Let result be a new Record.
333     // 2. For each element locale of requestedLocales in List order, do
334     uint32_t length = requestedLocales->GetLength();
335     JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
336     for (uint32_t i = 0; i < length; ++i) {
337         locale.Update(requestedLocales->Get(thread, i));
338         // 2. a. Let noExtensionsLocale be the String value that is locale
339         //       with all Unicode locale extension sequences removed.
340         ParsedLocale parsedResult = HandleLocale(locale);
341         // 2. b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
342         std::string availableLocale = BestAvailableLocale(thread, availableLocales, parsedResult.base);
343         // 2. c. If availableLocale is not undefined, append locale to the end of subset.
344         if (!availableLocale.empty()) {
345             result = {std::string(), std::string()};
346             // 2. c. i. Set result.[[locale]] to availableLocale.
347             result.locale = availableLocale;
348             // 2. c. ii. If locale and noExtensionsLocale are not the same String value, then
349             // 2. c. ii. 1. Let extension be the String value consisting of  the first substring of locale that is a
350             //              Unicode locale extension sequence.
351             if (!parsedResult.extension.empty()) {
352                 result.extension = parsedResult.extension;
353             }
354             // 2. c. ii. 2. Set result.[[extension]] to extension.
355             std::string res = result.locale + result.extension;
356             // 2. c. iii. Return result.
357             return factory->NewFromStdString(res);
358         }
359     }
360 
361     // 3. Let defLocale be DefaultLocale();
362     // 4. Set result.[[locale]] to defLocale.
363     // 5. Return result.
364     std::string defLocale = ConvertToStdString(DefaultLocale(thread));
365     result.locale = defLocale;
366     return factory->NewFromStdString(result.locale);
367 }
368 
BuildLocaleMatcher(JSThread * thread,uint32_t * availableLength,UErrorCode * status,const JSHandle<TaggedArray> & availableLocales)369 icu::LocaleMatcher BuildLocaleMatcher(JSThread *thread, uint32_t *availableLength, UErrorCode *status,
370                                       const JSHandle<TaggedArray> &availableLocales)
371 {
372     std::string locale = JSLocale::ConvertToStdString(JSLocale::DefaultLocale(thread));
373     icu::Locale defaultLocale = icu::Locale::forLanguageTag(locale, *status);
374     ASSERT_PRINT(U_SUCCESS(*status), "icu::Locale::forLanguageTag failed");
375     icu::LocaleMatcher::Builder builder;
376     builder.setDefaultLocale(&defaultLocale);
377     uint32_t length = availableLocales->GetLength();
378 
379     JSMutableHandle<EcmaString> item(thread, JSTaggedValue::Undefined());
380     for (*availableLength = 0; *availableLength < length; ++(*availableLength)) {
381         item.Update(availableLocales->Get(thread, *availableLength));
382         std::string itemStr = JSLocale::ConvertToStdString(item);
383         icu::Locale localeForLanguageTag = icu::Locale::forLanguageTag(itemStr, *status);
384         if (U_SUCCESS(*status) != 0) {
385             builder.addSupportedLocale(localeForLanguageTag);
386         } else {
387             break;
388         }
389     }
390     *status = U_ZERO_ERROR;
391     return builder.build(*status);
392 }
393 
394 // 9.2.4 BestFitMatcher ( availableLocales, requestedLocales )
BestFitMatcher(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales)395 JSHandle<EcmaString> JSLocale::BestFitMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
396                                               const JSHandle<TaggedArray> &requestedLocales)
397 {
398     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
399     UErrorCode status = U_ZERO_ERROR;
400     uint32_t availableLength = availableLocales->GetLength();
401     icu::LocaleMatcher matcher = BuildLocaleMatcher(thread, &availableLength, &status, availableLocales);
402     ASSERT(U_SUCCESS(status));
403 
404     uint32_t requestedLocalesLength = requestedLocales->GetLength();
405     JSIntlIterator iter(requestedLocales, requestedLocalesLength);
406     auto bestFit = matcher.getBestMatch(iter, status)->toLanguageTag<std::string>(status);
407 
408     if (U_FAILURE(status) != 0) {
409         return DefaultLocale(thread);
410     }
411 
412     for (uint32_t i = 0; i < requestedLocalesLength; ++i) {
413         if (iter[i] == bestFit) {
414             return JSHandle<EcmaString>(thread, requestedLocales->Get(thread, i));
415         }
416     }
417     return factory->NewFromStdString(bestFit);
418 }
419 
420 // 9.2.8 LookupSupportedLocales ( availableLocales, requestedLocales )
LookupSupportedLocales(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales)421 JSHandle<TaggedArray> JSLocale::LookupSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
422                                                        const JSHandle<TaggedArray> &requestedLocales)
423 {
424     uint32_t index = 0;
425     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
426     uint32_t length = requestedLocales->GetLength();
427     // 1. Let subset be a new empty List.
428     JSHandle<TaggedArray> subset = factory->NewTaggedArray(length);
429     JSMutableHandle<EcmaString> item(thread, JSTaggedValue::Undefined());
430     // 2. For each element locale of requestedLocales in List order, do
431     //    a. Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences
432     //       removed.
433     //    b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
434     //    c. If availableLocale is not undefined, append locale to the end of subset.
435     for (uint32_t i = 0; i < length; ++i) {
436         item.Update(requestedLocales->Get(thread, i));
437         ParsedLocale foundationResult = HandleLocale(item);
438         std::string availableLocale = BestAvailableLocale(thread, availableLocales, foundationResult.base);
439         if (!availableLocale.empty()) {
440             subset->Set(thread, index++, item.GetTaggedValue());
441         }
442     }
443     // 3. Return subset.
444     return TaggedArray::SetCapacity(thread, subset, index);
445 }
446 
447 // 9.2.9 BestFitSupportedLocales ( availableLocales, requestedLocales )
BestFitSupportedLocales(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales)448 JSHandle<TaggedArray> JSLocale::BestFitSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
449                                                         const JSHandle<TaggedArray> &requestedLocales)
450 {
451     UErrorCode status = U_ZERO_ERROR;
452     uint32_t requestLength = requestedLocales->GetLength();
453     uint32_t availableLength = availableLocales->GetLength();
454     icu::LocaleMatcher matcher = BuildLocaleMatcher(thread, &availableLength, &status, availableLocales);
455     ASSERT(U_SUCCESS(status));
456 
457     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
458     JSHandle<EcmaString> defaultLocale = DefaultLocale(thread);
459     JSHandle<TaggedArray> result = factory->NewTaggedArray(requestLength);
460 
461     uint32_t index = 0;
462     JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
463     for (uint32_t i = 0; i < requestLength; ++i) {
464         locale.Update(requestedLocales->Get(thread, i));
465         if (EcmaString::StringsAreEqual(*locale, *defaultLocale)) {
466             result->Set(thread, index++, locale.GetTaggedValue());
467         } else {
468             status = U_ZERO_ERROR;
469             std::string localeStr = ConvertToStdString(locale);
470             icu::Locale desired = icu::Locale::forLanguageTag(localeStr, status);
471             auto bestFit = matcher.getBestMatch(desired, status)->toLanguageTag<std::string>(status);
472             if ((U_SUCCESS(status) != 0) &&
473                 EcmaString::StringsAreEqual(*locale, *(factory->NewFromStdString(bestFit)))) {
474                 result->Set(thread, index++, locale.GetTaggedValue());
475             }
476         }
477     }
478     result = TaggedArray::SetCapacity(thread, result, index);
479     return result;
480 }
481 
ToLanguageTag(JSThread * thread,const icu::Locale & locale)482 JSHandle<EcmaString> JSLocale::ToLanguageTag(JSThread *thread, const icu::Locale &locale)
483 {
484     UErrorCode status = U_ZERO_ERROR;
485     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
486     auto result = locale.toLanguageTag<std::string>(status);
487     bool flag = (U_FAILURE(status) == 0) ? true : false;
488     if (!flag) {
489         THROW_RANGE_ERROR_AND_RETURN(thread, "invalid locale", factory->GetEmptyString());
490     }
491     size_t findBeginning = result.find("-u-");
492     std::string finalRes;
493     std::string tempRes;
494     if (findBeginning == std::string::npos) {
495         return factory->NewFromStdString(result);
496     }
497     size_t specialBeginning  = findBeginning + INTL_INDEX_THREE;
498     size_t specialCount = 0;
499     while (result[specialBeginning] != '-') {
500         specialCount++;
501         specialBeginning++;
502     }
503     if (findBeginning != std::string::npos) {
504         // It begin with "-u-xx" or with more elements.
505         tempRes = result.substr(0, findBeginning + INTL_INDEX_THREE + specialCount);
506         if (result.size() <= findBeginning + INTL_INDEX_THREE + specialCount) {
507             return factory->NewFromStdString(result);
508         }
509         std::string leftStr = result.substr(findBeginning + INTL_INDEX_THREE + specialCount + INTL_INDEX_ONE);
510         std::istringstream temp(leftStr);
511         std::string buffer;
512         std::vector<std::string> resContainer;
513         while (getline(temp, buffer, '-')) {
514             if (buffer != "true" && buffer != "yes") {
515                 resContainer.push_back(buffer);
516             }
517         }
518         for (auto it = resContainer.begin(); it != resContainer.end(); it++) {
519             std::string tag = "-";
520             tag += *it;
521             finalRes += tag;
522         }
523     }
524     if (!finalRes.empty()) {
525         tempRes += finalRes;
526     }
527     result = tempRes;
528     return factory->NewFromStdString(result);
529 }
530 
531 // 9.2.10 SupportedLocales ( availableLocales, requestedLocales, options )
SupportedLocales(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales,const JSHandle<JSTaggedValue> & options)532 JSHandle<JSArray> JSLocale::SupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
533                                              const JSHandle<TaggedArray> &requestedLocales,
534                                              const JSHandle<JSTaggedValue> &options)
535 {
536     const GlobalEnvConstants *globalConst = thread->GlobalConstants();
537     // 1. If options is not undefined, then
538     //    a. Let options be ? ToObject(options).
539     //    b. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
540     // 2. Else, let matcher be "best fit".
541     LocaleMatcherOption matcher = LocaleMatcherOption::BEST_FIT;
542     if (!options->IsUndefined()) {
543         JSHandle<JSObject> obj = JSTaggedValue::ToObject(thread, options);
544         RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
545 
546         matcher = GetOptionOfString<LocaleMatcherOption>(thread, obj, globalConst->GetHandledLocaleMatcherString(),
547                                                          {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
548                                                          {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
549         RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
550     }
551 
552     // 3. If matcher is "best fit", then
553     //    a. Let supportedLocales be BestFitSupportedLocales(availableLocales, requestedLocales).
554     // 4. Else,
555     //    a. Let supportedLocales be LookupSupportedLocales(availableLocales, requestedLocales).
556     JSMutableHandle<TaggedArray> supportedLocales(thread, JSTaggedValue::Undefined());
557     bool isBestfitSupport = false;
558     if (matcher == LocaleMatcherOption::BEST_FIT && isBestfitSupport) {
559         supportedLocales.Update(BestFitSupportedLocales(thread, availableLocales, requestedLocales).GetTaggedValue());
560     } else {
561         supportedLocales.Update(LookupSupportedLocales(thread, availableLocales, requestedLocales).GetTaggedValue());
562     }
563 
564     JSHandle<JSArray> subset = JSArray::CreateArrayFromList(thread, supportedLocales);
565     // 5. Return CreateArrayFromList(supportedLocales).
566     return subset;
567 }
568 
569 // 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)570 JSHandle<JSTaggedValue> JSLocale::GetOption(JSThread *thread, const JSHandle<JSObject> &options,
571                                             const JSHandle<JSTaggedValue> &property, OptionType type,
572                                             const JSHandle<JSTaggedValue> &values,
573                                             const JSHandle<JSTaggedValue> &fallback)
574 {
575     // 1. Let value be ? Get(options, property).
576     JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread, options, property).GetValue();
577     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
578 
579     // 2. If value is not undefined, then
580     if (!value->IsUndefined()) {
581         // a. Assert: type is "boolean" or "string".
582         ASSERT_PRINT(type == OptionType::BOOLEAN || type == OptionType::STRING, "type is not boolean or string");
583 
584         // b. If type is "boolean", then
585         //    i. Let value be ToBoolean(value).
586         if (type == OptionType::BOOLEAN) {
587             value = JSHandle<JSTaggedValue>(thread, JSTaggedValue(value->ToBoolean()));
588         }
589         // c. If type is "string", then
590         //    i. Let value be ? ToString(value).
591         if (type == OptionType::STRING) {
592             JSHandle<EcmaString> str = JSTaggedValue::ToString(thread, value);
593             RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
594             value = JSHandle<JSTaggedValue>(thread, str.GetTaggedValue());
595         }
596 
597         // d. If values is not undefined, then
598         //    i. If values does not contain an element equal to value, throw a RangeError exception.
599         if (!values->IsUndefined()) {
600             bool isExist = false;
601             JSHandle<TaggedArray> valuesArray = JSHandle<TaggedArray>::Cast(values);
602             uint32_t length = valuesArray->GetLength();
603             for (uint32_t i = 0; i < length; i++) {
604                 if (JSTaggedValue::SameValue(valuesArray->Get(thread, i), value.GetTaggedValue())) {
605                     isExist = true;
606                 }
607             }
608             if (!isExist) {
609                 JSHandle<JSTaggedValue> exception(thread, JSTaggedValue::Exception());
610                 THROW_RANGE_ERROR_AND_RETURN(thread, "values does not contain an element equal to value", exception);
611             }
612         }
613         // e. Return value.
614         return value;
615     }
616     // 3. Else, return fallback.
617     return fallback;
618 }
619 
GetOptionOfString(JSThread * thread,const JSHandle<JSObject> & options,const JSHandle<JSTaggedValue> & property,const std::vector<std::string> & values,std::string * optionValue)620 bool JSLocale::GetOptionOfString(JSThread *thread, const JSHandle<JSObject> &options,
621                                  const JSHandle<JSTaggedValue> &property, const std::vector<std::string> &values,
622                                  std::string *optionValue)
623 {
624     // 1. Let value be ? Get(options, property).
625     OperationResult operationResult = JSObject::GetProperty(thread, options, property);
626     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
627     JSHandle<JSTaggedValue> value = operationResult.GetValue();
628     // 2. If value is not undefined, then
629     if (value->IsUndefined()) {
630         return false;
631     }
632     //    c. If type is "string" "string", then
633     //       i. Let value be ? ToString(value).
634     JSHandle<EcmaString> valueEStr = JSTaggedValue::ToString(thread, value);
635     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
636     if (valueEStr->IsUtf16()) {
637         THROW_RANGE_ERROR_AND_RETURN(thread, "Value out of range for locale options property", false);
638     }
639     *optionValue = JSLocale::ConvertToStdString(valueEStr);
640     if (values.empty()) {
641         return true;
642     }
643     // d. If values is not undefined, then
644     //    i. If values does not contain an element equal to value, throw a RangeError exception.
645     for (const auto &item : values) {
646         if (item == *optionValue) {
647             return true;
648         }
649     }
650     THROW_RANGE_ERROR_AND_RETURN(thread, "Value out of range for locale options property", false);
651 }
652 
653 // 9.2.12 DefaultNumberOption ( value, minimum, maximum, fallback )
DefaultNumberOption(JSThread * thread,const JSHandle<JSTaggedValue> & value,int minimum,int maximum,int fallback)654 int JSLocale::DefaultNumberOption(JSThread *thread, const JSHandle<JSTaggedValue> &value, int minimum, int maximum,
655                                   int fallback)
656 {
657     // 1. If value is not undefined, then
658     if (!value->IsUndefined()) {
659         // a. Let value be ? ToNumber(value).
660         JSTaggedNumber number = JSTaggedValue::ToNumber(thread, value);
661         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
662         // b. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
663         double num = JSTaggedValue(number).GetNumber();
664         if (std::isnan(num) || num < minimum || num > maximum) {
665             THROW_RANGE_ERROR_AND_RETURN(thread, "", fallback);
666         }
667         // c. Return floor(value).
668         return std::floor(num);
669     }
670     // 2. Else, return fallback.
671     return fallback;
672 }
673 
674 // 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)675 int JSLocale::GetNumberOption(JSThread *thread, const JSHandle<JSObject> &options,
676                               const JSHandle<JSTaggedValue> &property, int min, int max, int fallback)
677 {
678     // 1. Let value be ? Get(options, property).
679     JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread, options, property).GetValue();
680     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
681 
682     // 2. Return ? DefaultNumberOption(value, minimum, maximum, fallback).
683     int result = DefaultNumberOption(thread, value, min, max, fallback);
684     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
685     return result;
686 }
687 
688 // 9.2.5 UnicodeExtensionValue ( extension, key )
UnicodeExtensionValue(const std::string extension,const std::string key)689 std::string JSLocale::UnicodeExtensionValue(const std::string extension, const std::string key)
690 {
691     // 1. Assert: The number of elements in key is 2.
692     // 2. Let size be the number of elements in extension.
693     ASSERT(key.size() == INTL_INDEX_TWO);
694     size_t size = extension.size();
695     // 3. Let searchValue be the concatenation of "-" , key, and "-".
696     std::string searchValue = "-" + key + "-";
697     // 4. Let pos be Call(%StringProto_indexOf%, extension, « searchValue »).
698     size_t pos = extension.find(searchValue);
699     // 5. If pos ≠ -1, then
700     if (pos != std::string::npos) {
701         // a. Let start be pos + 4.
702         size_t start = pos + INTL_INDEX_FOUR;
703         // b. Let end be start.
704         size_t end = start;
705         // c. Let k be start.
706         size_t k = start;
707         // d. Let done be false.
708         bool done = false;
709         // e. Repeat, while done is false
710         while (!done) {
711             // i. Let e be Call(%StringProto_indexOf%, extension, « "-", k »).
712             size_t e = extension.find("-", k);
713             size_t len;
714             // ii. If e = -1, let len be size - k; else let len be e - k.
715             if  (e == std::string::npos) {
716                 len = size - k;
717             } else {
718                 len = e - k;
719             }
720             // iii. If len = 2, then
721             //     1. Let done be true.
722             if (len == INTL_INDEX_TWO) {
723                 done = true;
724             // iv. Else if e = -1, then
725             //    1. Let end be size.
726             //    2. Let done be true.
727             } else if (e == std::string::npos) {
728                 end = size;
729                 done = true;
730             // v. Else,
731             //   1. Let end be e.
732             //   2. Let k be e + 1.
733             } else {
734                 end = e;
735                 k = e + INTL_INDEX_ONE;
736             }
737         }
738         // f. Return the String value equal to the substring of extension consisting of the code units at indices.
739         // start (inclusive) through end (exclusive).
740         std::string result = extension.substr(start, end - start);
741         return result;
742     }
743     // 6. Let searchValue be the concatenation of "-" and key.
744     searchValue = "-" + key;
745     // 7. Let pos be Call(%StringProto_indexOf%, extension, « searchValue »).
746     pos = extension.find(searchValue);
747     // 8. If pos ≠ -1 and pos + 3 = size, then
748     //    a. Return the empty String.
749     if (pos != std::string::npos && pos + INTL_INDEX_THREE == size) {
750         return "";
751     }
752     // 9. Return undefined.
753     return "undefined";
754 }
755 
ResolveLocale(JSThread * thread,const JSHandle<TaggedArray> & availableLocales,const JSHandle<TaggedArray> & requestedLocales,LocaleMatcherOption matcher,const std::set<std::string> & relevantExtensionKeys)756 ResolvedLocale JSLocale::ResolveLocale(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
757                                        const JSHandle<TaggedArray> &requestedLocales, LocaleMatcherOption matcher,
758                                        const std::set<std::string> &relevantExtensionKeys)
759 {
760     bool isBestfitSupport = false;
761     std::map<std::string, std::set<std::string>> localeMap = {
762         {"hc", {"h11", "h12", "h23", "h24"}},
763         {"lb", {"strict", "normal", "loose"}},
764         {"kn", {"true", "false"}},
765         {"kf", {"upper", "lower", "false"}}
766     };
767 
768     // 1. Let matcher be options.[[localeMatcher]].
769     // 2. If matcher is "lookup" "lookup", then
770     //    a. Let r be LookupMatcher(availableLocales, requestedLocales).
771     // 3. Else,
772     //    a. Let r be BestFitMatcher(availableLocales, requestedLocales).
773     JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
774     if (availableLocales->GetLength() == 0 && requestedLocales->GetLength() == 0) {
775         locale.Update(DefaultLocale(thread).GetTaggedValue());
776     } else {
777         if (matcher == LocaleMatcherOption::BEST_FIT && isBestfitSupport) {
778             locale.Update(BestFitMatcher(thread, availableLocales, requestedLocales).GetTaggedValue());
779         } else {
780             locale.Update(LookupMatcher(thread, availableLocales, requestedLocales).GetTaggedValue());
781         }
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.insert(std::pair<std::string, std::string>(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";
959 }
960 
IsWellFormedCurrencyCode(const std::string & currency)961 bool JSLocale::IsWellFormedCurrencyCode(const std::string &currency)
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 loc(locStr.c_str());
1370             bool res = false;
1371             if (!CheckLocales(loc, 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