OLD | NEW |
| (Empty) |
1 // Copyright 2013 the V8 project authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 // ECMAScript 402 API implementation. | |
6 | |
7 /** | |
8 * Intl object is a single object that has some named properties, | |
9 * all of which are constructors. | |
10 */ | |
11 (function(global, utils) { | |
12 | |
13 "use strict"; | |
14 | |
15 %CheckIsBootstrapping(); | |
16 | |
17 // ------------------------------------------------------------------- | |
18 // Imports | |
19 | |
20 var ArrayIndexOf; | |
21 var ArrayJoin; | |
22 var ArrayPush; | |
23 var IsFinite; | |
24 var IsNaN; | |
25 var GlobalBoolean = global.Boolean; | |
26 var GlobalDate = global.Date; | |
27 var GlobalNumber = global.Number; | |
28 var GlobalRegExp = global.RegExp; | |
29 var GlobalString = global.String; | |
30 var MathFloor; | |
31 var ObjectDefineProperties = utils.ImportNow("ObjectDefineProperties"); | |
32 var ObjectDefineProperty = utils.ImportNow("ObjectDefineProperty"); | |
33 var RegExpTest; | |
34 var StringIndexOf; | |
35 var StringLastIndexOf; | |
36 var StringMatch; | |
37 var StringReplace; | |
38 var StringSplit; | |
39 var StringSubstr; | |
40 var StringSubstring; | |
41 | |
42 utils.Import(function(from) { | |
43 ArrayIndexOf = from.ArrayIndexOf; | |
44 ArrayJoin = from.ArrayJoin; | |
45 ArrayPush = from.ArrayPush; | |
46 IsFinite = from.IsFinite; | |
47 IsNaN = from.IsNaN; | |
48 MathFloor = from.MathFloor; | |
49 RegExpTest = from.RegExpTest; | |
50 StringIndexOf = from.StringIndexOf; | |
51 StringLastIndexOf = from.StringLastIndexOf; | |
52 StringMatch = from.StringMatch; | |
53 StringReplace = from.StringReplace; | |
54 StringSplit = from.StringSplit; | |
55 StringSubstr = from.StringSubstr; | |
56 StringSubstring = from.StringSubstring; | |
57 }); | |
58 | |
59 // ------------------------------------------------------------------- | |
60 | |
61 var Intl = {}; | |
62 | |
63 %AddNamedProperty(global, "Intl", Intl, DONT_ENUM); | |
64 | |
65 /** | |
66 * Caches available locales for each service. | |
67 */ | |
68 var AVAILABLE_LOCALES = { | |
69 'collator': UNDEFINED, | |
70 'numberformat': UNDEFINED, | |
71 'dateformat': UNDEFINED, | |
72 'breakiterator': UNDEFINED | |
73 }; | |
74 | |
75 /** | |
76 * Caches default ICU locale. | |
77 */ | |
78 var DEFAULT_ICU_LOCALE = UNDEFINED; | |
79 | |
80 /** | |
81 * Unicode extension regular expression. | |
82 */ | |
83 var UNICODE_EXTENSION_RE = UNDEFINED; | |
84 | |
85 function GetUnicodeExtensionRE() { | |
86 if (IS_UNDEFINED(UNDEFINED)) { | |
87 UNICODE_EXTENSION_RE = new GlobalRegExp('-u(-[a-z0-9]{2,8})+', 'g'); | |
88 } | |
89 return UNICODE_EXTENSION_RE; | |
90 } | |
91 | |
92 /** | |
93 * Matches any Unicode extension. | |
94 */ | |
95 var ANY_EXTENSION_RE = UNDEFINED; | |
96 | |
97 function GetAnyExtensionRE() { | |
98 if (IS_UNDEFINED(ANY_EXTENSION_RE)) { | |
99 ANY_EXTENSION_RE = new GlobalRegExp('-[a-z0-9]{1}-.*', 'g'); | |
100 } | |
101 return ANY_EXTENSION_RE; | |
102 } | |
103 | |
104 /** | |
105 * Replace quoted text (single quote, anything but the quote and quote again). | |
106 */ | |
107 var QUOTED_STRING_RE = UNDEFINED; | |
108 | |
109 function GetQuotedStringRE() { | |
110 if (IS_UNDEFINED(QUOTED_STRING_RE)) { | |
111 QUOTED_STRING_RE = new GlobalRegExp("'[^']+'", 'g'); | |
112 } | |
113 return QUOTED_STRING_RE; | |
114 } | |
115 | |
116 /** | |
117 * Matches valid service name. | |
118 */ | |
119 var SERVICE_RE = UNDEFINED; | |
120 | |
121 function GetServiceRE() { | |
122 if (IS_UNDEFINED(SERVICE_RE)) { | |
123 SERVICE_RE = | |
124 new GlobalRegExp('^(collator|numberformat|dateformat|breakiterator)$'); | |
125 } | |
126 return SERVICE_RE; | |
127 } | |
128 | |
129 /** | |
130 * Validates a language tag against bcp47 spec. | |
131 * Actual value is assigned on first run. | |
132 */ | |
133 var LANGUAGE_TAG_RE = UNDEFINED; | |
134 | |
135 function GetLanguageTagRE() { | |
136 if (IS_UNDEFINED(LANGUAGE_TAG_RE)) { | |
137 BuildLanguageTagREs(); | |
138 } | |
139 return LANGUAGE_TAG_RE; | |
140 } | |
141 | |
142 /** | |
143 * Helps find duplicate variants in the language tag. | |
144 */ | |
145 var LANGUAGE_VARIANT_RE = UNDEFINED; | |
146 | |
147 function GetLanguageVariantRE() { | |
148 if (IS_UNDEFINED(LANGUAGE_VARIANT_RE)) { | |
149 BuildLanguageTagREs(); | |
150 } | |
151 return LANGUAGE_VARIANT_RE; | |
152 } | |
153 | |
154 /** | |
155 * Helps find duplicate singletons in the language tag. | |
156 */ | |
157 var LANGUAGE_SINGLETON_RE = UNDEFINED; | |
158 | |
159 function GetLanguageSingletonRE() { | |
160 if (IS_UNDEFINED(LANGUAGE_SINGLETON_RE)) { | |
161 BuildLanguageTagREs(); | |
162 } | |
163 return LANGUAGE_SINGLETON_RE; | |
164 } | |
165 | |
166 /** | |
167 * Matches valid IANA time zone names. | |
168 */ | |
169 var TIMEZONE_NAME_CHECK_RE = UNDEFINED; | |
170 | |
171 function GetTimezoneNameCheckRE() { | |
172 if (IS_UNDEFINED(TIMEZONE_NAME_CHECK_RE)) { | |
173 TIMEZONE_NAME_CHECK_RE = | |
174 new GlobalRegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$'); | |
175 } | |
176 return TIMEZONE_NAME_CHECK_RE; | |
177 } | |
178 | |
179 /** | |
180 * Adds bound method to the prototype of the given object. | |
181 */ | |
182 function addBoundMethod(obj, methodName, implementation, length) { | |
183 %CheckIsBootstrapping(); | |
184 function getter() { | |
185 if (!%IsInitializedIntlObject(this)) { | |
186 throw MakeTypeError(kMethodCalledOnWrongObject, methodName); | |
187 } | |
188 var internalName = '__bound' + methodName + '__'; | |
189 if (IS_UNDEFINED(this[internalName])) { | |
190 var that = this; | |
191 var boundMethod; | |
192 if (IS_UNDEFINED(length) || length === 2) { | |
193 boundMethod = function(x, y) { | |
194 if (%_IsConstructCall()) { | |
195 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
196 } | |
197 return implementation(that, x, y); | |
198 } | |
199 } else if (length === 1) { | |
200 boundMethod = function(x) { | |
201 if (%_IsConstructCall()) { | |
202 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
203 } | |
204 return implementation(that, x); | |
205 } | |
206 } else { | |
207 boundMethod = function() { | |
208 if (%_IsConstructCall()) { | |
209 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
210 } | |
211 // DateTimeFormat.format needs to be 0 arg method, but can stil | |
212 // receive optional dateValue param. If one was provided, pass it | |
213 // along. | |
214 if (%_ArgumentsLength() > 0) { | |
215 return implementation(that, %_Arguments(0)); | |
216 } else { | |
217 return implementation(that); | |
218 } | |
219 } | |
220 } | |
221 %FunctionSetName(boundMethod, internalName); | |
222 %FunctionRemovePrototype(boundMethod); | |
223 %SetNativeFlag(boundMethod); | |
224 this[internalName] = boundMethod; | |
225 } | |
226 return this[internalName]; | |
227 } | |
228 | |
229 %FunctionSetName(getter, methodName); | |
230 %FunctionRemovePrototype(getter); | |
231 %SetNativeFlag(getter); | |
232 | |
233 ObjectDefineProperty(obj.prototype, methodName, { | |
234 get: getter, | |
235 enumerable: false, | |
236 configurable: true | |
237 }); | |
238 } | |
239 | |
240 | |
241 /** | |
242 * Returns an intersection of locales and service supported locales. | |
243 * Parameter locales is treated as a priority list. | |
244 */ | |
245 function supportedLocalesOf(service, locales, options) { | |
246 if (IS_NULL(%_CallFunction(service, GetServiceRE(), StringMatch))) { | |
247 throw MakeError(kWrongServiceType, service); | |
248 } | |
249 | |
250 // Provide defaults if matcher was not specified. | |
251 if (IS_UNDEFINED(options)) { | |
252 options = {}; | |
253 } else { | |
254 options = TO_OBJECT(options); | |
255 } | |
256 | |
257 var matcher = options.localeMatcher; | |
258 if (!IS_UNDEFINED(matcher)) { | |
259 matcher = GlobalString(matcher); | |
260 if (matcher !== 'lookup' && matcher !== 'best fit') { | |
261 throw MakeRangeError(kLocaleMatcher, matcher); | |
262 } | |
263 } else { | |
264 matcher = 'best fit'; | |
265 } | |
266 | |
267 var requestedLocales = initializeLocaleList(locales); | |
268 | |
269 // Cache these, they don't ever change per service. | |
270 if (IS_UNDEFINED(AVAILABLE_LOCALES[service])) { | |
271 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service); | |
272 } | |
273 | |
274 // Use either best fit or lookup algorithm to match locales. | |
275 if (matcher === 'best fit') { | |
276 return initializeLocaleList(bestFitSupportedLocalesOf( | |
277 requestedLocales, AVAILABLE_LOCALES[service])); | |
278 } | |
279 | |
280 return initializeLocaleList(lookupSupportedLocalesOf( | |
281 requestedLocales, AVAILABLE_LOCALES[service])); | |
282 } | |
283 | |
284 | |
285 /** | |
286 * Returns the subset of the provided BCP 47 language priority list for which | |
287 * this service has a matching locale when using the BCP 47 Lookup algorithm. | |
288 * Locales appear in the same order in the returned list as in the input list. | |
289 */ | |
290 function lookupSupportedLocalesOf(requestedLocales, availableLocales) { | |
291 var matchedLocales = []; | |
292 for (var i = 0; i < requestedLocales.length; ++i) { | |
293 // Remove -u- extension. | |
294 var locale = %_CallFunction(requestedLocales[i], GetUnicodeExtensionRE(), | |
295 '', StringReplace); | |
296 do { | |
297 if (!IS_UNDEFINED(availableLocales[locale])) { | |
298 // Push requested locale not the resolved one. | |
299 %_CallFunction(matchedLocales, requestedLocales[i], ArrayPush); | |
300 break; | |
301 } | |
302 // Truncate locale if possible, if not break. | |
303 var pos = %_CallFunction(locale, '-', StringLastIndexOf); | |
304 if (pos === -1) { | |
305 break; | |
306 } | |
307 locale = %_CallFunction(locale, 0, pos, StringSubstring); | |
308 } while (true); | |
309 } | |
310 | |
311 return matchedLocales; | |
312 } | |
313 | |
314 | |
315 /** | |
316 * Returns the subset of the provided BCP 47 language priority list for which | |
317 * this service has a matching locale when using the implementation | |
318 * dependent algorithm. | |
319 * Locales appear in the same order in the returned list as in the input list. | |
320 */ | |
321 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) { | |
322 // TODO(cira): implement better best fit algorithm. | |
323 return lookupSupportedLocalesOf(requestedLocales, availableLocales); | |
324 } | |
325 | |
326 | |
327 /** | |
328 * Returns a getOption function that extracts property value for given | |
329 * options object. If property is missing it returns defaultValue. If value | |
330 * is out of range for that property it throws RangeError. | |
331 */ | |
332 function getGetOption(options, caller) { | |
333 if (IS_UNDEFINED(options)) throw MakeError(kDefaultOptionsMissing, caller); | |
334 | |
335 var getOption = function getOption(property, type, values, defaultValue) { | |
336 if (!IS_UNDEFINED(options[property])) { | |
337 var value = options[property]; | |
338 switch (type) { | |
339 case 'boolean': | |
340 value = GlobalBoolean(value); | |
341 break; | |
342 case 'string': | |
343 value = GlobalString(value); | |
344 break; | |
345 case 'number': | |
346 value = GlobalNumber(value); | |
347 break; | |
348 default: | |
349 throw MakeError(kWrongValueType); | |
350 } | |
351 | |
352 if (!IS_UNDEFINED(values) && | |
353 %_CallFunction(values, value, ArrayIndexOf) === -1) { | |
354 throw MakeRangeError(kValueOutOfRange, value, caller, property); | |
355 } | |
356 | |
357 return value; | |
358 } | |
359 | |
360 return defaultValue; | |
361 } | |
362 | |
363 return getOption; | |
364 } | |
365 | |
366 | |
367 /** | |
368 * Compares a BCP 47 language priority list requestedLocales against the locales | |
369 * in availableLocales and determines the best available language to meet the | |
370 * request. Two algorithms are available to match the locales: the Lookup | |
371 * algorithm described in RFC 4647 section 3.4, and an implementation dependent | |
372 * best-fit algorithm. Independent of the locale matching algorithm, options | |
373 * specified through Unicode locale extension sequences are negotiated | |
374 * separately, taking the caller's relevant extension keys and locale data as | |
375 * well as client-provided options into consideration. Returns an object with | |
376 * a locale property whose value is the language tag of the selected locale, | |
377 * and properties for each key in relevantExtensionKeys providing the selected | |
378 * value for that key. | |
379 */ | |
380 function resolveLocale(service, requestedLocales, options) { | |
381 requestedLocales = initializeLocaleList(requestedLocales); | |
382 | |
383 var getOption = getGetOption(options, service); | |
384 var matcher = getOption('localeMatcher', 'string', | |
385 ['lookup', 'best fit'], 'best fit'); | |
386 var resolved; | |
387 if (matcher === 'lookup') { | |
388 resolved = lookupMatcher(service, requestedLocales); | |
389 } else { | |
390 resolved = bestFitMatcher(service, requestedLocales); | |
391 } | |
392 | |
393 return resolved; | |
394 } | |
395 | |
396 | |
397 /** | |
398 * Returns best matched supported locale and extension info using basic | |
399 * lookup algorithm. | |
400 */ | |
401 function lookupMatcher(service, requestedLocales) { | |
402 if (IS_NULL(%_CallFunction(service, GetServiceRE(), StringMatch))) { | |
403 throw MakeError(kWrongServiceType, service); | |
404 } | |
405 | |
406 // Cache these, they don't ever change per service. | |
407 if (IS_UNDEFINED(AVAILABLE_LOCALES[service])) { | |
408 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service); | |
409 } | |
410 | |
411 for (var i = 0; i < requestedLocales.length; ++i) { | |
412 // Remove all extensions. | |
413 var locale = %_CallFunction(requestedLocales[i], GetAnyExtensionRE(), '', | |
414 StringReplace); | |
415 do { | |
416 if (!IS_UNDEFINED(AVAILABLE_LOCALES[service][locale])) { | |
417 // Return the resolved locale and extension. | |
418 var extensionMatch = | |
419 %_CallFunction(requestedLocales[i], GetUnicodeExtensionRE(), | |
420 StringMatch); | |
421 var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0]; | |
422 return {'locale': locale, 'extension': extension, 'position': i}; | |
423 } | |
424 // Truncate locale if possible. | |
425 var pos = %_CallFunction(locale, '-', StringLastIndexOf); | |
426 if (pos === -1) { | |
427 break; | |
428 } | |
429 locale = %_CallFunction(locale, 0, pos, StringSubstring); | |
430 } while (true); | |
431 } | |
432 | |
433 // Didn't find a match, return default. | |
434 if (IS_UNDEFINED(DEFAULT_ICU_LOCALE)) { | |
435 DEFAULT_ICU_LOCALE = %GetDefaultICULocale(); | |
436 } | |
437 | |
438 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1}; | |
439 } | |
440 | |
441 | |
442 /** | |
443 * Returns best matched supported locale and extension info using | |
444 * implementation dependend algorithm. | |
445 */ | |
446 function bestFitMatcher(service, requestedLocales) { | |
447 // TODO(cira): implement better best fit algorithm. | |
448 return lookupMatcher(service, requestedLocales); | |
449 } | |
450 | |
451 | |
452 /** | |
453 * Parses Unicode extension into key - value map. | |
454 * Returns empty object if the extension string is invalid. | |
455 * We are not concerned with the validity of the values at this point. | |
456 */ | |
457 function parseExtension(extension) { | |
458 var extensionSplit = %_CallFunction(extension, '-', StringSplit); | |
459 | |
460 // Assume ['', 'u', ...] input, but don't throw. | |
461 if (extensionSplit.length <= 2 || | |
462 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) { | |
463 return {}; | |
464 } | |
465 | |
466 // Key is {2}alphanum, value is {3,8}alphanum. | |
467 // Some keys may not have explicit values (booleans). | |
468 var extensionMap = {}; | |
469 var previousKey = UNDEFINED; | |
470 for (var i = 2; i < extensionSplit.length; ++i) { | |
471 var length = extensionSplit[i].length; | |
472 var element = extensionSplit[i]; | |
473 if (length === 2) { | |
474 extensionMap[element] = UNDEFINED; | |
475 previousKey = element; | |
476 } else if (length >= 3 && length <=8 && !IS_UNDEFINED(previousKey)) { | |
477 extensionMap[previousKey] = element; | |
478 previousKey = UNDEFINED; | |
479 } else { | |
480 // There is a value that's too long, or that doesn't have a key. | |
481 return {}; | |
482 } | |
483 } | |
484 | |
485 return extensionMap; | |
486 } | |
487 | |
488 | |
489 /** | |
490 * Populates internalOptions object with boolean key-value pairs | |
491 * from extensionMap and options. | |
492 * Returns filtered extension (number and date format constructors use | |
493 * Unicode extensions for passing parameters to ICU). | |
494 * It's used for extension-option pairs only, e.g. kn-normalization, but not | |
495 * for 'sensitivity' since it doesn't have extension equivalent. | |
496 * Extensions like nu and ca don't have options equivalent, so we place | |
497 * undefined in the map.property to denote that. | |
498 */ | |
499 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) { | |
500 var extension = ''; | |
501 | |
502 var updateExtension = function updateExtension(key, value) { | |
503 return '-' + key + '-' + GlobalString(value); | |
504 } | |
505 | |
506 var updateProperty = function updateProperty(property, type, value) { | |
507 if (type === 'boolean' && (typeof value === 'string')) { | |
508 value = (value === 'true') ? true : false; | |
509 } | |
510 | |
511 if (!IS_UNDEFINED(property)) { | |
512 defineWEProperty(outOptions, property, value); | |
513 } | |
514 } | |
515 | |
516 for (var key in keyValues) { | |
517 if (%HasOwnProperty(keyValues, key)) { | |
518 var value = UNDEFINED; | |
519 var map = keyValues[key]; | |
520 if (!IS_UNDEFINED(map.property)) { | |
521 // This may return true if user specifies numeric: 'false', since | |
522 // Boolean('nonempty') === true. | |
523 value = getOption(map.property, map.type, map.values); | |
524 } | |
525 if (!IS_UNDEFINED(value)) { | |
526 updateProperty(map.property, map.type, value); | |
527 extension += updateExtension(key, value); | |
528 continue; | |
529 } | |
530 // User options didn't have it, check Unicode extension. | |
531 // Here we want to convert strings 'true', 'false' into proper Boolean | |
532 // values (not a user error). | |
533 if (%HasOwnProperty(extensionMap, key)) { | |
534 value = extensionMap[key]; | |
535 if (!IS_UNDEFINED(value)) { | |
536 updateProperty(map.property, map.type, value); | |
537 extension += updateExtension(key, value); | |
538 } else if (map.type === 'boolean') { | |
539 // Boolean keys are allowed not to have values in Unicode extension. | |
540 // Those default to true. | |
541 updateProperty(map.property, map.type, true); | |
542 extension += updateExtension(key, true); | |
543 } | |
544 } | |
545 } | |
546 } | |
547 | |
548 return extension === ''? '' : '-u' + extension; | |
549 } | |
550 | |
551 | |
552 /** | |
553 * Converts all OwnProperties into | |
554 * configurable: false, writable: false, enumerable: true. | |
555 */ | |
556 function freezeArray(array) { | |
557 var l = array.length; | |
558 for (var i = 0; i < l; i++) { | |
559 if (i in array) { | |
560 ObjectDefineProperty(array, i, {value: array[i], | |
561 configurable: false, | |
562 writable: false, | |
563 enumerable: true}); | |
564 } | |
565 } | |
566 | |
567 ObjectDefineProperty(array, 'length', {value: l, writable: false}); | |
568 return array; | |
569 } | |
570 | |
571 | |
572 /** | |
573 * It's sometimes desireable to leave user requested locale instead of ICU | |
574 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter | |
575 * one, if that was what user requested). | |
576 * This function returns user specified tag if its maximized form matches ICU | |
577 * resolved locale. If not we return ICU result. | |
578 */ | |
579 function getOptimalLanguageTag(original, resolved) { | |
580 // Returns Array<Object>, where each object has maximized and base properties. | |
581 // Maximized: zh -> zh-Hans-CN | |
582 // Base: zh-CN-u-ca-gregory -> zh-CN | |
583 // Take care of grandfathered or simple cases. | |
584 if (original === resolved) { | |
585 return original; | |
586 } | |
587 | |
588 var locales = %GetLanguageTagVariants([original, resolved]); | |
589 if (locales[0].maximized !== locales[1].maximized) { | |
590 return resolved; | |
591 } | |
592 | |
593 // Preserve extensions of resolved locale, but swap base tags with original. | |
594 var resolvedBase = new GlobalRegExp('^' + locales[1].base); | |
595 return %_CallFunction(resolved, resolvedBase, locales[0].base, StringReplace); | |
596 } | |
597 | |
598 | |
599 /** | |
600 * Returns an Object that contains all of supported locales for a given | |
601 * service. | |
602 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ | |
603 * that is supported. This is required by the spec. | |
604 */ | |
605 function getAvailableLocalesOf(service) { | |
606 var available = %AvailableLocalesOf(service); | |
607 | |
608 for (var i in available) { | |
609 if (%HasOwnProperty(available, i)) { | |
610 var parts = %_CallFunction(i, /^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/, | |
611 StringMatch); | |
612 if (parts !== null) { | |
613 // Build xx-ZZ. We don't care about the actual value, | |
614 // as long it's not undefined. | |
615 available[parts[1] + '-' + parts[3]] = null; | |
616 } | |
617 } | |
618 } | |
619 | |
620 return available; | |
621 } | |
622 | |
623 | |
624 /** | |
625 * Defines a property and sets writable and enumerable to true. | |
626 * Configurable is false by default. | |
627 */ | |
628 function defineWEProperty(object, property, value) { | |
629 ObjectDefineProperty(object, property, | |
630 {value: value, writable: true, enumerable: true}); | |
631 } | |
632 | |
633 | |
634 /** | |
635 * Adds property to an object if the value is not undefined. | |
636 * Sets configurable descriptor to false. | |
637 */ | |
638 function addWEPropertyIfDefined(object, property, value) { | |
639 if (!IS_UNDEFINED(value)) { | |
640 defineWEProperty(object, property, value); | |
641 } | |
642 } | |
643 | |
644 | |
645 /** | |
646 * Defines a property and sets writable, enumerable and configurable to true. | |
647 */ | |
648 function defineWECProperty(object, property, value) { | |
649 ObjectDefineProperty(object, property, {value: value, | |
650 writable: true, | |
651 enumerable: true, | |
652 configurable: true}); | |
653 } | |
654 | |
655 | |
656 /** | |
657 * Adds property to an object if the value is not undefined. | |
658 * Sets all descriptors to true. | |
659 */ | |
660 function addWECPropertyIfDefined(object, property, value) { | |
661 if (!IS_UNDEFINED(value)) { | |
662 defineWECProperty(object, property, value); | |
663 } | |
664 } | |
665 | |
666 | |
667 /** | |
668 * Returns titlecased word, aMeRricA -> America. | |
669 */ | |
670 function toTitleCaseWord(word) { | |
671 return %StringToUpperCase(%_CallFunction(word, 0, 1, StringSubstr)) + | |
672 %StringToLowerCase(%_CallFunction(word, 1, StringSubstr)); | |
673 } | |
674 | |
675 /** | |
676 * Canonicalizes the language tag, or throws in case the tag is invalid. | |
677 */ | |
678 function canonicalizeLanguageTag(localeID) { | |
679 // null is typeof 'object' so we have to do extra check. | |
680 if (typeof localeID !== 'string' && typeof localeID !== 'object' || | |
681 IS_NULL(localeID)) { | |
682 throw MakeTypeError(kLanguageID); | |
683 } | |
684 | |
685 var localeString = GlobalString(localeID); | |
686 | |
687 if (isValidLanguageTag(localeString) === false) { | |
688 throw MakeRangeError(kInvalidLanguageTag, localeString); | |
689 } | |
690 | |
691 // This call will strip -kn but not -kn-true extensions. | |
692 // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265. | |
693 // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after | |
694 // upgrade to ICU 4.9. | |
695 var tag = %CanonicalizeLanguageTag(localeString); | |
696 if (tag === 'invalid-tag') { | |
697 throw MakeRangeError(kInvalidLanguageTag, localeString); | |
698 } | |
699 | |
700 return tag; | |
701 } | |
702 | |
703 | |
704 /** | |
705 * Returns an array where all locales are canonicalized and duplicates removed. | |
706 * Throws on locales that are not well formed BCP47 tags. | |
707 */ | |
708 function initializeLocaleList(locales) { | |
709 var seen = []; | |
710 if (IS_UNDEFINED(locales)) { | |
711 // Constructor is called without arguments. | |
712 seen = []; | |
713 } else { | |
714 // We allow single string localeID. | |
715 if (typeof locales === 'string') { | |
716 %_CallFunction(seen, canonicalizeLanguageTag(locales), ArrayPush); | |
717 return freezeArray(seen); | |
718 } | |
719 | |
720 var o = TO_OBJECT(locales); | |
721 var len = TO_UINT32(o.length); | |
722 | |
723 for (var k = 0; k < len; k++) { | |
724 if (k in o) { | |
725 var value = o[k]; | |
726 | |
727 var tag = canonicalizeLanguageTag(value); | |
728 | |
729 if (%_CallFunction(seen, tag, ArrayIndexOf) === -1) { | |
730 %_CallFunction(seen, tag, ArrayPush); | |
731 } | |
732 } | |
733 } | |
734 } | |
735 | |
736 return freezeArray(seen); | |
737 } | |
738 | |
739 | |
740 /** | |
741 * Validates the language tag. Section 2.2.9 of the bcp47 spec | |
742 * defines a valid tag. | |
743 * | |
744 * ICU is too permissible and lets invalid tags, like | |
745 * hant-cmn-cn, through. | |
746 * | |
747 * Returns false if the language tag is invalid. | |
748 */ | |
749 function isValidLanguageTag(locale) { | |
750 // Check if it's well-formed, including grandfadered tags. | |
751 if (!%_CallFunction(GetLanguageTagRE(), locale, RegExpTest)) { | |
752 return false; | |
753 } | |
754 | |
755 // Just return if it's a x- form. It's all private. | |
756 if (%_CallFunction(locale, 'x-', StringIndexOf) === 0) { | |
757 return true; | |
758 } | |
759 | |
760 // Check if there are any duplicate variants or singletons (extensions). | |
761 | |
762 // Remove private use section. | |
763 locale = %_CallFunction(locale, /-x-/, StringSplit)[0]; | |
764 | |
765 // Skip language since it can match variant regex, so we start from 1. | |
766 // We are matching i-klingon here, but that's ok, since i-klingon-klingon | |
767 // is not valid and would fail LANGUAGE_TAG_RE test. | |
768 var variants = []; | |
769 var extensions = []; | |
770 var parts = %_CallFunction(locale, /-/, StringSplit); | |
771 for (var i = 1; i < parts.length; i++) { | |
772 var value = parts[i]; | |
773 if (%_CallFunction(GetLanguageVariantRE(), value, RegExpTest) && | |
774 extensions.length === 0) { | |
775 if (%_CallFunction(variants, value, ArrayIndexOf) === -1) { | |
776 %_CallFunction(variants, value, ArrayPush); | |
777 } else { | |
778 return false; | |
779 } | |
780 } | |
781 | |
782 if (%_CallFunction(GetLanguageSingletonRE(), value, RegExpTest)) { | |
783 if (%_CallFunction(extensions, value, ArrayIndexOf) === -1) { | |
784 %_CallFunction(extensions, value, ArrayPush); | |
785 } else { | |
786 return false; | |
787 } | |
788 } | |
789 } | |
790 | |
791 return true; | |
792 } | |
793 | |
794 | |
795 /** | |
796 * Builds a regular expresion that validates the language tag | |
797 * against bcp47 spec. | |
798 * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF. | |
799 * Runs on load and initializes the global REs. | |
800 */ | |
801 function BuildLanguageTagREs() { | |
802 var alpha = '[a-zA-Z]'; | |
803 var digit = '[0-9]'; | |
804 var alphanum = '(' + alpha + '|' + digit + ')'; | |
805 var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' + | |
806 'zh-min|zh-min-nan|zh-xiang)'; | |
807 var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' + | |
808 'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' + | |
809 'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)'; | |
810 var grandfathered = '(' + irregular + '|' + regular + ')'; | |
811 var privateUse = '(x(-' + alphanum + '{1,8})+)'; | |
812 | |
813 var singleton = '(' + digit + '|[A-WY-Za-wy-z])'; | |
814 LANGUAGE_SINGLETON_RE = new GlobalRegExp('^' + singleton + '$', 'i'); | |
815 | |
816 var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)'; | |
817 | |
818 var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))'; | |
819 LANGUAGE_VARIANT_RE = new GlobalRegExp('^' + variant + '$', 'i'); | |
820 | |
821 var region = '(' + alpha + '{2}|' + digit + '{3})'; | |
822 var script = '(' + alpha + '{4})'; | |
823 var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})'; | |
824 var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' + | |
825 alpha + '{5,8})'; | |
826 var langTag = language + '(-' + script + ')?(-' + region + ')?(-' + | |
827 variant + ')*(-' + extension + ')*(-' + privateUse + ')?'; | |
828 | |
829 var languageTag = | |
830 '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$'; | |
831 LANGUAGE_TAG_RE = new GlobalRegExp(languageTag, 'i'); | |
832 } | |
833 | |
834 /** | |
835 * Initializes the given object so it's a valid Collator instance. | |
836 * Useful for subclassing. | |
837 */ | |
838 function initializeCollator(collator, locales, options) { | |
839 if (%IsInitializedIntlObject(collator)) { | |
840 throw MakeTypeError(kReinitializeIntl, "Collator"); | |
841 } | |
842 | |
843 if (IS_UNDEFINED(options)) { | |
844 options = {}; | |
845 } | |
846 | |
847 var getOption = getGetOption(options, 'collator'); | |
848 | |
849 var internalOptions = {}; | |
850 | |
851 defineWEProperty(internalOptions, 'usage', getOption( | |
852 'usage', 'string', ['sort', 'search'], 'sort')); | |
853 | |
854 var sensitivity = getOption('sensitivity', 'string', | |
855 ['base', 'accent', 'case', 'variant']); | |
856 if (IS_UNDEFINED(sensitivity) && internalOptions.usage === 'sort') { | |
857 sensitivity = 'variant'; | |
858 } | |
859 defineWEProperty(internalOptions, 'sensitivity', sensitivity); | |
860 | |
861 defineWEProperty(internalOptions, 'ignorePunctuation', getOption( | |
862 'ignorePunctuation', 'boolean', UNDEFINED, false)); | |
863 | |
864 var locale = resolveLocale('collator', locales, options); | |
865 | |
866 // ICU can't take kb, kc... parameters through localeID, so we need to pass | |
867 // them as options. | |
868 // One exception is -co- which has to be part of the extension, but only for | |
869 // usage: sort, and its value can't be 'standard' or 'search'. | |
870 var extensionMap = parseExtension(locale.extension); | |
871 | |
872 /** | |
873 * Map of Unicode extensions to option properties, and their values and types, | |
874 * for a collator. | |
875 */ | |
876 var COLLATOR_KEY_MAP = { | |
877 'kn': {'property': 'numeric', 'type': 'boolean'}, | |
878 'kf': {'property': 'caseFirst', 'type': 'string', | |
879 'values': ['false', 'lower', 'upper']} | |
880 }; | |
881 | |
882 setOptions( | |
883 options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions); | |
884 | |
885 var collation = 'default'; | |
886 var extension = ''; | |
887 if (%HasOwnProperty(extensionMap, 'co') && internalOptions.usage === 'sort') { | |
888 | |
889 /** | |
890 * Allowed -u-co- values. List taken from: | |
891 * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml | |
892 */ | |
893 var ALLOWED_CO_VALUES = [ | |
894 'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic', | |
895 'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin' | |
896 ]; | |
897 | |
898 if (%_CallFunction(ALLOWED_CO_VALUES, extensionMap.co, ArrayIndexOf) !== | |
899 -1) { | |
900 extension = '-u-co-' + extensionMap.co; | |
901 // ICU can't tell us what the collation is, so save user's input. | |
902 collation = extensionMap.co; | |
903 } | |
904 } else if (internalOptions.usage === 'search') { | |
905 extension = '-u-co-search'; | |
906 } | |
907 defineWEProperty(internalOptions, 'collation', collation); | |
908 | |
909 var requestedLocale = locale.locale + extension; | |
910 | |
911 // We define all properties C++ code may produce, to prevent security | |
912 // problems. If malicious user decides to redefine Object.prototype.locale | |
913 // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us"). | |
914 // ObjectDefineProperties will either succeed defining or throw an error. | |
915 var resolved = ObjectDefineProperties({}, { | |
916 caseFirst: {writable: true}, | |
917 collation: {value: internalOptions.collation, writable: true}, | |
918 ignorePunctuation: {writable: true}, | |
919 locale: {writable: true}, | |
920 numeric: {writable: true}, | |
921 requestedLocale: {value: requestedLocale, writable: true}, | |
922 sensitivity: {writable: true}, | |
923 strength: {writable: true}, | |
924 usage: {value: internalOptions.usage, writable: true} | |
925 }); | |
926 | |
927 var internalCollator = %CreateCollator(requestedLocale, | |
928 internalOptions, | |
929 resolved); | |
930 | |
931 // Writable, configurable and enumerable are set to false by default. | |
932 %MarkAsInitializedIntlObjectOfType(collator, 'collator', internalCollator); | |
933 ObjectDefineProperty(collator, 'resolved', {value: resolved}); | |
934 | |
935 return collator; | |
936 } | |
937 | |
938 | |
939 /** | |
940 * Constructs Intl.Collator object given optional locales and options | |
941 * parameters. | |
942 * | |
943 * @constructor | |
944 */ | |
945 %AddNamedProperty(Intl, 'Collator', function() { | |
946 var locales = %_Arguments(0); | |
947 var options = %_Arguments(1); | |
948 | |
949 if (!this || this === Intl) { | |
950 // Constructor is called as a function. | |
951 return new Intl.Collator(locales, options); | |
952 } | |
953 | |
954 return initializeCollator(TO_OBJECT(this), locales, options); | |
955 }, | |
956 DONT_ENUM | |
957 ); | |
958 | |
959 | |
960 /** | |
961 * Collator resolvedOptions method. | |
962 */ | |
963 %AddNamedProperty(Intl.Collator.prototype, 'resolvedOptions', function() { | |
964 if (%_IsConstructCall()) { | |
965 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
966 } | |
967 | |
968 if (!%IsInitializedIntlObjectOfType(this, 'collator')) { | |
969 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "Collator"); | |
970 } | |
971 | |
972 var coll = this; | |
973 var locale = getOptimalLanguageTag(coll.resolved.requestedLocale, | |
974 coll.resolved.locale); | |
975 | |
976 return { | |
977 locale: locale, | |
978 usage: coll.resolved.usage, | |
979 sensitivity: coll.resolved.sensitivity, | |
980 ignorePunctuation: coll.resolved.ignorePunctuation, | |
981 numeric: coll.resolved.numeric, | |
982 caseFirst: coll.resolved.caseFirst, | |
983 collation: coll.resolved.collation | |
984 }; | |
985 }, | |
986 DONT_ENUM | |
987 ); | |
988 %FunctionSetName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions'); | |
989 %FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions); | |
990 %SetNativeFlag(Intl.Collator.prototype.resolvedOptions); | |
991 | |
992 | |
993 /** | |
994 * Returns the subset of the given locale list for which this locale list | |
995 * has a matching (possibly fallback) locale. Locales appear in the same | |
996 * order in the returned list as in the input list. | |
997 * Options are optional parameter. | |
998 */ | |
999 %AddNamedProperty(Intl.Collator, 'supportedLocalesOf', function(locales) { | |
1000 if (%_IsConstructCall()) { | |
1001 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
1002 } | |
1003 | |
1004 return supportedLocalesOf('collator', locales, %_Arguments(1)); | |
1005 }, | |
1006 DONT_ENUM | |
1007 ); | |
1008 %FunctionSetName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf'); | |
1009 %FunctionRemovePrototype(Intl.Collator.supportedLocalesOf); | |
1010 %SetNativeFlag(Intl.Collator.supportedLocalesOf); | |
1011 | |
1012 | |
1013 /** | |
1014 * When the compare method is called with two arguments x and y, it returns a | |
1015 * Number other than NaN that represents the result of a locale-sensitive | |
1016 * String comparison of x with y. | |
1017 * The result is intended to order String values in the sort order specified | |
1018 * by the effective locale and collation options computed during construction | |
1019 * of this Collator object, and will be negative, zero, or positive, depending | |
1020 * on whether x comes before y in the sort order, the Strings are equal under | |
1021 * the sort order, or x comes after y in the sort order, respectively. | |
1022 */ | |
1023 function compare(collator, x, y) { | |
1024 return %InternalCompare(%GetImplFromInitializedIntlObject(collator), | |
1025 GlobalString(x), GlobalString(y)); | |
1026 }; | |
1027 | |
1028 | |
1029 addBoundMethod(Intl.Collator, 'compare', compare, 2); | |
1030 | |
1031 /** | |
1032 * Verifies that the input is a well-formed ISO 4217 currency code. | |
1033 * Don't uppercase to test. It could convert invalid code into a valid one. | |
1034 * For example \u00DFP (Eszett+P) becomes SSP. | |
1035 */ | |
1036 function isWellFormedCurrencyCode(currency) { | |
1037 return typeof currency == "string" && | |
1038 currency.length == 3 && | |
1039 %_CallFunction(currency, /[^A-Za-z]/, StringMatch) == null; | |
1040 } | |
1041 | |
1042 | |
1043 /** | |
1044 * Returns the valid digit count for a property, or throws RangeError on | |
1045 * a value out of the range. | |
1046 */ | |
1047 function getNumberOption(options, property, min, max, fallback) { | |
1048 var value = options[property]; | |
1049 if (!IS_UNDEFINED(value)) { | |
1050 value = GlobalNumber(value); | |
1051 if (IsNaN(value) || value < min || value > max) { | |
1052 throw MakeRangeError(kPropertyValueOutOfRange, property); | |
1053 } | |
1054 return MathFloor(value); | |
1055 } | |
1056 | |
1057 return fallback; | |
1058 } | |
1059 | |
1060 | |
1061 /** | |
1062 * Initializes the given object so it's a valid NumberFormat instance. | |
1063 * Useful for subclassing. | |
1064 */ | |
1065 function initializeNumberFormat(numberFormat, locales, options) { | |
1066 if (%IsInitializedIntlObject(numberFormat)) { | |
1067 throw MakeTypeError(kReinitializeIntl, "NumberFormat"); | |
1068 } | |
1069 | |
1070 if (IS_UNDEFINED(options)) { | |
1071 options = {}; | |
1072 } | |
1073 | |
1074 var getOption = getGetOption(options, 'numberformat'); | |
1075 | |
1076 var locale = resolveLocale('numberformat', locales, options); | |
1077 | |
1078 var internalOptions = {}; | |
1079 defineWEProperty(internalOptions, 'style', getOption( | |
1080 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal')); | |
1081 | |
1082 var currency = getOption('currency', 'string'); | |
1083 if (!IS_UNDEFINED(currency) && !isWellFormedCurrencyCode(currency)) { | |
1084 throw MakeRangeError(kInvalidCurrencyCode, currency); | |
1085 } | |
1086 | |
1087 if (internalOptions.style === 'currency' && IS_UNDEFINED(currency)) { | |
1088 throw MakeTypeError(kCurrencyCode); | |
1089 } | |
1090 | |
1091 var currencyDisplay = getOption( | |
1092 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol'); | |
1093 if (internalOptions.style === 'currency') { | |
1094 defineWEProperty(internalOptions, 'currency', %StringToUpperCase(currency)); | |
1095 defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay); | |
1096 } | |
1097 | |
1098 // Digit ranges. | |
1099 var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1); | |
1100 defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid); | |
1101 | |
1102 var mnfd = options['minimumFractionDigits']; | |
1103 var mxfd = options['maximumFractionDigits']; | |
1104 if (!IS_UNDEFINED(mnfd) || !internalOptions.style === 'currency') { | |
1105 mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0); | |
1106 defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd); | |
1107 } | |
1108 | |
1109 if (!IS_UNDEFINED(mxfd) || !internalOptions.style === 'currency') { | |
1110 mnfd = IS_UNDEFINED(mnfd) ? 0 : mnfd; | |
1111 fallback_limit = (mnfd > 3) ? mnfd : 3; | |
1112 mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, fallback_
limit); | |
1113 defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd); | |
1114 } | |
1115 | |
1116 var mnsd = options['minimumSignificantDigits']; | |
1117 var mxsd = options['maximumSignificantDigits']; | |
1118 if (!IS_UNDEFINED(mnsd) || !IS_UNDEFINED(mxsd)) { | |
1119 mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0); | |
1120 defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd); | |
1121 | |
1122 mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21); | |
1123 defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd); | |
1124 } | |
1125 | |
1126 // Grouping. | |
1127 defineWEProperty(internalOptions, 'useGrouping', getOption( | |
1128 'useGrouping', 'boolean', UNDEFINED, true)); | |
1129 | |
1130 // ICU prefers options to be passed using -u- extension key/values for | |
1131 // number format, so we need to build that. | |
1132 var extensionMap = parseExtension(locale.extension); | |
1133 | |
1134 /** | |
1135 * Map of Unicode extensions to option properties, and their values and types, | |
1136 * for a number format. | |
1137 */ | |
1138 var NUMBER_FORMAT_KEY_MAP = { | |
1139 'nu': {'property': UNDEFINED, 'type': 'string'} | |
1140 }; | |
1141 | |
1142 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP, | |
1143 getOption, internalOptions); | |
1144 | |
1145 var requestedLocale = locale.locale + extension; | |
1146 var resolved = ObjectDefineProperties({}, { | |
1147 currency: {writable: true}, | |
1148 currencyDisplay: {writable: true}, | |
1149 locale: {writable: true}, | |
1150 maximumFractionDigits: {writable: true}, | |
1151 minimumFractionDigits: {writable: true}, | |
1152 minimumIntegerDigits: {writable: true}, | |
1153 numberingSystem: {writable: true}, | |
1154 requestedLocale: {value: requestedLocale, writable: true}, | |
1155 style: {value: internalOptions.style, writable: true}, | |
1156 useGrouping: {writable: true} | |
1157 }); | |
1158 if (%HasOwnProperty(internalOptions, 'minimumSignificantDigits')) { | |
1159 defineWEProperty(resolved, 'minimumSignificantDigits', UNDEFINED); | |
1160 } | |
1161 if (%HasOwnProperty(internalOptions, 'maximumSignificantDigits')) { | |
1162 defineWEProperty(resolved, 'maximumSignificantDigits', UNDEFINED); | |
1163 } | |
1164 var formatter = %CreateNumberFormat(requestedLocale, | |
1165 internalOptions, | |
1166 resolved); | |
1167 | |
1168 if (internalOptions.style === 'currency') { | |
1169 ObjectDefineProperty(resolved, 'currencyDisplay', {value: currencyDisplay, | |
1170 writable: true}); | |
1171 } | |
1172 | |
1173 %MarkAsInitializedIntlObjectOfType(numberFormat, 'numberformat', formatter); | |
1174 ObjectDefineProperty(numberFormat, 'resolved', {value: resolved}); | |
1175 | |
1176 return numberFormat; | |
1177 } | |
1178 | |
1179 | |
1180 /** | |
1181 * Constructs Intl.NumberFormat object given optional locales and options | |
1182 * parameters. | |
1183 * | |
1184 * @constructor | |
1185 */ | |
1186 %AddNamedProperty(Intl, 'NumberFormat', function() { | |
1187 var locales = %_Arguments(0); | |
1188 var options = %_Arguments(1); | |
1189 | |
1190 if (!this || this === Intl) { | |
1191 // Constructor is called as a function. | |
1192 return new Intl.NumberFormat(locales, options); | |
1193 } | |
1194 | |
1195 return initializeNumberFormat(TO_OBJECT(this), locales, options); | |
1196 }, | |
1197 DONT_ENUM | |
1198 ); | |
1199 | |
1200 | |
1201 /** | |
1202 * NumberFormat resolvedOptions method. | |
1203 */ | |
1204 %AddNamedProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() { | |
1205 if (%_IsConstructCall()) { | |
1206 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
1207 } | |
1208 | |
1209 if (!%IsInitializedIntlObjectOfType(this, 'numberformat')) { | |
1210 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "NumberFormat"); | |
1211 } | |
1212 | |
1213 var format = this; | |
1214 var locale = getOptimalLanguageTag(format.resolved.requestedLocale, | |
1215 format.resolved.locale); | |
1216 | |
1217 var result = { | |
1218 locale: locale, | |
1219 numberingSystem: format.resolved.numberingSystem, | |
1220 style: format.resolved.style, | |
1221 useGrouping: format.resolved.useGrouping, | |
1222 minimumIntegerDigits: format.resolved.minimumIntegerDigits, | |
1223 minimumFractionDigits: format.resolved.minimumFractionDigits, | |
1224 maximumFractionDigits: format.resolved.maximumFractionDigits, | |
1225 }; | |
1226 | |
1227 if (result.style === 'currency') { | |
1228 defineWECProperty(result, 'currency', format.resolved.currency); | |
1229 defineWECProperty(result, 'currencyDisplay', | |
1230 format.resolved.currencyDisplay); | |
1231 } | |
1232 | |
1233 if (%HasOwnProperty(format.resolved, 'minimumSignificantDigits')) { | |
1234 defineWECProperty(result, 'minimumSignificantDigits', | |
1235 format.resolved.minimumSignificantDigits); | |
1236 } | |
1237 | |
1238 if (%HasOwnProperty(format.resolved, 'maximumSignificantDigits')) { | |
1239 defineWECProperty(result, 'maximumSignificantDigits', | |
1240 format.resolved.maximumSignificantDigits); | |
1241 } | |
1242 | |
1243 return result; | |
1244 }, | |
1245 DONT_ENUM | |
1246 ); | |
1247 %FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions, | |
1248 'resolvedOptions'); | |
1249 %FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions); | |
1250 %SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions); | |
1251 | |
1252 | |
1253 /** | |
1254 * Returns the subset of the given locale list for which this locale list | |
1255 * has a matching (possibly fallback) locale. Locales appear in the same | |
1256 * order in the returned list as in the input list. | |
1257 * Options are optional parameter. | |
1258 */ | |
1259 %AddNamedProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) { | |
1260 if (%_IsConstructCall()) { | |
1261 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
1262 } | |
1263 | |
1264 return supportedLocalesOf('numberformat', locales, %_Arguments(1)); | |
1265 }, | |
1266 DONT_ENUM | |
1267 ); | |
1268 %FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf'); | |
1269 %FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf); | |
1270 %SetNativeFlag(Intl.NumberFormat.supportedLocalesOf); | |
1271 | |
1272 | |
1273 /** | |
1274 * Returns a String value representing the result of calling ToNumber(value) | |
1275 * according to the effective locale and the formatting options of this | |
1276 * NumberFormat. | |
1277 */ | |
1278 function formatNumber(formatter, value) { | |
1279 // Spec treats -0 and +0 as 0. | |
1280 var number = TO_NUMBER(value) + 0; | |
1281 | |
1282 return %InternalNumberFormat(%GetImplFromInitializedIntlObject(formatter), | |
1283 number); | |
1284 } | |
1285 | |
1286 | |
1287 /** | |
1288 * Returns a Number that represents string value that was passed in. | |
1289 */ | |
1290 function parseNumber(formatter, value) { | |
1291 return %InternalNumberParse(%GetImplFromInitializedIntlObject(formatter), | |
1292 GlobalString(value)); | |
1293 } | |
1294 | |
1295 | |
1296 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1); | |
1297 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1); | |
1298 | |
1299 /** | |
1300 * Returns a string that matches LDML representation of the options object. | |
1301 */ | |
1302 function toLDMLString(options) { | |
1303 var getOption = getGetOption(options, 'dateformat'); | |
1304 | |
1305 var ldmlString = ''; | |
1306 | |
1307 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']); | |
1308 ldmlString += appendToLDMLString( | |
1309 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'}); | |
1310 | |
1311 option = getOption('era', 'string', ['narrow', 'short', 'long']); | |
1312 ldmlString += appendToLDMLString( | |
1313 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'}); | |
1314 | |
1315 option = getOption('year', 'string', ['2-digit', 'numeric']); | |
1316 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'}); | |
1317 | |
1318 option = getOption('month', 'string', | |
1319 ['2-digit', 'numeric', 'narrow', 'short', 'long']); | |
1320 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M', | |
1321 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'}); | |
1322 | |
1323 option = getOption('day', 'string', ['2-digit', 'numeric']); | |
1324 ldmlString += appendToLDMLString( | |
1325 option, {'2-digit': 'dd', 'numeric': 'd'}); | |
1326 | |
1327 var hr12 = getOption('hour12', 'boolean'); | |
1328 option = getOption('hour', 'string', ['2-digit', 'numeric']); | |
1329 if (IS_UNDEFINED(hr12)) { | |
1330 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'}); | |
1331 } else if (hr12 === true) { | |
1332 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'}); | |
1333 } else { | |
1334 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'}); | |
1335 } | |
1336 | |
1337 option = getOption('minute', 'string', ['2-digit', 'numeric']); | |
1338 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'}); | |
1339 | |
1340 option = getOption('second', 'string', ['2-digit', 'numeric']); | |
1341 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'}); | |
1342 | |
1343 option = getOption('timeZoneName', 'string', ['short', 'long']); | |
1344 ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'}); | |
1345 | |
1346 return ldmlString; | |
1347 } | |
1348 | |
1349 | |
1350 /** | |
1351 * Returns either LDML equivalent of the current option or empty string. | |
1352 */ | |
1353 function appendToLDMLString(option, pairs) { | |
1354 if (!IS_UNDEFINED(option)) { | |
1355 return pairs[option]; | |
1356 } else { | |
1357 return ''; | |
1358 } | |
1359 } | |
1360 | |
1361 | |
1362 /** | |
1363 * Returns object that matches LDML representation of the date. | |
1364 */ | |
1365 function fromLDMLString(ldmlString) { | |
1366 // First remove '' quoted text, so we lose 'Uhr' strings. | |
1367 ldmlString = %_CallFunction(ldmlString, GetQuotedStringRE(), '', | |
1368 StringReplace); | |
1369 | |
1370 var options = {}; | |
1371 var match = %_CallFunction(ldmlString, /E{3,5}/g, StringMatch); | |
1372 options = appendToDateTimeObject( | |
1373 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'}); | |
1374 | |
1375 match = %_CallFunction(ldmlString, /G{3,5}/g, StringMatch); | |
1376 options = appendToDateTimeObject( | |
1377 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'}); | |
1378 | |
1379 match = %_CallFunction(ldmlString, /y{1,2}/g, StringMatch); | |
1380 options = appendToDateTimeObject( | |
1381 options, 'year', match, {y: 'numeric', yy: '2-digit'}); | |
1382 | |
1383 match = %_CallFunction(ldmlString, /M{1,5}/g, StringMatch); | |
1384 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit', | |
1385 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'}); | |
1386 | |
1387 // Sometimes we get L instead of M for month - standalone name. | |
1388 match = %_CallFunction(ldmlString, /L{1,5}/g, StringMatch); | |
1389 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit', | |
1390 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'}); | |
1391 | |
1392 match = %_CallFunction(ldmlString, /d{1,2}/g, StringMatch); | |
1393 options = appendToDateTimeObject( | |
1394 options, 'day', match, {d: 'numeric', dd: '2-digit'}); | |
1395 | |
1396 match = %_CallFunction(ldmlString, /h{1,2}/g, StringMatch); | |
1397 if (match !== null) { | |
1398 options['hour12'] = true; | |
1399 } | |
1400 options = appendToDateTimeObject( | |
1401 options, 'hour', match, {h: 'numeric', hh: '2-digit'}); | |
1402 | |
1403 match = %_CallFunction(ldmlString, /H{1,2}/g, StringMatch); | |
1404 if (match !== null) { | |
1405 options['hour12'] = false; | |
1406 } | |
1407 options = appendToDateTimeObject( | |
1408 options, 'hour', match, {H: 'numeric', HH: '2-digit'}); | |
1409 | |
1410 match = %_CallFunction(ldmlString, /m{1,2}/g, StringMatch); | |
1411 options = appendToDateTimeObject( | |
1412 options, 'minute', match, {m: 'numeric', mm: '2-digit'}); | |
1413 | |
1414 match = %_CallFunction(ldmlString, /s{1,2}/g, StringMatch); | |
1415 options = appendToDateTimeObject( | |
1416 options, 'second', match, {s: 'numeric', ss: '2-digit'}); | |
1417 | |
1418 match = %_CallFunction(ldmlString, /z|zzzz/g, StringMatch); | |
1419 options = appendToDateTimeObject( | |
1420 options, 'timeZoneName', match, {z: 'short', zzzz: 'long'}); | |
1421 | |
1422 return options; | |
1423 } | |
1424 | |
1425 | |
1426 function appendToDateTimeObject(options, option, match, pairs) { | |
1427 if (IS_NULL(match)) { | |
1428 if (!%HasOwnProperty(options, option)) { | |
1429 defineWEProperty(options, option, UNDEFINED); | |
1430 } | |
1431 return options; | |
1432 } | |
1433 | |
1434 var property = match[0]; | |
1435 defineWEProperty(options, option, pairs[property]); | |
1436 | |
1437 return options; | |
1438 } | |
1439 | |
1440 | |
1441 /** | |
1442 * Returns options with at least default values in it. | |
1443 */ | |
1444 function toDateTimeOptions(options, required, defaults) { | |
1445 if (IS_UNDEFINED(options)) { | |
1446 options = {}; | |
1447 } else { | |
1448 options = TO_OBJECT(options); | |
1449 } | |
1450 | |
1451 var needsDefault = true; | |
1452 if ((required === 'date' || required === 'any') && | |
1453 (!IS_UNDEFINED(options.weekday) || !IS_UNDEFINED(options.year) || | |
1454 !IS_UNDEFINED(options.month) || !IS_UNDEFINED(options.day))) { | |
1455 needsDefault = false; | |
1456 } | |
1457 | |
1458 if ((required === 'time' || required === 'any') && | |
1459 (!IS_UNDEFINED(options.hour) || !IS_UNDEFINED(options.minute) || | |
1460 !IS_UNDEFINED(options.second))) { | |
1461 needsDefault = false; | |
1462 } | |
1463 | |
1464 if (needsDefault && (defaults === 'date' || defaults === 'all')) { | |
1465 ObjectDefineProperty(options, 'year', {value: 'numeric', | |
1466 writable: true, | |
1467 enumerable: true, | |
1468 configurable: true}); | |
1469 ObjectDefineProperty(options, 'month', {value: 'numeric', | |
1470 writable: true, | |
1471 enumerable: true, | |
1472 configurable: true}); | |
1473 ObjectDefineProperty(options, 'day', {value: 'numeric', | |
1474 writable: true, | |
1475 enumerable: true, | |
1476 configurable: true}); | |
1477 } | |
1478 | |
1479 if (needsDefault && (defaults === 'time' || defaults === 'all')) { | |
1480 ObjectDefineProperty(options, 'hour', {value: 'numeric', | |
1481 writable: true, | |
1482 enumerable: true, | |
1483 configurable: true}); | |
1484 ObjectDefineProperty(options, 'minute', {value: 'numeric', | |
1485 writable: true, | |
1486 enumerable: true, | |
1487 configurable: true}); | |
1488 ObjectDefineProperty(options, 'second', {value: 'numeric', | |
1489 writable: true, | |
1490 enumerable: true, | |
1491 configurable: true}); | |
1492 } | |
1493 | |
1494 return options; | |
1495 } | |
1496 | |
1497 | |
1498 /** | |
1499 * Initializes the given object so it's a valid DateTimeFormat instance. | |
1500 * Useful for subclassing. | |
1501 */ | |
1502 function initializeDateTimeFormat(dateFormat, locales, options) { | |
1503 | |
1504 if (%IsInitializedIntlObject(dateFormat)) { | |
1505 throw MakeTypeError(kReinitializeIntl, "DateTimeFormat"); | |
1506 } | |
1507 | |
1508 if (IS_UNDEFINED(options)) { | |
1509 options = {}; | |
1510 } | |
1511 | |
1512 var locale = resolveLocale('dateformat', locales, options); | |
1513 | |
1514 options = toDateTimeOptions(options, 'any', 'date'); | |
1515 | |
1516 var getOption = getGetOption(options, 'dateformat'); | |
1517 | |
1518 // We implement only best fit algorithm, but still need to check | |
1519 // if the formatMatcher values are in range. | |
1520 var matcher = getOption('formatMatcher', 'string', | |
1521 ['basic', 'best fit'], 'best fit'); | |
1522 | |
1523 // Build LDML string for the skeleton that we pass to the formatter. | |
1524 var ldmlString = toLDMLString(options); | |
1525 | |
1526 // Filter out supported extension keys so we know what to put in resolved | |
1527 // section later on. | |
1528 // We need to pass calendar and number system to the method. | |
1529 var tz = canonicalizeTimeZoneID(options.timeZone); | |
1530 | |
1531 // ICU prefers options to be passed using -u- extension key/values, so | |
1532 // we need to build that. | |
1533 var internalOptions = {}; | |
1534 var extensionMap = parseExtension(locale.extension); | |
1535 | |
1536 /** | |
1537 * Map of Unicode extensions to option properties, and their values and types, | |
1538 * for a date/time format. | |
1539 */ | |
1540 var DATETIME_FORMAT_KEY_MAP = { | |
1541 'ca': {'property': UNDEFINED, 'type': 'string'}, | |
1542 'nu': {'property': UNDEFINED, 'type': 'string'} | |
1543 }; | |
1544 | |
1545 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP, | |
1546 getOption, internalOptions); | |
1547 | |
1548 var requestedLocale = locale.locale + extension; | |
1549 var resolved = ObjectDefineProperties({}, { | |
1550 calendar: {writable: true}, | |
1551 day: {writable: true}, | |
1552 era: {writable: true}, | |
1553 hour12: {writable: true}, | |
1554 hour: {writable: true}, | |
1555 locale: {writable: true}, | |
1556 minute: {writable: true}, | |
1557 month: {writable: true}, | |
1558 numberingSystem: {writable: true}, | |
1559 pattern: {writable: true}, | |
1560 requestedLocale: {value: requestedLocale, writable: true}, | |
1561 second: {writable: true}, | |
1562 timeZone: {writable: true}, | |
1563 timeZoneName: {writable: true}, | |
1564 tz: {value: tz, writable: true}, | |
1565 weekday: {writable: true}, | |
1566 year: {writable: true} | |
1567 }); | |
1568 | |
1569 var formatter = %CreateDateTimeFormat( | |
1570 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved); | |
1571 | |
1572 if (!IS_UNDEFINED(tz) && tz !== resolved.timeZone) { | |
1573 throw MakeRangeError(kUnsupportedTimeZone, tz); | |
1574 } | |
1575 | |
1576 %MarkAsInitializedIntlObjectOfType(dateFormat, 'dateformat', formatter); | |
1577 ObjectDefineProperty(dateFormat, 'resolved', {value: resolved}); | |
1578 | |
1579 return dateFormat; | |
1580 } | |
1581 | |
1582 | |
1583 /** | |
1584 * Constructs Intl.DateTimeFormat object given optional locales and options | |
1585 * parameters. | |
1586 * | |
1587 * @constructor | |
1588 */ | |
1589 %AddNamedProperty(Intl, 'DateTimeFormat', function() { | |
1590 var locales = %_Arguments(0); | |
1591 var options = %_Arguments(1); | |
1592 | |
1593 if (!this || this === Intl) { | |
1594 // Constructor is called as a function. | |
1595 return new Intl.DateTimeFormat(locales, options); | |
1596 } | |
1597 | |
1598 return initializeDateTimeFormat(TO_OBJECT(this), locales, options); | |
1599 }, | |
1600 DONT_ENUM | |
1601 ); | |
1602 | |
1603 | |
1604 /** | |
1605 * DateTimeFormat resolvedOptions method. | |
1606 */ | |
1607 %AddNamedProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() { | |
1608 if (%_IsConstructCall()) { | |
1609 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
1610 } | |
1611 | |
1612 if (!%IsInitializedIntlObjectOfType(this, 'dateformat')) { | |
1613 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "DateTimeFormat"); | |
1614 } | |
1615 | |
1616 /** | |
1617 * Maps ICU calendar names into LDML type. | |
1618 */ | |
1619 var ICU_CALENDAR_MAP = { | |
1620 'gregorian': 'gregory', | |
1621 'japanese': 'japanese', | |
1622 'buddhist': 'buddhist', | |
1623 'roc': 'roc', | |
1624 'persian': 'persian', | |
1625 'islamic-civil': 'islamicc', | |
1626 'islamic': 'islamic', | |
1627 'hebrew': 'hebrew', | |
1628 'chinese': 'chinese', | |
1629 'indian': 'indian', | |
1630 'coptic': 'coptic', | |
1631 'ethiopic': 'ethiopic', | |
1632 'ethiopic-amete-alem': 'ethioaa' | |
1633 }; | |
1634 | |
1635 var format = this; | |
1636 var fromPattern = fromLDMLString(format.resolved.pattern); | |
1637 var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar]; | |
1638 if (IS_UNDEFINED(userCalendar)) { | |
1639 // Use ICU name if we don't have a match. It shouldn't happen, but | |
1640 // it would be too strict to throw for this. | |
1641 userCalendar = format.resolved.calendar; | |
1642 } | |
1643 | |
1644 var locale = getOptimalLanguageTag(format.resolved.requestedLocale, | |
1645 format.resolved.locale); | |
1646 | |
1647 var result = { | |
1648 locale: locale, | |
1649 numberingSystem: format.resolved.numberingSystem, | |
1650 calendar: userCalendar, | |
1651 timeZone: format.resolved.timeZone | |
1652 }; | |
1653 | |
1654 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName); | |
1655 addWECPropertyIfDefined(result, 'era', fromPattern.era); | |
1656 addWECPropertyIfDefined(result, 'year', fromPattern.year); | |
1657 addWECPropertyIfDefined(result, 'month', fromPattern.month); | |
1658 addWECPropertyIfDefined(result, 'day', fromPattern.day); | |
1659 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday); | |
1660 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12); | |
1661 addWECPropertyIfDefined(result, 'hour', fromPattern.hour); | |
1662 addWECPropertyIfDefined(result, 'minute', fromPattern.minute); | |
1663 addWECPropertyIfDefined(result, 'second', fromPattern.second); | |
1664 | |
1665 return result; | |
1666 }, | |
1667 DONT_ENUM | |
1668 ); | |
1669 %FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions, | |
1670 'resolvedOptions'); | |
1671 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions); | |
1672 %SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions); | |
1673 | |
1674 | |
1675 /** | |
1676 * Returns the subset of the given locale list for which this locale list | |
1677 * has a matching (possibly fallback) locale. Locales appear in the same | |
1678 * order in the returned list as in the input list. | |
1679 * Options are optional parameter. | |
1680 */ | |
1681 %AddNamedProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) { | |
1682 if (%_IsConstructCall()) { | |
1683 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
1684 } | |
1685 | |
1686 return supportedLocalesOf('dateformat', locales, %_Arguments(1)); | |
1687 }, | |
1688 DONT_ENUM | |
1689 ); | |
1690 %FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf'); | |
1691 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf); | |
1692 %SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf); | |
1693 | |
1694 | |
1695 /** | |
1696 * Returns a String value representing the result of calling ToNumber(date) | |
1697 * according to the effective locale and the formatting options of this | |
1698 * DateTimeFormat. | |
1699 */ | |
1700 function formatDate(formatter, dateValue) { | |
1701 var dateMs; | |
1702 if (IS_UNDEFINED(dateValue)) { | |
1703 dateMs = %DateCurrentTime(); | |
1704 } else { | |
1705 dateMs = TO_NUMBER(dateValue); | |
1706 } | |
1707 | |
1708 if (!IsFinite(dateMs)) throw MakeRangeError(kDateRange); | |
1709 | |
1710 return %InternalDateFormat(%GetImplFromInitializedIntlObject(formatter), | |
1711 new GlobalDate(dateMs)); | |
1712 } | |
1713 | |
1714 | |
1715 /** | |
1716 * Returns a Date object representing the result of calling ToString(value) | |
1717 * according to the effective locale and the formatting options of this | |
1718 * DateTimeFormat. | |
1719 * Returns undefined if date string cannot be parsed. | |
1720 */ | |
1721 function parseDate(formatter, value) { | |
1722 return %InternalDateParse(%GetImplFromInitializedIntlObject(formatter), | |
1723 GlobalString(value)); | |
1724 } | |
1725 | |
1726 | |
1727 // 0 because date is optional argument. | |
1728 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0); | |
1729 addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1); | |
1730 | |
1731 | |
1732 /** | |
1733 * Returns canonical Area/Location name, or throws an exception if the zone | |
1734 * name is invalid IANA name. | |
1735 */ | |
1736 function canonicalizeTimeZoneID(tzID) { | |
1737 // Skip undefined zones. | |
1738 if (IS_UNDEFINED(tzID)) { | |
1739 return tzID; | |
1740 } | |
1741 | |
1742 // Special case handling (UTC, GMT). | |
1743 var upperID = %StringToUpperCase(tzID); | |
1744 if (upperID === 'UTC' || upperID === 'GMT' || | |
1745 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') { | |
1746 return 'UTC'; | |
1747 } | |
1748 | |
1749 // We expect only _ and / beside ASCII letters. | |
1750 // All inputs should conform to Area/Location from now on. | |
1751 var match = %_CallFunction(tzID, GetTimezoneNameCheckRE(), StringMatch); | |
1752 if (IS_NULL(match)) throw MakeRangeError(kExpectedLocation, tzID); | |
1753 | |
1754 var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]); | |
1755 var i = 3; | |
1756 while (!IS_UNDEFINED(match[i]) && i < match.length) { | |
1757 result = result + '_' + toTitleCaseWord(match[i]); | |
1758 i++; | |
1759 } | |
1760 | |
1761 return result; | |
1762 } | |
1763 | |
1764 /** | |
1765 * Initializes the given object so it's a valid BreakIterator instance. | |
1766 * Useful for subclassing. | |
1767 */ | |
1768 function initializeBreakIterator(iterator, locales, options) { | |
1769 if (%IsInitializedIntlObject(iterator)) { | |
1770 throw MakeTypeError(kReinitializeIntl, "v8BreakIterator"); | |
1771 } | |
1772 | |
1773 if (IS_UNDEFINED(options)) { | |
1774 options = {}; | |
1775 } | |
1776 | |
1777 var getOption = getGetOption(options, 'breakiterator'); | |
1778 | |
1779 var internalOptions = {}; | |
1780 | |
1781 defineWEProperty(internalOptions, 'type', getOption( | |
1782 'type', 'string', ['character', 'word', 'sentence', 'line'], 'word')); | |
1783 | |
1784 var locale = resolveLocale('breakiterator', locales, options); | |
1785 var resolved = ObjectDefineProperties({}, { | |
1786 requestedLocale: {value: locale.locale, writable: true}, | |
1787 type: {value: internalOptions.type, writable: true}, | |
1788 locale: {writable: true} | |
1789 }); | |
1790 | |
1791 var internalIterator = %CreateBreakIterator(locale.locale, | |
1792 internalOptions, | |
1793 resolved); | |
1794 | |
1795 %MarkAsInitializedIntlObjectOfType(iterator, 'breakiterator', | |
1796 internalIterator); | |
1797 ObjectDefineProperty(iterator, 'resolved', {value: resolved}); | |
1798 | |
1799 return iterator; | |
1800 } | |
1801 | |
1802 | |
1803 /** | |
1804 * Constructs Intl.v8BreakIterator object given optional locales and options | |
1805 * parameters. | |
1806 * | |
1807 * @constructor | |
1808 */ | |
1809 %AddNamedProperty(Intl, 'v8BreakIterator', function() { | |
1810 var locales = %_Arguments(0); | |
1811 var options = %_Arguments(1); | |
1812 | |
1813 if (!this || this === Intl) { | |
1814 // Constructor is called as a function. | |
1815 return new Intl.v8BreakIterator(locales, options); | |
1816 } | |
1817 | |
1818 return initializeBreakIterator(TO_OBJECT(this), locales, options); | |
1819 }, | |
1820 DONT_ENUM | |
1821 ); | |
1822 | |
1823 | |
1824 /** | |
1825 * BreakIterator resolvedOptions method. | |
1826 */ | |
1827 %AddNamedProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions', | |
1828 function() { | |
1829 if (%_IsConstructCall()) { | |
1830 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
1831 } | |
1832 | |
1833 if (!%IsInitializedIntlObjectOfType(this, 'breakiterator')) { | |
1834 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "v8BreakIterator"); | |
1835 } | |
1836 | |
1837 var segmenter = this; | |
1838 var locale = getOptimalLanguageTag(segmenter.resolved.requestedLocale, | |
1839 segmenter.resolved.locale); | |
1840 | |
1841 return { | |
1842 locale: locale, | |
1843 type: segmenter.resolved.type | |
1844 }; | |
1845 }, | |
1846 DONT_ENUM | |
1847 ); | |
1848 %FunctionSetName(Intl.v8BreakIterator.prototype.resolvedOptions, | |
1849 'resolvedOptions'); | |
1850 %FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions); | |
1851 %SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions); | |
1852 | |
1853 | |
1854 /** | |
1855 * Returns the subset of the given locale list for which this locale list | |
1856 * has a matching (possibly fallback) locale. Locales appear in the same | |
1857 * order in the returned list as in the input list. | |
1858 * Options are optional parameter. | |
1859 */ | |
1860 %AddNamedProperty(Intl.v8BreakIterator, 'supportedLocalesOf', | |
1861 function(locales) { | |
1862 if (%_IsConstructCall()) { | |
1863 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
1864 } | |
1865 | |
1866 return supportedLocalesOf('breakiterator', locales, %_Arguments(1)); | |
1867 }, | |
1868 DONT_ENUM | |
1869 ); | |
1870 %FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf'); | |
1871 %FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf); | |
1872 %SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf); | |
1873 | |
1874 | |
1875 /** | |
1876 * Adopts text to segment using the iterator. Old text, if present, | |
1877 * gets discarded. | |
1878 */ | |
1879 function adoptText(iterator, text) { | |
1880 %BreakIteratorAdoptText(%GetImplFromInitializedIntlObject(iterator), | |
1881 GlobalString(text)); | |
1882 } | |
1883 | |
1884 | |
1885 /** | |
1886 * Returns index of the first break in the string and moves current pointer. | |
1887 */ | |
1888 function first(iterator) { | |
1889 return %BreakIteratorFirst(%GetImplFromInitializedIntlObject(iterator)); | |
1890 } | |
1891 | |
1892 | |
1893 /** | |
1894 * Returns the index of the next break and moves the pointer. | |
1895 */ | |
1896 function next(iterator) { | |
1897 return %BreakIteratorNext(%GetImplFromInitializedIntlObject(iterator)); | |
1898 } | |
1899 | |
1900 | |
1901 /** | |
1902 * Returns index of the current break. | |
1903 */ | |
1904 function current(iterator) { | |
1905 return %BreakIteratorCurrent(%GetImplFromInitializedIntlObject(iterator)); | |
1906 } | |
1907 | |
1908 | |
1909 /** | |
1910 * Returns type of the current break. | |
1911 */ | |
1912 function breakType(iterator) { | |
1913 return %BreakIteratorBreakType(%GetImplFromInitializedIntlObject(iterator)); | |
1914 } | |
1915 | |
1916 | |
1917 addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1); | |
1918 addBoundMethod(Intl.v8BreakIterator, 'first', first, 0); | |
1919 addBoundMethod(Intl.v8BreakIterator, 'next', next, 0); | |
1920 addBoundMethod(Intl.v8BreakIterator, 'current', current, 0); | |
1921 addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0); | |
1922 | |
1923 // Save references to Intl objects and methods we use, for added security. | |
1924 var savedObjects = { | |
1925 'collator': Intl.Collator, | |
1926 'numberformat': Intl.NumberFormat, | |
1927 'dateformatall': Intl.DateTimeFormat, | |
1928 'dateformatdate': Intl.DateTimeFormat, | |
1929 'dateformattime': Intl.DateTimeFormat | |
1930 }; | |
1931 | |
1932 | |
1933 // Default (created with undefined locales and options parameters) collator, | |
1934 // number and date format instances. They'll be created as needed. | |
1935 var defaultObjects = { | |
1936 'collator': UNDEFINED, | |
1937 'numberformat': UNDEFINED, | |
1938 'dateformatall': UNDEFINED, | |
1939 'dateformatdate': UNDEFINED, | |
1940 'dateformattime': UNDEFINED, | |
1941 }; | |
1942 | |
1943 | |
1944 /** | |
1945 * Returns cached or newly created instance of a given service. | |
1946 * We cache only default instances (where no locales or options are provided). | |
1947 */ | |
1948 function cachedOrNewService(service, locales, options, defaults) { | |
1949 var useOptions = (IS_UNDEFINED(defaults)) ? options : defaults; | |
1950 if (IS_UNDEFINED(locales) && IS_UNDEFINED(options)) { | |
1951 if (IS_UNDEFINED(defaultObjects[service])) { | |
1952 defaultObjects[service] = new savedObjects[service](locales, useOptions); | |
1953 } | |
1954 return defaultObjects[service]; | |
1955 } | |
1956 return new savedObjects[service](locales, useOptions); | |
1957 } | |
1958 | |
1959 | |
1960 function OverrideFunction(object, name, f) { | |
1961 %CheckIsBootstrapping(); | |
1962 ObjectDefineProperty(object, name, { value: f, | |
1963 writeable: true, | |
1964 configurable: true, | |
1965 enumerable: false }); | |
1966 %FunctionSetName(f, name); | |
1967 %FunctionRemovePrototype(f); | |
1968 %SetNativeFlag(f); | |
1969 } | |
1970 | |
1971 /** | |
1972 * Compares this and that, and returns less than 0, 0 or greater than 0 value. | |
1973 * Overrides the built-in method. | |
1974 */ | |
1975 OverrideFunction(GlobalString.prototype, 'localeCompare', function(that) { | |
1976 if (%_IsConstructCall()) { | |
1977 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
1978 } | |
1979 | |
1980 if (IS_NULL_OR_UNDEFINED(this)) { | |
1981 throw MakeTypeError(kMethodInvokedOnNullOrUndefined); | |
1982 } | |
1983 | |
1984 var locales = %_Arguments(1); | |
1985 var options = %_Arguments(2); | |
1986 var collator = cachedOrNewService('collator', locales, options); | |
1987 return compare(collator, this, that); | |
1988 } | |
1989 ); | |
1990 | |
1991 | |
1992 /** | |
1993 * Unicode normalization. This method is called with one argument that | |
1994 * specifies the normalization form. | |
1995 * If none is specified, "NFC" is assumed. | |
1996 * If the form is not one of "NFC", "NFD", "NFKC", or "NFKD", then throw | |
1997 * a RangeError Exception. | |
1998 */ | |
1999 | |
2000 OverrideFunction(GlobalString.prototype, 'normalize', function() { | |
2001 if (%_IsConstructCall()) { | |
2002 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
2003 } | |
2004 | |
2005 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize"); | |
2006 var s = TO_STRING(this); | |
2007 | |
2008 var formArg = %_Arguments(0); | |
2009 var form = IS_UNDEFINED(formArg) ? 'NFC' : TO_STRING(formArg); | |
2010 | |
2011 var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD']; | |
2012 | |
2013 var normalizationForm = | |
2014 %_CallFunction(NORMALIZATION_FORMS, form, ArrayIndexOf); | |
2015 if (normalizationForm === -1) { | |
2016 throw MakeRangeError(kNormalizationForm, | |
2017 %_CallFunction(NORMALIZATION_FORMS, ', ', ArrayJoin)); | |
2018 } | |
2019 | |
2020 return %StringNormalize(s, normalizationForm); | |
2021 } | |
2022 ); | |
2023 | |
2024 | |
2025 /** | |
2026 * Formats a Number object (this) using locale and options values. | |
2027 * If locale or options are omitted, defaults are used. | |
2028 */ | |
2029 OverrideFunction(GlobalNumber.prototype, 'toLocaleString', function() { | |
2030 if (%_IsConstructCall()) { | |
2031 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
2032 } | |
2033 | |
2034 if (!(this instanceof GlobalNumber) && typeof(this) !== 'number') { | |
2035 throw MakeTypeError(kMethodInvokedOnWrongType, "Number"); | |
2036 } | |
2037 | |
2038 var locales = %_Arguments(0); | |
2039 var options = %_Arguments(1); | |
2040 var numberFormat = cachedOrNewService('numberformat', locales, options); | |
2041 return formatNumber(numberFormat, this); | |
2042 } | |
2043 ); | |
2044 | |
2045 | |
2046 /** | |
2047 * Returns actual formatted date or fails if date parameter is invalid. | |
2048 */ | |
2049 function toLocaleDateTime(date, locales, options, required, defaults, service) { | |
2050 if (!(date instanceof GlobalDate)) { | |
2051 throw MakeTypeError(kMethodInvokedOnWrongType, "Date"); | |
2052 } | |
2053 | |
2054 if (IsNaN(date)) return 'Invalid Date'; | |
2055 | |
2056 var internalOptions = toDateTimeOptions(options, required, defaults); | |
2057 | |
2058 var dateFormat = | |
2059 cachedOrNewService(service, locales, options, internalOptions); | |
2060 | |
2061 return formatDate(dateFormat, date); | |
2062 } | |
2063 | |
2064 | |
2065 /** | |
2066 * Formats a Date object (this) using locale and options values. | |
2067 * If locale or options are omitted, defaults are used - both date and time are | |
2068 * present in the output. | |
2069 */ | |
2070 OverrideFunction(GlobalDate.prototype, 'toLocaleString', function() { | |
2071 if (%_IsConstructCall()) { | |
2072 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
2073 } | |
2074 | |
2075 var locales = %_Arguments(0); | |
2076 var options = %_Arguments(1); | |
2077 return toLocaleDateTime( | |
2078 this, locales, options, 'any', 'all', 'dateformatall'); | |
2079 } | |
2080 ); | |
2081 | |
2082 | |
2083 /** | |
2084 * Formats a Date object (this) using locale and options values. | |
2085 * If locale or options are omitted, defaults are used - only date is present | |
2086 * in the output. | |
2087 */ | |
2088 OverrideFunction(GlobalDate.prototype, 'toLocaleDateString', function() { | |
2089 if (%_IsConstructCall()) { | |
2090 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
2091 } | |
2092 | |
2093 var locales = %_Arguments(0); | |
2094 var options = %_Arguments(1); | |
2095 return toLocaleDateTime( | |
2096 this, locales, options, 'date', 'date', 'dateformatdate'); | |
2097 } | |
2098 ); | |
2099 | |
2100 | |
2101 /** | |
2102 * Formats a Date object (this) using locale and options values. | |
2103 * If locale or options are omitted, defaults are used - only time is present | |
2104 * in the output. | |
2105 */ | |
2106 OverrideFunction(GlobalDate.prototype, 'toLocaleTimeString', function() { | |
2107 if (%_IsConstructCall()) { | |
2108 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor); | |
2109 } | |
2110 | |
2111 var locales = %_Arguments(0); | |
2112 var options = %_Arguments(1); | |
2113 return toLocaleDateTime( | |
2114 this, locales, options, 'time', 'time', 'dateformattime'); | |
2115 } | |
2116 ); | |
2117 | |
2118 }) | |
OLD | NEW |