1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host.intl;
16
17 import static org.htmlunit.BrowserVersionFeatures.JS_INTL_V8_BREAK_ITERATOR;
18
19 import java.lang.reflect.Method;
20 import java.util.ArrayList;
21 import java.util.IllformedLocaleException;
22 import java.util.LinkedHashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 import org.apache.commons.lang3.LocaleUtils;
28 import org.htmlunit.BrowserVersion;
29 import org.htmlunit.corejs.javascript.Context;
30 import org.htmlunit.corejs.javascript.Function;
31 import org.htmlunit.corejs.javascript.FunctionObject;
32 import org.htmlunit.corejs.javascript.NativeArray;
33 import org.htmlunit.corejs.javascript.Scriptable;
34 import org.htmlunit.corejs.javascript.ScriptableObject;
35 import org.htmlunit.corejs.javascript.SymbolKey;
36 import org.htmlunit.corejs.javascript.TopLevel;
37 import org.htmlunit.corejs.javascript.VarScope;
38 import org.htmlunit.javascript.HtmlUnitScriptable;
39 import org.htmlunit.javascript.JavaScriptEngine;
40 import org.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration;
41 import org.htmlunit.javascript.configuration.ClassConfiguration;
42 import org.htmlunit.javascript.configuration.JsxClass;
43 import org.htmlunit.javascript.configuration.JsxStaticFunction;
44
45
46
47
48
49
50
51
52
53
54 @JsxClass
55 public class Intl extends HtmlUnitScriptable {
56
57
58
59
60
61
62
63 public static void init(final TopLevel scope, final ScriptableObject globalThis,
64 final BrowserVersion browserVersion) {
65 final Intl intl = new Intl();
66 intl.setParentScope(scope);
67 intl.defineProperty(SymbolKey.TO_STRING_TAG, "Intl", ScriptableObject.DONTENUM | ScriptableObject.READONLY);
68 intl.defineProperties(scope, browserVersion);
69
70
71 final ClassConfiguration intlConfig =
72 AbstractJavaScriptConfiguration.getClassConfiguration(Intl.class, browserVersion);
73 if (intlConfig != null) {
74 defineStaticFunctions(intlConfig, scope, intl);
75 }
76
77 globalThis.defineProperty(intl.getClassName(), intl, ScriptableObject.DONTENUM);
78 }
79
80 private void defineProperties(final TopLevel scope, final BrowserVersion browserVersion) {
81 define(scope, Collator.class, browserVersion);
82 define(scope, DateTimeFormat.class, browserVersion);
83 define(scope, Locale.class, browserVersion);
84 define(scope, NumberFormat.class, browserVersion);
85 if (browserVersion.hasFeature(JS_INTL_V8_BREAK_ITERATOR)) {
86 define(scope, V8BreakIterator.class, browserVersion);
87 }
88 }
89
90 private void define(final TopLevel scope, final Class<? extends HtmlUnitScriptable> c,
91 final BrowserVersion browserVersion) {
92 try {
93 final ClassConfiguration config = AbstractJavaScriptConfiguration.getClassConfiguration(c, browserVersion);
94 final HtmlUnitScriptable prototype = JavaScriptEngine.configureClass(config, scope);
95 final FunctionObject constructorFn = new FunctionObject(config.getJsConstructor().getKey(),
96 config.getJsConstructor().getValue(), scope);
97
98 JavaScriptEngine.setFunctionProtoAndParent(constructorFn, Context.getCurrentContext(), scope);
99 constructorFn.setImmunePrototypeProperty(prototype);
100 prototype.setParentScope(scope);
101 ScriptableObject.defineProperty(prototype, "constructor", constructorFn, ScriptableObject.DONTENUM);
102 constructorFn.setParentScope(scope);
103
104 ScriptableObject.defineProperty(this, prototype.getClassName(), constructorFn, ScriptableObject.DONTENUM);
105
106 defineStaticFunctions(config, scope, constructorFn);
107 }
108 catch (final Exception e) {
109 throw JavaScriptEngine.throwAsScriptRuntimeEx(e);
110 }
111 }
112
113 private static void defineStaticFunctions(final ClassConfiguration config,
114 final VarScope scope, final ScriptableObject target) {
115 final Map<String, Method> staticFunctionMap = config.getStaticFunctionMap();
116 if (staticFunctionMap != null) {
117 for (final Map.Entry<String, Method> entry : staticFunctionMap.entrySet()) {
118 final FunctionObject fn = new FunctionObject(entry.getKey(), entry.getValue(), scope);
119 target.defineProperty(entry.getKey(), fn, ScriptableObject.DONTENUM);
120 }
121 }
122 }
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 @JsxStaticFunction
138 public static Object getCanonicalLocales(final Context cx, final VarScope scope,
139 final Scriptable thisObj, final Object[] args, final Function funObj) {
140 if (args.length == 0 || JavaScriptEngine.isUndefined(args[0])) {
141 return cx.newArray(ScriptableObject.getTopLevelScope(scope), new Object[0]);
142 }
143
144 final Object localesArgument = args[0];
145 if (localesArgument == null) {
146 throw JavaScriptEngine.typeError("Cannot convert null to object");
147 }
148
149 final List<String> languageTags = new ArrayList<>();
150 if (localesArgument instanceof String s) {
151 languageTags.add(s);
152 }
153 else if (localesArgument instanceof Scriptable scriptable) {
154 if (JavaScriptEngine.isArrayLike(scriptable)) {
155 JavaScriptEngine.iterateArrayLike(cx, scriptable, elem -> {
156 if (elem instanceof String s) {
157 languageTags.add(s);
158 }
159 else if (elem instanceof ScriptableObject) {
160 languageTags.add(JavaScriptEngine.toString(elem));
161 }
162 else {
163 throw JavaScriptEngine.typeError("Invalid element in locales argument");
164 }
165 });
166 }
167 else {
168 languageTags.add(JavaScriptEngine.toString(localesArgument));
169 }
170 }
171
172 final Set<String> canonicalLocales = new LinkedHashSet<>(languageTags.size());
173 for (final String tag : languageTags) {
174 try {
175 canonicalLocales.add(
176 new java.util.Locale.Builder().setLanguageTag(tag).build().toLanguageTag());
177 }
178 catch (final IllformedLocaleException e) {
179 throw JavaScriptEngine.rangeError("Invalid language tag: '" + tag + "'");
180 }
181 }
182
183 return cx.newArray(ScriptableObject.getTopLevelScope(scope), canonicalLocales.toArray());
184 }
185
186
187
188
189
190
191 static Scriptable supportedLocalesOf(final Scriptable localesArgument) {
192 final String[] locales;
193 if (localesArgument instanceof NativeArray array) {
194 locales = new String[(int) array.getLength()];
195 for (int i = 0; i < locales.length; i++) {
196 locales[i] = JavaScriptEngine.toString(array.get(i));
197 }
198 }
199 else {
200 locales = new String[] {JavaScriptEngine.toString(localesArgument)};
201 }
202
203 final List<String> supportedLocales = new ArrayList<>();
204 for (final String locale : locales) {
205 if (locale.isEmpty()) {
206 throw JavaScriptEngine.rangeError("Invalid language tag: '" + locale + "'");
207 }
208 final java.util.Locale l = java.util.Locale.forLanguageTag(locale);
209 if (LocaleUtils.isAvailableLocale(l)) {
210 supportedLocales.add(locale);
211 }
212 }
213
214 return Context.getCurrentContext().newArray(
215 ScriptableObject.getTopLevelScope(localesArgument.getParentScope()), supportedLocales.toArray());
216 }
217 }