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