• 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 #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