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

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: Added currencyCode setting. Improved skeleton mapping code. 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
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 v8::Handle<v8::Object>,
55 UChar*);
56 static v8::Handle<v8::Value> ThrowUnexpectedObjectError();
57
58 icu::DecimalFormat* NumberFormat::UnpackNumberFormat(
59 v8::Handle<v8::Object> obj) {
60 if (number_format_template_->HasInstance(obj)) {
61 return static_cast<icu::DecimalFormat*>(
62 obj->GetPointerFromInternalField(0));
63 }
64
65 return NULL;
66 }
67
68 void NumberFormat::DeleteNumberFormat(v8::Persistent<v8::Value> object,
69 void* param) {
70 v8::Persistent<v8::Object> persistent_object =
71 v8::Persistent<v8::Object>::Cast(object);
72
73 // First delete the hidden C++ object.
74 // Unpacking should never return NULL here. That would only happen if
75 // this method is used as the weak callback for persistent handles not
76 // pointing to a number formatter.
77 delete UnpackNumberFormat(persistent_object);
78
79 // Then dispose of the persistent handle to JS object.
80 persistent_object.Dispose();
81 }
82
83 v8::Handle<v8::Value> NumberFormat::Format(const v8::Arguments& args) {
84 v8::HandleScope handle_scope;
85
86 if (args.Length() != 1 || !args[0]->IsNumber()) {
87 // Just return NaN on invalid input.
88 return v8::String::New("NaN");
89 }
90
91 icu::DecimalFormat* number_format = UnpackNumberFormat(args.Holder());
92 if (!number_format) {
93 return ThrowUnexpectedObjectError();
94 }
95
96 // ICU will handle actual NaN value properly and return NaN string.
97 icu::UnicodeString result;
98 number_format->format(args[0]->NumberValue(), result);
99
100 return v8::String::New(
101 reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length());
102 }
103
104 v8::Handle<v8::Value> NumberFormat::JSNumberFormat(const v8::Arguments& args) {
105 v8::HandleScope handle_scope;
106
107 // Expect locale id, region id and settings.
108 if (args.Length() != 3 ||
109 !args[0]->IsString() || !args[1]->IsString() || !args[2]->IsObject()) {
110 return v8::ThrowException(v8::Exception::SyntaxError(
111 v8::String::New("Locale, region and number settings are required.")));
112 }
113
114 icu::DecimalFormat* number_format = CreateNumberFormat(
115 args[0]->ToString(), args[1]->ToString(), args[2]->ToObject());
116
117 if (number_format_template_.IsEmpty()) {
118 v8::Local<v8::FunctionTemplate> raw_template(v8::FunctionTemplate::New());
119
120 raw_template->SetClassName(v8::String::New("v8Locale.NumberFormat"));
121
122 // Define internal field count on instance template.
123 v8::Local<v8::ObjectTemplate> object_template =
124 raw_template->InstanceTemplate();
125
126 // Set aside internal field for icu number formatter.
127 object_template->SetInternalFieldCount(1);
128
129 // Define all of the prototype methods on prototype template.
130 v8::Local<v8::ObjectTemplate> proto = raw_template->PrototypeTemplate();
131 proto->Set(v8::String::New("format"),
132 v8::FunctionTemplate::New(Format));
133
134 number_format_template_ =
135 v8::Persistent<v8::FunctionTemplate>::New(raw_template);
136 }
137
138 // Create an empty object wrapper.
139 v8::Local<v8::Object> local_object =
140 number_format_template_->GetFunction()->NewInstance();
141 v8::Persistent<v8::Object> wrapper =
142 v8::Persistent<v8::Object>::New(local_object);
143
144 // Set number formatter as internal field of the resulting JS object.
145 wrapper->SetPointerInInternalField(0, number_format);
146
147 // Create options key.
148 v8::Local<v8::Object> options = v8::Object::New();
149
150 // Show what ICU decided to use for easier problem tracking.
151 // Keep it as v8 specific extension.
152 icu::UnicodeString pattern;
153 number_format->toPattern(pattern);
154 options->Set(v8::String::New("v8ResolvedPattern"),
155 v8::String::New(reinterpret_cast<const uint16_t*>(
156 pattern.getBuffer()), pattern.length()));
157
158 // Set resolved currency code in options.currency if not empty.
159 icu::UnicodeString currency(number_format->getCurrency());
160 if (!currency.isEmpty()) {
161 options->Set(v8::String::New("currencyCode"),
162 v8::String::New(reinterpret_cast<const uint16_t*>(
163 currency.getBuffer()), currency.length()));
164 }
165
166 wrapper->Set(v8::String::New("options"), options);
167
168 // Make object handle weak so we can delete iterator once GC kicks in.
169 wrapper.MakeWeak(NULL, DeleteNumberFormat);
170
171 return wrapper;
172 }
173
174 // Returns DecimalFormat.
175 static icu::DecimalFormat* CreateNumberFormat(v8::Handle<v8::String> locale,
176 v8::Handle<v8::String> region,
177 v8::Handle<v8::Object> settings) {
178 v8::HandleScope handle_scope;
179
180 v8::String::AsciiValue ascii_locale(locale);
181 icu::Locale icu_locale(*ascii_locale);
182
183 // Make formatter from skeleton.
184 icu::DecimalFormat* number_format = NULL;
185 UErrorCode status = U_ZERO_ERROR;
186 icu::UnicodeString setting;
187
188 if (I18NUtils::ExtractStringSetting(settings, "skeleton", &setting)) {
189 // TODO(cira): Use ICU skeleton once
190 // http://bugs.icu-project.org/trac/ticket/8610 is resolved.
191 number_format = CreateFormatterFromSkeleton(icu_locale, setting, &status);
192 } else if (I18NUtils::ExtractStringSetting(settings, "pattern", &setting)) {
193 number_format =
194 new icu::DecimalFormat(setting, GetFormatSymbols(icu_locale), status);
195 } else if (I18NUtils::ExtractStringSetting(settings, "style", &setting)) {
196 if (setting == UNICODE_STRING_SIMPLE("currency")) {
197 number_format = static_cast<icu::DecimalFormat*>(
198 icu::NumberFormat::createCurrencyInstance(icu_locale, status));
199 } else if (setting == UNICODE_STRING_SIMPLE("percent")) {
200 number_format = static_cast<icu::DecimalFormat*>(
201 icu::NumberFormat::createPercentInstance(icu_locale, status));
202 } else if (setting == UNICODE_STRING_SIMPLE("scientific")) {
203 number_format = static_cast<icu::DecimalFormat*>(
204 icu::NumberFormat::createScientificInstance(icu_locale, status));
205 } else {
206 // Make it decimal in any other case.
207 number_format = static_cast<icu::DecimalFormat*>(
208 icu::NumberFormat::createInstance(icu_locale, status));
209 }
210 }
211
212 if (U_FAILURE(status)) {
213 delete number_format;
214 status = U_ZERO_ERROR;
215 number_format = static_cast<icu::DecimalFormat*>(
216 icu::NumberFormat::createInstance(icu_locale, status));
217 }
218
219 // Attach appropriate currency code to the formatter.
220 // It affects currency formatters only.
jungshik at Google 2011/06/16 18:08:19 Pls, add a comment that |region| here is actually
Nebojša Ćirić 2011/06/17 17:48:45 Done.
221 v8::String::AsciiValue ascii_region(region);
222 icu::Locale icu_region(*ascii_region);
223
224 UChar currency_code[NumberFormat::kCurrencyCodeLength];
225 if (GetCurrencyCode(icu_region, settings, currency_code)) {
226 number_format->setCurrency(currency_code, status);
227 }
jungshik at Google 2011/06/16 18:08:19 What if the locale id (icu_locale) itself has a cu
Nebojša Ćirić 2011/06/17 17:48:45 I made priority: - specified ISO code in currency
228
229 return number_format;
230 }
231
232 // Generates ICU number format pattern from given skeleton.
233 static icu::DecimalFormat* CreateFormatterFromSkeleton(
234 const icu::Locale& icu_locale,
235 const icu::UnicodeString& skeleton,
236 UErrorCode* status) {
237 icu::DecimalFormat skeleton_format(
238 skeleton, GetFormatSymbols(icu_locale), *status);
239
240 // Find out if skeleton contains currency or percent symbol and create
241 // proper instance to tweak.
242 icu::DecimalFormat* base_format = NULL;
243
244 // UChar representation of \x00A4 currency symbol.
jungshik at Google 2011/06/16 18:08:19 nit: \x00A4 => U+00A4
Nebojša Ćirić 2011/06/17 17:48:45 Done.
245 const UChar currency_symbol = 0xA4;
jungshik at Google 2011/06/16 18:08:19 nit: Didn't any complier complain? Perhaps, it's
Nebojša Ćirić 2011/06/17 17:48:45 Done.
246
247 int32_t index = skeleton.indexOf(currency_symbol);
248 if (index != -1) {
249 // Find how many 0xA4 are there. There is at least one.
jungshik at Google 2011/06/16 18:08:19 nit: U+00A4 BTW, the code below assumes that a sk
Nebojša Ćirić 2011/06/17 17:48:45 I did this as a regexp in JS code (i18n.js), \u00a
250 int32_t end_index = skeleton.lastIndexOf(currency_symbol, index);
251
252 icu::NumberFormat::EStyles style;
253 switch (end_index - index) {
254 case 0:
255 style = icu::NumberFormat::kCurrencyStyle;
256 break;
257 case 1:
258 style = icu::NumberFormat::kIsoCurrencyStyle;
259 break;
260 default:
261 style = icu::NumberFormat::kPluralCurrencyStyle;
262 }
263
264 base_format = static_cast<icu::DecimalFormat*>(
265 icu::NumberFormat::createInstance(icu_locale, style, *status));
266 } else if (skeleton.indexOf('%') != -1) {
267 base_format = static_cast<icu::DecimalFormat*>(
268 icu::NumberFormat::createPercentInstance(icu_locale, *status));
jungshik at Google 2011/06/16 18:08:19 no support for scientific with a skeleton?
Nebojša Ćirić 2011/06/17 17:48:45 Not for now. I want to see what Mark has to say ab
269 } else {
270 base_format = static_cast<icu::DecimalFormat*>(
271 icu::NumberFormat::createInstance(icu_locale, *status));
272 }
273
274 if (U_FAILURE(*status)) {
275 delete base_format;
276 return NULL;
277 }
278
279 // Copy important information from skeleton to the new formatter.
jungshik at Google 2011/06/16 18:08:19 I wonder if there's anything else to pass from ske
Nebojša Ćirić 2011/06/17 17:48:45 Added TODO. On 2011/06/16 18:08:19, Jungshik Shin
280 base_format->setGroupingUsed(skeleton_format.isGroupingUsed());
281
282 base_format->setMinimumIntegerDigits(
283 skeleton_format.getMinimumIntegerDigits());
284
285 base_format->setMinimumFractionDigits(
286 skeleton_format.getMinimumFractionDigits());
287
288 base_format->setMaximumFractionDigits(
289 skeleton_format.getMaximumFractionDigits());
290
291 return base_format;
292 }
293
294 // Gets decimal symbols for a locale.
295 static icu::DecimalFormatSymbols* GetFormatSymbols(
296 const icu::Locale& icu_locale) {
297 UErrorCode status = U_ZERO_ERROR;
298 icu::DecimalFormatSymbols* symbols =
299 new icu::DecimalFormatSymbols(icu_locale, status);
300
301 if (U_FAILURE(status)) {
302 delete symbols;
303 // Use symbols from default locale.
304 symbols = new icu::DecimalFormatSymbols(status);
305 }
306
307 return symbols;
308 }
309
310 // Gets currency ISO 4217 3-letter code based on user specified currency code
311 // or provided region id.
jungshik at Google 2011/06/16 18:08:19 Here again, it's confusing to say 'region id' beca
Nebojša Ćirić 2011/06/17 17:48:45 Done.
312 // Returns false in case of error.
313 static bool GetCurrencyCode(const icu::Locale& icu_locale,
314 v8::Handle<v8::Object> settings,
315 UChar* code) {
316 UErrorCode status = U_ZERO_ERROR;
317
318 // If there is user specified currency code, use it.
319 icu::UnicodeString currency;
320 if (I18NUtils::ExtractStringSetting(settings, "currencyCode", &currency)) {
321 currency.extract(code, NumberFormat::kCurrencyCodeLength, status);
322 return true;
323 }
324
325 // Otherwise infer currency code from the region id.
326 const char* region = icu_locale.getName();
jungshik at Google 2011/06/16 18:08:19 wrapping up 'und_' + regionID in Locale class only
Nebojša Ćirić 2011/06/17 17:48:45 Done.
327
328 ucurr_forLocale(region, code, NumberFormat::kCurrencyCodeLength, &status);
329 if (U_FAILURE(status)) {
330 return false;
331 }
332
333 return true;
334 }
335
336 // Throws a JavaScript exception.
337 static v8::Handle<v8::Value> ThrowUnexpectedObjectError() {
338 // Returns undefined, and schedules an exception to be thrown.
339 return v8::ThrowException(v8::Exception::Error(
340 v8::String::New("NumberFormat method called on an object "
341 "that is not a NumberFormat.")));
342 }
343
344 } } // namespace v8::internal
OLDNEW
« src/extensions/experimental/i18n.js ('K') | « src/extensions/experimental/number-format.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698