UNPKG

wolsey

Version:

Output numbers as numerals and ordinals

546 lines (519 loc) 20.6 kB
(function (moduleFactory) { if(typeof exports === "object") { module.exports = moduleFactory(); } else if (typeof define === "function" && define.amd) { define([], moduleFactory); } }(function () { /** * @module wolsey * @description Generates numerals as words and ordinals * * var Wolsey = require("wolsey"); * var cardinal = new Wolsey(); * * @returns {object} Wolsey {@link module:wolsey.Constructor} */ /** * @method lowestEN * @inner * @param {number} num Number to be converted * @param {object} numerals Object representing digits and irregular instances * @param {object} numeral Wolsey instance’s numeral object * @return {string} converted Numeral string */ function lowestEN (num, numerals, numeral) { var converted; if (!num) { converted = ""; } else if (numerals[num]) { converted = numerals[num]; } else { converted = numerals[Math.floor(num/10) * 10] + numeral.hyphenatecompound + numerals[num % 10]; } return converted; } /** * @method EN * @static * @param {object} [options] * @param {object} [powers=powers] Powers to use as units * @param {boolean} [longscale=false] Whether to use longscale units * @param {function} [lowest=lowestEN] Method to handle non-power units * @param {object} [numerals=numerals] Lookup map of numerals * @param {function} [ordinal=ordinal] Ordinal method * @param {function} [ordinalAsNumber=ordinalAsNumber] Ordinal as number method * @description Generic English lang number generator * * Calls {@link module:wolsey.LANG} * @return {object} LANG instance */ function EN (options) { options = options || {}; options.lowest = options.lowest || lowestEN; if (!options.powers) { options.powers = { 2: "hundred", 3: "thousand", 6: "million", 9: "billion", 12: "trillion", 15: "quadrillion", 18: "quintillion" }; if (options.longscale) { options.powers["9"] = "milliard" ; options.powers["15"] = "billiard" ; } } if (!options.numerals) { options.numerals = { "0": "zero", "1": "one", "2": "two", "3": "three", "4": "four", "5": "five", "6": "six", "7": "seven", "8": "eight", "9": "nine", "10": "ten", "11": "eleven", "12": "twelve", "13": "thirteen", "14": "fourteen", "15": "fifteen", "16": "sixteen", "17": "seventeen", "18": "eighteen", "19": "nineteen", "20": "twenty", "30": "thirty", "40": "forty", "50": "fifty", "60": "sixty", "70": "seventy", "80": "eighty", "90": "ninety" }; } if (!options.ordinal) { options.ordinal = function (num, options) { var numString = this.numeral(num); var map = { one: "first", two: "second", three: "third", five: "fifth", eight: "eighth", nine: "ninth", twelve: "twelfth" }; var words = numString.split(" "); var last = words.length - 1, lastword = words[last]; var lastmap = map[lastword]; if (lastmap) { words[last] = lastmap; } else { var lastchar = lastword.slice(-1); if (lastchar === "y") { lastword = lastword.replace("y", "ie"); } words[last] = lastword + "th"; } return words.join(" "); }; } if (!options.ordinalAsNumber) { options.ordinalAsNumber = function (num, options) { var map = { 1: "st", 2: "nd", 3: "rd", all: "th" }; var remainder = num % 100; if (remainder > 20) { remainder = remainder % 10; } return num + (map[remainder] ? map[remainder] : map.all); }; } return LANG(options); } /** * @method LANG * @static * @param {object} [options] * @param {object} [options.numerals] Digits and irregualr numbers to use * @param {array|object} [options.powers] Powers of 10 considered to be increments by language * @param {function} [options.lowest] Method to handle non-powers * @param {function} [options.ordinal] Method to handle ordinals * @param {function} [options.ordinalAsNumber] Method to handle ordinals as numbers * @param {string} [options.space=" "] Character to use for generic spaces between number words * @param {string} [options.unitone] Value to use when unit quotient is one * @param {boolean} [options.pluralizeunitexact=false] Pluralize unit only if no remnant * @param {boolean} [options.hyphenatecompound=false] Whether to hyphenate unit phrases * @param {string} [options.oneconjoin] Value to use when final part of remnant is one * @description Generic lang number generator * @return {object} methods instance */ function LANG (options) { options = options || {}; var numerals = options.numerals; var powers = options.powers; var lowestMethod = options.lowest; var ordinal = options.ordinal; var ordinalAsNumber = options.ordinalAsNumber; var space = options.space || " "; // Should throw an exception if we don't have these if (!Array.isArray(options.powers)) { var realpowers = []; for (var power in powers) { var realprop = {}; var powerprop = options.powers[power]; if (typeof powerprop === "string") { realprop.unit = powerprop; } else { for (var prop in powerprop) { realprop[prop] = powerprop[prop]; } } realprop.power = power; realpowers.push(realprop); } if (!powers["0"]) { realpowers.push({ power: 0 }); } powers = realpowers.sort(function (a, b) { return a.power*1 > b.power*1; }); } // conjoin should not be a default /** * @member defaults * @inner * @private * @type {object} * @property {string} conjoin=and String to conjoin word parts together * @property {number} conjoinfloor=100 Amount above which conjoining should not occur * @property {string} separator=, String to use as separator between unit phrases */ var defaults = { conjoin: "and", conjoinfloor: 100, separator: "," }; var numeral = {}; numeral.conjoin = options.conjoin !== undefined ? options.conjoin : defaults.conjoin; numeral.separator = options.separator !== undefined ? options.separator : defaults.separator; // pass this as a string rather than boolean numeral.hyphenatecompound = !options.hyphenatecompound ? " " : "-"; var conjoinmatch; if (numeral.conjoin) { conjoinmatch = new RegExp("^" + numeral.conjoin + " ") ; } /** * @method convertMethod * @inner * @private * @param {number} num Number to be converted * @param {object} poptions Describes options for the power level * @param {function} poptions.nextmethod Method to call for remnant * @param {function} poptions.highestmethod Method to call for quotient * @param {number} poptions.floor The power’s value * @param {string} [poptions.unitspace=" "] Character to use between quotient, unit and remnant * @param {boolean} [poptions.skiponeunit=false] Whether to suppress number when the unit’s quotient is one * @param {string} [poptions.plural] Plural form of power * @param {boolean} [poptions.pluralizeunitexact] Pluralize unit only if no remnant * @param {boolean} [poptions.invariable] Unit should not be pluralized * @param {object} poptions.{{moptions.unitkind}} Unit (or alternative kind) conversion is handling * @param {object} [moptions] Specific options for the conversion * @param {object} [moptions.lookup=numerals] Object to perform numeric lookups * @param {string} [moptions.unitkind=unit] Allows a different unit kind to be specified, eg. ordinal, which allows the numeral method to be reused as is * @description Generates a string by determing the quotient for the current power unit and joining it with its remnant along with spaces, conjoins and separators as required. * * The remant is generated by calling the next method, passing it the remainder and the same moptions. * * NB. options are the options passed to the lang generating method * @return {function} Method that converts the number at that power level and then moves to the next power level */ function convertMethod (num, poptions, moptions) { moptions = moptions || {}; var numlookup = moptions.lookup || numerals; var numunitkind = moptions.unitkind || "unit"; var nextmethod = poptions.nextmethod; var unitmethod = numeral.highestmethod; var space = options.space; if (space === undefined) { space = " "; } var unitspace = poptions.unitspace || ""; var unit = poptions[numunitkind]; var skiponeunit = poptions.skiponeunit; var floor = poptions.floor; var conjoinfloor = defaults.conjoinfloor; var converted = ""; if (num >= floor) { var remainder = num % floor; var remnant = nextmethod(remainder, moptions); if (numeral.conjoin && remainder && remainder < conjoinfloor) { remnant = numeral.conjoin + space + remnant; } var separator = numeral.separator; if (!remnant || (conjoinmatch && remnant.match(conjoinmatch))) { separator = ""; } separator += space; var unitunit = unit; var unitamount = Math.floor(num/floor); var unitvalue = unitmethod(unitamount); if (skiponeunit && unitamount === 1) { unitvalue = ""; } else { if (unitamount === 1 && options.unitone) { unitvalue = options.unitone; } unitvalue += space; if (unitamount > 1 && !poptions.invariable && !moptions.invariable) { if (options.pluralize || (!remainder && (poptions.pluralizeunitexact || options.pluralizeunitexact))) { unitunit = poptions.plural || unitunit + "s"; } } } var unitout = unitvalue + unitspace + unitunit; var numerallookup = unitamount * floor; if (numlookup[numerallookup+"="] && remainder === 0) { unitout = numlookup[numerallookup+"="]; } else if (numlookup[numerallookup]) { unitout = numlookup[numerallookup]; } converted = unitout + unitspace + separator + remnant; } else if (num) { converted = nextmethod(num, moptions); } return converted; } /** * @method convertLowest * @inner * @private * @param {number} num Number to be converted * @param {object} [options] Specific conversion options * @description Method for converting any non-powers numbers * @return {string} Converted number as string */ var convertLowest = function (num, options) { options = options || {}; var numlookup = options.lookup || numerals; return lowestMethod(num, numlookup, numeral); }; var doubleSpaceCheck = new RegExp(space + "{2,}", "g"); var endSpaceCheck = new RegExp(space + "$"); /** * @method convert * @inner * @private * @param {number} num Number to be converted * @param {object} [options] Specific conversion options * @description Tries to retrieve an explicit lookup value or else invokes the highest power method * @return {string} Converted number as string */ function convert (num, options) { options = options || {}; var numlookup = options.lookup || numerals; if (isNaN(num)) { return numeral.notanumber || num; } num = num * 1; var converted = numlookup[num] || numeral.highestmethod(num, options); // make sure there are no double or trailing spaces converted = converted.replace(doubleSpaceCheck, space).replace(endSpaceCheck, ""); return converted; } var powerlength = powers.length; var powerpos = {}; /** * @method createPowerMethod * @inner * @private * @param {object} poptions Describes options for the power level * @return {function} Method that converts the number at that power level and then moves to the next power level */ function createPowerMethod (poptions) { return function (num, options) { return convertMethod(num, poptions, options); }; } powers.forEach(function (pow, index) { pow.floor = Math.pow(10, pow.power); if (!pow.conjoinfloor) { if (pow.conjoinpower) { pow.conjoinfloor = Math.pow(10, pow.conjoinpower); } else { pow.conjoinfloor = pow.floor/10; } } pow.nextmethod = numeral.highestmethod; if (!pow.power) { pow.method = pow.method || convertLowest; } numeral.highestmethod = pow.method || createPowerMethod(pow); }); var methods = { numeral: function (num, options) { return convert(num, options); }, ordinal: ordinal, ordinalAsNumber: ordinalAsNumber }; return methods; } var langs = { "en-gb": new EN(), "en-us": new EN({conjoin: false, hyphenatecompound: true}) }; /** * @method Constructor * @static * @param {string} [lang=en-gb] Default lang * @param {object} [methods=en-gb methods] Lang methods * @description Create an instance of Wolsey * * var foo = { * numeral: function (num, options) { … }, * ordinal: function (num, options) { … }, * ordinalAsNumber: function (num, options) { … } * }; * var cardinal = new Wolsey("foo", foo); * * Or using one of the additional supplied languages * * require("wolsey/lang/fr"); * var cardinal = new Wolsey("fr", Wolsey.FR()); */ function Cardinal (lang, methods) { var external = {}; var internal = { cache: {} }; function CardinalAddLang (lang, methods) { langs[lang] = methods; CardinalSetCache(lang); } function CardinalSetDefault (lang) { if (langs[lang]) { internal.defaultlang = lang; } if (!internal.initialdefaultlang) { internal.initialdefaultlang = lang; } } function CardinalSetCache (lang) { internal.cache[lang] = { numeral: {}, ordinal: {}, ordinalAsNumber: {} }; } for (var clang in langs) { CardinalSetCache(clang); } if (lang && methods) { CardinalAddLang(lang, methods); } CardinalSetDefault(lang || "en-gb"); /** * @method genericMethod * @inner * @param {string} method Method to be invoked * @param {number} num Number to be converted * @param {object} options Options for the method * @return {string} converted number */ function genericMethod (method, num, options) { options = options || {}; var lang = options.lang || internal.defaultlang; var cachekey = num + JSON.stringify(options); if (internal.cache[lang][method][cachekey]) { return internal.cache[lang][method][cachekey]; } var computed = langs[lang][method](num, options); internal.cache[lang][method][cachekey] = computed; return computed; } /** * @method numeral * @instance * @param {number} num * @param {object} options * @description Calls {@link module:wolsey~genericMethod} * @return {string} numeral */ external.numeral = function CardinalNumeral (num, options) { return genericMethod("numeral", num, options); }; /** * @method ordinal * @instance * @param {number} num * @param {object} options * @description Calls {@link module:wolsey~genericMethod} * @return {string} ordinal */ external.ordinal = function CardinalOrdinal (num, options) { return genericMethod("ordinal", num, options); }; /** * @method ordinalAsNumber * @instance * @param {number} num * @param {object} options * @description Calls {@link module:wolsey~genericMethod} * @return {string} ordinalAsNUmber */ external.ordinalAsNumber = function CardinalOrdinalAsNumber (num, options) { return genericMethod("ordinalAsNumber", num, options); }; /** * @method addLang * @instance * @param {string} lang * @param {object} methods * @description Adds language to Wolsey instance */ external.addLang = CardinalAddLang; /** * @method setDefault * @instance * @param {string} lang * @description Sets language as Wolsey instance’s default */ external.setDefault = CardinalSetDefault; /** * @method resetDefault * @instance * @description Resets default language to Wolsey instance’s original default */ external.resetDefault = function CardinalResetDefault () { internal.defaultlang = internal.initialdefaultlang; }; return external; } Cardinal.util = { superscriptOridnal: function CardinalUtilSuperscriptOridnal (str, options) { options = options || {}; if (options.html) { str = str.replace(/([^0-9\.]+)$/, "<sup>$1</sup>"); } return str; } }; // Expose EN and LANG to world Cardinal.EN = EN; Cardinal.LANG = LANG; return Cardinal; }));