File

projects/wms-framework/src/lib/utils/FlexibleJsNumberFormatter.ts

Description

Flexible Numeric formatter

Extends

BaseNumericStringFormatter

Index

Properties
Methods

Properties

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'

Methods

Private CreateNewFormat
CreateNewFormat(value: number, format: string)
Parameters :
Name Type Optional
value number No
format string No
Returns : void
Private NumberFormat
NumberFormat(value: number, format: string, context: any)
Parameters :
Name Type Optional
value number No
format string No
context any No
Returns : string
Protected PerformCustomNumericFormatting
PerformCustomNumericFormatting(value: number, format: string)

Perform the formatting operation using the numeral library

Parameters :
Name Type Optional
value number No
format string No
Returns : string

{string}

Private ConvertAbsoluteNumericValueToString
ConvertAbsoluteNumericValueToString(value: number, decimalDigits: number, decimalSeparator: string, groupSizes: number[], groupSeparator: string)

Converts a number to a string using specifying the number of:

  • decimal digits to use
  • decimal separator string to use
  • group sizes to use
  • group separator string to use

The value returned will be the string representation of the absolute value

Parameters :
Name Type Optional
value number No
decimalDigits number No
decimalSeparator string No
groupSizes number[] No
groupSeparator string No
Returns : any
Private FormatCurrencyToNegativeValue
FormatCurrencyToNegativeValue(strValue: string, globalizationInfo: GlobalizationHelper)

Formats the number with the currency symbol considering it is a negative value

Parameters :
Name Type Optional
strValue string No
globalizationInfo GlobalizationHelper No
Returns : string

{string}

Private FormatCurrencyToPositiveValue
FormatCurrencyToPositiveValue(strValue: string, globalizationInfo: GlobalizationHelper)

Formats the number with the currency symbol considering it is a positive value

Parameters :
Name Type Optional
strValue string No
globalizationInfo GlobalizationHelper No
Returns : string

{string}

Public FormatNumber
FormatNumber(value: number, formatString: string, globalizationInfo?: GlobalizationHelper)

Format a number with the given format string and for the specified globalization info

Parameters :
Name Type Optional Description
value number No

number to format

formatString string No

formatting string

globalizationInfo GlobalizationHelper Yes

optional globalization fino

Returns : string

{string} the formatted number

Private FormatNumberToNegativeValue
FormatNumberToNegativeValue(strValue: string, globalizationInfo: GlobalizationHelper)

Formats the number a negative value

Parameters :
Name Type Optional
strValue string No
globalizationInfo GlobalizationHelper No
Returns : string

{string}

Private FormatPercentToNegativeValue
FormatPercentToNegativeValue(strValue: string, globalizationInfo: GlobalizationHelper)

Formats the number with the percent symbol considering it is a negative value

Parameters :
Name Type Optional
strValue string No
globalizationInfo GlobalizationHelper No
Returns : string

{string}

Private FormatPercentToPositiveValue
FormatPercentToPositiveValue(strValue: string, globalizationInfo: GlobalizationHelper)

Formats the number with the percent symbol considering it is a positive value

Parameters :
Name Type Optional
strValue string No
globalizationInfo GlobalizationHelper No
Returns : string

{string}

Protected PerformStandardCurrencyFormatting
PerformStandardCurrencyFormatting(value: number, precision: number, globalizationInfo: GlobalizationHelper)

Performs the string formatting to currency

Parameters :
Name Type Optional
value number No
precision number No
globalizationInfo GlobalizationHelper No
Returns : string

{string}

Protected PerformStandardNumericFormatting
PerformStandardNumericFormatting(value: number, precision: number, globalizationInfo: GlobalizationHelper)

Performs the string formatting to numeric

Parameters :
Name Type Optional
value number No
precision number No
globalizationInfo GlobalizationHelper No
Returns : string

{string}

Protected PerformStandardPercentFormatting
PerformStandardPercentFormatting(value: number, precision: number, globalizationInfo: GlobalizationHelper)

Performs the string formatting to percentage

Parameters :
Name Type Optional
value number No
precision number No
globalizationInfo GlobalizationHelper No
Returns : string

{string}

Private ResolveStandardFormatApplied
ResolveStandardFormatApplied(formatString: string)

Tryies to resolve if the format applied is a standard numeric format and if a precision was specified

Parameters :
Name Type Optional
formatString string No
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();

result-matching ""

    No results matching ""