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/intl/locale_helper.h"
19 #include "ecmascript/object_factory-inl.h"
20 #include "ecmascript/checkpoint/thread_state_transition.h"
21
22 #if defined(__clang__)
23 #pragma clang diagnostic push
24 #pragma clang diagnostic ignored "-Wshadow"
25 #elif defined(__GNUC__)
26 #pragma GCC diagnostic push
27 #pragma GCC diagnostic ignored "-Wshadow"
28 #endif
29 #include "unicode/localebuilder.h"
30 #if defined(__clang__)
31 #pragma clang diagnostic pop
32 #elif defined(__GNUC__)
33 #pragma GCC diagnostic pop
34 #endif
35
36 namespace panda::ecmascript {
37 const std::string JSLocale::LATN_STRING = "latn";
38
39 const std::vector<LocaleMatcherOption> JSLocale::LOCALE_MATCHER_OPTION = {
40 LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT
41 };
42 const std::vector<std::string> JSLocale::LOCALE_MATCHER_OPTION_NAME = {
43 "lookup", "best fit"
44 };
45
46 const std::map<std::string, std::set<std::string>> JSLocale::LOCALE_MAP = {
47 {"hc", {"h11", "h12", "h23", "h24"}},
48 {"lb", {"strict", "normal", "loose"}},
49 {"kn", {"true", "false"}},
50 {"kf", {"upper", "lower", "false"}}
51 };
52
53 const std::vector<std::string> JSLocale::HOUR_CYCLE = {"h11", "h12", "h23", "h24"};
54 const std::vector<std::string> JSLocale::CASE_FIRST = {"upper", "lower", "false"};
55
56 const std::set<std::string> JSLocale::WELL_NUMBER_SYSTEM = {"native", "traditio", "finance"};
57 const std::set<std::string> JSLocale::WELL_COLLATION = {"standard", "search"};
58 // 6.4.1 IsValidTimeZoneName ( timeZone )
IsValidTimeZoneName(const icu::TimeZone &tz)59 bool JSLocale::IsValidTimeZoneName(const icu::TimeZone &tz)
60 {
61 UErrorCode status = U_ZERO_ERROR;
62 icu::UnicodeString id;
63 tz.getID(id);
64 icu::UnicodeString canonical;
65 icu::TimeZone::getCanonicalID(id, canonical, status);
66 UBool canonicalFlag = (canonical != icu::UnicodeString("Etc/Unknown", -1, US_INV));
67 return (U_SUCCESS(status) != 0) && (canonicalFlag != 0);
68 }
69
70 // 9.2.3 LookupMatcher ( availableLocales, requestedLocales )
LookupMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales)71 JSHandle<EcmaString> JSLocale::LookupMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
72 const JSHandle<TaggedArray> &requestedLocales)
73 {
74 MatcherResult result = {std::string(), std::string()};
75 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
76 // 1. Let result be a new Record.
77 // 2. For each element locale of requestedLocales in List order, do
78 std::vector<std::string> availableStringLocales = GetAvailableStringLocales(thread, availableLocales);
79 uint32_t length = requestedLocales->GetLength();
80 JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
81 for (uint32_t i = 0; i < length; ++i) {
82 locale.Update(requestedLocales->Get(thread, i));
83 // 2. a. Let noExtensionsLocale be the String value that is locale
84 // with all Unicode locale extension sequences removed.
85 intl::LocaleHelper::ParsedLocale parsedResult = intl::LocaleHelper::HandleLocale(locale);
86 // 2. b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
87 std::string availableLocale =
88 intl::LocaleHelper::BestAvailableLocale(availableStringLocales, parsedResult.base);
89 // 2. c. If availableLocale is not undefined, append locale to the end of subset.
90 if (!availableLocale.empty()) {
91 result = {std::string(), std::string()};
92 // 2. c. i. Set result.[[locale]] to availableLocale.
93 result.locale = availableLocale;
94 // 2. c. ii. If locale and noExtensionsLocale are not the same String value, then
95 // 2. c. ii. 1. Let extension be the String value consisting of the first substring of locale that is a
96 // Unicode locale extension sequence.
97 if (!parsedResult.extension.empty()) {
98 result.extension = parsedResult.extension;
99 }
100 // 2. c. ii. 2. Set result.[[extension]] to extension.
101 std::string res = result.locale + result.extension;
102 // 2. c. iii. Return result.
103 return factory->NewFromStdString(res);
104 }
105 }
106
107 // 3. Let defLocale be DefaultLocale();
108 // 4. Set result.[[locale]] to defLocale.
109 // 5. Return result.
110 auto defLocale = intl::LocaleHelper::StdStringDefaultLocale(thread);
111 result.locale = defLocale;
112 return factory->NewFromStdString(result.locale);
113 }
114
BuildLocaleMatcher(JSThread *thread, uint32_t *availableLength, UErrorCode *status, const JSHandle<TaggedArray> &availableLocales)115 icu::LocaleMatcher BuildLocaleMatcher(JSThread *thread, uint32_t *availableLength, UErrorCode *status,
116 const JSHandle<TaggedArray> &availableLocales)
117 {
118 std::string locale = intl::LocaleHelper::StdStringDefaultLocale(thread);
119 icu::Locale defaultLocale = icu::Locale::forLanguageTag(locale, *status);
120 ASSERT_PRINT(U_SUCCESS(*status), "icu::Locale::forLanguageTag failed");
121 icu::LocaleMatcher::Builder builder;
122 builder.setDefaultLocale(&defaultLocale);
123 uint32_t length = availableLocales->GetLength();
124
125 JSMutableHandle<EcmaString> item(thread, JSTaggedValue::Undefined());
126 for (*availableLength = 0; *availableLength < length; ++(*availableLength)) {
127 item.Update(availableLocales->Get(thread, *availableLength));
128 std::string itemStr = intl::LocaleHelper::ConvertToStdString(item);
129 icu::Locale localeForLanguageTag = icu::Locale::forLanguageTag(itemStr, *status);
130 if (U_SUCCESS(*status) != 0) {
131 builder.addSupportedLocale(localeForLanguageTag);
132 } else {
133 break;
134 }
135 }
136 *status = U_ZERO_ERROR;
137 return builder.build(*status);
138 }
139
140 // 9.2.4 BestFitMatcher ( availableLocales, requestedLocales )
BestFitMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales)141 JSHandle<EcmaString> JSLocale::BestFitMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
142 const JSHandle<TaggedArray> &requestedLocales)
143 {
144 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
145 UErrorCode status = U_ZERO_ERROR;
146 uint32_t availableLength = availableLocales->GetLength();
147 icu::LocaleMatcher matcher = BuildLocaleMatcher(thread, &availableLength, &status, availableLocales);
148 ASSERT(U_SUCCESS(status));
149
150 uint32_t requestedLocalesLength = requestedLocales->GetLength();
151 JSIntlIterator iter(requestedLocales, requestedLocalesLength);
152 auto bestFit = matcher.getBestMatch(iter, status)->toLanguageTag<std::string>(status);
153
154 if (U_FAILURE(status) != 0) {
155 return intl::LocaleHelper::DefaultLocale(thread);
156 }
157
158 for (uint32_t i = 0; i < requestedLocalesLength; ++i) {
159 if (iter[i] == bestFit) {
160 return JSHandle<EcmaString>(thread, requestedLocales->Get(thread, i));
161 }
162 }
163 return factory->NewFromStdString(bestFit);
164 }
165
166 // 9.2.8 LookupSupportedLocales ( availableLocales, requestedLocales )
LookupSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales)167 JSHandle<TaggedArray> JSLocale::LookupSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
168 const JSHandle<TaggedArray> &requestedLocales)
169 {
170 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
171 uint32_t length = requestedLocales->GetLength();
172 JSMutableHandle<EcmaString> item(thread, JSTaggedValue::Undefined());
173 std::vector<std::string> availableStringLocales = GetAvailableStringLocales(thread, availableLocales);
174 std::vector<std::string> convertedLocales;
175 convertedLocales.reserve(length);
176 std::vector<uint32_t> indexAvailableLocales;
177 indexAvailableLocales.reserve(length);
178 for (uint32_t i = 0; i < length; ++i) {
179 item.Update(requestedLocales->Get(thread, i));
180 convertedLocales.push_back(intl::LocaleHelper::ConvertToStdString(item));
181 }
182 // 1. For each element locale of requestedLocales in List order, do
183 // a. Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences
184 // removed.
185 // b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
186 // c. If availableLocale is not undefined, append locale to the end of subset.
187 {
188 ThreadNativeScope nativeScope(thread);
189 for (uint32_t i = 0; i < length; ++i) {
190 intl::LocaleHelper::ParsedLocale foundationResult = intl::LocaleHelper::HandleLocale(convertedLocales[i]);
191 std::string availableLocale =
192 intl::LocaleHelper::BestAvailableLocale(availableStringLocales, foundationResult.base);
193 if (!availableLocale.empty()) {
194 indexAvailableLocales.push_back(i);
195 }
196 }
197 }
198 // 2. Let subset be a new empty List.
199 JSHandle<TaggedArray> subset = factory->NewTaggedArray(indexAvailableLocales.size());
200 uint32_t index = 0;
201 for (uint32_t i = 0; i < indexAvailableLocales.size(); ++i) {
202 subset->Set(thread, index++, requestedLocales->Get(thread, indexAvailableLocales[i]));
203 }
204 // 3. Return subset.
205 return subset;
206 }
207
208 // 9.2.9 BestFitSupportedLocales ( availableLocales, requestedLocales )
BestFitSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales)209 JSHandle<TaggedArray> JSLocale::BestFitSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
210 const JSHandle<TaggedArray> &requestedLocales)
211 {
212 UErrorCode status = U_ZERO_ERROR;
213 uint32_t requestLength = requestedLocales->GetLength();
214 uint32_t availableLength = availableLocales->GetLength();
215 icu::LocaleMatcher matcher = BuildLocaleMatcher(thread, &availableLength, &status, availableLocales);
216 ASSERT(U_SUCCESS(status));
217
218 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
219 JSHandle<EcmaString> defaultLocale = intl::LocaleHelper::DefaultLocale(thread);
220 JSHandle<TaggedArray> result = factory->NewTaggedArray(requestLength);
221
222 uint32_t index = 0;
223 JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
224 for (uint32_t i = 0; i < requestLength; ++i) {
225 locale.Update(requestedLocales->Get(thread, i));
226 if (EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), locale, defaultLocale)) {
227 result->Set(thread, index++, locale.GetTaggedValue());
228 } else {
229 status = U_ZERO_ERROR;
230 std::string localeStr = intl::LocaleHelper::ConvertToStdString(locale);
231 icu::Locale desired = icu::Locale::forLanguageTag(localeStr, status);
232 auto bestFit = matcher.getBestMatch(desired, status)->toLanguageTag<std::string>(status);
233 if ((U_SUCCESS(status) != 0) &&
234 EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), locale, factory->NewFromStdString(bestFit))) {
235 result->Set(thread, index++, locale.GetTaggedValue());
236 }
237 }
238 }
239 result = TaggedArray::SetCapacity(thread, result, index);
240 return result;
241 }
242
243 // 9.2.10 SupportedLocales ( availableLocales, requestedLocales, options )
SupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales, const JSHandle<JSTaggedValue> &options)244 JSHandle<JSArray> JSLocale::SupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
245 const JSHandle<TaggedArray> &requestedLocales,
246 const JSHandle<JSTaggedValue> &options)
247 {
248 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
249 // 1. If options is not undefined, then
250 // a. Let options be ? ToObject(options).
251 // b. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
252 // 2. Else, let matcher be "best fit".
253 if (!options->IsUndefined()) {
254 JSHandle<JSObject> obj = JSTaggedValue::ToObject(thread, options);
255 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
256
257 [[maybe_unused]] LocaleMatcherOption matcher = GetOptionOfString<LocaleMatcherOption>(thread,
258 obj, globalConst->GetHandledLocaleMatcherString(),
259 JSLocale::LOCALE_MATCHER_OPTION, JSLocale::LOCALE_MATCHER_OPTION_NAME,
260 LocaleMatcherOption::BEST_FIT);
261 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
262 }
263
264 // 3. If matcher is "best fit", then
265 // a. Let supportedLocales be BestFitSupportedLocales(availableLocales, requestedLocales).
266 // 4. Else,
267 // a. Let supportedLocales be LookupSupportedLocales(availableLocales, requestedLocales).
268 JSMutableHandle<TaggedArray> supportedLocales(thread, JSTaggedValue::Undefined());
269 supportedLocales.Update(LookupSupportedLocales(thread, availableLocales, requestedLocales).GetTaggedValue());
270
271 JSHandle<JSArray> subset = JSArray::CreateArrayFromList(thread, supportedLocales);
272 // 5. Return CreateArrayFromList(supportedLocales).
273 return subset;
274 }
275
276 // 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)277 JSHandle<JSTaggedValue> JSLocale::GetOption(JSThread *thread, const JSHandle<JSObject> &options,
278 const JSHandle<JSTaggedValue> &property, OptionType type,
279 const JSHandle<JSTaggedValue> &values,
280 const JSHandle<JSTaggedValue> &fallback)
281 {
282 // 1. Let value be ? Get(options, property).
283 JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread, options, property).GetValue();
284 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
285
286 // 2. If value is not undefined, then
287 if (!value->IsUndefined()) {
288 // a. Assert: type is "boolean" or "string".
289 ASSERT_PRINT(type == OptionType::BOOLEAN || type == OptionType::STRING, "type is not boolean or string");
290
291 // b. If type is "boolean", then
292 // i. Let value be ToBoolean(value).
293 if (type == OptionType::BOOLEAN) {
294 value = JSHandle<JSTaggedValue>(thread, JSTaggedValue(value->ToBoolean()));
295 }
296 // c. If type is "string", then
297 // i. Let value be ? ToString(value).
298 if (type == OptionType::STRING) {
299 JSHandle<EcmaString> str = JSTaggedValue::ToString(thread, value);
300 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
301 value = JSHandle<JSTaggedValue>(thread, str.GetTaggedValue());
302 }
303
304 // d. If values is not undefined, then
305 // i. If values does not contain an element equal to value, throw a RangeError exception.
306 if (!values->IsUndefined()) {
307 bool isExist = false;
308 JSHandle<TaggedArray> valuesArray = JSHandle<TaggedArray>::Cast(values);
309 uint32_t length = valuesArray->GetLength();
310 for (uint32_t i = 0; i < length; i++) {
311 if (JSTaggedValue::SameValue(valuesArray->Get(thread, i), value.GetTaggedValue())) {
312 isExist = true;
313 }
314 }
315 if (!isExist) {
316 JSHandle<JSTaggedValue> exception(thread, JSTaggedValue::Exception());
317 THROW_RANGE_ERROR_AND_RETURN(thread, "values does not contain an element equal to value", exception);
318 }
319 }
320 // e. Return value.
321 return value;
322 }
323 // 3. Else, return fallback.
324 return fallback;
325 }
326
GetOptionOfString(JSThread *thread, const JSHandle<JSObject> &options, const JSHandle<JSTaggedValue> &property, const std::vector<std::string> &values, std::string *optionValue)327 bool JSLocale::GetOptionOfString(JSThread *thread, const JSHandle<JSObject> &options,
328 const JSHandle<JSTaggedValue> &property, const std::vector<std::string> &values,
329 std::string *optionValue)
330 {
331 // 1. Let value be ? Get(options, property).
332 OperationResult operationResult = JSObject::GetProperty(thread, options, property);
333 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
334 JSHandle<JSTaggedValue> value = operationResult.GetValue();
335 // 2. If value is not undefined, then
336 if (value->IsUndefined()) {
337 return false;
338 }
339 // c. If type is "string" "string", then
340 // i. Let value be ? ToString(value).
341 JSHandle<EcmaString> valueEStr = JSTaggedValue::ToString(thread, value);
342 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
343 if (EcmaStringAccessor(valueEStr).IsUtf16()) {
344 THROW_RANGE_ERROR_AND_RETURN(thread, "Value out of range for locale options property", false);
345 }
346 *optionValue = intl::LocaleHelper::ConvertToStdString(valueEStr);
347 if (values.empty()) {
348 return true;
349 }
350 // d. If values is not undefined, then
351 // i. If values does not contain an element equal to value, throw a RangeError exception.
352 for (const auto &item : values) {
353 if (item == *optionValue) {
354 return true;
355 }
356 }
357 THROW_RANGE_ERROR_AND_RETURN(thread, "Value out of range for locale options property", false);
358 }
359
360 // 9.2.12 DefaultNumberOption ( value, minimum, maximum, fallback )
DefaultNumberOption(JSThread *thread, const JSHandle<JSTaggedValue> &value, int minimum, int maximum, int fallback)361 int JSLocale::DefaultNumberOption(JSThread *thread, const JSHandle<JSTaggedValue> &value, int minimum, int maximum,
362 int fallback)
363 {
364 // 1. If value is not undefined, then
365 if (!value->IsUndefined()) {
366 // a. Let value be ? ToNumber(value).
367 JSTaggedNumber number = JSTaggedValue::ToNumber(thread, value);
368 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
369 // b. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
370 double num = JSTaggedValue(number).GetNumber();
371 if (std::isnan(num) || num < minimum || num > maximum) {
372 THROW_RANGE_ERROR_AND_RETURN(thread, "", fallback);
373 }
374 // c. Return floor(value).
375 return std::floor(num);
376 }
377 // 2. Else, return fallback.
378 return fallback;
379 }
380
381 // 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)382 int JSLocale::GetNumberOption(JSThread *thread, const JSHandle<JSObject> &options,
383 const JSHandle<JSTaggedValue> &property, int min, int max, int fallback)
384 {
385 // 1. Let value be ? Get(options, property).
386 JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread, options, property).GetValue();
387 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
388
389 // 2. Return ? DefaultNumberOption(value, minimum, maximum, fallback).
390 int result = DefaultNumberOption(thread, value, min, max, fallback);
391 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
392 return result;
393 }
394
395 // 9.2.5 UnicodeExtensionValue ( extension, key )
UnicodeExtensionValue(const std::string extension, const std::string key)396 std::string JSLocale::UnicodeExtensionValue(const std::string extension, const std::string key)
397 {
398 // 1. Assert: The number of elements in key is 2.
399 // 2. Let size be the number of elements in extension.
400 ASSERT(key.size() == INTL_INDEX_TWO || key.size() == INTL_INDEX_ZERO);
401 size_t size = extension.size();
402 // 3. Let searchValue be the concatenation of "-" , key, and "-".
403 std::string searchValue = "-" + key + "-";
404 // 4. Let pos be Call(%StringProto_indexOf%, extension, « searchValue »).
405 size_t pos = extension.find(searchValue);
406 // 5. If pos ≠ -1, then
407 if (pos != std::string::npos) {
408 // a. Let start be pos + 4.
409 size_t start = pos + INTL_INDEX_FOUR;
410 // b. Let end be start.
411 size_t end = start;
412 // c. Let k be start.
413 size_t k = start;
414 // d. Let done be false.
415 bool done = false;
416 // e. Repeat, while done is false
417 while (!done) {
418 // i. Let e be Call(%StringProto_indexOf%, extension, « "-", k »).
419 size_t e = extension.find("-", k);
420 size_t len;
421 // ii. If e = -1, let len be size - k; else let len be e - k.
422 if (e == std::string::npos) {
423 len = size - k;
424 } else {
425 len = e - k;
426 }
427 // iii. If len = 2, then
428 // 1. Let done be true.
429 if (len == INTL_INDEX_TWO) {
430 done = true;
431 // iv. Else if e = -1, then
432 // 1. Let end be size.
433 // 2. Let done be true.
434 } else if (e == std::string::npos) {
435 end = size;
436 done = true;
437 // v. Else,
438 // 1. Let end be e.
439 // 2. Let k be e + 1.
440 } else {
441 end = e;
442 k = e + INTL_INDEX_ONE;
443 }
444 }
445 // f. Return the String value equal to the substring of extension consisting of the code units at indices.
446 // start (inclusive) through end (exclusive).
447 std::string result = extension.substr(start, end - start);
448 return result;
449 }
450 // 6. Let searchValue be the concatenation of "-" and key.
451 searchValue = "-" + key;
452 // 7. Let pos be Call(%StringProto_indexOf%, extension, « searchValue »).
453 pos = extension.find(searchValue);
454 // 8. If pos ≠ -1 and pos + 3 = size, then
455 // a. Return the empty String.
456 if (pos != std::string::npos && pos + INTL_INDEX_THREE == size) {
457 return "";
458 }
459 // 9. Return undefined.
460 return "undefined";
461 }
462
ResolveLocale(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales, [[maybe_unused]] LocaleMatcherOption matcher, const std::set<std::string> &relevantExtensionKeys)463 ResolvedLocale JSLocale::ResolveLocale(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
464 const JSHandle<TaggedArray> &requestedLocales,
465 [[maybe_unused]] LocaleMatcherOption matcher,
466 const std::set<std::string> &relevantExtensionKeys)
467 {
468 // 1. Let matcher be options.[[localeMatcher]].
469 // 2. If matcher is "lookup" "lookup", then
470 // a. Let r be LookupMatcher(availableLocales, requestedLocales).
471 // 3. Else,
472 // a. Let r be BestFitMatcher(availableLocales, requestedLocales).
473 JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
474 if (availableLocales->GetLength() == 0 && requestedLocales->GetLength() == 0) {
475 locale.Update(intl::LocaleHelper::DefaultLocale(thread).GetTaggedValue());
476 } else {
477 locale.Update(LookupMatcher(thread, availableLocales, requestedLocales).GetTaggedValue());
478 }
479
480 // 4. Let foundLocale be r.[[locale]].
481 // 5. Let result be a new Record.
482 // 6. Set result.[[dataLocale]] to foundLocale.
483 // 7. Let supportedExtension be "-u".
484 std::string foundLocale = intl::LocaleHelper::ConvertToStdString(locale);
485 icu::Locale foundLocaleData = BuildICULocale(foundLocale);
486 ResolvedLocale result;
487 result.localeData = foundLocaleData;
488 JSHandle<EcmaString> tag = intl::LocaleHelper::ToLanguageTag(thread, foundLocaleData);
489 result.locale = intl::LocaleHelper::ConvertToStdString(tag);
490 std::string supportedExtension = "-u";
491 icu::LocaleBuilder localeBuilder;
492 localeBuilder.setLocale(foundLocaleData).clearExtensions();
493 // 8. For each element key of relevantExtensionKeys in List order, do
494 for (auto &key : relevantExtensionKeys) {
495 auto doubleMatch = foundLocale.find(key);
496 if (doubleMatch == std::string::npos) {
497 continue;
498 }
499 UErrorCode status = U_ZERO_ERROR;
500 std::set<std::string> keyLocaleData;
501 std::unique_ptr<icu::StringEnumeration> wellFormKey(foundLocaleData.createKeywords(status));
502 if (U_FAILURE(status) != 0) {
503 return result;
504 }
505 if (!wellFormKey) {
506 return result;
507 }
508 std::string value;
509
510 // c. Let keyLocaleData be foundLocaleData.[[<key>]].
511 // e. Let value be keyLocaleData[0].
512 if ((key != "ca") && (key != "co") && (key != "nu")) {
513 auto find = JSLocale::LOCALE_MAP.find(key);
514 if (find != JSLocale::LOCALE_MAP.end()) {
515 keyLocaleData = find->second;
516 }
517 if (key == "") {
518 keyLocaleData = JSLocale::LOCALE_MAP.at("lb");
519 }
520 value = *keyLocaleData.begin();
521 }
522
523 // g. Let supportedExtensionAddition be "".
524 // h. If r has an [[extension]] field, then
525 std::string supportedExtensionAddition;
526 size_t found = foundLocale.find("-u-");
527 if (found != std::string::npos) {
528 std::string extension = foundLocale.substr(found + INTL_INDEX_ONE);
529
530 // i. Let requestedValue be UnicodeExtensionValue(r.[[extension]], key).
531 std::string requestedValue = UnicodeExtensionValue(extension, key);
532 if (key == "kn" && requestedValue.empty()) {
533 requestedValue = "true";
534 }
535
536 // ii. If requestedValue is not undefined, then
537 if (requestedValue != "undefined") {
538 // 1. If requestedValue is not the empty String, then
539 if (!requestedValue.empty()) {
540 // a. If keyLocaleData contains requestedValue, then
541 // i. Let value be requestedValue.
542 // ii. Let supportedExtensionAddition be the concatenation of "-", key, "-", and value.
543 if (key == "ca" || key == "co") {
544 if (key == "co") {
545 bool isValidValue = IsWellCollation(foundLocaleData, requestedValue);
546 if (!isValidValue) {
547 continue;
548 }
549 value = requestedValue;
550 supportedExtensionAddition = "-" + key + "-" + value;
551 localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
552 } else {
553 bool isValidValue = IsWellCalendar(foundLocaleData, requestedValue);
554 if (!isValidValue) {
555 continue;
556 }
557 value = requestedValue;
558 supportedExtensionAddition = "-" + key + "-" + value;
559 localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
560 }
561 } else if (key == "nu") {
562 bool isValidValue = IsWellNumberingSystem(requestedValue);
563 if (!isValidValue) {
564 continue;
565 }
566 value = requestedValue;
567 supportedExtensionAddition = "-" + key + "-" + value;
568 localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
569 } else if (keyLocaleData.find(requestedValue) != keyLocaleData.end()) {
570 value = requestedValue;
571 supportedExtensionAddition = "-" + key + "-" + value;
572 localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
573 }
574 }
575 }
576 }
577 result.extensions.emplace(key, value);
578 supportedExtension += supportedExtensionAddition;
579 }
580 size_t found = foundLocale.find("-u-");
581 if (found != std::string::npos) {
582 foundLocale = foundLocale.substr(0, found);
583 }
584
585 // 9. If the number of elements in supportedExtension is greater than 2, then
586 if (supportedExtension.size() > 2) {
587 // a. Let privateIndex be Call(%StringProto_indexOf%, foundLocale, « "-x-" »).
588 size_t privateIndex = foundLocale.find("-x-");
589 // b. If privateIndex = -1, then
590 // i. Let foundLocale be the concatenation of foundLocale and supportedExtension.
591 if (privateIndex == std::string::npos) {
592 foundLocale = foundLocale + supportedExtension;
593 } else {
594 std::string preExtension = foundLocale.substr(0, privateIndex);
595 std::string postExtension = foundLocale.substr(privateIndex);
596 foundLocale = preExtension + supportedExtension + postExtension;
597 }
598
599 tag = intl::LocaleHelper::ToLanguageTag(thread, foundLocaleData);
600 if (!intl::LocaleHelper::IsStructurallyValidLanguageTag(tag)) {
601 result.extensions.erase(result.extensions.begin(), result.extensions.end());
602 result.locale = foundLocale;
603 }
604 tag = intl::LocaleHelper::CanonicalizeUnicodeLocaleId(thread, tag);
605 foundLocale = intl::LocaleHelper::ConvertToStdString(tag);
606 }
607
608 // 10. Set result.[[locale]] to foundLocale.
609 result.locale = foundLocale;
610 UErrorCode status = U_ZERO_ERROR;
611 foundLocaleData = localeBuilder.build(status);
612 result.localeData = foundLocaleData;
613
614 // 11. Return result.
615 return result;
616 }
617
BuildICULocale(const std::string &bcp47Locale)618 icu::Locale JSLocale::BuildICULocale(const std::string &bcp47Locale)
619 {
620 UErrorCode status = U_ZERO_ERROR;
621 icu::Locale icuLocale = icu::Locale::forLanguageTag(bcp47Locale, status);
622 return icuLocale;
623 }
624
ConstructLocaleList(JSThread *thread, const std::vector<std::string> &icuAvailableLocales)625 JSHandle<TaggedArray> JSLocale::ConstructLocaleList(JSThread *thread,
626 const std::vector<std::string> &icuAvailableLocales)
627 {
628 EcmaVM *ecmaVm = thread->GetEcmaVM();
629 ObjectFactory *factory = ecmaVm->GetFactory();
630 JSHandle<TaggedArray> locales = factory->NewTaggedArray(icuAvailableLocales.size());
631 int32_t index = 0;
632 for (const std::string &locale : icuAvailableLocales) {
633 JSHandle<EcmaString> localeStr = factory->NewFromStdString(locale);
634 locales->Set(thread, index++, localeStr);
635 }
636 return locales;
637 }
638
GetNumberingSystem(const icu::Locale &icuLocale)639 std::string JSLocale::GetNumberingSystem(const icu::Locale &icuLocale)
640 {
641 UErrorCode status = U_ZERO_ERROR;
642 std::unique_ptr<icu::NumberingSystem> numberingSystem(icu::NumberingSystem::createInstance(icuLocale, status));
643 if (U_SUCCESS(status) != 0) {
644 return numberingSystem->getName();
645 }
646 return JSLocale::LATN_STRING;
647 }
648
IsWellFormedCurrencyCode(const std::string ¤cy)649 bool JSLocale::IsWellFormedCurrencyCode(const std::string ¤cy)
650 {
651 if (currency.length() != INTL_INDEX_THREE) {
652 return false;
653 }
654 return (IsAToZ(currency[INTL_INDEX_ZERO]) && IsAToZ(currency[INTL_INDEX_ONE]) && IsAToZ(currency[INTL_INDEX_TWO]));
655 }
656
IsWellFormedCalendarCode(const std::string& calendar)657 bool JSLocale::IsWellFormedCalendarCode(const std::string& calendar)
658 {
659 std::string value = calendar;
660 while (true) {
661 std::size_t found_dash = value.find('-');
662 if (found_dash == std::string::npos) {
663 return IsAlphanum(value, INTL_INDEX_THREE, INTL_INDEX_EIGHT);
664 }
665 if (!IsAlphanum(value.substr(0, found_dash), INTL_INDEX_THREE, INTL_INDEX_EIGHT)) {
666 return false;
667 }
668 value = value.substr(found_dash + 1);
669 }
670 }
671
PutElement(JSThread *thread, int index, const JSHandle<JSArray> &array, const JSHandle<JSTaggedValue> &fieldTypeString, const JSHandle<JSTaggedValue> &value)672 JSHandle<JSObject> JSLocale::PutElement(JSThread *thread, int index, const JSHandle<JSArray> &array,
673 const JSHandle<JSTaggedValue> &fieldTypeString,
674 const JSHandle<JSTaggedValue> &value)
675 {
676 auto ecmaVm = thread->GetEcmaVM();
677 ObjectFactory *factory = ecmaVm->GetFactory();
678
679 // Let record be ! ObjectCreate(%ObjectPrototype%).
680 JSHandle<JSObject> record = factory->NewEmptyJSObject();
681
682 auto globalConst = thread->GlobalConstants();
683 // obj.type = field_type_string
684 JSObject::CreateDataPropertyOrThrow(thread, record, globalConst->GetHandledTypeString(), fieldTypeString);
685 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread);
686 // obj.value = value
687 JSObject::CreateDataPropertyOrThrow(thread, record, globalConst->GetHandledValueString(), value);
688 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread);
689
690 JSTaggedValue::SetProperty(thread, JSHandle<JSTaggedValue>::Cast(array), index,
691 JSHandle<JSTaggedValue>::Cast(record), true);
692 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread);
693 return record;
694 }
695
696 // 9.2.11 GetOption ( options, property, type, values, fallback )
GetOptionOfBool(JSThread *thread, const JSHandle<JSObject> &options, const JSHandle<JSTaggedValue> &property, bool fallback, bool *res)697 bool JSLocale::GetOptionOfBool(JSThread *thread, const JSHandle<JSObject> &options,
698 const JSHandle<JSTaggedValue> &property, bool fallback, bool *res)
699 {
700 // 1. Let value be ? Get(options, property).
701 OperationResult operationResult = JSObject::GetProperty(thread, options, property);
702 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
703 JSHandle<JSTaggedValue> value = operationResult.GetValue();
704 *res = fallback;
705 // 2. If value is not undefined, then
706 if (!value->IsUndefined()) {
707 // b. Let value be ToBoolean(value).
708 *res = value->ToBoolean();
709 return true;
710 }
711 // 3. not found
712 return false;
713 }
714
GetNumberFieldType(JSThread *thread, JSTaggedValue x, int32_t fieldId)715 JSHandle<JSTaggedValue> JSLocale::GetNumberFieldType(JSThread *thread, JSTaggedValue x, int32_t fieldId)
716 {
717 ASSERT(x.IsNumber() || x.IsBigInt());
718 double number = 0;
719 auto globalConst = thread->GlobalConstants();
720 if (static_cast<UNumberFormatFields>(fieldId) == UNUM_INTEGER_FIELD) {
721 number = x.IsBigInt() ? number : x.GetNumber();
722 if (x.IsBigInt() || std::isfinite(number)) {
723 return globalConst->GetHandledIntegerString();
724 }
725 if (std::isnan(number)) {
726 return globalConst->GetHandledNanString();
727 }
728 return globalConst->GetHandledInfinityString();
729 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_FRACTION_FIELD) {
730 return globalConst->GetHandledFractionString();
731 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_DECIMAL_SEPARATOR_FIELD) {
732 return globalConst->GetHandledDecimalString();
733 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) {
734 return globalConst->GetHandledGroupString();
735 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_CURRENCY_FIELD) {
736 return globalConst->GetHandledCurrencyString();
737 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_PERCENT_FIELD) {
738 return globalConst->GetHandledPercentSignString();
739 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_SIGN_FIELD) {
740 if (x.IsBigInt()) {
741 JSHandle<JSTaggedValue> bigint(thread, x);
742 JSHandle<BigInt> value(thread, JSTaggedValue::ToBigInt(thread, bigint));
743 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
744 return value->GetSign() ? globalConst->GetHandledMinusSignString()
745 : globalConst->GetHandledPlusSignString();
746 }
747 number = x.GetNumber();
748 return std::signbit(number) ? globalConst->GetHandledMinusSignString()
749 : globalConst->GetHandledPlusSignString();
750 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_SYMBOL_FIELD) {
751 return globalConst->GetHandledExponentSeparatorString();
752 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_SIGN_FIELD) {
753 return globalConst->GetHandledExponentMinusSignString();
754 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_FIELD) {
755 return globalConst->GetHandledExponentIntegerString();
756 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_COMPACT_FIELD) {
757 return globalConst->GetHandledCompactString();
758 } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_MEASURE_UNIT_FIELD) {
759 return globalConst->GetHandledUnitString();
760 } else {
761 LOG_ECMA(FATAL) << "this branch is unreachable";
762 UNREACHABLE();
763 }
764 }
765
766 // 10.1.1 ApplyOptionsToTag( tag, options )
ApplyOptionsToTag(JSThread *thread, const JSHandle<EcmaString> &tag, const JSHandle<JSObject> &options, TagElements &tagElements)767 bool JSLocale::ApplyOptionsToTag(JSThread *thread, const JSHandle<EcmaString> &tag, const JSHandle<JSObject> &options,
768 TagElements &tagElements)
769 {
770 EcmaVM *ecmaVm = thread->GetEcmaVM();
771 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
772 ObjectFactory *factory = ecmaVm->GetFactory();
773 if (*tag == *(factory->GetEmptyString())) {
774 return false;
775 }
776 // 2. If intl::LocaleHelper::IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
777 if (!intl::LocaleHelper::IsStructurallyValidLanguageTag(tag)) {
778 return false;
779 }
780
781 tagElements.language =
782 GetOption(thread, options, globalConst->GetHandledLanguageString(), OptionType::STRING,
783 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
784 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
785
786 // 4. If language is not undefined, then
787 // a. If language does not match the unicode_language_subtag production, throw a RangeError exception.
788 if (!tagElements.language->IsUndefined()) {
789 std::string languageStr =
790 intl::LocaleHelper::ConvertToStdString(JSHandle<EcmaString>::Cast(tagElements.language));
791 if (languageStr[INTL_INDEX_ZERO] == '\0' ||
792 IsAlpha(languageStr, INTL_INDEX_FOUR, INTL_INDEX_FOUR)) {
793 return false;
794 }
795 }
796
797 // 5. Let script be ? GetOption(options, "script", "string", undefined, undefined).
798 tagElements.script =
799 GetOption(thread, options, globalConst->GetHandledScriptString(), OptionType::STRING,
800 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
801 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
802
803 // 6. If script is not undefined, then
804 // a. If script does not match the unicode_script_subtag production, throw a RangeError exception.
805 if (!tagElements.script->IsUndefined()) {
806 std::string scriptStr =
807 intl::LocaleHelper::ConvertToStdString((JSHandle<EcmaString>::Cast(tagElements.script)));
808 if (scriptStr[INTL_INDEX_ZERO] == '\0') {
809 return false;
810 }
811 }
812
813 // 7. Let region be ? GetOption(options, "region", "string", undefined, undefined).
814 // 8. If region is not undefined, then
815 // a. If region does not match the unicode_region_subtag production, throw a RangeError exception.
816 tagElements.region =
817 GetOption(thread, options, globalConst->GetHandledRegionString(), OptionType::STRING,
818 globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
819 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
820
821 if (!tagElements.region->IsUndefined()) {
822 std::string regionStr = intl::LocaleHelper::ConvertToStdString(JSHandle<EcmaString>::Cast(tagElements.region));
823 if (regionStr[INTL_INDEX_ZERO] == '\0') {
824 return false;
825 }
826 }
827 return true;
828 }
829
BuildOptionsTags(const JSHandle<EcmaString> &tag, icu::LocaleBuilder *builder, JSHandle<JSTaggedValue> language, JSHandle<JSTaggedValue> script, JSHandle<JSTaggedValue> region)830 bool BuildOptionsTags(const JSHandle<EcmaString> &tag, icu::LocaleBuilder *builder, JSHandle<JSTaggedValue> language,
831 JSHandle<JSTaggedValue> script, JSHandle<JSTaggedValue> region)
832 {
833 std::string tagStr = intl::LocaleHelper::ConvertToStdString(tag);
834 int32_t len = static_cast<int32_t>(tagStr.length());
835 ASSERT(len > 0);
836 builder->setLanguageTag({ tagStr.c_str(), len });
837 UErrorCode status = U_ZERO_ERROR;
838 icu::Locale locale = builder->build(status);
839 locale.canonicalize(status);
840 if (U_FAILURE(status) != 0) {
841 return false;
842 }
843 builder->setLocale(locale);
844
845 if (!language->IsUndefined()) {
846 std::string languageStr = intl::LocaleHelper::ConvertToStdString(JSHandle<EcmaString>::Cast(language));
847 builder->setLanguage(languageStr);
848 builder->build(status);
849 if ((U_FAILURE(status) != 0)) {
850 return false;
851 }
852 }
853
854 if (!script->IsUndefined()) {
855 std::string scriptStr = intl::LocaleHelper::ConvertToStdString((JSHandle<EcmaString>::Cast(script)));
856 builder->setScript(scriptStr);
857 builder->build(status);
858 if ((U_FAILURE(status) != 0)) {
859 return false;
860 }
861 }
862
863 if (!region->IsUndefined()) {
864 std::string regionStr = intl::LocaleHelper::ConvertToStdString(JSHandle<EcmaString>::Cast(region));
865 builder->setRegion(regionStr);
866 builder->build(status);
867 if ((U_FAILURE(status) != 0)) {
868 return false;
869 }
870 }
871 return true;
872 }
873
InsertOptions(JSThread *thread, const JSHandle<JSObject> &options, icu::LocaleBuilder *builder)874 bool InsertOptions(JSThread *thread, const JSHandle<JSObject> &options, icu::LocaleBuilder *builder)
875 {
876 const std::vector<std::string> emptyValues = {};
877 const GlobalEnvConstants *globalConst = thread->GlobalConstants();
878 std::string strResult;
879 bool findca =
880 JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCalendarString(), emptyValues, &strResult);
881 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
882 if (findca) {
883 if (!uloc_toLegacyType(uloc_toLegacyKey("ca"), strResult.c_str())) {
884 return false;
885 }
886 ThreadNativeScope nativeScope(thread);
887 builder->setUnicodeLocaleKeyword("ca", strResult.c_str());
888 }
889
890 bool findco =
891 JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCollationString(), emptyValues, &strResult);
892 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
893 if (findco) {
894 if (!uloc_toLegacyType(uloc_toLegacyKey("co"), strResult.c_str())) {
895 return false;
896 }
897 ThreadNativeScope nativeScope(thread);
898 builder->setUnicodeLocaleKeyword("co", strResult.c_str());
899 }
900
901 bool findhc = JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledHourCycleString(),
902 JSLocale::HOUR_CYCLE, &strResult);
903 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
904 if (findhc) {
905 if (!uloc_toLegacyType(uloc_toLegacyKey("hc"), strResult.c_str())) {
906 return false;
907 }
908 ThreadNativeScope nativeScope(thread);
909 builder->setUnicodeLocaleKeyword("hc", strResult.c_str());
910 }
911
912 bool findkf = JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCaseFirstString(),
913 JSLocale::CASE_FIRST, &strResult);
914 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
915 if (findkf) {
916 if (!uloc_toLegacyType(uloc_toLegacyKey("kf"), strResult.c_str())) {
917 return false;
918 }
919 ThreadNativeScope nativeScope(thread);
920 builder->setUnicodeLocaleKeyword("kf", strResult.c_str());
921 }
922
923 bool boolResult = false;
924 bool findkn =
925 JSLocale::GetOptionOfBool(thread, options, globalConst->GetHandledNumericString(), false, &boolResult);
926 if (findkn) {
927 strResult = boolResult ? "true" : "false";
928 if (!uloc_toLegacyType(uloc_toLegacyKey("kn"), strResult.c_str())) {
929 return false;
930 }
931 ThreadNativeScope nativeScope(thread);
932 builder->setUnicodeLocaleKeyword("kn", strResult.c_str());
933 }
934
935 bool findnu =
936 JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledNumberingSystemString(), emptyValues,
937 &strResult);
938 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
939 if (findnu) {
940 if (!uloc_toLegacyType(uloc_toLegacyKey("nu"), strResult.c_str())) {
941 return false;
942 }
943 ThreadNativeScope nativeScope(thread);
944 builder->setUnicodeLocaleKeyword("nu", strResult.c_str());
945 }
946 return true;
947 }
948
InitializeLocale(JSThread *thread, const JSHandle<JSLocale> &locale, const JSHandle<EcmaString> &localeString, const JSHandle<JSObject> &options)949 JSHandle<JSLocale> JSLocale::InitializeLocale(JSThread *thread, const JSHandle<JSLocale> &locale,
950 const JSHandle<EcmaString> &localeString,
951 const JSHandle<JSObject> &options)
952 {
953 icu::LocaleBuilder builder;
954 TagElements tagElements;
955 if (!ApplyOptionsToTag(thread, localeString, options, tagElements)) {
956 THROW_RANGE_ERROR_AND_RETURN(thread, "apply option to tag failed", locale);
957 }
958
959 bool res = BuildOptionsTags(localeString, &builder, tagElements.language, tagElements.script, tagElements.region);
960 if (!res) {
961 THROW_RANGE_ERROR_AND_RETURN(thread, "apply option to tag failed", locale);
962 }
963 bool insertResult = InsertOptions(thread, options, &builder);
964 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, locale);
965 UErrorCode status = U_ZERO_ERROR;
966 icu::Locale icuLocale = builder.build(status);
967 icuLocale.canonicalize(status);
968
969 if (!insertResult || (U_FAILURE(status) != 0)) {
970 THROW_RANGE_ERROR_AND_RETURN(thread, "insert or build failed", locale);
971 }
972 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
973 factory->NewJSIntlIcuData(locale, icuLocale, JSLocale::FreeIcuLocale);
974 return locale;
975 }
976
ConvertValue(const UErrorCode &status, std::string &value, const std::string &key)977 int ConvertValue(const UErrorCode &status, std::string &value, const std::string &key)
978 {
979 if (status == U_ILLEGAL_ARGUMENT_ERROR || value.empty()) {
980 return 1;
981 }
982
983 if (value == "yes") {
984 value = "true";
985 }
986
987 if (key == "kf" && value == "true") {
988 return 2; // 2: in this case normalizedKeyword is empty string
989 }
990 return 0;
991 }
992
NormalizeKeywordValue(JSThread *thread, const JSHandle<JSLocale> &locale, const std::string &key)993 JSTaggedValue JSLocale::NormalizeKeywordValue(JSThread *thread, const JSHandle<JSLocale> &locale,
994 const std::string &key)
995 {
996 icu::Locale *icuLocale = locale->GetIcuLocale();
997 UErrorCode status = U_ZERO_ERROR;
998 auto value = icuLocale->getUnicodeKeywordValue<std::string>(key, status);
999
1000 EcmaVM *ecmaVm = thread->GetEcmaVM();
1001 ObjectFactory *factory = ecmaVm->GetFactory();
1002
1003 int result = ConvertValue(status, value, key);
1004 if (result == 1) {
1005 return JSTaggedValue::Undefined();
1006 }
1007 if (result == 2) { // 2: in this case normalizedKeyword is empty string
1008 return factory->GetEmptyString().GetTaggedValue();
1009 }
1010 return factory->NewFromStdString(value).GetTaggedValue();
1011 }
1012
ToString(JSThread *thread, const JSHandle<JSLocale> &locale)1013 JSHandle<EcmaString> JSLocale::ToString(JSThread *thread, const JSHandle<JSLocale> &locale)
1014 {
1015 icu::Locale *icuLocale = locale->GetIcuLocale();
1016 if (icuLocale == nullptr) {
1017 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
1018 return factory->GetEmptyString();
1019 }
1020 JSHandle<EcmaString> result = intl::LocaleHelper::ToLanguageTag(thread, *icuLocale);
1021 RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread);
1022 return result;
1023 }
1024
GetAvailableStringLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales)1025 std::vector<std::string> JSLocale::GetAvailableStringLocales(JSThread *thread,
1026 const JSHandle<TaggedArray> &availableLocales)
1027 {
1028 std::vector<std::string> availableStringLocales;
1029 JSMutableHandle<EcmaString> availableItem(thread, JSTaggedValue::Undefined());
1030 uint32_t availablecalesLength = availableLocales->GetLength();
1031 for (uint32_t i = 0; i < availablecalesLength; i++) {
1032 availableItem.Update(availableLocales->Get(thread, i));
1033 availableStringLocales.emplace_back(intl::LocaleHelper::ConvertToStdString(availableItem));
1034 }
1035 return availableStringLocales;
1036 }
1037
1038 } // namespace panda::ecmascript
1039