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