Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: src/extensions/experimental/number-format.cc

Issue 7129051: Adding support for number formating to the JS i18n API. (Closed) Base URL: http://v8.googlecode.com/svn/branches/bleeding_edge/
Patch Set: Final fixes. Created 9 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « src/extensions/experimental/number-format.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright 2011 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
28 #include "src/extensions/experimental/number-format.h"
29
30 #include <string.h>
31
32 #include "src/extensions/experimental/i18n-utils.h"
33 #include "unicode/dcfmtsym.h"
34 #include "unicode/decimfmt.h"
35 #include "unicode/locid.h"
36 #include "unicode/numfmt.h"
37 #include "unicode/uchar.h"
38 #include "unicode/ucurr.h"
39
40 namespace v8 {
41 namespace internal {
42
43 const int NumberFormat::kCurrencyCodeLength = 4;
44
45 v8::Persistent<v8::FunctionTemplate> NumberFormat::number_format_template_;
46
47 static icu::DecimalFormat* CreateNumberFormat(v8::Handle<v8::String>,
48 v8::Handle<v8::String>,
49 v8::Handle<v8::Object>);
50 static icu::DecimalFormat* CreateFormatterFromSkeleton(
51 const icu::Locale&, const icu::UnicodeString&, UErrorCode*);
52 static icu::DecimalFormatSymbols* GetFormatSymbols(const icu::Locale&);
53 static bool GetCurrencyCode(const icu::Locale&,
54 const char* const,
55 v8::Handle<v8::Object>,
56 UChar*);
57 static v8::Handle<v8::Value> ThrowUnexpectedObjectError();
58
59 icu::DecimalFormat* NumberFormat::UnpackNumberFormat(
60 v8::Handle<v8::Object> obj) {
61 if (number_format_template_->HasInstance(obj)) {
62 return static_cast<icu::DecimalFormat*>(
63 obj->GetPointerFromInternalField(0));
64 }
65
66 return NULL;
67 }
68
69 void NumberFormat::DeleteNumberFormat(v8::Persistent<v8::Value> object,
70 void* param) {
71 v8::Persistent<v8::Object> persistent_object =
72 v8::Persistent<v8::Object>::Cast(object);
73
74 // First delete the hidden C++ object.
75 // Unpacking should never return NULL here. That would only happen if
76 // this method is used as the weak callback for persistent handles not
77 // pointing to a number formatter.
78 delete UnpackNumberFormat(persistent_object);
79
80 // Then dispose of the persistent handle to JS object.
81 persistent_object.Dispose();
82 }
83
84 v8::Handle<v8::Value> NumberFormat::Format(const v8::Arguments& args) {
85 v8::HandleScope handle_scope;
86
87 if (args.Length() != 1 || !args[0]->IsNumber()) {
88 // Just return NaN on invalid input.
89 return v8::String::New("NaN");
90 }
91
92 icu::DecimalFormat* number_format = UnpackNumberFormat(args.Holder());
93 if (!number_format) {
94 return ThrowUnexpectedObjectError();
95 }
96
97 // ICU will handle actual NaN value properly and return NaN string.
98 icu::UnicodeString result;
99 number_format->format(args[0]->NumberValue(), result);
100
101 return v8::String::New(
102 reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length());
103 }
104
105 v8::Handle<v8::Value> NumberFormat::JSNumberFormat(const v8::Arguments& args) {
106 v8::HandleScope handle_scope;
107
108 // Expect locale id, region id and settings.
109 if (args.Length() != 3 ||
110 !args[0]->IsString() || !args[1]->IsString() || !args[2]->IsObject()) {
111 return v8::ThrowException(v8::Exception::SyntaxError(
112 v8::String::New("Locale, region and number settings are required.")));
113 }
114
115 icu::DecimalFormat* number_format = CreateNumberFormat(
116 args[0]->ToString(), args[1]->ToString(), args[2]->ToObject());
117
118 if (number_format_template_.IsEmpty()) {
119 v8::Local<v8::FunctionTemplate> raw_template(v8::FunctionTemplate::New());
120
121 raw_template->SetClassName(v8::String::New("v8Locale.NumberFormat"));
122
123 // Define internal field count on instance template.
124 v8::Local<v8::ObjectTemplate> object_template =
125 raw_template->InstanceTemplate();
126
127 // Set aside internal field for icu number formatter.
128 object_template->SetInternalFieldCount(1);
129
130 // Define all of the prototype methods on prototype template.
131 v8::Local<v8::ObjectTemplate> proto = raw_template->PrototypeTemplate();
132 proto->Set(v8::String::New("format"),
133 v8::FunctionTemplate::New(Format));
134
135 number_format_template_ =
136 v8::Persistent<v8::FunctionTemplate>::New(raw_template);
137 }
138
139 // Create an empty object wrapper.
140 v8::Local<v8::Object> local_object =
141 number_format_template_->GetFunction()->NewInstance();
142 v8::Persistent<v8::Object> wrapper =
143 v8::Persistent<v8::Object>::New(local_object);
144
145 // Set number formatter as internal field of the resulting JS object.
146 wrapper->SetPointerInInternalField(0, number_format);
147
148 // Create options key.
149 v8::Local<v8::Object> options = v8::Object::New();
150
151 // Show what ICU decided to use for easier problem tracking.
152 // Keep it as v8 specific extension.
153 icu::UnicodeString pattern;
154 number_format->toPattern(pattern);
155 options->Set(v8::String::New("v8ResolvedPattern"),
156 v8::String::New(reinterpret_cast<const uint16_t*>(
157 pattern.getBuffer()), pattern.length()));
158
159 // Set resolved currency code in options.currency if not empty.
160 icu::UnicodeString currency(number_format->getCurrency());
161 if (!currency.isEmpty()) {
162 options->Set(v8::String::New("currencyCode"),
163 v8::String::New(reinterpret_cast<const uint16_t*>(
164 currency.getBuffer()), currency.length()));
165 }
166
167 wrapper->Set(v8::String::New("options"), options);
168
169 // Make object handle weak so we can delete iterator once GC kicks in.
170 wrapper.MakeWeak(NULL, DeleteNumberFormat);
171
172 return wrapper;
173 }
174
175 // Returns DecimalFormat.
176 static icu::DecimalFormat* CreateNumberFormat(v8::Handle<v8::String> locale,
177 v8::Handle<v8::String> region,
178 v8::Handle<v8::Object> settings) {
179 v8::HandleScope handle_scope;
180
181 v8::String::AsciiValue ascii_locale(locale);
182 icu::Locale icu_locale(*ascii_locale);
183
184 // Make formatter from skeleton.
185 icu::DecimalFormat* number_format = NULL;
186 UErrorCode status = U_ZERO_ERROR;
187 icu::UnicodeString setting;
188
189 if (I18NUtils::ExtractStringSetting(settings, "skeleton", &setting)) {
190 // TODO(cira): Use ICU skeleton once
191 // http://bugs.icu-project.org/trac/ticket/8610 is resolved.
192 number_format = CreateFormatterFromSkeleton(icu_locale, setting, &status);
193 } else if (I18NUtils::ExtractStringSetting(settings, "pattern", &setting)) {
194 number_format =
195 new icu::DecimalFormat(setting, GetFormatSymbols(icu_locale), status);
196 } else if (I18NUtils::ExtractStringSetting(settings, "style", &setting)) {
197 if (setting == UNICODE_STRING_SIMPLE("currency")) {
198 number_format = static_cast<icu::DecimalFormat*>(
199 icu::NumberFormat::createCurrencyInstance(icu_locale, status));
200 } else if (setting == UNICODE_STRING_SIMPLE("percent")) {
201 number_format = static_cast<icu::DecimalFormat*>(
202 icu::NumberFormat::createPercentInstance(icu_locale, status));
203 } else if (setting == UNICODE_STRING_SIMPLE("scientific")) {
204 number_format = static_cast<icu::DecimalFormat*>(
205 icu::NumberFormat::createScientificInstance(icu_locale, status));
206 } else {
207 // Make it decimal in any other case.
208 number_format = static_cast<icu::DecimalFormat*>(
209 icu::NumberFormat::createInstance(icu_locale, status));
210 }
211 }
212
213 if (U_FAILURE(status)) {
214 delete number_format;
215 status = U_ZERO_ERROR;
216 number_format = static_cast<icu::DecimalFormat*>(
217 icu::NumberFormat::createInstance(icu_locale, status));
218 }
219
220 // Attach appropriate currency code to the formatter.
221 // It affects currency formatters only.
222 // Region is full language identifier in form 'und_' + region id.
223 v8::String::AsciiValue ascii_region(region);
224
225 UChar currency_code[NumberFormat::kCurrencyCodeLength];
226 if (GetCurrencyCode(icu_locale, *ascii_region, settings, currency_code)) {
227 number_format->setCurrency(currency_code, status);
228 }
229
230 return number_format;
231 }
232
233 // Generates ICU number format pattern from given skeleton.
234 static icu::DecimalFormat* CreateFormatterFromSkeleton(
235 const icu::Locale& icu_locale,
236 const icu::UnicodeString& skeleton,
237 UErrorCode* status) {
238 icu::DecimalFormat skeleton_format(
239 skeleton, GetFormatSymbols(icu_locale), *status);
240
241 // Find out if skeleton contains currency or percent symbol and create
242 // proper instance to tweak.
243 icu::DecimalFormat* base_format = NULL;
244
245 // UChar representation of U+00A4 currency symbol.
246 const UChar currency_symbol = 0xA4u;
247
248 int32_t index = skeleton.indexOf(currency_symbol);
249 if (index != -1) {
250 // Find how many U+00A4 are there. There is at least one.
251 // Case of non-consecutive U+00A4 is taken care of in i18n.js.
252 int32_t end_index = skeleton.lastIndexOf(currency_symbol, index);
253
254 icu::NumberFormat::EStyles style;
255 switch (end_index - index) {
256 case 0:
257 style = icu::NumberFormat::kCurrencyStyle;
258 break;
259 case 1:
260 style = icu::NumberFormat::kIsoCurrencyStyle;
261 break;
262 default:
263 style = icu::NumberFormat::kPluralCurrencyStyle;
264 }
265
266 base_format = static_cast<icu::DecimalFormat*>(
267 icu::NumberFormat::createInstance(icu_locale, style, *status));
268 } else if (skeleton.indexOf('%') != -1) {
269 base_format = static_cast<icu::DecimalFormat*>(
270 icu::NumberFormat::createPercentInstance(icu_locale, *status));
271 } else {
272 // TODO(cira): Handle scientific skeleton.
273 base_format = static_cast<icu::DecimalFormat*>(
274 icu::NumberFormat::createInstance(icu_locale, *status));
275 }
276
277 if (U_FAILURE(*status)) {
278 delete base_format;
279 return NULL;
280 }
281
282 // Copy important information from skeleton to the new formatter.
283 // TODO(cira): copy rounding information from skeleton?
284 base_format->setGroupingUsed(skeleton_format.isGroupingUsed());
285
286 base_format->setMinimumIntegerDigits(
287 skeleton_format.getMinimumIntegerDigits());
288
289 base_format->setMinimumFractionDigits(
290 skeleton_format.getMinimumFractionDigits());
291
292 base_format->setMaximumFractionDigits(
293 skeleton_format.getMaximumFractionDigits());
294
295 return base_format;
296 }
297
298 // Gets decimal symbols for a locale.
299 static icu::DecimalFormatSymbols* GetFormatSymbols(
300 const icu::Locale& icu_locale) {
301 UErrorCode status = U_ZERO_ERROR;
302 icu::DecimalFormatSymbols* symbols =
303 new icu::DecimalFormatSymbols(icu_locale, status);
304
305 if (U_FAILURE(status)) {
306 delete symbols;
307 // Use symbols from default locale.
308 symbols = new icu::DecimalFormatSymbols(status);
309 }
310
311 return symbols;
312 }
313
314 // Gets currency ISO 4217 3-letter code.
315 // Check currencyCode setting first, then @currency=code and in the end
316 // try to infer currency code from locale in the form 'und_' + region id.
317 // Returns false in case of error.
318 static bool GetCurrencyCode(const icu::Locale& icu_locale,
319 const char* const und_region_locale,
320 v8::Handle<v8::Object> settings,
321 UChar* code) {
322 UErrorCode status = U_ZERO_ERROR;
323
324 // If there is user specified currency code, use it.
325 icu::UnicodeString currency;
326 if (I18NUtils::ExtractStringSetting(settings, "currencyCode", &currency)) {
327 currency.extract(code, NumberFormat::kCurrencyCodeLength, status);
328 return true;
329 }
330
331 // If ICU locale has -cu- currency code use it.
332 char currency_code[NumberFormat::kCurrencyCodeLength];
333 int32_t length = icu_locale.getKeywordValue(
334 "currency", currency_code, NumberFormat::kCurrencyCodeLength, status);
335 if (length != 0) {
336 I18NUtils::AsciiToUChar(currency_code, length + 1,
337 code, NumberFormat::kCurrencyCodeLength);
338 return true;
339 }
340
341 // Otherwise infer currency code from the region id.
342 ucurr_forLocale(
343 und_region_locale, code, NumberFormat::kCurrencyCodeLength, &status);
344
345 return !!U_SUCCESS(status);
346 }
347
348 // Throws a JavaScript exception.
349 static v8::Handle<v8::Value> ThrowUnexpectedObjectError() {
350 // Returns undefined, and schedules an exception to be thrown.
351 return v8::ThrowException(v8::Exception::Error(
352 v8::String::New("NumberFormat method called on an object "
353 "that is not a NumberFormat.")));
354 }
355
356 } } // namespace v8::internal
OLDNEW
« no previous file with comments | « src/extensions/experimental/number-format.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698