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