OLD | NEW |
| (Empty) |
1 // Copyright 2013 the V8 project authors. All rights reserved. | |
2 // Redistribution and use in source and binary forms, with or without | |
3 // modification, are permitted provided that the following conditions are | |
4 // met: | |
5 // | |
6 // * Redistributions of source code must retain the above copyright | |
7 // notice, this list of conditions and the following disclaimer. | |
8 // * Redistributions in binary form must reproduce the above | |
9 // copyright notice, this list of conditions and the following | |
10 // disclaimer in the documentation and/or other materials provided | |
11 // with the distribution. | |
12 // * Neither the name of Google Inc. nor the names of its | |
13 // contributors may be used to endorse or promote products derived | |
14 // from this software without specific prior written permission. | |
15 // | |
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
27 // limitations under the License. | |
28 | |
29 // ECMAScript 402 API implementation is broken into separate files for | |
30 // each service. The build system combines them together into one | |
31 // Intl namespace. | |
32 | |
33 /** | |
34 * Verifies that the input is a well-formed ISO 4217 currency code. | |
35 * Don't uppercase to test. It could convert invalid code into a valid one. | |
36 * For example \u00DFP (Eszett+P) becomes SSP. | |
37 */ | |
38 function isWellFormedCurrencyCode(currency) { | |
39 return typeof currency == "string" && | |
40 currency.length == 3 && | |
41 currency.match(/[^A-Za-z]/) == null; | |
42 } | |
43 | |
44 | |
45 /** | |
46 * Returns the valid digit count for a property, or throws RangeError on | |
47 * a value out of the range. | |
48 */ | |
49 function getNumberOption(options, property, min, max, fallback) { | |
50 var value = options[property]; | |
51 if (value !== undefined) { | |
52 value = Number(value); | |
53 if (isNaN(value) || value < min || value > max) { | |
54 throw new RangeError(property + ' value is out of range.'); | |
55 } | |
56 return Math.floor(value); | |
57 } | |
58 | |
59 return fallback; | |
60 } | |
61 | |
62 | |
63 /** | |
64 * Initializes the given object so it's a valid NumberFormat instance. | |
65 * Useful for subclassing. | |
66 */ | |
67 function initializeNumberFormat(numberFormat, locales, options) { | |
68 if (numberFormat.hasOwnProperty('__initializedIntlObject')) { | |
69 throw new TypeError('Trying to re-initialize NumberFormat object.'); | |
70 } | |
71 | |
72 if (options === undefined) { | |
73 options = {}; | |
74 } | |
75 | |
76 var getOption = getGetOption(options, 'numberformat'); | |
77 | |
78 var locale = resolveLocale('numberformat', locales, options); | |
79 | |
80 var internalOptions = {}; | |
81 defineWEProperty(internalOptions, 'style', getOption( | |
82 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal')); | |
83 | |
84 var currency = getOption('currency', 'string'); | |
85 if (currency !== undefined && !isWellFormedCurrencyCode(currency)) { | |
86 throw new RangeError('Invalid currency code: ' + currency); | |
87 } | |
88 | |
89 if (internalOptions.style === 'currency' && currency === undefined) { | |
90 throw new TypeError('Currency code is required with currency style.'); | |
91 } | |
92 | |
93 var currencyDisplay = getOption( | |
94 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol'); | |
95 if (internalOptions.style === 'currency') { | |
96 defineWEProperty(internalOptions, 'currency', currency.toUpperCase()); | |
97 defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay); | |
98 } | |
99 | |
100 // Digit ranges. | |
101 var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1); | |
102 defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid); | |
103 | |
104 var mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0); | |
105 defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd); | |
106 | |
107 var mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, 3); | |
108 defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd); | |
109 | |
110 var mnsd = options['minimumSignificantDigits']; | |
111 var mxsd = options['maximumSignificantDigits']; | |
112 if (mnsd !== undefined || mxsd !== undefined) { | |
113 mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0); | |
114 defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd); | |
115 | |
116 mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21); | |
117 defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd); | |
118 } | |
119 | |
120 // Grouping. | |
121 defineWEProperty(internalOptions, 'useGrouping', getOption( | |
122 'useGrouping', 'boolean', undefined, true)); | |
123 | |
124 // ICU prefers options to be passed using -u- extension key/values for | |
125 // number format, so we need to build that. | |
126 var extensionMap = parseExtension(locale.extension); | |
127 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP, | |
128 getOption, internalOptions); | |
129 | |
130 var requestedLocale = locale.locale + extension; | |
131 var resolved = Object.defineProperties({}, { | |
132 currency: {writable: true}, | |
133 currencyDisplay: {writable: true}, | |
134 locale: {writable: true}, | |
135 maximumFractionDigits: {writable: true}, | |
136 minimumFractionDigits: {writable: true}, | |
137 minimumIntegerDigits: {writable: true}, | |
138 numberingSystem: {writable: true}, | |
139 requestedLocale: {value: requestedLocale, writable: true}, | |
140 style: {value: internalOptions.style, writable: true}, | |
141 useGrouping: {writable: true} | |
142 }); | |
143 if (internalOptions.hasOwnProperty('minimumSignificantDigits')) { | |
144 defineWEProperty(resolved, 'minimumSignificantDigits', undefined); | |
145 } | |
146 if (internalOptions.hasOwnProperty('maximumSignificantDigits')) { | |
147 defineWEProperty(resolved, 'maximumSignificantDigits', undefined); | |
148 } | |
149 var formatter = %CreateNumberFormat(requestedLocale, | |
150 internalOptions, | |
151 resolved); | |
152 | |
153 // We can't get information about number or currency style from ICU, so we | |
154 // assume user request was fulfilled. | |
155 if (internalOptions.style === 'currency') { | |
156 Object.defineProperty(resolved, 'currencyDisplay', {value: currencyDisplay, | |
157 writable: true}); | |
158 } | |
159 | |
160 Object.defineProperty(numberFormat, 'formatter', {value: formatter}); | |
161 Object.defineProperty(numberFormat, 'resolved', {value: resolved}); | |
162 Object.defineProperty(numberFormat, '__initializedIntlObject', | |
163 {value: 'numberformat'}); | |
164 | |
165 return numberFormat; | |
166 } | |
167 | |
168 | |
169 /** | |
170 * Constructs Intl.NumberFormat object given optional locales and options | |
171 * parameters. | |
172 * | |
173 * @constructor | |
174 */ | |
175 %SetProperty(Intl, 'NumberFormat', function() { | |
176 var locales = arguments[0]; | |
177 var options = arguments[1]; | |
178 | |
179 if (!this || this === Intl) { | |
180 // Constructor is called as a function. | |
181 return new Intl.NumberFormat(locales, options); | |
182 } | |
183 | |
184 return initializeNumberFormat(toObject(this), locales, options); | |
185 }, | |
186 ATTRIBUTES.DONT_ENUM | |
187 ); | |
188 | |
189 | |
190 /** | |
191 * NumberFormat resolvedOptions method. | |
192 */ | |
193 %SetProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() { | |
194 if (%_IsConstructCall()) { | |
195 throw new TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); | |
196 } | |
197 | |
198 if (!this || typeof this !== 'object' || | |
199 this.__initializedIntlObject !== 'numberformat') { | |
200 throw new TypeError('resolvedOptions method called on a non-object' + | |
201 ' or on a object that is not Intl.NumberFormat.'); | |
202 } | |
203 | |
204 var format = this; | |
205 var locale = getOptimalLanguageTag(format.resolved.requestedLocale, | |
206 format.resolved.locale); | |
207 | |
208 var result = { | |
209 locale: locale, | |
210 numberingSystem: format.resolved.numberingSystem, | |
211 style: format.resolved.style, | |
212 useGrouping: format.resolved.useGrouping, | |
213 minimumIntegerDigits: format.resolved.minimumIntegerDigits, | |
214 minimumFractionDigits: format.resolved.minimumFractionDigits, | |
215 maximumFractionDigits: format.resolved.maximumFractionDigits, | |
216 }; | |
217 | |
218 if (result.style === 'currency') { | |
219 defineWECProperty(result, 'currency', format.resolved.currency); | |
220 defineWECProperty(result, 'currencyDisplay', | |
221 format.resolved.currencyDisplay); | |
222 } | |
223 | |
224 if (format.resolved.hasOwnProperty('minimumSignificantDigits')) { | |
225 defineWECProperty(result, 'minimumSignificantDigits', | |
226 format.resolved.minimumSignificantDigits); | |
227 } | |
228 | |
229 if (format.resolved.hasOwnProperty('maximumSignificantDigits')) { | |
230 defineWECProperty(result, 'maximumSignificantDigits', | |
231 format.resolved.maximumSignificantDigits); | |
232 } | |
233 | |
234 return result; | |
235 }, | |
236 ATTRIBUTES.DONT_ENUM | |
237 ); | |
238 %FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions, | |
239 'resolvedOptions'); | |
240 %FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions); | |
241 %SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions); | |
242 | |
243 | |
244 /** | |
245 * Returns the subset of the given locale list for which this locale list | |
246 * has a matching (possibly fallback) locale. Locales appear in the same | |
247 * order in the returned list as in the input list. | |
248 * Options are optional parameter. | |
249 */ | |
250 %SetProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) { | |
251 if (%_IsConstructCall()) { | |
252 throw new TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); | |
253 } | |
254 | |
255 return supportedLocalesOf('numberformat', locales, arguments[1]); | |
256 }, | |
257 ATTRIBUTES.DONT_ENUM | |
258 ); | |
259 %FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf'); | |
260 %FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf); | |
261 %SetNativeFlag(Intl.NumberFormat.supportedLocalesOf); | |
262 | |
263 | |
264 /** | |
265 * Returns a String value representing the result of calling ToNumber(value) | |
266 * according to the effective locale and the formatting options of this | |
267 * NumberFormat. | |
268 */ | |
269 function formatNumber(formatter, value) { | |
270 // Spec treats -0 and +0 as 0. | |
271 var number = Number(value); | |
272 if (number === -0) { | |
273 number = 0; | |
274 } | |
275 | |
276 return %InternalNumberFormat(formatter.formatter, number); | |
277 } | |
278 | |
279 | |
280 /** | |
281 * Returns a Number that represents string value that was passed in. | |
282 */ | |
283 function parseNumber(formatter, value) { | |
284 return %InternalNumberParse(formatter.formatter, String(value)); | |
285 } | |
286 | |
287 | |
288 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1); | |
289 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1); | |
OLD | NEW |