libs-base/Source/NSNumberFormatter.m

714 lines
22 KiB
Mathematica
Raw Normal View History

/**
NSNumberFormatter class
Copyright (C) 2000 Free Software Foundation, Inc.
Written by: Fred Kiefer <FredKiefer@gmx.de>
Created: July 2000
Updated by: Richard Frith-Macdonald <rfm@gnu.org> Sept 2001
This file is part of the GNUstep Base Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111 USA.
<title>NSNumberFormatter class reference</title>
$Date$ $Revision$
*/
#include "Foundation/NSAttributedString.h"
#include "Foundation/NSDecimalNumber.h"
#include "Foundation/NSDictionary.h"
#include "Foundation/NSException.h"
#include "Foundation/NSNumberFormatter.h"
#include "Foundation/NSString.h"
#include "Foundation/NSUserDefaults.h"
#include "Foundation/NSCharacterSet.h"
@implementation NSNumberFormatter
- (BOOL) allowsFloats
{
return _allowsFloats;
}
- (NSAttributedString*) attributedStringForObjectValue: (id)anObject
withDefaultAttributes: (NSDictionary*)attr
{
NSDecimalNumber *zeroNumber = [NSDecimalNumber zero];
NSDecimalNumber *nanNumber = [NSDecimalNumber notANumber];
if (anObject == nil)
{
return [self attributedStringForNil];
}
else if (![anObject isKindOfClass: [NSNumber class]])
{
return [self attributedStringForNotANumber];
}
else if ([anObject isEqual: nanNumber])
{
return [self attributedStringForNotANumber];
}
else if ([anObject isEqual: zeroNumber])
{
return [self attributedStringForZero];
}
if (([anObject compare: zeroNumber] == NSOrderedDescending) && (_attributesForPositiveValues))
{
attr = _attributesForPositiveValues;
}
else if (([anObject compare: zeroNumber] == NSOrderedAscending) && (_attributesForNegativeValues))
{
attr = _attributesForNegativeValues;
}
return AUTORELEASE([[NSAttributedString alloc] initWithString:
[self stringForObjectValue: anObject] attributes: attr]);
}
- (NSAttributedString*) attributedStringForNil
{
return _attributedStringForNil;
}
- (NSAttributedString*) attributedStringForNotANumber
{
return _attributedStringForNotANumber;
}
- (NSAttributedString*) attributedStringForZero
{
return _attributedStringForZero;
}
- (id) copyWithZone: (NSZone *)zone
{
NSNumberFormatter *c = (NSNumberFormatter*) NSCopyObject(self, 0, zone);
RETAIN(c->_negativeFormat);
RETAIN(c->_positiveFormat);
RETAIN(c->_attributesForPositiveValues);
RETAIN(c->_attributesForNegativeValues);
RETAIN(c->_maximum);
RETAIN(c->_minimum);
RETAIN(c->_roundingBehavior);
RETAIN(c->_roundingBehavior);
RETAIN(c->_attributedStringForNil);
RETAIN(c->_attributedStringForNotANumber);
RETAIN(c->_attributedStringForZero);
return c;
}
- (void) dealloc
{
RELEASE(_negativeFormat);
RELEASE(_positiveFormat);
RELEASE(_attributesForPositiveValues);
RELEASE(_attributesForNegativeValues);
RELEASE(_maximum);
RELEASE(_minimum);
RELEASE(_roundingBehavior);
RELEASE(_roundingBehavior);
RELEASE(_attributedStringForNil);
RELEASE(_attributedStringForNotANumber);
RELEASE(_attributedStringForZero);
[super dealloc];
}
- (NSString*) decimalSeparator
{
if (_decimalSeparator == 0)
return @"";
else
return [NSString stringWithCharacters: &_decimalSeparator length: 1];
}
- (NSString*) editingStringForObjectValue: (id)anObject
{
return [self stringForObjectValue: anObject];
}
- (void) encodeWithCoder: (NSCoder*)encoder
{
[encoder encodeValueOfObjCType: @encode(BOOL) at: &_hasThousandSeparators];
[encoder encodeValueOfObjCType: @encode(BOOL) at: &_allowsFloats];
[encoder encodeValueOfObjCType: @encode(BOOL) at: &_localizesFormat];
[encoder encodeValueOfObjCType: @encode(unichar) at: &_thousandSeparator];
[encoder encodeValueOfObjCType: @encode(unichar) at: &_decimalSeparator];
[encoder encodeObject: _roundingBehavior];
[encoder encodeObject: _maximum];
[encoder encodeObject: _minimum];
[encoder encodeObject: _attributedStringForNil];
[encoder encodeObject: _attributedStringForNotANumber];
[encoder encodeObject: _attributedStringForZero];
[encoder encodeObject: _negativeFormat];
[encoder encodeObject: _positiveFormat];
[encoder encodeObject: _attributesForPositiveValues];
[encoder encodeObject: _attributesForNegativeValues];
}
- (NSString*) format
{
if (_attributedStringForZero != nil)
{
return [NSString stringWithFormat: @"%@;%@;%@",
_positiveFormat, [_attributedStringForZero string], _negativeFormat];
}
else
{
return [NSString stringWithFormat: @"%@;%@",
_positiveFormat, _negativeFormat];
}
}
- (BOOL) getObjectValue: (id*)anObject
forString: (NSString*)string
errorDescription: (NSString**)error
{
/* FIXME: This is just a quick hack implementation. */
NSLog(@"NSNumberFormatter-getObjectValue:forString:... not fully implemented");
/* Just assume nothing else has been setup and do a simple conversion. */
if ([self hasThousandSeparators])
{
NSRange range;
range = [string rangeOfString: [self thousandSeparator]];
if (range.length != 0)
{
string = AUTORELEASE([string mutableCopy]);
[(NSMutableString*)string replaceOccurrencesOfString: [self thousandSeparator]
withString: @""
options: 0
range: NSMakeRange(0, [string length])];
}
}
if (anObject)
{
NSDictionary *locale;
locale = [NSDictionary dictionaryWithObject: [self decimalSeparator]
forKey: NSDecimalSeparator];
*anObject = [NSDecimalNumber decimalNumberWithString: string
locale: locale];
if (*anObject)
{
return YES;
}
}
return NO;
}
- (BOOL) hasThousandSeparators
{
return _hasThousandSeparators;
}
- (id) init
{
id o;
_allowsFloats = YES;
_decimalSeparator = '.';
_thousandSeparator = ',';
_hasThousandSeparators = YES;
o = [[NSAttributedString alloc] initWithString: @""];
[self setAttributedStringForNil: o];
RELEASE(o);
o = [[NSAttributedString alloc] initWithString: @"NaN"];
[self setAttributedStringForNotANumber: o];
RELEASE(o);
return self;
}
- (id) initWithCoder: (NSCoder*)decoder
{
if ([decoder allowsKeyedCoding])
{
if ([decoder containsValueForKey: @"NS.allowsfloats"])
{
[self setAllowsFloats: [decoder decodeBoolForKey: @"NS.allowsfloats"]];
}
if ([decoder containsValueForKey: @"NS.decimal"])
{
[self setDecimalSeparator: [decoder decodeObjectForKey: @"NS.decimal"]];
}
if ([decoder containsValueForKey: @"NS.hasthousands"])
{
[self setHasThousandSeparators: [decoder decodeBoolForKey: @"NS.hasthousands"]];
}
if ([decoder containsValueForKey: @"NS.localized"])
{
[self setLocalizesFormat: [decoder decodeBoolForKey: @"NS.localized"]];
}
if ([decoder containsValueForKey: @"NS.max"])
{
[self setMaximum: [decoder decodeObjectForKey: @"NS.max"]];
}
if ([decoder containsValueForKey: @"NS.min"])
{
[self setMinimum: [decoder decodeObjectForKey: @"NS.min"]];
}
if ([decoder containsValueForKey: @"NS.nan"])
{
[self setAttributedStringForNotANumber: [decoder decodeObjectForKey: @"NS.nan"]];
}
if ([decoder containsValueForKey: @"NS.negativeattrs"])
{
[self setTextAttributesForNegativeValues: [decoder decodeObjectForKey: @"NS.negativeattrs"]];
}
if ([decoder containsValueForKey: @"NS.negativeformat"])
{
[self setNegativeFormat: [decoder decodeObjectForKey: @"NS.negativeformat"]];
}
if ([decoder containsValueForKey: @"NS.nil"])
{
[self setAttributedStringForNil: [decoder decodeObjectForKey: @"NS.nil"]];
}
if ([decoder containsValueForKey: @"NS.positiveattrs"])
{
[self setTextAttributesForPositiveValues: [decoder decodeObjectForKey: @"NS.positiveattrs"]];
}
if ([decoder containsValueForKey: @"NS.positiveformat"])
{
[self setPositiveFormat: [decoder decodeObjectForKey: @"NS.positiveformat"]];
}
if ([decoder containsValueForKey: @"NS.rounding"])
{
[self setRoundingBehavior: [decoder decodeObjectForKey: @"NS.rounding"]];
}
if ([decoder containsValueForKey: @"NS.thousand"])
{
[self setThousandSeparator: [decoder decodeObjectForKey: @"NS.thousand"]];
}
if ([decoder containsValueForKey: @"NS.zero"])
{
[self setAttributedStringForZero: [decoder decodeObjectForKey: @"NS.zero"]];
}
}
else
{
[decoder decodeValueOfObjCType: @encode(BOOL) at: &_hasThousandSeparators];
[decoder decodeValueOfObjCType: @encode(BOOL) at: &_allowsFloats];
[decoder decodeValueOfObjCType: @encode(BOOL) at: &_localizesFormat];
[decoder decodeValueOfObjCType: @encode(unichar) at: &_thousandSeparator];
[decoder decodeValueOfObjCType: @encode(unichar) at: &_decimalSeparator];
[decoder decodeValueOfObjCType: @encode(id) at: &_roundingBehavior];
[decoder decodeValueOfObjCType: @encode(id) at: &_maximum];
[decoder decodeValueOfObjCType: @encode(id) at: &_minimum];
[decoder decodeValueOfObjCType: @encode(id) at: &_attributedStringForNil];
[decoder decodeValueOfObjCType: @encode(id)
at: &_attributedStringForNotANumber];
[decoder decodeValueOfObjCType: @encode(id) at: &_attributedStringForZero];
[decoder decodeValueOfObjCType: @encode(id) at: &_negativeFormat];
[decoder decodeValueOfObjCType: @encode(id) at: &_positiveFormat];
[decoder decodeValueOfObjCType: @encode(id)
at: &_attributesForPositiveValues];
[decoder decodeValueOfObjCType: @encode(id)
at: &_attributesForNegativeValues];
}
return self;
}
- (BOOL) isPartialStringValid: (NSString*)partialString
newEditingString: (NSString**)newString
errorDescription: (NSString**)error
{
// FIXME
if (newString != NULL)
{
*newString = partialString;
}
if (error)
{
*error = nil;
}
return YES;
}
- (BOOL) localizesFormat
{
return _localizesFormat;
}
- (NSDecimalNumber*) maximum
{
return _maximum;
}
- (NSDecimalNumber*) minimum
{
return _minimum;
}
- (NSString*) negativeFormat
{
return _negativeFormat;
}
- (NSString*) positiveFormat
{
return _positiveFormat;
}
- (NSDecimalNumberHandler*) roundingBehavior
{
return _roundingBehavior;
}
- (void) setAllowsFloats: (BOOL)flag
{
_allowsFloats = flag;
}
- (void) setAttributedStringForNil: (NSAttributedString*)newAttributedString
{
ASSIGN(_attributedStringForNil, newAttributedString);
}
- (void) setAttributedStringForNotANumber:
(NSAttributedString*)newAttributedString
{
ASSIGN(_attributedStringForNotANumber, newAttributedString);
}
- (void) setAttributedStringForZero: (NSAttributedString*)newAttributedString
{
ASSIGN(_attributedStringForZero, newAttributedString);
}
- (void) setDecimalSeparator: (NSString*)newSeparator
{
if ([newSeparator length] > 0)
_decimalSeparator = [newSeparator characterAtIndex: 0];
else
_decimalSeparator = 0;
}
- (void) setFormat: (NSString*)aFormat
{
NSRange r;
r = [aFormat rangeOfString: @";"];
if (r.length == 0)
{
[self setPositiveFormat: aFormat];
[self setNegativeFormat: [@"-" stringByAppendingString: aFormat]];
}
else
{
[self setPositiveFormat: [aFormat substringToIndex: r.location]];
aFormat = [aFormat substringFromIndex: NSMaxRange(r)];
r = [aFormat rangeOfString: @";"];
if (r.length == 0)
{
[self setNegativeFormat: aFormat];
}
else
{
RELEASE(_attributedStringForZero);
_attributedStringForZero = [[NSAttributedString alloc] initWithString:
[aFormat substringToIndex: r.location]];
[self setNegativeFormat: [aFormat substringFromIndex: NSMaxRange(r)]];
}
}
}
- (void) setHasThousandSeparators: (BOOL)flag
{
_hasThousandSeparators = flag;
}
- (void) setLocalizesFormat: (BOOL)flag
{
_localizesFormat = flag;
}
- (void) setMaximum: (NSDecimalNumber*)aMaximum
{
ASSIGN(_maximum, aMaximum);
}
- (void) setMinimum: (NSDecimalNumber*)aMinimum
{
ASSIGN(_minimum, aMinimum);
}
- (void) setNegativeFormat: (NSString*)aFormat
{
// FIXME: Should extract separators and attributes
ASSIGN(_negativeFormat, aFormat);
}
- (void) setPositiveFormat: (NSString*)aFormat
{
// FIXME: Should extract separators and attributes
ASSIGN(_positiveFormat, aFormat);
}
- (void) setRoundingBehavior: (NSDecimalNumberHandler*)newRoundingBehavior
{
ASSIGN(_roundingBehavior, newRoundingBehavior);
}
- (void) setTextAttributesForNegativeValues: (NSDictionary*)newAttributes
{
ASSIGN(_attributesForNegativeValues, newAttributes);
}
- (void) setTextAttributesForPositiveValues: (NSDictionary*)newAttributes
{
ASSIGN(_attributesForPositiveValues, newAttributes);
}
- (void) setThousandSeparator: (NSString*)newSeparator
{
if ([newSeparator length] > 0)
_thousandSeparator = [newSeparator characterAtIndex: 0];
else
_thousandSeparator = 0;
}
- (NSString*) stringForObjectValue: (id)anObject
{
NSMutableDictionary *locale;
NSCharacterSet *formattingCharacters = [NSCharacterSet
characterSetWithCharactersInString: @"0123456789#.,_"],
*placeHolders = [NSCharacterSet
characterSetWithCharactersInString: @"0123456789#_"];
NSString *prefix, *suffix, *wholeString, *fracPad;
NSMutableString *formattedNumber, *intPartString, *fracPartString, *intPad;
NSRange prefixRange, decimalPlaceRange, suffixRange, intPartRange;
NSDecimal representativeDecimal, roundedDecimal;
NSDecimalNumber *roundedNumber, *intPart, *fracPart;
int decimalPlaces = 0;
BOOL displayThousandsSeparators = NO,
displayFractionalPart = NO,
negativeNumber = NO;
if (nil == anObject)
return [[self attributedStringForNil] string];
if (![anObject isKindOfClass: [NSNumber class]])
return [[self attributedStringForNotANumber] string];
if ([anObject isEqual: [NSDecimalNumber notANumber]])
return [[self attributedStringForNotANumber] string];
if ([anObject isEqual: [NSDecimalNumber zero]])
return [[self attributedStringForZero] string];
NSString *useFormat = _positiveFormat;
if ([anObject compare: [NSDecimalNumber zero]] == NSOrderedAscending)
{
useFormat = _negativeFormat;
negativeNumber = YES;
}
// if no format specified, use the same default that Cocoa does
if (nil == useFormat)
useFormat = negativeNumber ? @"-#,###.##" : @"#,###.##";
prefixRange = [useFormat rangeOfCharacterFromSet: formattingCharacters];
if (NSNotFound != prefixRange.location)
{
prefix = [useFormat substringToIndex: prefixRange.location];
}
else
{
prefix = @"";
}
locale = [NSMutableDictionary dictionaryWithCapacity: 3];
[locale setObject: @"" forKey: NSThousandsSeparator];
[locale setObject: @"" forKey: NSDecimalSeparator];
//should also set NSDecimalDigits?
if ([self hasThousandSeparators]
&& (0 != [useFormat rangeOfString:@","].length))
{
displayThousandsSeparators = YES;
}
if ([self allowsFloats]
&& (NSNotFound != [useFormat rangeOfString:@"." ].location))
{
decimalPlaceRange = [useFormat rangeOfString: @"."
options: NSBackwardsSearch];
if (NSMaxRange(decimalPlaceRange) == [useFormat length])
{
decimalPlaces = 0;
}
else
{
while ([placeHolders characterIsMember:
[useFormat characterAtIndex:
NSMaxRange(decimalPlaceRange)]])
{
decimalPlaceRange.length++;
if (NSMaxRange(decimalPlaceRange) == [useFormat length])
break;
}
decimalPlaces=decimalPlaceRange.length -= 1;
decimalPlaceRange.location += 1;
fracPad = [useFormat substringWithRange:decimalPlaceRange];
}
if (0 != decimalPlaces)
displayFractionalPart = YES;
}
representativeDecimal = [anObject decimalValue];
NSDecimalRound(&roundedDecimal,
&representativeDecimal,
decimalPlaces,
NSRoundPlain);
roundedNumber = [NSDecimalNumber decimalNumberWithDecimal: roundedDecimal];
/* Arguably this fiddling could be done by GSDecimalString() but I
* thought better to leave that behaviour as it is and provide the
* desired prettification here
*/
if (negativeNumber)
roundedNumber = [roundedNumber decimalNumberByMultiplyingBy:
[NSDecimalNumber numberWithInt: -1]];
intPart = [NSDecimalNumber numberWithInt: [roundedNumber intValue]];
fracPart = [roundedNumber decimalNumberBySubtracting: intPart];
intPartString = [intPart descriptionWithLocale:locale];
//sort out the padding for the integer part
intPartRange = [useFormat rangeOfCharacterFromSet: placeHolders];
while (([placeHolders characterIsMember:
[useFormat characterAtIndex: NSMaxRange(intPartRange)]]
|| [[useFormat substringFromRange:
NSMakeRange(NSMaxRange(intPartRange),1)] isEqual: @","])
&& NSMaxRange(intPartRange)<[useFormat length]-1)
{
intPartRange.length++;
}
intPad = [[useFormat substringWithRange:intPartRange] mutableCopy];
[intPad replaceOccurrencesOfString: @","
withString: @""
options: 0
range: NSMakeRange(0, [intPad length])];
[intPad replaceOccurrencesOfString: @"#"
withString: @""
options: NSAnchoredSearch
range: NSMakeRange(0, [intPad length])];
if ([intPad length] > [intPartString length])
{
NSRange ipRange = NSMakeRange(0,
[intPad length] - [intPartString length] + 1);
intPartString = [[[intPad substringWithRange: ipRange]
stringByAppendingString: intPartString] mutableCopy];
[intPartString replaceOccurrencesOfString: @"_"
withString: @" "
options: nil
range: NSMakeRange(0, [intPartString length])];
[intPartString replaceOccurrencesOfString: @"#"
withString: @"0"
options: nil
range: NSMakeRange(0, [intPartString length])];
}
// fix the thousands separators up
if (displayThousandsSeparators && [intPartString length] > 3)
{
int index = [intPartString length];
while (0 < (index-=3))
{
[intPartString insertString:[self thousandSeparator] atIndex: index];
}
}
formattedNumber = [intPartString mutableCopy];
//fix up the fractional part
if (displayFractionalPart)
{
if (0 != decimalPlaces)
{
fracPart = [fracPart decimalNumberByMultiplyingByPowerOf10:
decimalPlaces];
fracPartString = [fracPart descriptionWithLocale: locale];
[fracPartString replaceOccurrencesOfString: @"0"
withString: @""
options: (NSBackwardsSearch
| NSAnchoredSearch)
range: NSMakeRange(0, [fracPartString length])];
if ([fracPad length] > [fracPartString length])
{
NSRange fpRange = NSMakeRange([fracPartString length],
( [fracPad length] -
[fracPartString length]));
[fracPartString appendString:
[fracPad substringWithRange: fpRange]];
[fracPartString replaceOccurrencesOfString: @"#"
withString: @""
options: (NSBackwardsSearch
| NSAnchoredSearch)
range: NSMakeRange(0, [fracPartString length])];
[fracPartString replaceOccurrencesOfString: @"#"
withString: @"0"
options: 0
range: NSMakeRange(0, [fracPartString length])];
[fracPartString replaceOccurrencesOfString: @"_"
withString: @" "
options: 0
range: NSMakeRange(0, [fracPartString length])];
}
}
else
{
fracPartString=@"0";
}
[formattedNumber appendString:[self decimalSeparator]];
[formattedNumber appendString:fracPartString];
}
/*FIXME - the suffix doesn't behave the same as on Mac OS X. Our suffix is everything which
*follows the final formatting character. Cocoa's suffix is everything which isn't a formatting character
*nor in the prefix
*/
suffixRange = [useFormat rangeOfCharacterFromSet: formattingCharacters options: NSBackwardsSearch];
suffix = [useFormat substringFromIndex: NSMaxRange(suffixRange)];
wholeString = [[prefix stringByAppendingString: formattedNumber] stringByAppendingString: suffix];
[formattedNumber release];
return wholeString;
}
- (NSDictionary*) textAttributesForNegativeValues
{
return _attributesForNegativeValues;
}
- (NSDictionary*) textAttributesForPositiveValues
{
return _attributesForPositiveValues;
}
- (NSString*) thousandSeparator
{
if (!_thousandSeparator)
return @"";
else
return [NSString stringWithCharacters: &_thousandSeparator length: 1];
}
@end