• 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 "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 &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 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