zk/src/main/resources/web/js/zk/fmt/numfmt.ts
/* numfmt.ts
Purpose:
Description:
History:
Fri Jan 16 19:13:43 2009, Created by tomyeh
Copyright (C) 2008 Potix Corporation. All Rights Reserved.
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
*/
var _defaultSymbols = {
GROUPING: zk.GROUPING,
DECIMAL: zk.DECIMAL,
PERCENT: zk.PERCENT,
PER_MILL: zk.PER_MILL,
MINUS: zk.MINUS
};
function down(valStr: string, ri: number): string {
return valStr.substring(0, ri);
}
function up(valStr: string, ri: number): string {
var k = 1, val = '';
for (var j = ri; k && --j >= 0;) {
var ch = valStr.charAt(j);
if (k == 1) {
if (ch >= '0' && ch < '9') {
ch = ch.$inc(1);
k = 0;
} else if (ch == '9')
ch = '0';
}
val = ch + val;
}
if (j >= 0)
val = valStr.substring(0, j) + val;
return k ? '1' + val : val;
}
function compareHalf(valStr: string, ri: number): number | undefined {
var result: number | undefined,
base = 5;
for (var j = ri, len = valStr.length; j < len; ++j) {
result = parseInt(valStr.charAt(j)) - base;
if (j == ri) { //first digit
base = 0;
}
if (result != 0)
return result;
}
return result;
}
function preDigit(valStr: string, ri: number): string | undefined {
for (var j = ri; --j >= 0;) {
var ch = valStr.charAt(j);
if (ch >= '0' && ch <= '9')
return ch;
}
return undefined;
}
interface Efmt {
shift: number;
fmt: string;
pureFmtStr: string;
jdot: number;
purejdot: number;
prej: number;
}
export let Number = {
setScale(val: zk.BigDecimal, scale: number, rounding: number): zk.BigDecimal { //bug #3089502: setScale in decimalbox not working
if (scale === undefined || scale < 0)
return val;
var valStr = val.$toString(),
indVal = valStr.indexOf('.'),
valFixed = indVal >= 0 ? valStr.length - indVal - 1 : 0;
if (valFixed <= scale) // no need to do any thing
return val;
else {
var ri = indVal + scale + 1;
valStr = this.rounding(valStr, ri, rounding, valStr != '' && parseInt(valStr) < 0);
return new zk.BigDecimal(valStr);
}
},
//Test if rounding is required (used if Rounding is UNNECESSARY
isRoundingRequired(val: string | number, fmt: string, localizedSymbols?: zk.LocalizedSymbols): boolean {
if (!fmt || val == null || val == '')
return false;
var useMinsuFmt;
if (fmt.indexOf(';') != -1) {
var fmtArr = fmt.split(';');
useMinsuFmt = (val as number) < 0;
fmt = fmtArr[useMinsuFmt ? 1 : 0];
}
// localized symbols
localizedSymbols = localizedSymbols || _defaultSymbols;
//calculate number of fixed decimals
var efmt = this._escapeQuote(fmt, localizedSymbols);
fmt = efmt.fmt;
var pureFmtStr = efmt.pureFmtStr,
ind = efmt.purejdot,
fixed = ind >= 0 ? pureFmtStr.length - ind - 1 : 0,
valStr = (val + '').replace(/[^e\-0123456789.]/g, '').substring((val as number) < 0 ? 1 : 0),
ei = valStr.lastIndexOf('e'),
indVal = valStr.indexOf('.'),
valFixed = indVal >= 0 ? (ei < 0 ? valStr.length : ei) - indVal - 1 : 0,
shift = efmt.shift + (ei < 0 ? 0 : parseInt(valStr.substring(ei + 1), 10));
if (ei > 0) valStr = valStr.substring(0, ei);
if (shift > 0) {
if (indVal >= 0) { //with dot
if (valFixed > shift) {
valFixed -= shift;
} else {
valFixed = 0;
}
}
} else if (shift < 0) {
// ZK-1737 varStr is not correct variable name
var nind = (indVal < 0 ? valStr.length : indVal) + shift;
if (nind > 0) {
valFixed -= shift;
} else {
//TODO finetune logic to prevent string operation.
if (indVal >= 0)
valStr = valStr.substring(0, indVal) + valStr.substring(indVal + 1);
for (; nind++ < 0;)
valStr = '0' + valStr;
valStr = '0.' + valStr;
valFixed = valStr.length - 2;
}
}
return valFixed > fixed;
},
rounding(valStr: string, ri: number, rounding: number, minus: boolean): string {
switch (rounding) {
case 0: //UP
valStr = up(valStr, ri);
break;
case 1: //DOWN
valStr = down(valStr, ri);
break;
case 2: //CELING
valStr = minus ? down(valStr, ri) : up(valStr, ri);
break;
case 3: //FLOOR
valStr = !minus ? down(valStr, ri) : up(valStr, ri);
break;
case 4: {//HALF_UP
const r = compareHalf(valStr, ri);
valStr = r != null && r < 0 ? down(valStr, ri) : up(valStr, ri);
break;
} case 5: {//HALF_DOWN
const r = compareHalf(valStr, ri);
valStr = r != null && r > 0 ? up(valStr, ri) : down(valStr, ri);
break;
} case 6: //HALF_EVEN
//fallthrough
default:
var r = compareHalf(valStr, ri);
if (r == 0) { //half
var evenChar = preDigit(valStr, ri);
valStr = evenChar && (parseInt(evenChar) & 1) ? up(valStr, ri) : down(valStr, ri);
} else
valStr = r != null && r < 0 ? down(valStr, ri) : up(valStr, ri);
}
return valStr;
},
format(fmt: string, val: string, rounding: number, localizedSymbols?: zk.LocalizedSymbols): string {
if (val == null) return '';
if (!fmt) return val + '';
if (fmt.startsWith('locale:')) {
const fractionDigits = new zk.BigDecimal(val).getPrecision();
return new Intl.NumberFormat(fmt.substring(fmt.indexOf(':') + 1), {
minimumFractionDigits: fractionDigits,
maximumFractionDigits: fractionDigits,
}).format(parseFloat(val));
}
var useMinsuFmt;
if (fmt.indexOf(';') != -1) {
var fmtArr = fmt.split(';');
useMinsuFmt = val && parseFloat(val) < 0;
fmt = fmtArr[useMinsuFmt ? 1 : 0];
}
// localized symbols
localizedSymbols = localizedSymbols || _defaultSymbols;
//calculate number of fixed decimals
var efmt = this._escapeQuote(fmt, localizedSymbols);
fmt = efmt.fmt;
var pureFmtStr = efmt.pureFmtStr,
ind = efmt.purejdot,
fixed = ind >= 0 ? pureFmtStr.length - ind - 1 : 0,
valStr = (val + '').replace(/[^e\-0123456789.]/g, '').substring(val && parseFloat(val) < 0 ? 1 : 0),
ei = valStr.lastIndexOf('e'),
indVal = valStr.indexOf('.'),
valFixed = indVal >= 0 ? (ei < 0 ? valStr.length : ei) - indVal - 1 : 0,
shift: number = efmt.shift + (ei < 0 ? 0 : parseInt(valStr.substring(ei + 1), 10));
if (ei > 0) valStr = valStr.substring(0, ei);
if (shift > 0) {
if (indVal >= 0) { //with dot
if (valFixed > shift) {
valStr = valStr.substring(0, indVal) + valStr.substring(indVal + 1, indVal + 1 + shift) + '.' + valStr.substring(indVal + 1 + shift);
valFixed -= shift;
indVal += shift;
} else {
valStr = valStr.substring(0, indVal) + valStr.substring(indVal + 1);
for (var len = shift - valFixed; len-- > 0;)
valStr = valStr + '0';
indVal = -1;
valFixed = 0;
}
} else { //without dot
for (var len = shift; len-- > 0;)
valStr = valStr + '0';
}
} else if (shift < 0) {
// ZK-1737 varStr is not correct variable name
var nind = (indVal < 0 ? valStr.length : indVal) + shift;
if (nind > 0) {
valStr = valStr.substring(0, nind) + '.'
+ (indVal < 0 ? valStr.substring(nind) : valStr.substring(nind, indVal) + valStr.substring(indVal + 1));
valFixed -= shift;
indVal = nind;
} else {
if (indVal >= 0)
valStr = valStr.substring(0, indVal) + valStr.substring(indVal + 1);
for (; nind++ < 0;)
valStr = '0' + valStr;
valStr = '0.' + valStr;
indVal = 1;
valFixed = valStr.length - 2;
}
}
//fix value subpart
if (valFixed <= fixed) {
if (indVal == -1)
valStr += '.';
for (var len = fixed - valFixed; len-- > 0;)
valStr = valStr + '0';
} else { //preprocess for rounding
var ri = indVal + fixed + 1;
valStr = this.rounding(valStr, ri, rounding, (val != '' && parseFloat(val) < 0));
}
var indFmt = efmt.jdot,
pre = '', suf = '';
//pre part
indVal = valStr.indexOf('.');
if (indVal == -1)
indVal = valStr.length;
if (indFmt == -1)
indFmt = fmt.length;
if (ind == -1)
ind = pureFmtStr.length;
// Bug 2911379
var prefmt = indVal - ind;
if (prefmt > 0) {
var xfmt = '';
for (var len = prefmt; --len >= 0; indFmt++)
xfmt += '#';
// insert extra format into correct place.
var beg = this._extraFmtIndex(fmt);
prefmt += beg;
fmt = fmt.substring(0, beg) + xfmt + fmt.substring(beg, fmt.length);
}
for (var len = ind - indVal; --len >= 0; indVal++)
valStr = '0' + valStr;
var groupDigit = indFmt - fmt.substring(0, indFmt).lastIndexOf(',');
for (var g = 1, i = indFmt - 1, j = indVal - 1; i >= 0 && j >= 0;) {
if (g % groupDigit == 0 && pre.charAt(0) != ',') {
pre = localizedSymbols.GROUPING + pre;
g++;
}
var fmtcc = fmt.charAt(i);
if (fmtcc == '#' || fmtcc == '0') {
var cc = valStr.charAt(j);
pre = (cc == '0' ? fmtcc : cc) + pre;
i--;
j--;
g++;
} else {
var c = fmt.charAt(i);
if (c != ',') {
pre = c + pre;
g++;
}
i--;
}
}
if (j >= 0)
pre = valStr.substr(0, j + 1) + pre;
//sufpart
for (var i: number = indFmt + 1, j = indVal + 1, fl = fmt.length, vl = valStr.length; i < fl; i++) {
var fmtcc = fmt.charAt(i);
if (fmtcc == '#' || fmtcc == '0') {
if (j < vl) {
suf += valStr.charAt(j);
j++;
}
} else
suf += fmtcc == '%' ? localizedSymbols.PERCENT : fmtcc;
}
if (j < valStr.length)
suf = valStr.substr(j, valStr.length);
//remove optional '0' digit in sufpart
var e = -1;
for (var m = suf.length, n = fmt.length; m > 0; --m) {
var cc = suf.charAt(m - 1),
fmtcc = fmt.charAt(--n);
if (cc == '0' && fmtcc == '#') { //optional 0
if (e < 0) e = m;
} else if (e >= 0 || /[1-9]/.test(cc))
break;
}
if (e >= 0)
suf = suf.substring(0, m) + suf.substring(e);
//combine
if (pre)
pre = fmt.substring(0, efmt.prej) + this._removePrefixSharps(pre, localizedSymbols);
if (!pre && fmt.charAt(indFmt + 1) == '#')
pre = '0';
if (!suf && (pre == localizedSymbols.PERCENT || pre == localizedSymbols.PER_MILL))
pre = '0' + pre;
var rexp = new RegExp('^0*[' + localizedSymbols.PERCENT + '|' + localizedSymbols.PER_MILL + ']?$'),
shownZero = suf ? rexp.test(suf) && /^0*$/.test(pre) : rexp.test(pre);
return (val != '' && parseFloat(val) < 0 && !shownZero && !useMinsuFmt ? localizedSymbols.MINUS : '') + (suf ? pre + (/[\d]/.test(suf.charAt(0)) ? localizedSymbols.DECIMAL : '') + suf : pre);
},
/** @internal */
_escapeQuote(fmt: string, localizedSymbols: zk.LocalizedSymbols): Efmt {
//note we do NOT support mixing of quoted and unquoted percent
var cc, q = -2, shift = 0, ret = '', jdot = -1, purejdot = -1, pure = '', prej = -1,
validPercent = fmt ? !new RegExp('(\'[' + localizedSymbols.PERCENT + '|' + localizedSymbols.PER_MILL + ']+\')', 'g').test(fmt) : true;
//note we do NOT support mixing of quoted and unquoted percent|permill
for (var j = 0, len = fmt.length; j < len; ++j) {
cc = fmt.charAt(j);
if (cc == '%' && validPercent)
shift += 2;
else if (cc == localizedSymbols.PER_MILL && validPercent)
shift += 3;
if (cc == '\'') { // a single quote
if (q >= 0) {//close single quote
ret += q == (j - 1) ? '\'' : fmt.substring(q + 1, j);
q = -2;
} else
q = j; //open single quote
} else if (q < 0) { //not in quote
if (prej < 0
&& (cc == '#' || cc == '0' || cc == '.' || cc == '-' || cc == ',' || cc == 'E'))
prej = ret.length;
if (cc == '#' || cc == '0')
pure += cc;
else if (cc == '.') {
if (purejdot < 0)
purejdot = pure.length;
if (jdot < 0)
jdot = ret.length;
pure += cc;
}
ret += cc;
}
}
return {shift: shift, fmt: ret, pureFmtStr: pure, jdot: jdot, purejdot: purejdot, prej: prej};
},
/** @internal */
_extraFmtIndex(fmt: string): number {
var j = 0;
for (var len = fmt.length; j < len; ++j) {
var c = fmt.charAt(j);
if (c == '#' || c == '0' || c == ',')
break;
}
return j;
},
/** @internal */
_removePrefixSharps(val: string, localizedSymbols: zk.LocalizedSymbols): string {
var ret = '',
sharp = true;
for (var len = val.length, j = 0; j < len; ++j) {
var cc = val.charAt(j);
if (sharp) {
if (cc == '#' || cc == localizedSymbols.GROUPING) continue;
else if (/[\d]/.test(cc)) sharp = false; // Bug 2990659
}
ret = ret + (cc == '#' ? '0' : cc);
}
return ret;
},
unformat(fmt: string, val: string, ignoreLocale: boolean, localizedSymbols?: zk.LocalizedSymbols): {raw: string; divscale: number} {
if (!val) return {raw: val, divscale: 0};
// localized symbols
localizedSymbols = localizedSymbols || {
GROUPING: zk.GROUPING,
DECIMAL: zk.DECIMAL,
PERCENT: zk.PERCENT,
PER_MILL: zk.PER_MILL,
MINUS: zk.MINUS
};
var divscale = 0, //the second element
minus,
sb: string | undefined,
cc: string,
ignore,
zkMinus = ignoreLocale ? '-' : localizedSymbols.MINUS,
zkDecimal = ignoreLocale ? '.' : localizedSymbols.DECIMAL, //bug #2932443, no format and German Locale
zkPercent = ignoreLocale ? '%' : localizedSymbols.PERCENT,
permill = String.fromCharCode(0x2030),
zkPermill = ignoreLocale ? permill : localizedSymbols.PER_MILL,
zkGrouping = ignoreLocale ? ',' : localizedSymbols.GROUPING,
validPercent = !new RegExp('(\'[%|' + permill + ']+\')', 'g').test(fmt);
//note we do NOT support mixing of quoted and unquoted percent|permill
for (var j = 0, len = val.length; j < len; ++j) {
cc = val.charAt(j);
ignore = true;
//We handle percent and (nnn) specially
if (cc == zkPercent && validPercent) divscale += 2;
else if (cc == zkPermill && validPercent) divscale += 3;
else if (cc == '(') minus = true;
else if (cc != '+') ignore = false;
//We don't add if cc shall be ignored (not alphanum but in fmt)
if (!ignore)
ignore = (cc < '0' || cc > '9')
&& cc != zkDecimal && cc != zkMinus && cc != '+'
&& (zUtl.isChar(cc, {whitespace: 1}) || cc == zkGrouping
|| cc == ')' || (fmt && fmt.indexOf(cc) >= 0));
if (ignore) {
if (sb == null) sb = val.substring(0, j);
} else {
var c2 = cc == zkMinus ? '-' :
cc == zkDecimal ? '.' : cc;
if (cc != c2 && sb == null)
sb = val.substring(0, j);
if (sb != null) sb += c2;
}
}
if (sb == null) sb = val;
if (parseFloat(sb) == 0) return {raw: '0', divscale: divscale};
if (minus) sb = '-' + sb;
for (;;) {
cc = sb.charAt(0);
if (cc == '+')
sb = sb.substring(1);
else if (cc == '-' && sb.charAt(1) == '-')
sb = sb.substring(2);
else
break;
}
//remove leading 0
//keep the zero after the decimal point (to preserve precision)
for (var j = 0, k: number | undefined, len: number = sb.length; j < len; ++j) {
cc = sb.charAt(j);
if (cc > '0' && cc <= '9') {
if (k !== undefined)
sb = sb.substring(0, k) + sb.substring(j);
break; //done
} else if (cc == '0') {
if (k === undefined)
k = j;
} else if (k !== undefined) {
if (cc == '.' && j > ++k)
sb = sb.substring(0, k) + sb.substring(j);
break;
} else if (cc == '.') { //.xxx or . // B50-3297864
break;
}
}
return {raw: sb, divscale: divscale};
}
};
zk.fmt.Number = Number;