UNPKG

numbro

Version:

Format and manipulate numbers.

1,544 lines (1,351 loc) 177 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.numbro = factory()); })(this, (function () { 'use strict'; var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } /*! * Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ var enUS; var hasRequiredEnUS; function requireEnUS () { if (hasRequiredEnUS) return enUS; hasRequiredEnUS = 1; enUS = { languageTag: "en-US", delimiters: { thousands: ",", decimal: "." }, abbreviations: { thousand: "k", million: "m", billion: "b", trillion: "t" }, spaceSeparated: false, ordinal: function(number) { let b = number % 10; return (~~(number % 100 / 10) === 1) ? "th" : (b === 1) ? "st" : (b === 2) ? "nd" : (b === 3) ? "rd" : "th"; }, bytes: { binarySuffixes: ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"], decimalSuffixes: ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] }, currency: { symbol: "$", position: "prefix", code: "USD" }, currencyFormat: { thousandSeparated: true, totalLength: 4, spaceSeparated: true, spaceSeparatedCurrency: true }, formats: { fourDigits: { totalLength: 4, spaceSeparated: true }, fullWithTwoDecimals: { output: "currency", thousandSeparated: true, mantissa: 2 }, fullWithTwoDecimalsNoCurrency: { thousandSeparated: true, mantissa: 2 }, fullWithNoDecimals: { output: "currency", thousandSeparated: true, mantissa: 0 } } }; return enUS; } /*! * Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ var unformatting; var hasRequiredUnformatting; function requireUnformatting () { if (hasRequiredUnformatting) return unformatting; hasRequiredUnformatting = 1; const allSuffixes = [ {key: "ZiB", factor: Math.pow(1024, 7)}, {key: "ZB", factor: Math.pow(1000, 7)}, {key: "YiB", factor: Math.pow(1024, 8)}, {key: "YB", factor: Math.pow(1000, 8)}, {key: "TiB", factor: Math.pow(1024, 4)}, {key: "TB", factor: Math.pow(1000, 4)}, {key: "PiB", factor: Math.pow(1024, 5)}, {key: "PB", factor: Math.pow(1000, 5)}, {key: "MiB", factor: Math.pow(1024, 2)}, {key: "MB", factor: Math.pow(1000, 2)}, {key: "KiB", factor: Math.pow(1024, 1)}, {key: "KB", factor: Math.pow(1000, 1)}, {key: "GiB", factor: Math.pow(1024, 3)}, {key: "GB", factor: Math.pow(1000, 3)}, {key: "EiB", factor: Math.pow(1024, 6)}, {key: "EB", factor: Math.pow(1000, 6)}, {key: "B", factor: 1} ]; /** * Generate a RegExp where S get all RegExp specific characters escaped. * * @param {string} s - string representing a RegExp * @return {string} */ function escapeRegExp(s) { return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); } /** * Recursively compute the unformatted value. * * @param {string} inputString - string to unformat * @param {*} delimiters - Delimiters used to generate the inputString * @param {string} [currencySymbol] - symbol used for currency while generating the inputString * @param {function} ordinal - function used to generate an ordinal out of a number * @param {string} zeroFormat - string representing zero * @param {*} abbreviations - abbreviations used while generating the inputString * @param {NumbroFormat} format - format used while generating the inputString * @return {number|undefined} */ function computeUnformattedValue(inputString, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations, format) { if (!isNaN(+inputString)) { return +inputString; } let stripped = ""; // Negative let newInput = inputString.replace(/(^[^(]*)\((.*)\)([^)]*$)/, "$1$2$3"); if (newInput !== inputString) { return -1 * computeUnformattedValue(newInput, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations); } // Byte for (let i = 0; i < allSuffixes.length; i++) { let suffix = allSuffixes[i]; stripped = inputString.replace(RegExp(`([0-9 ])(${suffix.key})$`), "$1"); if (stripped !== inputString) { return computeUnformattedValue(stripped, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations) * suffix.factor; } } // Percent stripped = inputString.replace("%", ""); if (stripped !== inputString) { return computeUnformattedValue(stripped, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations) / 100; } // Ordinal let possibleOrdinalValue = parseFloat(inputString); if (isNaN(possibleOrdinalValue)) { return undefined; } let ordinalString = ordinal(possibleOrdinalValue); if (ordinalString && ordinalString !== ".") { // if ordinal is "." it will be caught next round in the +inputString stripped = inputString.replace(new RegExp(`${escapeRegExp(ordinalString)}$`), ""); if (stripped !== inputString) { return computeUnformattedValue(stripped, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations); } } // Average let inversedAbbreviations = {}; Object.keys(abbreviations).forEach((key) => { inversedAbbreviations[abbreviations[key]] = key; }); let abbreviationValues = Object.keys(inversedAbbreviations).sort().reverse(); let numberOfAbbreviations = abbreviationValues.length; for (let i = 0; i < numberOfAbbreviations; i++) { let value = abbreviationValues[i]; let key = inversedAbbreviations[value]; stripped = inputString.replace(value, ""); if (stripped !== inputString) { let factor = undefined; switch (key) { // eslint-disable-line default-case case "thousand": factor = Math.pow(10, 3); break; case "million": factor = Math.pow(10, 6); break; case "billion": factor = Math.pow(10, 9); break; case "trillion": factor = Math.pow(10, 12); break; } return computeUnformattedValue(stripped, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations) * factor; } } return undefined; } /** * Removes in one pass all formatting symbols. * * @param {string} inputString - string to unformat * @param {*} delimiters - Delimiters used to generate the inputString * @param {string} [currencySymbol] - symbol used for currency while generating the inputString * @return {string} */ function removeFormattingSymbols(inputString, delimiters, currencySymbol) { // Currency let stripped = inputString.replace(currencySymbol, ""); // Thousand separators stripped = stripped.replace(new RegExp(`([0-9])${escapeRegExp(delimiters.thousands)}([0-9])`, "g"), "$1$2"); // Decimal stripped = stripped.replace(delimiters.decimal, "."); return stripped; } /** * Unformat a numbro-generated string to retrieve the original value. * * @param {string} inputString - string to unformat * @param {*} delimiters - Delimiters used to generate the inputString * @param {string} [currencySymbol] - symbol used for currency while generating the inputString * @param {function} ordinal - function used to generate an ordinal out of a number * @param {string} zeroFormat - string representing zero * @param {*} abbreviations - abbreviations used while generating the inputString * @param {NumbroFormat} format - format used while generating the inputString * @return {number|undefined} */ function unformatValue(inputString, delimiters, currencySymbol = "", ordinal, zeroFormat, abbreviations, format) { if (inputString === "") { return undefined; } // Zero Format if (inputString === zeroFormat) { return 0; } let value = removeFormattingSymbols(inputString, delimiters, currencySymbol); return computeUnformattedValue(value, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations); } /** * Check if the INPUTSTRING represents a time. * * @param {string} inputString - string to check * @param {*} delimiters - Delimiters used while generating the inputString * @return {boolean} */ function matchesTime(inputString, delimiters) { let separators = inputString.indexOf(":") && delimiters.thousands !== ":"; if (!separators) { return false; } let segments = inputString.split(":"); if (segments.length !== 3) { return false; } let hours = +segments[0]; let minutes = +segments[1]; let seconds = +segments[2]; return !isNaN(hours) && !isNaN(minutes) && !isNaN(seconds); } /** * Unformat a numbro-generated string representing a time to retrieve the original value. * * @param {string} inputString - string to unformat * @return {number} */ function unformatTime(inputString) { let segments = inputString.split(":"); let hours = +segments[0]; let minutes = +segments[1]; let seconds = +segments[2]; return seconds + 60 * minutes + 3600 * hours; } /** * Unformat a numbro-generated string to retrieve the original value. * * @param {string} inputString - string to unformat * @param {NumbroFormat} format - format used while generating the inputString * @return {number} */ function unformat(inputString, format) { // Avoid circular references const globalState = requireGlobalState(); let delimiters = globalState.currentDelimiters(); let currencySymbol = globalState.currentCurrency().symbol; let ordinal = globalState.currentOrdinal(); let zeroFormat = globalState.getZeroFormat(); let abbreviations = globalState.currentAbbreviations(); let value = undefined; if (typeof inputString === "string") { if (matchesTime(inputString, delimiters)) { value = unformatTime(inputString); } else { value = unformatValue(inputString, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations); } } else if (typeof inputString === "number") { value = inputString; } else { return undefined; } if (value === undefined) { return undefined; } return value; } unformatting = { unformat }; return unformatting; } /*! * Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ var validating$1; var hasRequiredValidating; function requireValidating () { if (hasRequiredValidating) return validating$1; hasRequiredValidating = 1; let unformatter = requireUnformatting(); // Simplified regexp supporting only `language`, `script`, and `region` const bcp47RegExp = /^[a-z]{2,3}(-[a-zA-Z]{4})?(-([A-Z]{2}|[0-9]{3}))?$/; const validOutputValues = [ "currency", "percent", "byte", "time", "ordinal", "number" ]; const validForceAverageValues = [ "trillion", "billion", "million", "thousand" ]; const validCurrencyPosition = [ "prefix", "infix", "postfix" ]; const validNegativeValues = [ "sign", "parenthesis" ]; const validMandatoryAbbreviations = { type: "object", children: { thousand: { type: "string", mandatory: true }, million: { type: "string", mandatory: true }, billion: { type: "string", mandatory: true }, trillion: { type: "string", mandatory: true } }, mandatory: true }; const validAbbreviations = { type: "object", children: { thousand: "string", million: "string", billion: "string", trillion: "string" } }; const validBaseValues = [ "decimal", "binary", "general" ]; const validFormat = { output: { type: "string", validValues: validOutputValues }, base: { type: "string", validValues: validBaseValues, restriction: (number, format) => format.output === "byte", message: "`base` must be provided only when the output is `byte`", mandatory: (format) => format.output === "byte" }, characteristic: { type: "number", restriction: (number) => number >= 0, message: "value must be positive" }, prefix: "string", postfix: "string", forceAverage: { type: "string", validValues: validForceAverageValues }, average: "boolean", lowPrecision: { type: "boolean", restriction: (number, format) => format.average === true, message: "`lowPrecision` must be provided only when the option `average` is set" }, currencyPosition: { type: "string", validValues: validCurrencyPosition }, currencySymbol: "string", totalLength: { type: "number", restrictions: [ { restriction: (number) => number >= 0, message: "value must be positive" }, { restriction: (number, format) => !format.exponential, message: "`totalLength` is incompatible with `exponential`" } ] }, mantissa: { type: "number", restriction: (number) => number >= 0, message: "value must be positive" }, optionalMantissa: "boolean", trimMantissa: "boolean", roundingFunction: "function", optionalCharacteristic: "boolean", thousandSeparated: "boolean", spaceSeparated: "boolean", spaceSeparatedCurrency: "boolean", spaceSeparatedAbbreviation: "boolean", abbreviations: validAbbreviations, negative: { type: "string", validValues: validNegativeValues }, forceSign: "boolean", exponential: { type: "boolean" }, prefixSymbol: { type: "boolean", restriction: (number, format) => format.output === "percent", message: "`prefixSymbol` can be provided only when the output is `percent`" } }; const validLanguage = { languageTag: { type: "string", mandatory: true, restriction: (tag) => { return tag.match(bcp47RegExp); }, message: "the language tag must follow the BCP 47 specification (see https://tools.ieft.org/html/bcp47)" }, delimiters: { type: "object", children: { thousands: "string", decimal: "string", thousandsSize: "number" }, mandatory: true }, abbreviations: validMandatoryAbbreviations, spaceSeparated: "boolean", spaceSeparatedCurrency: "boolean", ordinal: { type: "function", mandatory: true }, bytes: { type: "object", children: { binarySuffixes: "object", decimalSuffixes: "object" } }, currency: { type: "object", children: { symbol: "string", position: "string", code: "string" }, mandatory: true }, defaults: "format", ordinalFormat: "format", byteFormat: "format", percentageFormat: "format", currencyFormat: "format", timeDefaults: "format", formats: { type: "object", children: { fourDigits: { type: "format", mandatory: true }, fullWithTwoDecimals: { type: "format", mandatory: true }, fullWithTwoDecimalsNoCurrency: { type: "format", mandatory: true }, fullWithNoDecimals: { type: "format", mandatory: true } } } }; /** * Check the validity of the provided input and format. * The check is NOT lazy. * * @param {string|number|Numbro} input - input to check * @param {NumbroFormat} format - format to check * @return {boolean} True when everything is correct */ function validate(input, format) { let validInput = validateInput(input); let isFormatValid = validateFormat(format); return validInput && isFormatValid; } /** * Check the validity of the numbro input. * * @param {string|number|Numbro} input - input to check * @return {boolean} True when everything is correct */ function validateInput(input) { let value = unformatter.unformat(input); return value !== undefined; } /** * Check the validity of the provided format TOVALIDATE against SPEC. * * @param {NumbroFormat} toValidate - format to check * @param {*} spec - specification against which to check * @param {string} prefix - prefix use for error messages * @param {boolean} skipMandatoryCheck - `true` when the check for mandatory key must be skipped * @return {boolean} True when everything is correct */ function validateSpec(toValidate, spec, prefix, skipMandatoryCheck = false) { let results = Object.keys(toValidate).map((key) => { if (!spec[key]) { console.error(`${prefix} Invalid key: ${key}`); // eslint-disable-line no-console return false; } let value = toValidate[key]; let data = spec[key]; if (typeof data === "string") { data = {type: data}; } if (data.type === "format") { // all formats are partial (a.k.a will be merged with some default values) thus no need to check mandatory values let valid = validateSpec(value, validFormat, `[Validate ${key}]`, true); if (!valid) { return false; } } else if (typeof value !== data.type) { console.error(`${prefix} ${key} type mismatched: "${data.type}" expected, "${typeof value}" provided`); // eslint-disable-line no-console return false; } if (data.restrictions && data.restrictions.length) { let length = data.restrictions.length; for (let i = 0; i < length; i++) { let {restriction, message} = data.restrictions[i]; if (!restriction(value, toValidate)) { console.error(`${prefix} ${key} invalid value: ${message}`); // eslint-disable-line no-console return false; } } } if (data.restriction && !data.restriction(value, toValidate)) { console.error(`${prefix} ${key} invalid value: ${data.message}`); // eslint-disable-line no-console return false; } if (data.validValues && data.validValues.indexOf(value) === -1) { console.error(`${prefix} ${key} invalid value: must be among ${JSON.stringify(data.validValues)}, "${value}" provided`); // eslint-disable-line no-console return false; } if (data.children) { let valid = validateSpec(value, data.children, `[Validate ${key}]`); if (!valid) { return false; } } return true; }); if (!skipMandatoryCheck) { results.push(...Object.keys(spec).map((key) => { let data = spec[key]; if (typeof data === "string") { data = {type: data}; } if (data.mandatory) { let mandatory = data.mandatory; if (typeof mandatory === "function") { mandatory = mandatory(toValidate); } if (mandatory && toValidate[key] === undefined) { console.error(`${prefix} Missing mandatory key "${key}"`); // eslint-disable-line no-console return false; } } return true; })); } return results.reduce((acc, current) => { return acc && current; }, true); } /** * Check the provided FORMAT. * * @param {NumbroFormat} format - format to check * @return {boolean} */ function validateFormat(format) { return validateSpec(format, validFormat, "[Validate format]"); } /** * Check the provided LANGUAGE. * * @param {NumbroLanguage} language - language to check * @return {boolean} */ function validateLanguage(language) { return validateSpec(language, validLanguage, "[Validate language]"); } validating$1 = { validate, validateFormat, validateInput, validateLanguage }; return validating$1; } /*! * Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * Parse the format STRING looking for a prefix. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parsePrefix(string, result) { let match = string.match(/^{([^}]*)}/); if (match) { result.prefix = match[1]; return string.slice(match[0].length); } return string; } /** * Parse the format STRING looking for a postfix. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parsePostfix(string, result) { let match = string.match(/{([^}]*)}$/); if (match) { result.postfix = match[1]; return string.slice(0, -match[0].length); } return string; } /** * Parse the format STRING looking for the output value. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator */ function parseOutput(string, result) { if (string.indexOf("$") !== -1) { result.output = "currency"; return; } if (string.indexOf("%") !== -1) { result.output = "percent"; return; } if (string.indexOf("bd") !== -1) { result.output = "byte"; result.base = "general"; return; } if (string.indexOf("b") !== -1) { result.output = "byte"; result.base = "binary"; return; } if (string.indexOf("d") !== -1) { result.output = "byte"; result.base = "decimal"; return; } if (string.indexOf(":") !== -1) { result.output = "time"; return; } if (string.indexOf("o") !== -1) { result.output = "ordinal"; } } /** * Parse the format STRING looking for the thousand separated value. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parseThousandSeparated(string, result) { if (string.indexOf(",") !== -1) { result.thousandSeparated = true; } } /** * Parse the format STRING looking for the space separated value. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parseSpaceSeparated(string, result) { if (string.indexOf(" ") !== -1) { result.spaceSeparated = true; result.spaceSeparatedCurrency = true; if (result.average || result.forceAverage) { result.spaceSeparatedAbbreviation = true; } } } /** * Parse the format STRING looking for the total length. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parseTotalLength(string, result) { let match = string.match(/[1-9]+[0-9]*/); if (match) { result.totalLength = +match[0]; } } /** * Parse the format STRING looking for the characteristic length. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parseCharacteristic(string, result) { let characteristic = string.split(".")[0]; let match = characteristic.match(/0+/); if (match) { result.characteristic = match[0].length; } } /** * Parse the format STRING looking for the mantissa length. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parseMantissa(string, result) { let mantissa = string.split(".")[1]; if (mantissa) { let match = mantissa.match(/0+/); if (match) { result.mantissa = match[0].length; } } } /** * Parse the format STRING looking for a trimmed mantissa. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator */ function parseTrimMantissa(string, result) { const mantissa = string.split(".")[1]; if (mantissa) { result.trimMantissa = mantissa.indexOf("[") !== -1; } } /** * Parse the format STRING looking for the average value. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parseAverage(string, result) { if (string.indexOf("a") !== -1) { result.average = true; } } /** * Parse the format STRING looking for a forced average precision. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parseForceAverage(string, result) { if (string.indexOf("K") !== -1) { result.forceAverage = "thousand"; } else if (string.indexOf("M") !== -1) { result.forceAverage = "million"; } else if (string.indexOf("B") !== -1) { result.forceAverage = "billion"; } else if (string.indexOf("T") !== -1) { result.forceAverage = "trillion"; } } /** * Parse the format STRING finding if the mantissa is optional. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parseOptionalMantissa(string, result) { if (string.match(/\[\.]/)) { result.optionalMantissa = true; } else if (string.match(/\./)) { result.optionalMantissa = false; } } /** * Parse the format STRING finding if the characteristic is optional. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parseOptionalCharacteristic(string, result) { if (string.indexOf(".") !== -1) { let characteristic = string.split(".")[0]; result.optionalCharacteristic = characteristic.indexOf("0") === -1; } } /** * Parse the format STRING looking for the negative format. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {string} - format */ function parseNegative(string, result) { if (string.match(/^\+?\([^)]*\)$/)) { result.negative = "parenthesis"; } if (string.match(/^\+?-/)) { result.negative = "sign"; } } /** * Parse the format STRING finding if the sign is mandatory. Append it to RESULT when found. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator */ function parseForceSign(string, result) { if (string.match(/^\+/)) { result.forceSign = true; } } /** * Parse the format STRING and accumulating the values ie RESULT. * * @param {string} string - format * @param {NumbroFormat} result - Result accumulator * @return {NumbroFormat} - format */ function parseFormat(string, result = {}) { if (typeof string !== "string") { return string; } string = parsePrefix(string, result); string = parsePostfix(string, result); parseOutput(string, result); parseTotalLength(string, result); parseCharacteristic(string, result); parseOptionalCharacteristic(string, result); parseAverage(string, result); parseForceAverage(string, result); parseMantissa(string, result); parseOptionalMantissa(string, result); parseTrimMantissa(string, result); parseThousandSeparated(string, result); parseSpaceSeparated(string, result); parseNegative(string, result); parseForceSign(string, result); return result; } var parsing$2 = { parseFormat }; /*! * Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ var globalState$2; var hasRequiredGlobalState; function requireGlobalState () { if (hasRequiredGlobalState) return globalState$2; hasRequiredGlobalState = 1; const enUS = requireEnUS(); const validating = requireValidating(); const parsing = parsing$2; let state = {}; let currentLanguageTag = undefined; let languages = {}; let zeroFormat = null; let globalDefaults = {}; function chooseLanguage(tag) { currentLanguageTag = tag; } function currentLanguageData() { return languages[currentLanguageTag]; } /** * Return all the register languages * * @return {{}} */ state.languages = () => Object.assign({}, languages); // // Current language accessors // /** * Return the current language tag * * @return {string} */ state.currentLanguage = () => currentLanguageTag; /** * Return the current language bytes data * * @return {{}} */ state.currentBytes = () => currentLanguageData().bytes || {}; /** * Return the current language currency data * * @return {{}} */ state.currentCurrency = () => currentLanguageData().currency; /** * Return the current language abbreviations data * * @return {{}} */ state.currentAbbreviations = () => currentLanguageData().abbreviations; /** * Return the current language delimiters data * * @return {{}} */ state.currentDelimiters = () => currentLanguageData().delimiters; /** * Return the current language ordinal function * * @return {function} */ state.currentOrdinal = () => currentLanguageData().ordinal; // // Defaults // /** * Return the current formatting defaults. * First use the current language default, then fallback to the globally defined defaults. * * @return {{}} */ state.currentDefaults = () => Object.assign({}, currentLanguageData().defaults, globalDefaults); /** * Return the ordinal default-format. * First use the current language ordinal default, then fallback to the regular defaults. * * @return {{}} */ state.currentOrdinalDefaultFormat = () => Object.assign({}, state.currentDefaults(), currentLanguageData().ordinalFormat); /** * Return the byte default-format. * First use the current language byte default, then fallback to the regular defaults. * * @return {{}} */ state.currentByteDefaultFormat = () => Object.assign({}, state.currentDefaults(), currentLanguageData().byteFormat); /** * Return the percentage default-format. * First use the current language percentage default, then fallback to the regular defaults. * * @return {{}} */ state.currentPercentageDefaultFormat = () => Object.assign({}, state.currentDefaults(), currentLanguageData().percentageFormat); /** * Return the currency default-format. * First use the current language currency default, then fallback to the regular defaults. * * @return {{}} */ state.currentCurrencyDefaultFormat = () => Object.assign({}, state.currentDefaults(), currentLanguageData().currencyFormat); /** * Return the time default-format. * First use the current language currency default, then fallback to the regular defaults. * * @return {{}} */ state.currentTimeDefaultFormat = () => Object.assign({}, state.currentDefaults(), currentLanguageData().timeFormat); /** * Set the global formatting defaults. * * @param {{}|string} format - formatting options to use as defaults */ state.setDefaults = (format) => { format = parsing.parseFormat(format); if (validating.validateFormat(format)) { globalDefaults = format; } }; // // Zero format // /** * Return the format string for 0. * * @return {string} */ state.getZeroFormat = () => zeroFormat; /** * Set a STRING to output when the value is 0. * * @param {{}|string} string - string to set */ state.setZeroFormat = (string) => zeroFormat = typeof(string) === "string" ? string : null; /** * Return true if a format for 0 has been set already. * * @return {boolean} */ state.hasZeroFormat = () => zeroFormat !== null; // // Getters/Setters // /** * Return the language data for the provided TAG. * Return the current language data if no tag is provided. * * Throw an error if the tag doesn't match any registered language. * * @param {string} [tag] - language tag of a registered language * @return {{}} */ state.languageData = (tag) => { if (tag) { if (languages[tag]) { return languages[tag]; } throw new Error(`Unknown tag "${tag}"`); } return currentLanguageData(); }; /** * Register the provided DATA as a language if and only if the data is valid. * If the data is not valid, an error is thrown. * * When USELANGUAGE is true, the registered language is then used. * * @param {{}} data - language data to register * @param {boolean} [useLanguage] - `true` if the provided data should become the current language */ state.registerLanguage = (data, useLanguage = false) => { if (!validating.validateLanguage(data)) { throw new Error("Invalid language data"); } languages[data.languageTag] = data; if (useLanguage) { chooseLanguage(data.languageTag); } }; /** * Set the current language according to TAG. * If TAG doesn't match a registered language, another language matching * the "language" part of the tag (according to BCP47: https://tools.ietf.org/rfc/bcp/bcp47.txt). * If none, the FALLBACKTAG is used. If the FALLBACKTAG doesn't match a register language, * `en-US` is finally used. * * @param tag * @param fallbackTag */ state.setLanguage = (tag, fallbackTag = enUS.languageTag) => { if (!languages[tag]) { let suffix = tag.split("-")[0]; let matchingLanguageTag = Object.keys(languages).find(each => { return each.split("-")[0] === suffix; }); if (!languages[matchingLanguageTag]) { chooseLanguage(fallbackTag); return; } chooseLanguage(matchingLanguageTag); return; } chooseLanguage(tag); }; state.registerLanguage(enUS); currentLanguageTag = enUS.languageTag; globalState$2 = state; return globalState$2; } function commonjsRequire(path) { throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.'); } /*! * Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * Load languages matching TAGS. Silently pass over the failing load. * * We assume here that we are in a node environment, so we don't check for it. * @param {[String]} tags - list of tags to load * @param {Numbro} numbro - the numbro singleton */ function loadLanguagesInNode(tags, numbro) { tags.forEach((tag) => { let data = undefined; try { data = commonjsRequire(`../languages/${tag}`); } catch (e) { console.error(`Unable to load "${tag}". No matching language file found.`); // eslint-disable-line no-console } if (data) { numbro.registerLanguage(data); } }); } var loading = (numbro) => ({ loadLanguagesInNode: (tags) => loadLanguagesInNode(tags, numbro) }); var bignumber = {exports: {}}; (function (module) { (function (globalObject) { /* * bignumber.js v9.1.2 * A JavaScript library for arbitrary-precision arithmetic. * https://github.com/MikeMcl/bignumber.js * Copyright (c) 2022 Michael Mclaughlin <M8ch88l@gmail.com> * MIT Licensed. * * BigNumber.prototype methods | BigNumber methods * | * absoluteValue abs | clone * comparedTo | config set * decimalPlaces dp | DECIMAL_PLACES * dividedBy div | ROUNDING_MODE * dividedToIntegerBy idiv | EXPONENTIAL_AT * exponentiatedBy pow | RANGE * integerValue | CRYPTO * isEqualTo eq | MODULO_MODE * isFinite | POW_PRECISION * isGreaterThan gt | FORMAT * isGreaterThanOrEqualTo gte | ALPHABET * isInteger | isBigNumber * isLessThan lt | maximum max * isLessThanOrEqualTo lte | minimum min * isNaN | random * isNegative | sum * isPositive | * isZero | * minus | * modulo mod | * multipliedBy times | * negated | * plus | * precision sd | * shiftedBy | * squareRoot sqrt | * toExponential | * toFixed | * toFormat | * toFraction | * toJSON | * toNumber | * toPrecision | * toString | * valueOf | * */ var BigNumber, isNumeric = /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i, mathceil = Math.ceil, mathfloor = Math.floor, bignumberError = '[BigNumber Error] ', tooManyDigits = bignumberError + 'Number primitive has more than 15 significant digits: ', BASE = 1e14, LOG_BASE = 14, MAX_SAFE_INTEGER = 0x1fffffffffffff, // 2^53 - 1 // MAX_INT32 = 0x7fffffff, // 2^31 - 1 POWS_TEN = [1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13], SQRT_BASE = 1e7, // EDITABLE // The limit on the value of DECIMAL_PLACES, TO_EXP_NEG, TO_EXP_POS, MIN_EXP, MAX_EXP, and // the arguments to toExponential, toFixed, toFormat, and toPrecision. MAX = 1E9; // 0 to MAX_INT32 /* * Create and return a BigNumber constructor. */ function clone(configObject) { var div, convertBase, parseNumeric, P = BigNumber.prototype = { constructor: BigNumber, toString: null, valueOf: null }, ONE = new BigNumber(1), //----------------------------- EDITABLE CONFIG DEFAULTS ------------------------------- // The default values below must be integers within the inclusive ranges stated. // The values can also be changed at run-time using BigNumber.set. // The maximum number of decimal places for operations involving division. DECIMAL_PLACES = 20, // 0 to MAX // The rounding mode used when rounding to the above decimal places, and when using // toExponential, toFixed, toFormat and toPrecision, and round (default value). // UP 0 Away from zero. // DOWN 1 Towards zero. // CEIL 2 Towards +Infinity. // FLOOR 3 Towards -Infinity. // HALF_UP 4 Towards nearest neighbour. If equidistant, up. // HALF_DOWN 5 Towards nearest neighbour. If equidistant, down. // HALF_EVEN 6 Towards nearest neighbour. If equidistant, towards even neighbour. // HALF_CEIL 7 Towards nearest neighbour. If equidistant, towards +Infinity. // HALF_FLOOR 8 Towards nearest neighbour. If equidistant, towards -Infinity. ROUNDING_MODE = 4, // 0 to 8 // EXPONENTIAL_AT : [TO_EXP_NEG , TO_EXP_POS] // The exponent value at and beneath which toString returns exponential notation. // Number type: -7 TO_EXP_NEG = -7, // 0 to -MAX // The exponent value at and above which toString returns exponential notation. // Number type: 21 TO_EXP_POS = 21, // 0 to MAX // RANGE : [MIN_EXP, MAX_EXP] // The mi