blob: d685fcabf44ec96db40eac01754516b641f07b6a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014-2015 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
/**
* Provides formatting of numbers using java format pattern.
* <p>
* Compared to the java DecimalFormat the following pattern characters are not considered:
* <ul>
* <li>prefix and suffix</li>
* <li>E</li>
* <li>%</li>
* </ul>
*/
scout.DecimalFormat = function(locale, decimalFormatConfiguration) {
// format function will use these (defaults)
this.positivePrefix = '';
this.positiveSuffix = '';
this.negativePrefix = locale.decimalFormatSymbols.minusSign;
this.negativeSuffix = '';
this.groupingChar = locale.decimalFormatSymbols.groupingSeparator;
this.groupLength = 0;
this.decimalSeparatorChar = locale.decimalFormatSymbols.decimalSeparator;
this.zeroBefore = 1;
this.zeroAfter = 0;
this.allAfter = 0;
decimalFormatConfiguration = decimalFormatConfiguration || {};
this.pattern = decimalFormatConfiguration.pattern || locale.decimalFormatPatternDefault;
this.multiplier = decimalFormatConfiguration.multiplier || 1;
this.roundingMode = decimalFormatConfiguration.roundingMode || scout.numbers.RoundingMode.HALF_UP;
var SYMBOLS = scout.DecimalFormat.PATTERN_SYMBOLS;
// Check if there are separate subpatterns for positive and negative numbers ("PositivePattern;NegativePattern")
var split = this.pattern.split(SYMBOLS.patternSeparator);
// Use the first subpattern as positive prefix/suffix
var positivePrefixAndSuffix = findPrefixAndSuffix(split[0]);
this.positivePrefix = positivePrefixAndSuffix.prefix;
this.positiveSuffix = positivePrefixAndSuffix.suffix;
if (split.length > 1) {
// Yes, there is a negative subpattern
var negativePrefixAndSuffix = findPrefixAndSuffix(split[1]);
this.negativePrefix = negativePrefixAndSuffix.prefix;
this.negativeSuffix = negativePrefixAndSuffix.suffix;
// from now on, only look at the positive subpattern
this.pattern = split[0];
} else {
// No, there is no negative subpattern, so the positive prefix/suffix are used for both positive and negative numbers.
// Check if there is a minus sign in the prefix/suffix.
if (this.positivePrefix.indexOf(SYMBOLS.minusSign) !== -1 || this.positiveSuffix.indexOf(SYMBOLS.minusSign) !== -1) {
// Yes, there is a minus sign in the prefix/suffix. Use this a negativePrefix/Suffix and remove the minus sign from the posistivePrefix/Suffix.
this.negativePrefix = this.positivePrefix.replace(SYMBOLS.minusSign, locale.decimalFormatSymbols.minusSign);
this.negativeSuffix = this.positiveSuffix.replace(SYMBOLS.minusSign, locale.decimalFormatSymbols.minusSign);
this.positivePrefix = this.positivePrefix.replace(SYMBOLS.minusSign, '');
this.positiveSuffix = this.positiveSuffix.replace(SYMBOLS.minusSign, '');
} else {
// No, there is no minus sign in the prefix/suffix. Therefore, use the default negativePrefix/Suffix, but append the positivePrefix/Suffix
this.negativePrefix = this.positivePrefix + this.negativePrefix;
this.negativeSuffix = this.negativeSuffix + this.positiveSuffix;
}
}
// find group length
var posDecimalSeparator = this.pattern.indexOf(SYMBOLS.decimalSeparator);
if (posDecimalSeparator === -1) {
posDecimalSeparator = this.pattern.length; // assume decimal separator at end
}
var posGroupingSeparator = this.pattern.lastIndexOf(SYMBOLS.groupingSeparator, posDecimalSeparator); // only search before decimal separator
if (posGroupingSeparator > 0) {
this.groupLength = posDecimalSeparator - posGroupingSeparator - 1;
}
this.pattern = this.pattern.replace(new RegExp('[' + SYMBOLS.groupingSeparator + ']', 'g'), '');
// split on decimal point
split = this.pattern.split(SYMBOLS.decimalSeparator);
// find digits before and after decimal point
this.zeroBefore = scout.strings.count(split[0], SYMBOLS.zeroDigit);
if (split.length > 1) { // has decimal point?
this.zeroAfter = scout.strings.count(split[1], SYMBOLS.zeroDigit);
this.allAfter = this.zeroAfter + scout.strings.count(split[1], SYMBOLS.digit);
}
// Returns an object with the properties 'prefix' and 'suffix', which contain all characters
// before or after any 'digit-like' character in the given pattern string.
function findPrefixAndSuffix(pattern) {
var result = {
prefix: '',
suffix: ''
};
// Find prefix (anything before the first 'digit-like' character)
var digitLikeCharacters = SYMBOLS.digit + SYMBOLS.zeroDigit + SYMBOLS.decimalSeparator + SYMBOLS.groupingSeparator;
var r = new RegExp('^(.*?)[' + digitLikeCharacters + '].*$');
var matches = r.exec(pattern);
if (matches !== null) {
// Ignore single quotes (for special, quoted characters - e.g. Java quotes percentage sign like '%')
result.prefix = matches[1].replace(new RegExp('\'([^\']+)\'', 'g'), '$1');
}
// Find suffix (anything before the first 'digit-like' character)
r = new RegExp('^.*[' + digitLikeCharacters + '](.*?)$');
matches = r.exec(pattern);
if (matches !== null) {
// Ignore single quotes (for special, quoted characters - e.g. Java quotes percentage sign like '%')
result.suffix = matches[1].replace(new RegExp('\'([^\']+)\'', 'g'), '$1');
}
return result;
}
};
/**
* Returns a number for the given numberString, if the string can be converted into a number.
* Throws an Error otherwise
*/
scout.DecimalFormat.prototype.parse = function(numberString) {
if (scout.strings.empty(numberString)) {
return null;
}
var pureNumber = numberString
.replace(new RegExp('[' + this.groupingChar + ']', 'g'), '')
.replace(new RegExp('[' + this.decimalSeparatorChar + ']', 'g'), '.')
.replace(/\s/g, '');
var number = Number(pureNumber);
if (isNaN(number)) {
throw new Error(numberString + ' is not a number (NaN)');
}
return number;
};
scout.DecimalFormat.prototype.format = function(number, applyMultiplier) {
applyMultiplier = scout.nvl(applyMultiplier, true);
if (number === null || number === undefined) {
return null;
}
var prefix = this.positivePrefix;
var suffix = this.positiveSuffix;
// apply multiplier
if (applyMultiplier && this.multiplier !== 1) {
number *= this.multiplier;
}
// round
number = this.round(number);
// after decimal point
var after = '';
if (this.allAfter) {
after = number.toFixed(this.allAfter).split('.')[1];
for (var j = after.length - 1; j > this.zeroAfter - 1; j--) {
if (after[j] !== '0') {
break;
}
after = after.slice(0, -1);
}
if (after) { // did we find any non-zero characters?
after = this.decimalSeparatorChar + after;
}
}
// absolute value
if (number < 0) {
prefix = this.negativePrefix;
suffix = this.negativeSuffix;
number = -number;
}
// before decimal point
var before = Math.floor(number);
before = (before === 0) ? '' : String(before);
before = scout.strings.padZeroLeft(before, this.zeroBefore);
// group digits
if (this.groupLength) {
for (var i = before.length - this.groupLength; i > 0; i -= this.groupLength) {
before = before.substr(0, i) + this.groupingChar + before.substr(i);
}
}
// put together and return
return prefix + before + after + suffix;
};
/**
* Rounds a number according to the properties of the DecimalFormat.
*/
scout.DecimalFormat.prototype.round = function(number) {
return scout.numbers.round(number, this.roundingMode, this.allAfter);
};
/* --- STATIC HELPERS ------------------------------------------------------------- */
/**
* Literal (not localized!) pattern symbols as defined in http://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html
*/
scout.DecimalFormat.PATTERN_SYMBOLS = {
digit: '#',
zeroDigit: '0',
decimalSeparator: '.',
groupingSeparator: ',',
minusSign: '-',
patternSeparator: ';'
};
scout.DecimalFormat.ensure = function(locale, format) {
if (!format) {
return format;
}
if (format instanceof scout.DecimalFormat) {
return format;
}
return new scout.DecimalFormat(locale, format);
};