(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;
}));