1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host.intl;
16
17 import java.util.IllformedLocaleException;
18 import java.util.List;
19
20 import org.apache.commons.lang3.LocaleUtils;
21 import org.htmlunit.corejs.javascript.Context;
22 import org.htmlunit.corejs.javascript.Function;
23 import org.htmlunit.corejs.javascript.FunctionObject;
24 import org.htmlunit.corejs.javascript.Scriptable;
25 import org.htmlunit.corejs.javascript.ScriptableObject;
26 import org.htmlunit.corejs.javascript.VarScope;
27 import org.htmlunit.javascript.HtmlUnitScriptable;
28 import org.htmlunit.javascript.JavaScriptEngine;
29 import org.htmlunit.javascript.configuration.JsxClass;
30 import org.htmlunit.javascript.configuration.JsxConstructor;
31 import org.htmlunit.javascript.configuration.JsxFunction;
32 import org.htmlunit.javascript.configuration.JsxGetter;
33 import org.htmlunit.javascript.configuration.JsxSymbolConstant;
34
35
36
37
38
39
40
41 @JsxClass
42 public class Locale extends HtmlUnitScriptable {
43
44
45 @JsxSymbolConstant
46 public static final String TO_STRING_TAG = "Intl.Locale";
47
48 private static final List<String> ALLOWED_HOUR_CYCLES = List.of("h11", "h12", "h23", "h24");
49 private static final List<String> ALLOWED_CASE_FIRSTS = List.of("upper", "lower", "false");
50
51 private java.util.Locale locale_;
52 private String language_;
53 private String script_;
54 private String region_;
55 private String calendar_;
56 private String collation_;
57 private String numberingSystem_;
58 private String caseFirst_;
59 private String hourCycle_;
60 private boolean numeric_;
61
62
63
64
65 public Locale() {
66 super();
67 }
68
69 private Locale(final java.util.Locale locale) {
70 super();
71 locale_ = locale;
72 language_ = locale.getLanguage();
73 if (!locale.getScript().isEmpty()) {
74 script_ = locale.getScript();
75 }
76 if (!locale.getCountry().isEmpty()) {
77 region_ = locale.getCountry();
78 }
79 if (locale.hasExtensions()) {
80 calendar_ = locale.getUnicodeLocaleType("ca");
81 collation_ = locale.getUnicodeLocaleType("co");
82 numberingSystem_ = locale.getUnicodeLocaleType("nu");
83 caseFirst_ = locale.getUnicodeLocaleType("kf");
84 hourCycle_ = locale.getUnicodeLocaleType("hc");
85 numeric_ = Boolean.parseBoolean(locale.getUnicodeLocaleType("kn"));
86 }
87 }
88
89
90
91
92
93
94
95
96
97
98 @JsxConstructor
99 public static Scriptable jsConstructor(final Context cx, final VarScope scope,
100 final Object[] args, final Function ctorObj, final boolean inNewExpr) {
101 if (args.length == 0 || JavaScriptEngine.isUndefined(args[0])) {
102 throw JavaScriptEngine.typeError("Invalid element in locales argument");
103 }
104
105 final String languageTag = JavaScriptEngine.toString(args[0]);
106 if (languageTag.isEmpty()) {
107 throw JavaScriptEngine.rangeError("Invalid language tag: ");
108 }
109
110 java.util.Locale locale;
111 try {
112 locale = new java.util.Locale.Builder()
113 .setLanguageTag(languageTag)
114 .build();
115 }
116 catch (final IllformedLocaleException e) {
117 throw JavaScriptEngine.rangeError("Invalid language tag: " + languageTag);
118 }
119
120
121 if (args.length > 1 && !JavaScriptEngine.isUndefined(args[1])) {
122 locale = overrideExistingWithOptions(locale, ScriptableObject.ensureScriptableObject(args[1]));
123 }
124
125 final Locale l = new Locale(locale);
126 l.setParentScope(getTopLevelScope(scope));
127 l.setPrototype(((FunctionObject) ctorObj).getClassPrototype());
128 return l;
129 }
130
131 private static java.util.Locale overrideExistingWithOptions(
132 final java.util.Locale existing, final ScriptableObject options) {
133 final java.util.Locale.Builder builder = new java.util.Locale.Builder().setLocale(existing);
134
135 setStringOption(builder, options, "language");
136 setStringOption(builder, options, "script");
137 setStringOption(builder, options, "region");
138 setUnicodeKeyword(builder, options, "calendar", "ca", null);
139 setUnicodeKeyword(builder, options, "collation", "co", null);
140 setUnicodeKeyword(builder, options, "numberingSystem", "nu", null);
141 setUnicodeKeyword(builder, options, "caseFirst", "kf", ALLOWED_CASE_FIRSTS);
142 setUnicodeKeyword(builder, options, "hourCycle", "hc", ALLOWED_HOUR_CYCLES);
143
144 final Object numeric = ScriptableObject.getProperty(options, "numeric");
145 if (numeric != Scriptable.NOT_FOUND && !JavaScriptEngine.isUndefined(numeric)) {
146 final boolean isNumeric = numeric instanceof Boolean ? (Boolean) numeric : true;
147 builder.setUnicodeLocaleKeyword("kn", Boolean.toString(isNumeric));
148 }
149
150 return builder.build();
151 }
152
153 private static void setStringOption(final java.util.Locale.Builder builder,
154 final ScriptableObject options, final String optionName) {
155 final Object value = ScriptableObject.getProperty(options, optionName);
156 if (value == Scriptable.NOT_FOUND || JavaScriptEngine.isUndefined(value)) {
157 return;
158 }
159 try {
160 final String s = JavaScriptEngine.toString(value);
161 switch (optionName) {
162 case "language":
163 builder.setLanguage(s);
164 break;
165 case "script":
166 builder.setScript(s);
167 break;
168 case "region":
169 builder.setRegion(s);
170 break;
171 default:
172 break;
173 }
174 }
175 catch (final Exception e) {
176 throw JavaScriptEngine.rangeError("Invalid value for option \"" + optionName + "\"");
177 }
178 }
179
180 private static void setUnicodeKeyword(final java.util.Locale.Builder builder,
181 final ScriptableObject options, final String optionName, final String unicodeKey,
182 final List<String> allowedValues) {
183 final Object value = ScriptableObject.getProperty(options, optionName);
184 if (value == Scriptable.NOT_FOUND || JavaScriptEngine.isUndefined(value)) {
185 return;
186 }
187 final String s;
188 try {
189 s = JavaScriptEngine.toString(value);
190 }
191 catch (final Exception e) {
192 throw JavaScriptEngine.rangeError("Invalid value for option \"" + optionName + "\"");
193 }
194 if (allowedValues != null && !allowedValues.contains(s)) {
195 throw JavaScriptEngine.rangeError("Invalid value for option \"" + optionName + "\"");
196 }
197 builder.setUnicodeLocaleKeyword(unicodeKey, s);
198 }
199
200
201
202
203 @JsxGetter
204 public Object getLanguage() {
205 return language_ != null ? language_ : JavaScriptEngine.UNDEFINED;
206 }
207
208
209
210
211 @JsxGetter
212 public Object getScript() {
213 return script_ != null ? script_ : JavaScriptEngine.UNDEFINED;
214 }
215
216
217
218
219 @JsxGetter
220 public Object getRegion() {
221 return region_ != null ? region_ : JavaScriptEngine.UNDEFINED;
222 }
223
224
225
226
227 @JsxGetter
228 public Object getCalendar() {
229 return calendar_ != null ? calendar_ : JavaScriptEngine.UNDEFINED;
230 }
231
232
233
234
235 @JsxGetter
236 public Object getCollation() {
237 return collation_ != null ? collation_ : JavaScriptEngine.UNDEFINED;
238 }
239
240
241
242
243 @JsxGetter
244 public Object getNumberingSystem() {
245 return numberingSystem_ != null ? numberingSystem_ : JavaScriptEngine.UNDEFINED;
246 }
247
248
249
250
251 @JsxGetter
252 public Object getCaseFirst() {
253 return caseFirst_ != null ? caseFirst_ : JavaScriptEngine.UNDEFINED;
254 }
255
256
257
258
259 @JsxGetter
260 public Object getHourCycle() {
261 return hourCycle_ != null ? hourCycle_ : JavaScriptEngine.UNDEFINED;
262 }
263
264
265
266
267 @JsxGetter
268 public boolean isNumeric() {
269 return numeric_;
270 }
271
272
273
274
275 @JsxGetter
276 public Object getBaseName() {
277 final String variant = locale_.getVariant().replace("_", "-");
278 return language_
279 + (script_ != null ? "-" + script_ : "")
280 + (region_ != null ? "-" + region_ : "")
281 + (!variant.isEmpty() ? "-" + variant : "");
282 }
283
284
285
286
287
288 @JsxFunction
289 public Locale maximize() {
290 final String region;
291 if (region_ != null) {
292 region = region_;
293 }
294 else {
295 final java.util.List<java.util.Locale> locales =
296 LocaleUtils.countriesByLanguage(language_);
297 if (!locales.isEmpty()) {
298 region = locales.get(0).getCountry();
299 }
300 else {
301 region = null;
302 }
303 }
304
305 final java.util.Locale locale = new java.util.Locale.Builder()
306 .setLanguage(language_)
307 .setScript(script_)
308 .setRegion(region)
309 .setExtension('u', locale_.getExtension('u'))
310 .build();
311
312 final Locale l = new Locale(locale);
313 l.setParentScope(getTopLevelScope(getParentScope()));
314 l.setPrototype(this.getPrototype());
315 return l;
316 }
317
318
319
320
321
322 @JsxFunction
323 public Locale minimize() {
324 final java.util.Locale locale = new java.util.Locale.Builder()
325 .setLanguage(language_)
326 .setExtension('u', locale_.getExtension('u'))
327 .build();
328
329 final Locale l = new Locale(locale);
330 l.setParentScope(getTopLevelScope(getParentScope()));
331 l.setPrototype(this.getPrototype());
332 return l;
333 }
334
335
336
337
338 @JsxFunction(functionName = "toString")
339 public String jsToString() {
340 if (locale_ == null) {
341 return super.toString();
342 }
343 return locale_.toLanguageTag();
344 }
345
346
347
348
349 @Override
350 public Object getDefaultValue(final Class<?> hint) {
351 if (getPrototype() != null && (String.class.equals(hint) || hint == null)) {
352 return jsToString();
353 }
354 return super.getDefaultValue(hint);
355 }
356 }