numbo
Version:
Convert number and monetary amount to written text. Also helpful for writing cheques (checks). Supports English. 支援中文
474 lines (457 loc) • 15.9 kB
JavaScript
// Generated by CoffeeScript 1.12.7
/*
Numbo is open source in:
https://github.com/Edditoria/numbo
under MIT license:
https://github.com/Edditoria/numbo/blob/master/LICENSE.md
*/
(function() {
var Numbo, numbo,
slice = [].slice;
Numbo = (function() {
var convert_enUS, tools;
function Numbo() {}
tools = {
trimWhitespace: function(str) {
return str.replace(/^\s+|\s+$/g, '');
},
parse99: function(arr1, arr10, separator) {
var i, lenArr1, num, results;
if (separator == null) {
separator = ' ';
}
lenArr1 = arr1.length;
results = [];
for (num = i = 0; i <= 99; num = ++i) {
if (num < lenArr1) {
results.push(arr1[num]);
} else if (num % 10 === 0) {
results.push(arr10[Math.floor(num / 10)]);
} else {
results.push(arr10[Math.floor(num / 10)] + separator + arr1[num % 10].toLowerCase());
}
}
return results;
},
parseCents: function(str, option) {
var mathOption, str3dp;
if (option == null) {
option = 'ceil';
}
console.log('Depreciated: Parse the whole string using adjustDecimal() instead');
str3dp = (str + '000').slice(0, 3);
mathOption = (function() {
switch (option) {
case 'round':
return 'round';
case 'floor':
return 'floor';
case 'ceil':
return 'ceil';
default:
console.log('Error: Option in parseCents() is invalid. Use the default option (Math.ceil)');
return 'ceil';
}
})();
return Math[mathOption](parseInt(str3dp, 10) / 10);
},
speakByDigit: function(str, n1, separator) {
var i, item, len1, output;
if (separator == null) {
separator = ' ';
}
if (typeof str === 'string' && str.search(/\D/g) < 0) {
output = [];
for (i = 0, len1 = str.length; i < len1; i++) {
item = str[i];
output.push(n1[+item]);
}
return output.join(separator);
} else {
console.log('Error: Invalid argument of speakByDigit()');
return null;
}
},
check: function(input, characters) {
var acceptRegex, commaAfterDot, dotCount, illegalStr, inputStr, result;
if (characters == null) {
characters = '';
}
result = false;
inputStr = input.toString();
acceptRegex = /^[\+\-\$]*[\d\.\,]*|[\d\.\,]*/g;
illegalStr = inputStr.replace(acceptRegex, '');
dotCount = (inputStr.match(/\./g) || []).length;
commaAfterDot = inputStr.indexOf(',', inputStr.indexOf('.')) > 0;
if (illegalStr === '' && dotCount < 2 && !commaAfterDot) {
result = true;
}
return result;
},
normalize: function(input, characters) {
var dotIndex, inputArr, output, regexHead, regexTail;
if (characters == null) {
characters = '';
}
if (typeof input !== 'string') {
input = input.toString();
}
regexHead = /,|^[$|\-]*(0|,)*(?!\.)/g;
regexTail = /0*$/g;
dotIndex = input.indexOf('.');
if (dotIndex === -1) {
output = input.replace(regexHead, '');
} else {
inputArr = tools.splitNum(input);
inputArr[0] = inputArr[0].replace(regexHead, '');
inputArr[1] = inputArr[1].replace(regexTail, '');
output = inputArr.join('.');
}
if (output.indexOf('.') === 0) {
output = '0' + output;
}
if (output === '') {
output = '0';
}
return output.replace(/\.$/, '');
},
adjustDecimal: function(str, type, dp) {
var carryInt, dec, dotIndex, int, parseDec;
if (type == null) {
type = 'ceil';
}
if (dp == null) {
dp = 2;
}
if (typeof str !== 'string') {
str = str.toString();
}
dotIndex = str.indexOf('.');
if (dotIndex === -1) {
return str;
}
if (type !== 'ceil' && type !== 'floor' && type !== 'round') {
type = 'ceil';
console.log('Error: type in adjustDecimal() is invalid. Use the default option (Math.ceil)');
}
if (typeof dp !== 'number' || dp < 0) {
dp = 2;
console.log('Error: dp in adjustDecimal() is invalid. Use the default option (adjust to 2 decimal place)');
}
parseDec = function(dec, type, dp) {
dec = '0.' + dec;
dec = Math[type](+(dec + 'e+' + dp));
dec = +(dec + 'e-' + dp);
if (dec === 0) {
return '';
} else if (dec === 1) {
return '1';
} else {
return (dec + '').replace(/^0/g, '');
}
};
carryInt = function(int) {
var index, intArr, isCarry, len, thisChar;
len = int.length;
if (len < 16) {
return +int + 1 + '';
} else {
intArr = int.split('');
index = len;
isCarry = true;
while (index && isCarry) {
index--;
thisChar = +intArr[index] + 1 + '';
if (thisChar === '10') {
thisChar = '0';
} else {
isCarry = false;
}
intArr[index] = thisChar;
}
if (isCarry) {
intArr.unshift('1');
}
return intArr.join('');
}
};
if (type === 'ceil' || type === 'round') {
int = str.slice(0, dotIndex);
dec = str.slice(dotIndex + 1);
dec = parseDec(dec, type, dp);
if (dec === '1') {
dec = '';
int = carryInt(int);
}
return int + dec;
} else if (type === 'floor') {
int = str.slice(0, dotIndex);
dec = str.slice(dotIndex, dotIndex + dp + 1);
if (+dec === 0) {
dec = '';
}
return int + dec;
} else {
console.log('Error: Unknown error in tools.adjustDecimal()');
return str;
}
},
splitNum: function(input) {
var output;
if (typeof input !== 'string') {
input = input.toString();
}
if (input.indexOf('.') === -1) {
output = [input, ''];
} else {
output = input.split('.');
}
if (output[0] === '') {
output[0] = '0';
}
return output;
},
splitInt: function(int, digit) {
var intTail, len, output, times;
if (digit == null) {
digit = 3;
}
if (typeof int !== 'string') {
int = int.toString();
}
output = [];
times = Math.ceil(int.length / digit);
while (times) {
len = int.length;
intTail = int.substring(len - digit, len);
int = int.substring(0, len - digit);
output.unshift(intTail);
times--;
}
return output;
}
};
Numbo.prototype.tools = {
trimWhitespace: tools.trimWhitespace,
parse99: tools.parse99,
parseCent: tools.parseCents,
parseCents: tools.parseCents,
speakByDigit: tools.speakByDigit,
check: tools.check,
validate: tools.check,
normalize: tools.normalize,
normalise: tools.normalize,
adjustDecimal: tools.adjustDecimal,
splitNum: tools.splitNum,
splitInt: tools.splitInt
};
convert_enUS = function(input, options, zeroCent) {
var main, n1, n10, n1000, n1withZero, n99, speak999, speakAmt, speakInt, speakNum;
if (options == null) {
options = 'default';
}
if (zeroCent == null) {
zeroCent = false;
}
n1 = ['', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen'];
n1withZero = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen'];
n10 = ['', 'Ten', 'Twenty', 'Thirty', 'Forty', 'Fifty', 'Sixty', 'Seventy', 'Eighty', 'Ninety'];
n1000 = ['', 'Thousand', 'Million', 'Billion', 'Trillion', 'Quadrillion', 'Quintillion', 'Sextillion', 'Septillion', 'Octillion', 'Nonillion', 'Decillion', 'Undecillion', 'Duodecillion', 'Tredecillion', 'Quattuordecillion', 'Quindecillion', 'Sexdecillion', 'Septendecillion', 'Octodecillion', 'Novemdecillion', 'Vigintillion'];
n99 = tools.parse99(n1, n10, '-');
speak999 = function(num, addAnd) {
var d100, hundred, output;
if (addAnd == null) {
addAnd = true;
}
if (num <= 99) {
return n99[num];
} else if (num % 100 === 0) {
return n1[Math.floor(num / 100)] + ' Hundred';
} else {
d100 = Math.floor(num / 100);
hundred = addAnd ? ' Hundred and ' : ' Hundred ';
return output = n1[d100] + hundred + n99[num % 100];
}
};
speakInt = function(numArr, addAnd) {
var i, item, len1, num, output, times, unit;
if (addAnd == null) {
addAnd = true;
}
output = [];
times = numArr.length;
for (num = i = 0, len1 = numArr.length; i < len1; num = ++i) {
item = numArr[num];
times--;
if (item !== '000') {
unit = ' ' + n1000[times];
item = speak999(parseInt(item, 10), addAnd);
output.push(item + unit);
}
}
return output.join(' ').replace(/^\s+|\s+$/g, '');
};
speakNum = function(str) {
var dec, int, intArr, strSplited;
if (str === '0') {
return 'zero';
} else if (str === '1') {
return 'one';
} else {
strSplited = tools.splitNum(str);
intArr = tools.splitInt(strSplited[0]);
int = speakInt(intArr);
if (int === '') {
int = 'zero';
}
dec = strSplited[1] === '' ? '' : ' point ' + tools.speakByDigit(strSplited[1], n1withZero, ' ');
return (int + dec).toLowerCase();
}
};
speakAmt = function(str, options, zeroCent) {
var andWord, centUnit, cents, dec, dollarUnit, dollars, int, intArr, output, strSplited;
if (options == null) {
options = 'amount';
}
if (str === '0') {
output = 'Null';
} else {
dollarUnit = ['Dollar', 'Dollars'];
centUnit = ['Cent', 'Cents'];
str = tools.adjustDecimal(str, 'ceil', 2);
strSplited = tools.splitNum(str);
intArr = tools.splitInt(strSplited[0]);
int = speakInt(intArr, false);
dollars = int === '' ? '' : int === 'One' ? ' ' + dollarUnit[0] : ' ' + dollarUnit[1];
dec = +((strSplited[1] + '0').slice(0, 2));
dec = n99[dec];
cents = dec === '' && int === '' ? 'Null' : dec === '' ? 'No Cent' : dec === 'One' ? ' Cent' : ' Cents';
andWord = dollars === '' ? '' : ' and ';
output = int + dollars + andWord + dec + cents;
}
output = options === 'cheque' ? output === 'Null' ? 'Null' : output + ' Only' : options === 'amount' ? output.replace(' and No Cent', '').toLowerCase() : (console.log('Error: Option in speakAmt() is invalid.'), output = null);
if (zeroCent === true) {
output = output.replace('and No Cent', 'and Zero Cent');
}
return output;
};
main = function(input, options, zeroCent) {
if (options == null) {
options = 'default';
}
if (zeroCent == null) {
zeroCent = false;
}
if (input === '') {
return null;
} else if (input === '1e+100') {
return 'Ding! One Google... Oops... One Googol!!';
} else {
if (tools.check(input) === false) {
console.log('Error: Invalid input value. Return null');
return null;
} else {
input = tools.normalize(input);
switch (options) {
case 'default':
case 'number':
case 'num':
return speakNum(input);
case 'cheque':
case 'check':
case 'chk':
case 'chq':
return speakAmt(input, 'cheque', zeroCent);
case 'amount':
case 'amt':
return speakAmt(input, 'amount');
default:
console.log('Error: Option in enUS is not valid');
return null;
}
}
}
};
return main(input, options, zeroCent);
};
Numbo.prototype.enUS = convert_enUS;
Numbo.prototype.convert = function() {
var error, i, input, item, len1, options, otherOptions, plugin, template, zeroCent;
input = arguments[0], options = 2 <= arguments.length ? slice.call(arguments, 1) : [];
if (options.length === 0) {
return convert_enUS(input);
} else {
plugin = '';
template = '';
zeroCent = false;
otherOptions = [];
error = false;
for (i = 0, len1 = options.length; i < len1; i++) {
item = options[i];
if (typeof item === 'string') {
if (this[item] != null) {
if (plugin === '') {
plugin = item;
} else {
console.log('Error: Invalid option. You have selected more than one language/plugin. Returns null');
error = true;
}
} else if (template === '') {
switch (item) {
case 'default':
template = 'default';
break;
case 'number':
case 'num':
template = 'number';
break;
case 'check':
case 'cheque':
case 'chk':
case 'chq':
template = 'cheque';
break;
case 'amount':
case 'amt':
template = 'amount';
break;
default:
otherOptions.push(item);
}
} else {
otherOptions.push(item);
}
} else {
console.log('Error: Invalid option. Each option has to be a string.');
error = true;
}
}
if (error === true) {
return null;
} else if (otherOptions.length > 0 && otherOptions.toString() !== 'zeroCent') {
console.log('Error: Invalid option. Possibly more than one template is selected. Or, some option(s) are parsed into [otherOptions], but it is not allowed in current version yet.');
return null;
} else {
if (template === '') {
template = 'default';
}
if (plugin === '') {
plugin = 'enUS';
}
if (otherOptions.toString() === 'zeroCent') {
zeroCent = true;
}
return this[plugin](input, template, zeroCent);
}
}
};
return Numbo;
})();
numbo = new Numbo();
if ((typeof module !== "undefined" && module !== null) && module.exports) {
module.exports = numbo;
}
if (typeof window !== "undefined" && window !== null) {
window.numbo = numbo;
}
}).call(this);