API  1.0.0
CPNumberFormatter.j
Go to the documentation of this file.
1 /*
2  * CPNumberFormatter.j
3  * Foundation
4  *
5  * Created by Alexander Ljungberg.
6  * Copyright 2011, WireLoad Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 @typedef CPNumberFormatterStyle
31 
32 @typedef CPNumberFormatterRoundingMode
38 CPNumberFormatterRoundHalfDown = _CPRoundHalfDown;
40 
41 var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?');
42 
43 #define SET_NEEDS_NUMBER_HANDLER_UPDATE() _numberHandler = nil
44 
45 
53 @implementation CPNumberFormatter : CPFormatter
54 {
55  CPNumberFormatterStyle _numberStyle;
56  CPString _perMillSymbol;
57  CPString _groupingSeparator;
58  CPNumberFormatterRoundingMode _roundingMode;
59  CPUInteger _minimumFractionDigits;
60  CPUInteger _maximumFractionDigits;
61  CPUInteger _minimum;
62  CPUInteger _maximum;
63  CPString _currencyCode;
64  CPString _currencySymbol;
65  BOOL _generatesDecimalNumbers;
66 
67  // Note that we do not implement the 10.0 style number formatter, but the 10.4+ formatter. Therefore
68  // we don't expose this through a `roundingBehavior` property.
69  CPDecimalNumberHandler _numberHandler;
70 }
71 
72 - (id)init
73 {
74  if (self = [super init])
75  {
76  _roundingMode = CPNumberFormatterRoundHalfEven;
77  _minimumFractionDigits = 0;
78  _maximumFractionDigits = 0;
79  _groupingSeparator = @",";
80  _generatesDecimalNumbers = YES;
81  _minimum = nil;
82  _maximum = nil;
83 
84  // FIXME Add locale support.
85  _currencyCode = @"USD";
86  _currencySymbol = @"$";
87  }
88 
89  return self;
90 }
91 
92 - (CPString)stringFromNumber:(CPNumber)number
93 {
94  if (_numberStyle == CPNumberFormatterPercentStyle)
95  {
96  number *= 100.0;
97  }
98 
99  var dcmn = [number isKindOfClass:CPDecimalNumber] ? number : [[CPDecimalNumber alloc] _initWithJSNumber:number];
100 
101  // TODO Add locale support.
102  switch (_numberStyle)
103  {
107  [self _updateNumberHandlerIfNecessary];
108 
109  dcmn = [dcmn decimalNumberByRoundingAccordingToBehavior:_numberHandler];
110 
111  var output = [dcmn descriptionWithLocale:nil],
112  // FIXME this is probably locale dependent.
113  parts = output.match(NumberRegex) || ["", undefined, "", undefined, undefined],
114  negativePrefix = parts[1] || "",
115  preFraction = parts[2] || "",
116  fraction = parts[4] || "",
117  preFractionLength = [preFraction length],
118  commaPosition = 3;
119 
120  while (fraction.length < _minimumFractionDigits)
121  fraction += "0";
122 
123  // TODO This is just a temporary solution. Should be generalised.
124  // Add in thousands separators.
125  if (_groupingSeparator)
126  {
127  for (var commaPosition = 3, prefLength = [preFraction length]; commaPosition < prefLength; commaPosition += 4)
128  {
129  preFraction = [preFraction stringByReplacingCharactersInRange:CPMakeRange(prefLength - commaPosition, 0) withString:_groupingSeparator];
130  prefLength += 1;
131  }
132  }
133 
134  var string = preFraction;
135 
136  if (fraction)
137  string += "." + fraction;
138 
139  if (_numberStyle === CPNumberFormatterCurrencyStyle)
140  {
141  if (_currencySymbol)
142  string = _currencySymbol + string;
143  else
144  string = _currencyCode + string;
145  }
146 
147  if (_numberStyle == CPNumberFormatterPercentStyle)
148  string += "%";
149 
150  if (negativePrefix)
151  string = negativePrefix + string;
152 
153  return string;
154 
155  default:
156  return [number description];
157  }
158 }
159 
160 + (CPString)localizedStringFromNumber:(CPNumber)num numberStyle:(CPNumberFormatterStyle)localizationStyle
161 {
162  var formatter = [[CPNumberFormatter alloc] init];
163  [formatter setNumberStyle:localizationStyle];
164 
165  return [formatter stringFromNumber:num];
166 }
167 
168 - (CPNumber)numberFromString:(CPString)aString
169 {
170  if (_generatesDecimalNumbers)
171  return [CPDecimalNumber decimalNumberWithString:aString];
172  else
173  return parseFloat(aString);
174 }
175 
176 - (CPString)stringForObjectValue:(id)anObject
177 {
178  if ([anObject isKindOfClass:[CPNumber class]])
179  return [self stringFromNumber:anObject];
180  else
181  return [anObject description];
182 }
183 
184 - (CPString)editingStringForObjectValue:(id)anObject
185 {
186  return [self stringForObjectValue:anObject];
187 }
188 
189 - (BOOL)getObjectValue:(idRef)anObjectRef forString:(CPString)aString errorDescription:(CPStringRef)anErrorRef
190 {
191  // Interpret an empty string as nil, like in Cocoa.
192  if (aString === @"")
193  {
194  @deref(anObjectRef) = nil;
195  return YES;
196  }
197 
198  var value = [self numberFromString:aString],
199  error = @"";
200 
201  // this will return false if we've received anything but a number, most likely NaN
202  if (!isFinite(value))
203  error = @"Value is not a number";
204  else if (_minimum !== nil && value < _minimum)
205  error = @"Value is less than the minimum allowed value";
206  else if (_maximum !== nil && value > _maximum)
207  error = @"Value is greater than the maximum allowed value";
208 
209  if (error)
210  {
211  if (anErrorRef)
212  @deref(anErrorRef) = error;
213 
214  return NO;
215  }
216 
217  @deref(anObjectRef) = value;
218 
219  return YES;
220 }
221 
222 - (void)setNumberStyle:(CPNumberFormatterStyle)aStyle
223 {
224  _numberStyle = aStyle;
225 
226  switch (aStyle)
227  {
229  _minimumFractionDigits = 0;
230  _maximumFractionDigits = 3;
232  break;
233 
235  _minimumFractionDigits = 2;
236  _maximumFractionDigits = 2;
238  break;
239  }
240 }
241 
242 - (void)setRoundingMode:(CPNumberFormatterRoundingMode)aRoundingMode
243 {
244  _roundingMode = aRoundingMode;
246 }
247 
248 - (void)setMinimumFractionDigits:(CPUInteger)aNumber
249 {
250  _minimumFractionDigits = aNumber;
252 }
253 
254 - (void)setMaximumFractionDigits:(CPUInteger)aNumber
255 {
256  _maximumFractionDigits = aNumber;
258 }
259 
260 - (void)setMinimum:(CPUInteger)aNumber
261 {
262  _minimum = aNumber;
264 }
265 
266 - (void)setMaximum:(CPUInteger)aNumber
267 {
268  _maximum = aNumber;
270 }
271 
272 #pragma mark Private
273 
274 - (void)_updateNumberHandlerIfNecessary
275 {
276  if (!_numberHandler)
277  _numberHandler = [CPDecimalNumberHandler decimalNumberHandlerWithRoundingMode:_roundingMode
278  scale:_maximumFractionDigits
280  raiseOnOverflow:NO
282  raiseOnDivideByZero:YES];
283 }
284 
285 @end
286 
287 var CPNumberFormatterStyleKey = @"CPNumberFormatterStyleKey",
288  CPNumberFormatterMinimumFractionDigitsKey = @"CPNumberFormatterMinimumFractionDigitsKey",
289  CPNumberFormatterMaximumFractionDigitsKey = @"CPNumberFormatterMaximumFractionDigitsKey",
290  CPNumberFormatterMinimumKey = @"CPNumberFormatterMinimumKey",
291  CPNumberFormatterMaximumKey = @"CPNumberFormatterMaximumKey",
292  CPNumberFormatterRoundingModeKey = @"CPNumberFormatterRoundingModeKey",
293  CPNumberFormatterGroupingSeparatorKey = @"CPNumberFormatterGroupingSeparatorKey",
294  CPNumberFormatterCurrencyCodeKey = @"CPNumberFormatterCurrencyCodeKey",
295  CPNumberFormatterCurrencySymbolKey = @"CPNumberFormatterCurrencySymbolKey",
296  CPNumberFormatterGeneratesDecimalNumbers = @"CPNumberFormatterGeneratesDecimalNumbers";
297 
299 
300 - (id)initWithCoder:(CPCoder)aCoder
301 {
302  self = [super initWithCoder:aCoder];
303 
304  if (self)
305  {
306  _numberStyle = [aCoder decodeIntForKey:CPNumberFormatterStyleKey];
307  _minimumFractionDigits = [aCoder decodeIntForKey:CPNumberFormatterMinimumFractionDigitsKey];
308  _maximumFractionDigits = [aCoder decodeIntForKey:CPNumberFormatterMaximumFractionDigitsKey];
309  _roundingMode = [aCoder decodeIntForKey:CPNumberFormatterRoundingModeKey];
310  _groupingSeparator = [aCoder decodeObjectForKey:CPNumberFormatterGroupingSeparatorKey];
311  _currencyCode = [aCoder decodeObjectForKey:CPNumberFormatterCurrencyCodeKey];
312  _currencySymbol = [aCoder decodeObjectForKey:CPNumberFormatterCurrencySymbolKey];
313  _generatesDecimalNumbers = [aCoder decodeBoolForKey:CPNumberFormatterGeneratesDecimalNumbers];
314 
315  // We decode _minimum and _maximum as object here because otherwise, nil values are not preserved
316  // causing a min and max always set to 0 after an decoding.
317  _minimum = [aCoder decodeObjectForKey:CPNumberFormatterMinimumKey];
318  _maximum = [aCoder decodeObjectForKey:CPNumberFormatterMaximumKey];
319  }
320 
321  return self;
322 }
323 
324 - (void)encodeWithCoder:(CPCoder)aCoder
325 {
326  [super encodeWithCoder:aCoder];
327 
328  [aCoder encodeInt:_numberStyle forKey:CPNumberFormatterStyleKey];
329  [aCoder encodeInt:_minimumFractionDigits forKey:CPNumberFormatterMinimumFractionDigitsKey];
330  [aCoder encodeInt:_maximumFractionDigits forKey:CPNumberFormatterMaximumFractionDigitsKey];
331  [aCoder encodeInt:_minimum forKey:CPNumberFormatterMinimumKey];
332  [aCoder encodeInt:_maximum forKey:CPNumberFormatterMaximumKey];
333  [aCoder encodeInt:_roundingMode forKey:CPNumberFormatterRoundingModeKey];
334  [aCoder encodeObject:_groupingSeparator forKey:CPNumberFormatterGroupingSeparatorKey];
335  [aCoder encodeObject:_currencyCode forKey:CPNumberFormatterCurrencyCodeKey];
336  [aCoder encodeObject:_currencySymbol forKey:CPNumberFormatterCurrencySymbolKey];
337  [aCoder encodeBool:_generatesDecimalNumbers forKey:CPNumberFormatterGeneratesDecimalNumbers];
338 }
339 
340 @end
341 
343 
347 - (CPNumberFormatterStyle)numberStyle
348 {
349  return _numberStyle;
350 }
351 
355 - (void)setNumberStyle:(CPNumberFormatterStyle)aValue
356 {
357  _numberStyle = aValue;
358 }
359 
363 - (CPString)perMillSymbol
364 {
365  return _perMillSymbol;
366 }
367 
371 - (void)setPerMillSymbol:(CPString)aValue
372 {
373  _perMillSymbol = aValue;
374 }
375 
379 - (CPString)groupingSeparator
380 {
381  return _groupingSeparator;
382 }
383 
387 - (void)setGroupingSeparator:(CPString)aValue
388 {
389  _groupingSeparator = aValue;
390 }
391 
395 - (CPNumberFormatterRoundingMode)roundingMode
396 {
397  return _roundingMode;
398 }
399 
403 - (void)setRoundingMode:(CPNumberFormatterRoundingMode)aValue
404 {
405  _roundingMode = aValue;
406 }
407 
411 - (CPUInteger)minimumFractionDigits
412 {
413  return _minimumFractionDigits;
414 }
415 
419 - (void)setMinimumFractionDigits:(CPUInteger)aValue
420 {
421  _minimumFractionDigits = aValue;
422 }
423 
427 - (CPUInteger)maximumFractionDigits
428 {
429  return _maximumFractionDigits;
430 }
431 
435 - (void)setMaximumFractionDigits:(CPUInteger)aValue
436 {
437  _maximumFractionDigits = aValue;
438 }
439 
443 - (CPUInteger)minimum
444 {
445  return _minimum;
446 }
447 
451 - (void)setMinimum:(CPUInteger)aValue
452 {
453  _minimum = aValue;
454 }
455 
459 - (CPUInteger)maximum
460 {
461  return _maximum;
462 }
463 
467 - (void)setMaximum:(CPUInteger)aValue
468 {
469  _maximum = aValue;
470 }
471 
475 - (CPString)currencyCode
476 {
477  return _currencyCode;
478 }
479 
483 - (void)setCurrencyCode:(CPString)aValue
484 {
485  _currencyCode = aValue;
486 }
487 
491 - (CPString)currencySymbol
492 {
493  return _currencySymbol;
494 }
495 
499 - (void)setCurrencySymbol:(CPString)aValue
500 {
501  _currencySymbol = aValue;
502 }
503 
507 - (BOOL)generatesDecimalNumbers
508 {
509  return _generatesDecimalNumbers;
510 }
511 
515 - (void)setGeneratesDecimalNumbers:(BOOL)aValue
516 {
517  _generatesDecimalNumbers = aValue;
518 }
519 
520 @end
#define SET_NEEDS_NUMBER_HANDLER_UPDATE()
var CPNumberFormatterCurrencyCodeKey
CPNumberFormatterCurrencyStyle
CPString stringFromNumber:(CPNumber number)
id init()
Definition: CALayer.j:126
CPRoundBankers
Definition: CPDecimal.j:75
CPNumberFormatterRoundDown
CPNumberFormatterPercentStyle
CPString stringForObjectValue:(id anObject)
var CPNumberFormatterCurrencySymbolKey
CPNumberFormatterRoundHalfEven
CPNumberFormatterRoundHalfUp
var CPNumberFormatterRoundingModeKey
CPDecimalNumber decimalNumberWithString:(CPString numberValue)
CPNumberFormatterStyle CPNumberFormatterNoStyle
An immutable string (collection of characters).
Definition: CPString.h:2
var CPNumberFormatterMinimumKey
CPNumberFormatterRoundHalfDown
CPNumberFormatterRoundFloor
CPNumberFormatterDecimalStyle
Decimal floating point number.
CPNumberFormatterScientificStyle
id initWithCoder:(CPCoder aCoder)
Definition: CPFormatter.j:167
var CPNumberFormatterGeneratesDecimalNumbers
Decimal floating point number exception and rounding behavior. This class is mutable.
CPFormatter is an abstract class that declares an interface for objects that create, interpret, and validate the textual representation of cell contents. The Foundation framework provides two concrete subclasses of CPFormatter to generate these objects: CPNumberFormatter and CPDateFormatter.
Definition: CPFormatter.h:2
CPString description()
Definition: CPNumber.j:233
BOOL isKindOfClass:(Class aClass)
Definition: CPObject.j:219
CPRoundDown
Definition: CPDecimal.j:73
var CPNumberFormatterMinimumFractionDigitsKey
var CPNumberFormatterMaximumKey
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
id decimalNumberHandlerWithRoundingMode:scale:raiseOnExactness:raiseOnOverflow:raiseOnUnderflow:raiseOnDivideByZero:(CPRoundingMode roundingMode, [scale] short scale, [raiseOnExactness] BOOL exact, [raiseOnOverflow] BOOL overflow, [raiseOnUnderflow] BOOL underflow, [raiseOnDivideByZero] BOOL divideByZero)
var CPNumberFormatterGroupingSeparatorKey
CPRoundingMode CPRoundPlain
Definition: CPDecimal.j:72
var CPNumberFormatterMaximumFractionDigitsKey
var NumberRegex
CPNumber numberFromString:(CPString aString)
CPRoundUp
Definition: CPDecimal.j:74
CPNumberFormatterSpellOutStyle
CPNumberFormatterRoundUp
A bridged object to native Javascript numbers.
Definition: CPNumber.h:2
void encodeWithCoder:(CPCoder aCoder)
Definition: CPFormatter.j:172
var CPNumberFormatterStyleKey
CPNumberFormatterRoundingMode CPNumberFormatterRoundCeiling
id alloc()
Definition: CPObject.j:130