1 /*
2 * Copyright (c) 2021-2022 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_displaynames.h"
17
18 #include <cstring>
19
20 #include "ecmascript/intl/locale_helper.h"
21 #include "ecmascript/global_env.h"
22 #include "ecmascript/global_env_constants.h"
23 #include "ecmascript/object_factory-inl.h"
24
25 #include "unicode/errorcode.h"
26 #include "unicode/locdspnm.h"
27 #include "unicode/locid.h"
28 #include "unicode/udisplaycontext.h"
29 #include "unicode/uloc.h"
30 #include "unicode/unistr.h"
31 #include "unicode/uscript.h"
32 #include "unicode/ustring.h"
33
34 #if defined(__clang__)
35 #pragma clang diagnostic push
36 #pragma clang diagnostic ignored "-Wshadow"
37 #elif defined(__GNUC__)
38 #pragma GCC diagnostic push
39 #pragma GCC diagnostic ignored "-Wshadow"
40 #endif
41 #include "unicode/localebuilder.h"
42 #if defined(__clang__)
43 #pragma clang diagnostic pop
44 #elif defined(__GNUC__)
45 #pragma GCC diagnostic pop
46 #endif
47
48 namespace panda::ecmascript {
GetIcuLocaleDisplayNames() const49 icu::LocaleDisplayNames *JSDisplayNames::GetIcuLocaleDisplayNames() const
50 {
51 ASSERT(GetIcuLDN().IsJSNativePointer());
52 auto result = JSNativePointer::Cast(GetIcuLDN().GetTaggedObject())->GetExternalPointer();
53 return reinterpret_cast<icu::LocaleDisplayNames *>(result);
54 }
55
FreeIcuLocaleDisplayNames(void * pointer,void * hint)56 void JSDisplayNames::FreeIcuLocaleDisplayNames(void *pointer, [[maybe_unused]] void* hint)
57 {
58 if (pointer == nullptr) {
59 return;
60 }
61 auto icuLocaleDisplayNames = reinterpret_cast<icu::LocaleDisplayNames *>(pointer);
62 delete icuLocaleDisplayNames;
63 }
64
SetIcuLocaleDisplayNames(JSThread * thread,const JSHandle<JSDisplayNames> & displayNames,icu::LocaleDisplayNames * iculocaledisplaynames,const DeleteEntryPoint & callback)65 void JSDisplayNames::SetIcuLocaleDisplayNames(JSThread *thread, const JSHandle<JSDisplayNames> &displayNames,
66 icu::LocaleDisplayNames* iculocaledisplaynames,
67 const DeleteEntryPoint &callback)
68 {
69 EcmaVM *ecmaVm = thread->GetEcmaVM();
70 ObjectFactory *factory = ecmaVm->GetFactory();
71
72 ASSERT(iculocaledisplaynames != nullptr);
73 JSTaggedValue data = displayNames->GetIcuLDN();
74 if (data.IsJSNativePointer()) {
75 JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject());
76 native->ResetExternalPointer(iculocaledisplaynames);
77 return;
78 }
79 JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(iculocaledisplaynames, callback);
80 displayNames->SetIcuLDN(thread, pointer.GetTaggedValue());
81 }
82
GetAvailableLocales(JSThread * thread)83 JSHandle<TaggedArray> JSDisplayNames::GetAvailableLocales(JSThread *thread)
84 {
85 const char *key = "calendar";
86 const char *path = nullptr;
87 std::vector<std::string> availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, key, path);
88 JSHandle<TaggedArray> availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales);
89 return availableLocales;
90 }
91
92 namespace {
IsUnicodeScriptSubtag(const std::string & value)93 bool IsUnicodeScriptSubtag(const std::string& value)
94 {
95 UErrorCode status = U_ZERO_ERROR;
96 icu::LocaleBuilder builder;
97 builder.setScript(value).build(status);
98 return U_SUCCESS(status);
99 }
100
IsUnicodeRegionSubtag(const std::string & value)101 bool IsUnicodeRegionSubtag(const std::string& value)
102 {
103 UErrorCode status = U_ZERO_ERROR;
104 icu::LocaleBuilder builder;
105 builder.setRegion(value).build(status);
106 return U_SUCCESS(status);
107 }
108 }
109
110 // InitializeDisplayNames ( displayNames, locales, options )
InitializeDisplayNames(JSThread * thread,const JSHandle<JSDisplayNames> & displayNames,const JSHandle<JSTaggedValue> & locales,const JSHandle<JSTaggedValue> & options)111 JSHandle<JSDisplayNames> JSDisplayNames::InitializeDisplayNames(JSThread *thread,
112 const JSHandle<JSDisplayNames> &displayNames,
113 const JSHandle<JSTaggedValue> &locales,
114 const JSHandle<JSTaggedValue> &options)
115 {
116 [[maybe_unused]] EcmaHandleScope scope(thread);
117 EcmaVM *ecmaVm = thread->GetEcmaVM();
118 ObjectFactory *factory = ecmaVm->GetFactory();
119 auto globalConst = thread->GlobalConstants();
120 // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
121 JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
122 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDisplayNames, thread);
123
124 // 4. If options is undefined, throw a TypeError exception.
125 if (options->IsUndefined()) {
126 THROW_TYPE_ERROR_AND_RETURN(thread, "options is undefined", displayNames);
127 }
128
129 // 5. Let options be ? GetOptionsObject(options).
130 JSHandle<JSObject> optionsObject = JSTaggedValue::ToObject(thread, options);
131 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDisplayNames, thread);
132
133 // Note: No need to create a record. It's not observable.
134 // 6. Let opt be a new Record.
135 // 7. Let localeData be %DisplayNames%.[[LocaleData]].
136 // 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
137 JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
138 auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
139 thread, optionsObject, property, {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
140 {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
141 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDisplayNames, thread);
142
143 // 10. Let r be ResolveLocale(%DisplayNames%.[[AvailableLocales]], requestedLocales, opt,
144 // %DisplayNames%.[[RelevantExtensionKeys]]).
145 JSHandle<TaggedArray> availableLocales;
146 if (requestedLocales->GetLength() == 0) {
147 availableLocales = factory->EmptyArray();
148 } else {
149 availableLocales = JSDisplayNames::GetAvailableLocales(thread);
150 }
151 std::set<std::string> relevantExtensionKeys {""};
152 ResolvedLocale r =
153 JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
154 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDisplayNames, thread);
155 icu::Locale icuLocale = r.localeData;
156
157 // 11. Let style be ? GetOption(options, "style", "string", « "narrow", "short", "long" », "long").
158 property = globalConst->GetHandledStyleString();
159 auto StyOpt = JSLocale::GetOptionOfString<StyOption>(thread, optionsObject, property,
160 {StyOption::NARROW, StyOption::SHORT, StyOption::LONG},
161 {"narrow", "short", "long"}, StyOption::LONG);
162 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDisplayNames, thread);
163
164 // 12. Set DisplayNames.[[Style]] to style.
165 displayNames->SetStyle(StyOpt);
166
167 // 13. Let type be ? GetOption(options, "type", "string", « "language", "region", "script", "currency" »,
168 // "undefined").
169 property = globalConst->GetHandledTypeString();
170 auto type = JSLocale::GetOptionOfString<TypednsOption>(thread, optionsObject, property,
171 {TypednsOption::LANGUAGE, TypednsOption::REGION,
172 TypednsOption::SCRIPT, TypednsOption::CURRENCY},
173 {"language", "region", "script", "currency"},
174 TypednsOption::UNDEFINED);
175 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDisplayNames, thread);
176
177 // 14. If type is undefined, throw a TypeError exception.
178 if (type == TypednsOption::UNDEFINED) {
179 THROW_TYPE_ERROR_AND_RETURN(thread, "type is undefined", displayNames);
180 }
181
182 // 15. Set displayNames.[[Type]] to type.
183 displayNames->SetType(type);
184
185 // 16. Let fallback be ? GetOption(options, "fallback", "string", « "code", "none" », "code").
186 property = globalConst->GetHandledFallbackString();
187 auto fallback = JSLocale::GetOptionOfString<FallbackOption>(thread, optionsObject, property,
188 {FallbackOption::CODE, FallbackOption::NONE},
189 {"code", "none"}, FallbackOption::CODE);
190 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDisplayNames, thread);
191
192 // 17. Set displayNames.[[Fallback]] to fallback.
193 displayNames->SetFallback(fallback);
194
195 // 18. Set displayNames.[[Locale]] to the value of r.[[Locale]].
196 JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
197 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSDisplayNames, thread);
198 displayNames->SetLocale(thread, localeStr.GetTaggedValue());
199 // 19. Let dataLocale be r.[[dataLocale]].
200 // 20. Let dataLocaleData be localeData.[[<dataLocale>]].
201 // 21. Let types be dataLocaleData.[[types]].
202 // 22. Assert: types is a Record (see 12.3.3).
203 // 23. Let typeFields be types.[[<type>]].
204 // 24. Assert: typeFields is a Record (see 12.3.3).
205 // 25. Let styleFields be typeFields.[[<style>]].
206 // 26. Assert: styleFields is a Record (see 12.3.3).
207 // 27. Set displayNames.[[Fields]] to styleFields.
208 // 28. Return displayNames.
209
210 // Trans StyOption to ICU Style
211 UDisplayContext uStyle;
212 switch (StyOpt) {
213 case StyOption::LONG:
214 uStyle = UDISPCTX_LENGTH_FULL;
215 break;
216 case StyOption::SHORT:
217 uStyle = UDISPCTX_LENGTH_SHORT;
218 break;
219 case StyOption::NARROW:
220 uStyle = UDISPCTX_LENGTH_SHORT;
221 break;
222 default:
223 LOG_ECMA(FATAL) << "this branch is unreachable";
224 UNREACHABLE();
225 }
226 UDisplayContext displayContext[] = {uStyle};
227 icu::LocaleDisplayNames *icudisplaynames(icu::LocaleDisplayNames::createInstance(icuLocale, displayContext, 1));
228 if (icudisplaynames == nullptr) {
229 delete icudisplaynames;
230 THROW_RANGE_ERROR_AND_RETURN(thread, "create icu::LocaleDisplayNames failed", displayNames);
231 }
232 SetIcuLocaleDisplayNames(thread, displayNames, icudisplaynames, JSDisplayNames::FreeIcuLocaleDisplayNames);
233 return displayNames;
234 }
235
236 // CanonicalCodeForDisplayNames ( type, code )
CanonicalCodeForDisplayNames(JSThread * thread,const JSHandle<JSDisplayNames> & displayNames,const TypednsOption & typeOpt,const JSHandle<EcmaString> & code)237 JSHandle<EcmaString> JSDisplayNames::CanonicalCodeForDisplayNames(JSThread *thread,
238 const JSHandle<JSDisplayNames> &displayNames,
239 const TypednsOption &typeOpt,
240 const JSHandle<EcmaString> &code)
241 {
242 if (typeOpt == TypednsOption::LANGUAGE) {
243 // a. If code does not match the unicode_language_id production, throw a RangeError exception.
244 UErrorCode status = U_ZERO_ERROR;
245 std::string codeSt = intl::LocaleHelper::ConvertToStdString(code);
246 icu::Locale loc = icu::Locale(icu::Locale::forLanguageTag(codeSt, status).getBaseName());
247 std::string checked = loc.toLanguageTag<std::string>(status);
248 if (checked.size() == 0) {
249 THROW_TYPE_ERROR_AND_RETURN(thread, "not match the language id", code);
250 }
251 if (U_FAILURE(status)) {
252 THROW_TYPE_ERROR_AND_RETURN(thread, "not match the unicode_language_id", code);
253 }
254 // b. If IsStructurallyValidLanguageTag(code) is false, throw a RangeError exception.
255 // c. Set code to CanonicalizeUnicodeLocaleId(code).
256 // d. Return code.
257 if (!intl::LocaleHelper::IsStructurallyValidLanguageTag(code)) {
258 THROW_TYPE_ERROR_AND_RETURN(thread, "not a structurally valid", code);
259 }
260 JSHandle<EcmaString> codeStr = intl::LocaleHelper::CanonicalizeUnicodeLocaleId(thread, code);
261 RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread);
262 icu::LocaleDisplayNames *icuLocaldisplaynames = displayNames->GetIcuLocaleDisplayNames();
263 icu::UnicodeString result;
264 std::string codeString = intl::LocaleHelper::ConvertToStdString(codeStr);
265 icuLocaldisplaynames->languageDisplayName(codeString.c_str(), result);
266 JSHandle<EcmaString> codeResult = intl::LocaleHelper::UStringToString(thread, result);
267 return codeResult;
268 } else if (typeOpt == TypednsOption::REGION) {
269 // a. If code does not match the unicode_region_subtag production, throw a RangeError exception.
270 std::string regionCode = intl::LocaleHelper::ConvertToStdString(code);
271 if (!IsUnicodeRegionSubtag(regionCode)) {
272 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid region", code);
273 }
274 // b. Let code be the result of mapping code to upper case as described in 6.1.
275 // c. Return code.
276 icu::LocaleDisplayNames *icuLocaldisplaynames = displayNames->GetIcuLocaleDisplayNames();
277 icu::UnicodeString result;
278 icuLocaldisplaynames->regionDisplayName(regionCode.c_str(), result);
279 JSHandle<EcmaString> codeResult = intl::LocaleHelper::UStringToString(thread, result);
280 return codeResult;
281 } else if (typeOpt == TypednsOption::SCRIPT) {
282 std::string scriptCode = intl::LocaleHelper::ConvertToStdString(code);
283 if (!IsUnicodeScriptSubtag(scriptCode)) {
284 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid script", code);
285 }
286 icu::LocaleDisplayNames *icuLocaldisplaynames = displayNames->GetIcuLocaleDisplayNames();
287 icu::UnicodeString result;
288 icuLocaldisplaynames->scriptDisplayName(scriptCode.c_str(), result);
289 JSHandle<EcmaString> codeResult = intl::LocaleHelper::UStringToString(thread, result);
290 return codeResult;
291 }
292 // 4. 4. Assert: type is "currency".
293 // 5. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception.
294 ASSERT(typeOpt == TypednsOption::CURRENCY);
295 std::string cCode = intl::LocaleHelper::ConvertToStdString(code);
296 if (!JSLocale::IsWellFormedCurrencyCode(cCode)) {
297 THROW_RANGE_ERROR_AND_RETURN(thread, "not a wellformed currency code", code);
298 }
299 icu::LocaleDisplayNames *icuLocaldisplaynames = displayNames->GetIcuLocaleDisplayNames();
300 icu::UnicodeString result;
301 icuLocaldisplaynames->keyValueDisplayName("currency", cCode.c_str(), result);
302 JSHandle<EcmaString> codeResult = intl::LocaleHelper::UStringToString(thread, result);
303 return codeResult;
304 }
305
StyOptionToEcmaString(JSThread * thread,StyOption style)306 JSHandle<JSTaggedValue> StyOptionToEcmaString(JSThread *thread, StyOption style)
307 {
308 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
309 auto globalConst = thread->GlobalConstants();
310 switch (style) {
311 case StyOption::LONG:
312 result.Update(globalConst->GetHandledLongString().GetTaggedValue());
313 break;
314 case StyOption::SHORT:
315 result.Update(globalConst->GetHandledShortString().GetTaggedValue());
316 break;
317 case StyOption::NARROW:
318 result.Update(globalConst->GetHandledNarrowString().GetTaggedValue());
319 break;
320 default:
321 LOG_ECMA(FATAL) << "this branch is unreachable";
322 UNREACHABLE();
323 }
324 return result;
325 }
326
TypeOptionToEcmaString(JSThread * thread,TypednsOption type)327 JSHandle<JSTaggedValue> TypeOptionToEcmaString(JSThread *thread, TypednsOption type)
328 {
329 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
330 auto globalConst = thread->GlobalConstants();
331 switch (type) {
332 case TypednsOption::LANGUAGE:
333 result.Update(globalConst->GetHandledLanguageString().GetTaggedValue());
334 break;
335 case TypednsOption::CALENDAR:
336 result.Update(globalConst->GetHandledCalendarString().GetTaggedValue());
337 break;
338 case TypednsOption::CURRENCY:
339 result.Update(globalConst->GetHandledCurrencyString().GetTaggedValue());
340 break;
341 case TypednsOption::DATETIMEFIELD:
342 result.Update(globalConst->GetHandledDateTimeFieldString().GetTaggedValue());
343 break;
344 case TypednsOption::REGION:
345 result.Update(globalConst->GetHandledRegionString().GetTaggedValue());
346 break;
347 case TypednsOption::SCRIPT:
348 result.Update(globalConst->GetHandledScriptString().GetTaggedValue());
349 break;
350 default:
351 LOG_ECMA(FATAL) << "this branch is unreachable";
352 UNREACHABLE();
353 }
354 return result;
355 }
356
FallbackOptionToEcmaString(JSThread * thread,FallbackOption fallback)357 JSHandle<JSTaggedValue> FallbackOptionToEcmaString(JSThread *thread, FallbackOption fallback)
358 {
359 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
360 auto globalConst = thread->GlobalConstants();
361 switch (fallback) {
362 case FallbackOption::CODE:
363 result.Update(globalConst->GetHandledCodeString().GetTaggedValue());
364 break;
365 case FallbackOption::NONE:
366 result.Update(globalConst->GetHandledNoneString().GetTaggedValue());
367 break;
368 default:
369 LOG_ECMA(FATAL) << "this branch is unreachable";
370 UNREACHABLE();
371 }
372 return result;
373 }
374
ResolvedOptions(JSThread * thread,const JSHandle<JSDisplayNames> & displayNames,const JSHandle<JSObject> & options)375 void JSDisplayNames::ResolvedOptions(JSThread *thread, const JSHandle<JSDisplayNames> &displayNames,
376 const JSHandle<JSObject> &options)
377 {
378 auto globalConst = thread->GlobalConstants();
379
380 // [[Locale]]
381 JSHandle<JSTaggedValue> propertyKey = globalConst->GetHandledLocaleString();
382 JSHandle<JSTaggedValue> locale(thread, displayNames->GetLocale());
383 JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, locale);
384 RETURN_IF_ABRUPT_COMPLETION(thread);
385
386 // [[Style]]
387 StyOption style = displayNames->GetStyle();
388 propertyKey = globalConst->GetHandledStyleString();
389 JSHandle<JSTaggedValue> styleString = StyOptionToEcmaString(thread, style);
390 JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, styleString);
391 RETURN_IF_ABRUPT_COMPLETION(thread);
392
393 // [[type]]
394 TypednsOption type = displayNames->GetType();
395 propertyKey = globalConst->GetHandledTypeString();
396 JSHandle<JSTaggedValue> typeString = TypeOptionToEcmaString(thread, type);
397 JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, typeString);
398 RETURN_IF_ABRUPT_COMPLETION(thread);
399
400 // [[fallback]]
401 FallbackOption fallback = displayNames->GetFallback();
402 propertyKey = globalConst->GetHandledFallbackString();
403 JSHandle<JSTaggedValue> fallbackString = FallbackOptionToEcmaString(thread, fallback);
404 JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, fallbackString);
405 RETURN_IF_ABRUPT_COMPLETION(thread);
406 }
407 } // namespace panda::ecmascript
408