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 ¤cy)
962 {
963 if (currency.length() != INTL_INDEX_THREE) {
964 return false;
965 }
966 return (IsAToZ(currency[INTL_INDEX_ZERO]) && IsAToZ(currency[INTL_INDEX_ONE]) && IsAToZ(currency[INTL_INDEX_TWO]));
967 }
968
PutElement(JSThread * thread,int index,const JSHandle<JSArray> & array,const JSHandle<JSTaggedValue> & fieldTypeString,const JSHandle<JSTaggedValue> & value)969 JSHandle<JSObject> JSLocale::PutElement(JSThread *thread, int index, const JSHandle<JSArray> &array,
970 const JSHandle<JSTaggedValue> &fieldTypeString,
971 const JSHandle<JSTaggedValue> &value)
972 {
973 auto ecmaVm = thread->GetEcmaVM();
974 ObjectFactory *factory = ecmaVm->GetFactory();
975
976 // Let record be ! ObjectCreate(%ObjectPrototype%).
977 JSHandle<JSObject> record = factory->NewEmptyJSObject();
978
979 auto globalConst = thread->GlobalConstants();
980 // obj.type = field_type_string
981 JSObject::CreateDataPropertyOrThrow(thread, record, globalConst->GetHandledTypeString(), fieldTypeString);
982 // obj.value = value
983 JSObject::CreateDataPropertyOrThrow(thread, record, globalConst->GetHandledValueString(), value);
984
985 JSTaggedValue::SetProperty(thread, JSHandle<JSTaggedValue>::Cast(array), index,
986 JSHandle<JSTaggedValue>::Cast(record), true);
987 return record;
988 }
989
990 // 9.2.11 GetOption ( options, property, type, values, fallback )
GetOptionOfBool(JSThread * thread,const JSHandle<JSObject> & options,const JSHandle<JSTaggedValue> & property,bool fallback,bool * res)991 bool JSLocale::GetOptionOfBool(JSThread *thread, const JSHandle<JSObject> &options,
992 const JSHandle<JSTaggedValue> &property, bool fallback, bool *res)
993 {
994 // 1. Let value be ? Get(options, property).
995 OperationResult operationResult = JSObject::GetProperty(thread, options, property);
996 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
997 JSHandle<JSTaggedValue> value = operationResult.GetValue();
998 *res = fallback;
999 // 2. If value is not undefined, then
1000 if (!value->IsUndefined()) {
1001 // b. Let value be ToBoolean(value).
1002 *res = value->ToBoolean();
1003 return true;
1004 }
1005 // 3. not found
1006 return false;
1007 }
1008
GetNumberFieldType(JSThread * thread,JSTaggedValue x,int32_t fieldId)1009 JSHandle<JSTaggedValue> JSLocale::GetNumberFieldType(JSThread *thread, JSTaggedValue x, int32_t fieldId)
1010 {
1011 ASSERT(x.IsNumber());
1012 double number = 0;
1013 auto globalConst = thread->GlobalConstants();
1014 if (static_cast<UNumberFormatFields>(fieldId) == UNUM_INTEGER_FIELD) {
1015 number = x.GetNumber();
1016 if (std::isfinite(number)) {
1017 return globalConst->GetHandledIntegerString();
1018 }
1019 if (std::isnan(number)) {
1020 return globalConst->GetHandledNanString();
1021 }
1022 return globalConst->GetHandledInfinityString();
1023 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_FRACTION_FIELD) {
1024 return globalConst->GetHandledFractionString();
1025 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_DECIMAL_SEPARATOR_FIELD) {
1026 return globalConst->GetHandledDecimalString();
1027 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) {
1028 return globalConst->GetHandledGroupString();
1029 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_CURRENCY_FIELD) {
1030 return globalConst->GetHandledCurrencyString();
1031 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_PERCENT_FIELD) {
1032 return globalConst->GetHandledPercentSignString();
1033 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_SIGN_FIELD) {
1034 number = x.GetNumber();
1035 return std::signbit(number) ? globalConst->GetHandledMinusSignString()
1036 : globalConst->GetHandledPlusSignString();
1037 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_SYMBOL_FIELD) {
1038 return globalConst->GetHandledExponentSeparatorString();
1039 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_SIGN_FIELD) {
1040 return globalConst->GetHandledExponentMinusSignString();
1041 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_FIELD) {
1042 return globalConst->GetHandledExponentIntegerString();
1043 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_COMPACT_FIELD) {
1044 return globalConst->GetHandledCompactString();
1045 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_MEASURE_UNIT_FIELD) {
1046 return globalConst->GetHandledUnitString();
1047 } else {
1048 UNREACHABLE();
1049 }
1050 }
1051
1052 // 10.1.1 ApplyOptionsToTag( tag, options )
ApplyOptionsToTag(JSThread * thread,const JSHandle<EcmaString> & tag,const JSHandle<JSObject> & options,TagElements & tagElements)1053 bool JSLocale::ApplyOptionsToTag(JSThread *thread, const JSHandle<EcmaString> &tag, const JSHandle<JSObject> &options,
1054 TagElements &tagElements)
1055 {
1056 EcmaVM *ecmaVm = thread->GetEcmaVM();
1057 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
1058 ObjectFactory *factory = ecmaVm->GetFactory();
1059 if (*tag == *(factory->GetEmptyString())) {
1060 return false;
1061 }
1062 // 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
1063 if (!IsStructurallyValidLanguageTag(tag)) {
1064 return false;
1065 }
1066
1067 tagElements.language =
1068 GetOption(thread, options, globalConst->GetHandledLanguageString(), OptionType::STRING,
1069 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
1070 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1071
1072 // 4. If language is not undefined, then
1073 // a. If language does not match the unicode_language_subtag production, throw a RangeError exception.
1074 if (!tagElements.language->IsUndefined()) {
1075 std::string languageStr = ConvertToStdString(JSHandle<EcmaString>::Cast(tagElements.language));
1076 if (languageStr[INTL_INDEX_ZERO] == '\0' ||
1077 IsAlpha(languageStr, INTL_INDEX_FOUR, INTL_INDEX_FOUR)) {
1078 return false;
1079 }
1080 }
1081
1082 // 5. Let script be ? GetOption(options, "script", "string", undefined, undefined).
1083 tagElements.script =
1084 GetOption(thread, options, globalConst->GetHandledScriptString(), OptionType::STRING,
1085 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
1086 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1087
1088 // 6. If script is not undefined, then
1089 // a. If script does not match the unicode_script_subtag production, throw a RangeError exception.
1090 if (!tagElements.script->IsUndefined()) {
1091 std::string scriptStr = JSLocale::ConvertToStdString((JSHandle<EcmaString>::Cast(tagElements.script)));
1092 if (scriptStr[INTL_INDEX_ZERO] == '\0') {
1093 return false;
1094 }
1095 }
1096
1097 // 7. Let region be ? GetOption(options, "region", "string", undefined, undefined).
1098 // 8. If region is not undefined, then
1099 // a. If region does not match the unicode_region_subtag production, throw a RangeError exception.
1100 tagElements.region =
1101 GetOption(thread, options, globalConst->GetHandledRegionString(), OptionType::STRING,
1102 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
1103 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1104
1105 if (!tagElements.region->IsUndefined()) {
1106 std::string regionStr = ConvertToStdString(JSHandle<EcmaString>::Cast(tagElements.region));
1107 if (regionStr[INTL_INDEX_ZERO] == '\0') {
1108 return false;
1109 }
1110 }
1111 return true;
1112 }
1113
BuildOptionsTags(const JSHandle<EcmaString> & tag,icu::LocaleBuilder * builder,JSHandle<JSTaggedValue> language,JSHandle<JSTaggedValue> script,JSHandle<JSTaggedValue> region)1114 bool BuildOptionsTags(const JSHandle<EcmaString> &tag, icu::LocaleBuilder *builder, JSHandle<JSTaggedValue> language,
1115 JSHandle<JSTaggedValue> script, JSHandle<JSTaggedValue> region)
1116 {
1117 std::string tagStr = JSLocale::ConvertToStdString(tag);
1118 int32_t len = static_cast<int32_t>(tagStr.length());
1119 ASSERT(len > 0);
1120 builder->setLanguageTag({ tagStr.c_str(), len });
1121 UErrorCode status = U_ZERO_ERROR;
1122 icu::Locale locale = builder->build(status);
1123 locale.canonicalize(status);
1124 if (U_FAILURE(status) != 0) {
1125 return false;
1126 }
1127 builder->setLocale(locale);
1128
1129 if (!language->IsUndefined()) {
1130 std::string languageStr = JSLocale::ConvertToStdString(JSHandle<EcmaString>::Cast(language));
1131 builder->setLanguage(languageStr);
1132 builder->build(status);
1133 if ((U_FAILURE(status) != 0)) {
1134 return false;
1135 }
1136 }
1137
1138 if (!script->IsUndefined()) {
1139 std::string scriptStr = JSLocale::ConvertToStdString((JSHandle<EcmaString>::Cast(script)));
1140 builder->setScript(scriptStr);
1141 builder->build(status);
1142 if ((U_FAILURE(status) != 0)) {
1143 return false;
1144 }
1145 }
1146
1147 if (!region->IsUndefined()) {
1148 std::string regionStr = JSLocale::ConvertToStdString(JSHandle<EcmaString>::Cast(region));
1149 builder->setRegion(regionStr);
1150 builder->build(status);
1151 if ((U_FAILURE(status) != 0)) {
1152 return false;
1153 }
1154 }
1155 return true;
1156 }
1157
InsertOptions(JSThread * thread,const JSHandle<JSObject> & options,icu::LocaleBuilder * builder)1158 bool InsertOptions(JSThread *thread, const JSHandle<JSObject> &options, icu::LocaleBuilder *builder)
1159 {
1160 const std::vector<std::string> hourCycleValues = {"h11", "h12", "h23", "h24"};
1161 const std::vector<std::string> caseFirstValues = {"upper", "lower", "false"};
1162 const std::vector<std::string> emptyValues = {};
1163 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
1164 std::string strResult;
1165 bool findca =
1166 JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCalendarString(), emptyValues, &strResult);
1167 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1168 if (findca) {
1169 if (!uloc_toLegacyType(uloc_toLegacyKey("ca"), strResult.c_str())) {
1170 return false;
1171 }
1172 builder->setUnicodeLocaleKeyword("ca", strResult.c_str());
1173 }
1174
1175 bool findco =
1176 JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCollationString(), emptyValues, &strResult);
1177 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1178 if (findco) {
1179 if (!uloc_toLegacyType(uloc_toLegacyKey("co"), strResult.c_str())) {
1180 return false;
1181 }
1182 builder->setUnicodeLocaleKeyword("co", strResult.c_str());
1183 }
1184
1185 bool findhc = JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledHourCycleString(),
1186 hourCycleValues, &strResult);
1187 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1188 if (findhc) {
1189 if (!uloc_toLegacyType(uloc_toLegacyKey("hc"), strResult.c_str())) {
1190 return false;
1191 }
1192 builder->setUnicodeLocaleKeyword("hc", strResult.c_str());
1193 }
1194
1195 bool findkf = JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCaseFirstString(),
1196 caseFirstValues, &strResult);
1197 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1198 if (findkf) {
1199 if (!uloc_toLegacyType(uloc_toLegacyKey("kf"), strResult.c_str())) {
1200 return false;
1201 }
1202 builder->setUnicodeLocaleKeyword("kf", strResult.c_str());
1203 }
1204
1205 bool boolResult = false;
1206 bool findkn =
1207 JSLocale::GetOptionOfBool(thread, options, globalConst->GetHandledNumericString(), false, &boolResult);
1208 if (findkn) {
1209 strResult = boolResult ? "true" : "false";
1210 if (!uloc_toLegacyType(uloc_toLegacyKey("kn"), strResult.c_str())) {
1211 return false;
1212 }
1213 builder->setUnicodeLocaleKeyword("kn", strResult.c_str());
1214 }
1215
1216 bool findnu =
1217 JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledNumberingSystemString(), emptyValues,
1218 &strResult);
1219 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
1220 if (findnu) {
1221 if (!uloc_toLegacyType(uloc_toLegacyKey("nu"), strResult.c_str())) {
1222 return false;
1223 }
1224 builder->setUnicodeLocaleKeyword("nu", strResult.c_str());
1225 }
1226 return true;
1227 }
1228
InitializeLocale(JSThread * thread,const JSHandle<JSLocale> & locale,const JSHandle<EcmaString> & localeString,const JSHandle<JSObject> & options)1229 JSHandle<JSLocale> JSLocale::InitializeLocale(JSThread *thread, const JSHandle<JSLocale> &locale,
1230 const JSHandle<EcmaString> &localeString,
1231 const JSHandle<JSObject> &options)
1232 {
1233 icu::LocaleBuilder builder;
1234 TagElements tagElements;
1235 if (!ApplyOptionsToTag(thread, localeString, options, tagElements)) {
1236 THROW_RANGE_ERROR_AND_RETURN(thread, "apply option to tag failed", locale);
1237 }
1238
1239 bool res = BuildOptionsTags(localeString, &builder, tagElements.language, tagElements.script, tagElements.region);
1240 if (!res) {
1241 THROW_RANGE_ERROR_AND_RETURN(thread, "apply option to tag failed", locale);
1242 }
1243 bool insertResult = InsertOptions(thread, options, &builder);
1244 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, locale);
1245 UErrorCode status = U_ZERO_ERROR;
1246 icu::Locale icuLocale = builder.build(status);
1247 icuLocale.canonicalize(status);
1248
1249 if (!insertResult || (U_FAILURE(status) != 0)) {
1250 THROW_RANGE_ERROR_AND_RETURN(thread, "insert or build failed", locale);
1251 }
1252 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
1253 factory->NewJSIntlIcuData(locale, icuLocale, JSLocale::FreeIcuLocale);
1254 return locale;
1255 }
1256
DealwithLanguageTag(const std::vector<std::string> & containers,size_t & address)1257 bool JSLocale::DealwithLanguageTag(const std::vector<std::string> &containers, size_t &address)
1258 {
1259 // The abstract operation returns true if locale can be generated from the ABNF grammar in section 2.1 of the RFC,
1260 // starting with Language-Tag, and does not contain duplicate variant or singleton subtags
1261 // If language tag is empty, return false.
1262 if (containers.empty()) {
1263 return false;
1264 }
1265
1266 // a. if the first tag is not language, return false.
1267 if (!IsLanguageSubtag(containers[0])) {
1268 return false;
1269 }
1270
1271 // if the tag include language only, like "zh" or "de", return true;
1272 if (containers.size() == 1) {
1273 return true;
1274 }
1275
1276 // Else, then
1277 // if is unique singleton subtag, script and region tag.
1278 if (IsExtensionSingleton(containers[1])) {
1279 return true;
1280 }
1281
1282 if (IsScriptSubtag(containers[address])) {
1283 address++;
1284 if (containers.size() == address) {
1285 return true;
1286 }
1287 }
1288
1289 if (IsRegionSubtag(containers[address])) {
1290 address++;
1291 }
1292
1293 for (size_t i = address; i < containers.size(); i++) {
1294 if (IsExtensionSingleton(containers[i])) {
1295 return true;
1296 }
1297 if (!IsVariantSubtag(containers[i])) {
1298 return false;
1299 }
1300 }
1301 return true;
1302 }
1303
ConvertValue(const UErrorCode & status,std::string & value,const std::string & key)1304 int ConvertValue(const UErrorCode &status, std::string &value, const std::string &key)
1305 {
1306 if (status == U_ILLEGAL_ARGUMENT_ERROR || value.empty()) {
1307 return 1;
1308 }
1309
1310 if (value == "yes") {
1311 value = "true";
1312 }
1313
1314 if (key == "kf" && value == "true") {
1315 return 2; // 2: in this case normalizedKeyword is empty string
1316 }
1317 return 0;
1318 }
1319
NormalizeKeywordValue(JSThread * thread,const JSHandle<JSLocale> & locale,const std::string & key)1320 JSHandle<EcmaString> JSLocale::NormalizeKeywordValue(JSThread *thread, const JSHandle<JSLocale> &locale,
1321 const std::string &key)
1322 {
1323 icu::Locale *icuLocale = locale->GetIcuLocale();
1324 UErrorCode status = U_ZERO_ERROR;
1325 auto value = icuLocale->getUnicodeKeywordValue<std::string>(key, status);
1326
1327 EcmaVM *ecmaVm = thread->GetEcmaVM();
1328 ObjectFactory *factory = ecmaVm->GetFactory();
1329
1330 int result = ConvertValue(status, value, key);
1331 if (result == 1) {
1332 return JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledUndefinedString());
1333 }
1334 if (result == 2) { // 2: in this case normalizedKeyword is empty string
1335 return factory->GetEmptyString();
1336 }
1337 return factory->NewFromStdString(value);
1338 }
1339
ToString(JSThread * thread,const JSHandle<JSLocale> & locale)1340 JSHandle<EcmaString> JSLocale::ToString(JSThread *thread, const JSHandle<JSLocale> &locale)
1341 {
1342 icu::Locale *icuLocale = locale->GetIcuLocale();
1343 if (icuLocale == nullptr) {
1344 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
1345 return factory->GetEmptyString();
1346 }
1347 JSHandle<EcmaString> result = ToLanguageTag(thread, *icuLocale);
1348 return result;
1349 }
1350
GetAvailableLocales(JSThread * thread,const char * localeKey,const char * localePath)1351 JSHandle<TaggedArray> JSLocale::GetAvailableLocales(JSThread *thread, const char *localeKey, const char *localePath)
1352 {
1353 UErrorCode status = U_ZERO_ERROR;
1354 auto globalConst = thread->GlobalConstants();
1355 JSHandle<EcmaString> specialValue = JSHandle<EcmaString>::Cast(globalConst->GetHandledEnUsPosixString());
1356 std::string specialString = ConvertToStdString(specialValue);
1357 UEnumeration *uenum = uloc_openAvailableByType(ULOC_AVAILABLE_WITH_LEGACY_ALIASES, &status);
1358 std::vector<std::string> allLocales;
1359 const char *loc = nullptr;
1360 for (loc = uenum_next(uenum, nullptr, &status); loc != nullptr; loc = uenum_next(uenum, nullptr, &status)) {
1361 ASSERT(U_SUCCESS(status));
1362 std::string locStr(loc);
1363 std::replace(locStr.begin(), locStr.end(), '_', '-');
1364 if (locStr == specialString) {
1365 locStr = "en-US-u-va-posix";
1366 }
1367
1368 if (localePath != nullptr || localeKey != nullptr) {
1369 icu::Locale 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