projects/wms-framework/src/lib/utils/FlexibleJsNumberFormatter.ts
Flexible Numeric formatter
Properties |
|
Methods |
|
Private formatFunctions |
Type : object
|
Default value : { count: 0 }
|
Private Static Readonly NaNstring |
Type : string
|
Default value : 'NaN'
|
Private Static Readonly negInfinity |
Type : string
|
Default value : '-Infinity'
|
Private Static Readonly posInfinity |
Type : string
|
Default value : 'Infinity'
|
Private CreateNewFormat |
CreateNewFormat(value: number, format: string)
|
Returns :
void
|
Private NumberFormat |
NumberFormat(value: number, format: string, context: any)
|
Returns :
string
|
Protected PerformCustomNumericFormatting |
PerformCustomNumericFormatting(value: number, format: string)
|
Inherited from
BaseNumericStringFormatter
|
Defined in
BaseNumericStringFormatter:44
|
Perform the formatting operation using the
Returns :
string
{string} |
Private ConvertAbsoluteNumericValueToString | ||||||||||||||||||
ConvertAbsoluteNumericValueToString(value: number, decimalDigits: number, decimalSeparator: string, groupSizes: number[], groupSeparator: string)
|
||||||||||||||||||
Inherited from
BaseNumericStringFormatter
|
||||||||||||||||||
Defined in
BaseNumericStringFormatter:294
|
||||||||||||||||||
Converts a number to a string using specifying the number of:
The value returned will be the string representation of the absolute value
Parameters :
Returns :
any
|
Private FormatCurrencyToNegativeValue | |||||||||
FormatCurrencyToNegativeValue(strValue: string, globalizationInfo: GlobalizationHelper)
|
|||||||||
Inherited from
BaseNumericStringFormatter
|
|||||||||
Defined in
BaseNumericStringFormatter:367
|
|||||||||
Formats the number with the currency symbol considering it is a negative value
Parameters :
Returns :
string
{string} |
Private FormatCurrencyToPositiveValue | |||||||||
FormatCurrencyToPositiveValue(strValue: string, globalizationInfo: GlobalizationHelper)
|
|||||||||
Inherited from
BaseNumericStringFormatter
|
|||||||||
Defined in
BaseNumericStringFormatter:339
|
|||||||||
Formats the number with the currency symbol considering it is a positive value
Parameters :
Returns :
string
{string} |
Public FormatNumber | ||||||||||||||||
FormatNumber(value: number, formatString: string, globalizationInfo?: GlobalizationHelper)
|
||||||||||||||||
Inherited from
BaseNumericStringFormatter
|
||||||||||||||||
Defined in
BaseNumericStringFormatter:38
|
||||||||||||||||
Format a number with the given format string and for the specified globalization info
Parameters :
Returns :
string
{string} the formatted number |
Private FormatNumberToNegativeValue | |||||||||
FormatNumberToNegativeValue(strValue: string, globalizationInfo: GlobalizationHelper)
|
|||||||||
Inherited from
BaseNumericStringFormatter
|
|||||||||
Defined in
BaseNumericStringFormatter:493
|
|||||||||
Formats the number a negative value
Parameters :
Returns :
string
{string} |
Private FormatPercentToNegativeValue | |||||||||
FormatPercentToNegativeValue(strValue: string, globalizationInfo: GlobalizationHelper)
|
|||||||||
Inherited from
BaseNumericStringFormatter
|
|||||||||
Defined in
BaseNumericStringFormatter:448
|
|||||||||
Formats the number with the percent symbol considering it is a negative value
Parameters :
Returns :
string
{string} |
Private FormatPercentToPositiveValue | |||||||||
FormatPercentToPositiveValue(strValue: string, globalizationInfo: GlobalizationHelper)
|
|||||||||
Inherited from
BaseNumericStringFormatter
|
|||||||||
Defined in
BaseNumericStringFormatter:420
|
|||||||||
Formats the number with the percent symbol considering it is a positive value
Parameters :
Returns :
string
{string} |
Protected PerformStandardCurrencyFormatting | ||||||||||||
PerformStandardCurrencyFormatting(value: number, precision: number, globalizationInfo: GlobalizationHelper)
|
||||||||||||
Inherited from
BaseNumericStringFormatter
|
||||||||||||
Defined in
BaseNumericStringFormatter:155
|
||||||||||||
Performs the string formatting to currency
Parameters :
Returns :
string
{string} |
Protected PerformStandardNumericFormatting | ||||||||||||
PerformStandardNumericFormatting(value: number, precision: number, globalizationInfo: GlobalizationHelper)
|
||||||||||||
Inherited from
BaseNumericStringFormatter
|
||||||||||||
Defined in
BaseNumericStringFormatter:243
|
||||||||||||
Performs the string formatting to numeric
Parameters :
Returns :
string
{string} |
Protected PerformStandardPercentFormatting | ||||||||||||
PerformStandardPercentFormatting(value: number, precision: number, globalizationInfo: GlobalizationHelper)
|
||||||||||||
Inherited from
BaseNumericStringFormatter
|
||||||||||||
Defined in
BaseNumericStringFormatter:198
|
||||||||||||
Performs the string formatting to percentage
Parameters :
Returns :
string
{string} |
Private ResolveStandardFormatApplied | ||||||
ResolveStandardFormatApplied(formatString: string)
|
||||||
Inherited from
BaseNumericStringFormatter
|
||||||
Defined in
BaseNumericStringFormatter:77
|
||||||
Tryies to resolve if the format applied is a standard numeric format and if a precision was specified
Parameters :
Returns :
literal type
{{ StandardFormat: StandardNumericFormatsEnum, Precision: number }} |
import { BaseNumericStringFormatter } from './BaseNumericStringFormatter';
/**
* Flexible Numeric formatter
*
* @export
* @class FlexibleJsNumberFormatter
* @extends {BaseNumericStringFormatter}
*/
export class FlexibleJsNumberFormatter extends BaseNumericStringFormatter {
private formatFunctions = { count: 0 };
// Constants useful for controlling the format of numbers in special cases.
private static readonly NaNstring = 'NaN';
private static readonly posInfinity = 'Infinity';
private static readonly negInfinity = '-Infinity';
/**
* Perform the formatting operation using the `numeral` library
*
* @protected
* @param {number} value
* @param {string} preprocessedValue
* @return {*} {string}
* @memberof NumericalJsStringFormatter
*/
protected PerformCustomNumericFormatting(
value: number,
format: string
): string {
return this.NumberFormat(value, format, null);
}
private NumberFormat(value: number, format: string, context: any): string {
if (isNaN(value)) {
return FlexibleJsNumberFormatter.NaNstring;
} else if (value === Infinity) {
return FlexibleJsNumberFormatter.posInfinity;
} else if (value === -Infinity) {
return FlexibleJsNumberFormatter.negInfinity;
} else if (this.formatFunctions[format] == null) {
this.CreateNewFormat(value, format);
}
return this.formatFunctions[format].Run(value, context);
}
private CreateNewFormat(value: number, format: string): void {
let code = `({
Run: (value, context) => {
`;
// Decide whether the function is a terminal or a pos/neg/zero function
let formats = format.split(';');
switch (formats.length) {
case 1:
code += CreateTerminalFormat(format);
break;
case 2:
code +=
'return (value < 0) ? this.NumberFormat(value, "' +
EscapeString(formats[1]) +
'", 1) : this.NumberFormat(value, "' +
EscapeString(formats[0]) +
'", 2);';
break;
case 3:
code +=
'return (value < 0) ? this.NumberFormat(value, "' +
EscapeString(formats[1]) +
'", 1) : ((value == 0) ? this.NumberFormat(value, "' +
EscapeString(formats[2]) +
'", 2) : this.NumberFormat(value, "' +
EscapeString(formats[0]) +
'", 3));';
break;
default:
code += "throw 'Too many semicolons in format string';";
break;
}
const result =
code +
`}
})`;
let runnable: any = eval(result);
this.formatFunctions[format] = runnable;
}
}
function CreateTerminalFormat(format: string) {
// If there is no work to do, just return the literal value
if (format.length > 0 && format.search(/[0#?]/) === -1) {
return `return '${EscapeString(format)}';\n`;
}
// Negative values are always displayed without a minus sign when section separators are used.
let code =
'var val = (context == null) ? new Number(value) : Math.abs(value);\n';
let thousands = false;
let lodp = format;
let rodp = '';
let ldigits = 0;
let rdigits = 0;
let scidigits = 0;
let scishowsign = false;
let sciletter = '';
// Look for (and remove) scientific notation instructions, which can be anywhere
let m = format.match(/\..*(e)([+-]?)(0+)/i);
if (m) {
sciletter = m[1];
scishowsign = m[2] === '+';
scidigits = m[3].length;
format = format.replace(/(e)([+-]?)(0+)/i, '');
}
// Split around the decimal point
m = format.match(/^([^.]*)\.(.*)$/);
if (m) {
lodp = m[1].replace(/\./g, '');
rodp = m[2].replace(/\./g, '');
}
// Look for %
if (format.indexOf('%') >= 0) {
code += 'val *= 100;\n';
}
// Look for comma-scaling to the left of the decimal point
m = lodp.match(/(,+)(?:$|[^0#?,])/);
if (m) {
code += 'val /= ' + Math.pow(1000, m[1].length) + '\n;';
}
// Look for comma-separators
if (lodp.search(/[0#?],[0#?]/) >= 0) {
thousands = true;
}
// Nuke any extraneous commas
if (m || thousands) {
lodp = lodp.replace(/,/g, '');
}
// Figure out how many digits to the l/r of the decimal place
m = lodp.match(/0[0#?]*/);
if (m) {
ldigits = m[0].length;
}
m = rodp.match(/[0#?]*/);
if (m) {
rdigits = m[0].length;
}
// Scientific notation takes precedence over rounding etc
if (scidigits > 0) {
code += `var sci = ToScientific(val, ${ldigits}, ${rdigits}, ${scidigits}, ${scishowsign});\n`;
code += 'var arr = [sci.l, sci.r];\n';
} else {
// If there is no decimal point, round to nearest integer, AWAY from zero
code = RoundAwayFromZero(format, code, rdigits, ldigits);
}
// Add thousands separators
code = AddThousandsSeparators(thousands, code);
// Insert the digits into the formatting string. On the LHS, extra digits are copied
// into the result. On the RHS, rounding has chopped them off.
code += `arr[0] = ReverseString(InjectIntoFormat(ReverseString(arr[0]), '${EscapeString(
ReverseString(lodp)
)}', true));\n`;
if (rdigits > 0) {
code += `arr[1] = InjectIntoFormat(arr[1], '${EscapeString(
rodp
)}', false);\n`;
}
if (scidigits > 0) {
code += `arr[1] = arr[1].replace(/(\\d{${rdigits}})/, '$1${sciletter}' + sci.s);\n`;
}
return code + "return arr.join('.');\n";
}
function AddThousandsSeparators(thousands: boolean, code: string) {
if (thousands) {
code += 'arr[0] = AddSeparators(arr[0]);\n';
}
return code;
}
function RoundAwayFromZero(
format: string,
code: string,
rdigits: number,
ldigits: number
) {
// Numbers are rounded to the correct number of digits to the right of the decimal
code += `var arr = RoundNumber(val, ${rdigits}).toFixed(${rdigits}).split('.');\n`;
// There are at least "ldigits" digits to the left of the decimal, so add zeros if needed.
code += `arr[0] = (val < 0 ? '-' : '') + LeftPad((val < 0 ? arr[0].substring(1) : arr[0]), ${ldigits}, '0');\n`;
return code;
}
function ToScientific(
val: number,
ldigits: number,
rdigits: number,
scidigits: number,
showsign: boolean
): any {
let result = { l: '', r: '', s: undefined };
let ex = '';
// Make ldigits + rdigits significant figures
let before = Trim(Math.abs(val).toFixed(ldigits + rdigits + 1), '0');
// Move the decimal point to the right of all digits we want to keep,
// and round the resulting value off
let after = Math.round(
Number.parseFloat(
before
.replace('.', '')
.replace(new RegExp(`(\\d{${ldigits + rdigits}})(.*)`), '$1.$2')
)
).toFixed(0);
// Place the decimal point in the new string
if (after.length >= ldigits) {
after = after.substring(0, ldigits) + '.' + after.substring(ldigits);
} else {
after += '.';
}
// Find how much the decimal point moved. This is #places to LODP in the original
// number, minus the #places in the new number. There are no left-padded zeroes in
// the new number, so the calculation for it is simpler than for the old number.
result.s = before.indexOf('.') - before.search(/[1-9]/) - after.indexOf('.');
// The exponent is off by 1 when it gets moved to the left.
if (result.s < 0) {
result.s++;
}
// Split the value around the decimal point and pad the parts appropriately.
result.l =
(val < 0 ? '-' : '') +
LeftPad(after.substring(0, after.indexOf('.')), ldigits, '0');
result.r = after.substring(after.indexOf('.') + 1);
if (result.s < 0) {
ex = '-';
} else if (showsign) {
ex = '+';
}
result.s = ex + LeftPad(Math.abs(result.s).toFixed(0), scidigits, '0');
return result;
}
function RoundNumber(value: number, decimals: number): number {
if (decimals > 0) {
let m = value
.toFixed(decimals + 1)
.match(new RegExp(`(-?\\d*)\.(\\d{${decimals}})(\\d)\\d*$`));
if (m && m.length) {
return Number.parseFloat(
m[1] +
'.' +
LeftPad(
Math.round(Number.parseFloat(m[2] + '.' + m[3])).toString(),
decimals,
'0'
)
);
}
}
return value;
}
function InjectIntoFormat(val: string, format: string, stuffExtras: boolean) {
let i = 0;
let j = 0;
let result = '';
let revneg = val.charAt(val.length - 1) === '-';
if (revneg) {
val = val.substring(0, val.length - 1);
}
while (
i < format.length &&
j < val.length &&
format.substring(i).search(/[0#?]/) >= 0
) {
({ i, j, result } = InjectZerosFromFormat(format, i, val, j, result));
}
if (revneg && j === val.length) {
result += '-';
}
if (j < val.length) {
if (stuffExtras) {
result += val.substring(j);
}
if (revneg) {
result += '-';
}
}
if (i < format.length) {
result += format.substring(i);
}
return result.replace(/#/g, '').replace(/\?/g, ' ');
}
function InjectZerosFromFormat(
format: string,
i: number,
val: string,
j: number,
result: string
) {
if (format.charAt(i).match(/[0#?]/)) {
// It's a formatting character; copy the corresponding character
// in the value to the result
if (val.charAt(j) !== '-') {
result += val.charAt(j);
} else {
result += '0';
}
j++;
} else {
result += format.charAt(i);
}
++i;
return { i, j, result };
}
function EscapeString(literal: string): string {
return literal.replace(/('|\\)/g, '\\$1');
}
function ReverseString(literal: string): string {
return [...literal].reverse().join('');
}
function Trim(literal: string, ch: string): string {
if (!ch) {
ch = ' ';
}
return literal.replace(new RegExp(`^${ch}+|${ch}+$`, 'g'), '');
}
function AddSeparators(val: string): string {
return ReverseString(ReverseString(val).replace(/(\d{3})/g, '$1,')).replace(
/^(-)?,/,
'$1'
);
}
function LeftPad(val: string, size: number, ch: string) {
var result = val;
if (ch == null) {
ch = ' ';
}
while (result.length < size) {
result = ch + result;
}
return result;
}
/**
* Formatter to use in the library
*/
export const DefaultNumberFormatter = new FlexibleJsNumberFormatter();