/* compromise-numbers 1.1.0 MIT */ (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.compromiseNumbers = factory()); }(this, (function () { 'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } var tens = 'twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety|fourty'; var teens = 'eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen'; // this is a bit of a mess var findNumbers = function findNumbers(doc, n) { var match = doc.match('#Value+'); //"50 83" if (match.has('#NumericValue #NumericValue')) { //a comma may mean two numbers if (match.has('#Value @hasComma #Value')) { match.splitAfter('@hasComma'); } else if (match.has('#NumericValue #Fraction')) { match.splitAfter('#NumericValue #Fraction'); } else { match = match.splitAfter('#NumericValue'); } } //three-length if (match.has('#Value #Value #Value') && !match.has('#Multiple')) { //twenty-five-twenty if (match.has('(' + tens + ') #Cardinal #Cardinal')) { match = match.splitAfter('(' + tens + ') #Cardinal'); } } //two-length ones if (match.has('#Value #Value')) { //june 21st 1992 is two seperate values if (match.has('#NumericValue #NumericValue')) { match = match.splitOn('#Year'); } //sixty fifteen if (match.has('(' + tens + ') (' + teens + ')')) { match = match.splitAfter('(' + tens + ')'); } //"72 82" var _double = match.match('#Cardinal #Cardinal'); if (_double.found && !match.has('(point|decimal)')) { //not 'two hundred' if (!_double.has('#Cardinal (#Multiple|point|decimal)')) { //one proper way, 'twenty one', or 'hundred one' if (!_double.has('(' + tens + ') #Cardinal') && !_double.has('#Multiple #Value')) { // double = double.firstTerm() _double.terms().forEach(function (d) { match = match.splitOn(d); }); } } } //seventh fifth if (match.match('#Ordinal #Ordinal').match('#TextValue').found && !match.has('#Multiple')) { //the one proper way, 'twenty first' if (!match.has('(' + tens + ') #Ordinal')) { match = match.splitAfter('#Ordinal'); } } //fifth five if (match.has('#Ordinal #Cardinal')) { match = match.splitBefore('#Cardinal+'); } //five 2017 (support '5 hundred', and 'twenty 5' if (match.has('#TextValue #NumericValue') && !match.has('(' + tens + '|#Multiple)')) { match = match.splitBefore('#NumericValue+'); } } //5-8 if (match.has('#NumberRange')) { match = match.splitAfter('#NumberRange'); } //grab (n)th result if (typeof n === 'number') { match = match.get(n); } return match; }; var find = findNumbers; //support global multipliers, like 'half-million' by doing 'million' then multiplying by 0.5 var findModifiers = function findModifiers(str) { var mults = [{ reg: /^(minus|negative)[\s\-]/i, mult: -1 }, { reg: /^(a\s)?half[\s\-](of\s)?/i, mult: 0.5 } // { // reg: /^(a\s)?quarter[\s\-]/i, // mult: 0.25 // } ]; for (var i = 0; i < mults.length; i++) { if (mults[i].reg.test(str) === true) { return { amount: mults[i].mult, str: str.replace(mults[i].reg, '') }; } } return { amount: 1, str: str }; }; var findModifiers_1 = findModifiers; var data = { ones: { zeroth: 0, first: 1, second: 2, third: 3, fourth: 4, fifth: 5, sixth: 6, seventh: 7, eighth: 8, ninth: 9, zero: 0, one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9 }, teens: { tenth: 10, eleventh: 11, twelfth: 12, thirteenth: 13, fourteenth: 14, fifteenth: 15, sixteenth: 16, seventeenth: 17, eighteenth: 18, nineteenth: 19, ten: 10, eleven: 11, twelve: 12, thirteen: 13, fourteen: 14, fifteen: 15, sixteen: 16, seventeen: 17, eighteen: 18, nineteen: 19 }, tens: { twentieth: 20, thirtieth: 30, fortieth: 40, fourtieth: 40, fiftieth: 50, sixtieth: 60, seventieth: 70, eightieth: 80, ninetieth: 90, twenty: 20, thirty: 30, forty: 40, fourty: 40, fifty: 50, sixty: 60, seventy: 70, eighty: 80, ninety: 90 }, multiples: { hundredth: 100, thousandth: 1000, millionth: 1e6, billionth: 1e9, trillionth: 1e12, quadrillionth: 1e15, quintillionth: 1e18, sextillionth: 1e21, septillionth: 1e24, hundred: 100, thousand: 1000, million: 1e6, billion: 1e9, trillion: 1e12, quadrillion: 1e15, quintillion: 1e18, sextillion: 1e21, septillion: 1e24, grand: 1000 } }; var isValid = function isValid(w, has) { if (data.ones.hasOwnProperty(w)) { if (has.ones || has.teens) { return false; } } else if (data.teens.hasOwnProperty(w)) { if (has.ones || has.teens || has.tens) { return false; } } else if (data.tens.hasOwnProperty(w)) { if (has.ones || has.teens || has.tens) { return false; } } return true; }; var validate = isValid; var parseDecimals = function parseDecimals(arr) { var str = '0.'; for (var i = 0; i < arr.length; i++) { var w = arr[i]; if (data.ones.hasOwnProperty(w) === true) { str += data.ones[w]; } else if (data.teens.hasOwnProperty(w) === true) { str += data.teens[w]; } else if (data.tens.hasOwnProperty(w) === true) { str += data.tens[w]; } else if (/^[0-9]$/.test(w) === true) { str += w; } else { return 0; } } return parseFloat(str); }; var parseDecimals_1 = parseDecimals; //parse a string like "4,200.1" into Number 4200.1 var parseNumeric = function parseNumeric(str) { //remove ordinal - 'th/rd' str = str.replace(/1st$/, '1'); str = str.replace(/2nd$/, '2'); str = str.replace(/3rd$/, '3'); str = str.replace(/([4567890])r?th$/, '$1'); //remove prefixes str = str.replace(/^[$€¥£¢]/, ''); //remove suffixes str = str.replace(/[%$€¥£¢]$/, ''); //remove commas str = str.replace(/,/g, ''); //split '5kg' from '5' str = str.replace(/([0-9])([a-z\u00C0-\u00FF]{1,2})$/, '$1'); return str; }; var parseNumeric_1 = parseNumeric; var improperFraction = /^([0-9,\. ]+)\/([0-9,\. ]+)$/; //some numbers we know var casualForms = { // 'a few': 3, 'a couple': 2, 'a dozen': 12, 'two dozen': 24, zero: 0 }; // a 'section' is something like 'fifty-nine thousand' // turn a section into something we can add to - like 59000 var section_sum = function section_sum(obj) { return Object.keys(obj).reduce(function (sum, k) { sum += obj[k]; return sum; }, 0); }; //turn a string into a number var parse = function parse(str) { //convert some known-numbers if (casualForms.hasOwnProperty(str) === true) { return casualForms[str]; } //'a/an' is 1 if (str === 'a' || str === 'an') { return 1; } var modifier = findModifiers_1(str); str = modifier.str; var last_mult = null; var has = {}; var sum = 0; var isNegative = false; var terms = str.split(/[ -]/); for (var i = 0; i < terms.length; i++) { var w = terms[i]; w = parseNumeric_1(w); if (!w || w === 'and') { continue; } if (w === '-' || w === 'negative') { isNegative = true; continue; } if (w.charAt(0) === '-') { isNegative = true; w = w.substr(1); } //decimal mode if (w === 'point') { sum += section_sum(has); sum += parseDecimals_1(terms.slice(i + 1, terms.length)); sum *= modifier.amount; return sum; } //improper fraction var fm = w.match(improperFraction); if (fm) { var num = parseFloat(fm[1].replace(/[, ]/g, '')); var denom = parseFloat(fm[2].replace(/[, ]/g, '')); if (denom) { sum += num / denom || 0; } continue; } //prevent mismatched units, like 'seven eleven' if (validate(w, has) === false) { return null; } //buildOut section, collect 'has' values if (/^[0-9\.]+$/.test(w)) { has['ones'] = parseFloat(w); //not technically right } else if (data.ones.hasOwnProperty(w) === true) { has['ones'] = data.ones[w]; } else if (data.teens.hasOwnProperty(w) === true) { has['teens'] = data.teens[w]; } else if (data.tens.hasOwnProperty(w) === true) { has['tens'] = data.tens[w]; } else if (data.multiples.hasOwnProperty(w) === true) { var mult = data.multiples[w]; //something has gone wrong : 'two hundred five hundred' if (mult === last_mult) { return null; } //support 'hundred thousand' //this one is tricky.. if (mult === 100 && terms[i + 1] !== undefined) { // has['hundreds']= var w2 = terms[i + 1]; if (data.multiples[w2]) { mult *= data.multiples[w2]; //hundredThousand/hundredMillion i += 1; } } //natural order of things //five thousand, one hundred.. if (last_mult === null || mult < last_mult) { sum += (section_sum(has) || 1) * mult; last_mult = mult; has = {}; } else { //maybe hundred .. thousand sum += section_sum(has); last_mult = mult; sum = (sum || 1) * mult; has = {}; } } } //dump the remaining has values sum += section_sum(has); //post-process add modifier sum *= modifier.amount; sum *= isNegative ? -1 : 1; //dont return 0, if it went straight-through if (sum === 0 && Object.keys(has).length === 0) { return null; } return sum; }; var toNumber = parse; var parseNumeric$1 = function parseNumeric(str, p) { str = str.replace(/,/g, ''); //parse a numeric-number (easy) var arr = str.split(/^([^0-9]*)([0-9.,]*)([^0-9]*)$/); if (arr && arr[2] && p.terms().length < 2) { var num = parseFloat(arr[2] || str); //ensure that num is an actual number if (typeof num !== 'number') { num = null; } // strip an ordinal off the suffix var suffix = arr[3] || ''; if (suffix === 'st' || suffix === 'nd' || suffix === 'rd' || suffix === 'th') { suffix = ''; } // support M for million, k for thousand if (suffix === 'm' || suffix === 'M') { num *= 1000000; suffix = ''; } if (suffix === 'k' || suffix === 'k') { num *= 1000; suffix = ''; } return { prefix: arr[1] || '', num: num, suffix: suffix }; } return null; }; // get a numeric value from this phrase var parseNumber = function parseNumber(p) { var str = p.text('reduced'); // is it in '3,123' format? var hasComma = /[0-9],[0-9]/.test(p.text('text')); // parse a numeric-number like '$4.00' var res = parseNumeric$1(str, p); if (res !== null) { res.hasComma = hasComma; return res; } //parse a text-numer (harder) var num = toNumber(str); return { hasComma: hasComma, prefix: '', num: num, suffix: '' }; }; var parse$1 = parseNumber; // handle 'one bottle', 'two bottles' var agreeUnits = function agreeUnits(agree, val, obj) { if (agree === false) { return; } var unit = val.lookAhead('^(#Unit|#Noun)'); // don't do these if (unit.has('(#Address|#Money|#Percent)') || val.has('#Ordinal')) { return; } if (obj.num === 1) { unit.nouns().toSingular(); } else if (unit.has('#Singular')) { unit.nouns().toPlural(); } }; var _agreeUnits = agreeUnits; /** * turn big numbers, like 2.3e+22, into a string with a ton of trailing 0's * */ var numToString = function numToString(n) { if (n < 1000000) { return String(n); } var str; if (typeof n === 'number') { str = n.toFixed(0); } else { str = n; } if (str.indexOf('e+') === -1) { return str; } return str.replace('.', '').split('e+').reduce(function (p, b) { return p + Array(b - p.length + 2).join(0); }); }; var _toString = numToString; // console.log(numToString(2.5e+22)); /** * turns an integer/float into.ber, like 'fifty-five' */ var tens_mapping = [['ninety', 90], ['eighty', 80], ['seventy', 70], ['sixty', 60], ['fifty', 50], ['forty', 40], ['thirty', 30], ['twenty', 20]]; var ones_mapping = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen']; var sequence = [[1e24, 'septillion'], [1e20, 'hundred sextillion'], [1e21, 'sextillion'], [1e20, 'hundred quintillion'], [1e18, 'quintillion'], [1e17, 'hundred quadrillion'], [1e15, 'quadrillion'], [1e14, 'hundred trillion'], [1e12, 'trillion'], [1e11, 'hundred billion'], [1e9, 'billion'], [1e8, 'hundred million'], [1e6, 'million'], [100000, 'hundred thousand'], [1000, 'thousand'], [100, 'hundred'], [1, 'one']]; //turn number into an array of magnitudes, like [[5, million], [2, hundred]] var breakdown_magnitudes = function breakdown_magnitudes(num) { var working = num; var have = []; sequence.forEach(function (a) { if (num >= a[0]) { var howmany = Math.floor(working / a[0]); working -= howmany * a[0]; if (howmany) { have.push({ unit: a[1], count: howmany }); } } }); return have; }; //turn numbers from 100-0 into their text var breakdown_hundred = function breakdown_hundred(num) { var arr = []; if (num > 100) { return arr; //something bad happened.. } for (var i = 0; i < tens_mapping.length; i++) { if (num >= tens_mapping[i][1]) { num -= tens_mapping[i][1]; arr.push(tens_mapping[i][0]); } } //(hopefully) we should only have 20-0 now if (ones_mapping[num]) { arr.push(ones_mapping[num]); } return arr; }; /** print-out 'point eight nine'*/ var handle_decimal = function handle_decimal(num) { var names = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']; var arr = []; //parse it out like a string, because js math is such shit var str = _toString(num); var decimal = str.match(/\.([0-9]+)/); if (!decimal || !decimal[0]) { return arr; } arr.push('point'); var decimals = decimal[0].split(''); for (var i = 0; i < decimals.length; i++) { arr.push(names[decimals[i]]); } return arr; }; /** turns an integer into a textual number */ var to_text = function to_text(num) { // handle zero, quickly if (num === 0 || num === '0') { return 'zero'; // no? } //big numbers, north of sextillion, aren't gonna work well.. //keep them small.. if (num > 1e21) { num = _toString(num); } var arr = []; //handle negative numbers if (num < 0) { arr.push('minus'); num = Math.abs(num); } //break-down into units, counts var units = breakdown_magnitudes(num); //build-up the string from its components for (var i = 0; i < units.length; i++) { var unit_name = units[i].unit; if (unit_name === 'one') { unit_name = ''; //put an 'and' in here if (arr.length > 1) { arr.push('and'); } } arr = arr.concat(breakdown_hundred(units[i].count)); arr.push(unit_name); } //also support decimals - 'point eight' arr = arr.concat(handle_decimal(num)); //remove empties arr = arr.filter(function (s) { return s; }); if (arr.length === 0) { arr[0] = ''; } return arr.join(' '); }; var toText = to_text; // console.log(to_text(-1000.8)); /** * turn a number like 5 into an ordinal like 5th */ var numOrdinal = function numOrdinal(num) { if (!num && num !== 0) { return null; } //the teens are all 'th' var tens = num % 100; if (tens > 10 && tens < 20) { return String(num) + 'th'; } //the rest of 'em var mapping = { 0: 'th', 1: 'st', 2: 'nd', 3: 'rd' }; var str = _toString(num); var last = str.slice(str.length - 1, str.length); if (mapping[last]) { str += mapping[last]; } else { str += 'th'; } return str; }; var numOrdinal_1 = numOrdinal; var irregulars = { one: 'first', two: 'second', three: 'third', five: 'fifth', eight: 'eighth', nine: 'ninth', twelve: 'twelfth', twenty: 'twentieth', thirty: 'thirtieth', forty: 'fortieth', fourty: 'fourtieth', fifty: 'fiftieth', sixty: 'sixtieth', seventy: 'seventieth', eighty: 'eightieth', ninety: 'ninetieth' }; /** * convert a javascript number to 'twentieth' format * */ var textOrdinal = function textOrdinal(num) { var words = toText(num).split(' '); //convert the last number to an ordinal var last = words[words.length - 1]; if (irregulars.hasOwnProperty(last)) { words[words.length - 1] = irregulars[last]; } else { words[words.length - 1] = last.replace(/y$/, 'i') + 'th'; } return words.join(' '); }; var textOrdinal_1 = textOrdinal; var prefixes = { '¢': 'cents', $: 'dollars', '£': 'pounds', '¥': 'yen', '€': 'euros', '₡': 'colón', '฿': 'baht', '₭': 'kip', '₩': 'won', '₹': 'rupees', '₽': 'ruble', '₺': 'liras' }; var suffixes = { '%': 'percent', s: 'seconds', cm: 'centimetres', km: 'kilometres' }; var _symbols = { prefixes: prefixes, suffixes: suffixes }; var prefixes$1 = _symbols.prefixes; var suffixes$1 = _symbols.suffixes; var isCurrency = { usd: true, eur: true, jpy: true, gbp: true, cad: true, aud: true, chf: true, cny: true, hkd: true, nzd: true, kr: true, rub: true }; // convert $ to 'dollars', etc var prefixToText = function prefixToText(obj) { // turn 5% to 'five percent' if (prefixes$1.hasOwnProperty(obj.prefix)) { obj.suffix += prefixes$1[obj.prefix]; obj.prefix = ''; } //turn 5km to 'five kilometres' if (suffixes$1.hasOwnProperty(obj.suffix)) { obj.suffix = suffixes$1[obj.suffix]; } //uppercase lost case for 'USD', etc if (isCurrency.hasOwnProperty(obj.suffix)) { obj.suffix = obj.suffix.toUpperCase(); } // add a space, if it exists if (obj.suffix) { obj.suffix = ' ' + obj.suffix; } return obj; }; //business-logic for converting a cardinal-number to other forms var makeNumber = function makeNumber(obj, isText, isOrdinal) { var num = String(obj.num); if (isText) { obj = prefixToText(obj); if (isOrdinal) { //ordinal-text num = textOrdinal_1(num); return "".concat(obj.prefix || '').concat(num).concat(obj.suffix || ''); } //cardinal-text num = toText(num); return "".concat(obj.prefix || '').concat(num).concat(obj.suffix || ''); } //ordinal-number if (isOrdinal) { num = numOrdinal_1(num); // support '5th percent' obj = prefixToText(obj); return "".concat(obj.prefix || '').concat(num).concat(obj.suffix || ''); } // support comma format if (obj.hasComma === true) { num = obj.num.toLocaleString(); } // cardinal-number num = _toString(num); // support very large numbers return "".concat(obj.prefix || '').concat(num).concat(obj.suffix || ''); }; var makeNumber_1 = makeNumber; var methods = { /** overloaded json method with additional number information */ json: function json(options) { var n = null; if (typeof options === 'number') { n = options; options = null; } options = options || { text: true, normal: true, trim: true, terms: true }; var res = []; this.forEach(function (doc) { var json = doc.json(options)[0]; var obj = parse$1(doc); json.prefix = obj.prefix; json.number = obj.num; json.suffix = obj.suffix; json.cardinal = makeNumber_1(obj, false, false); json.ordinal = makeNumber_1(obj, false, true); json.textCardinal = makeNumber_1(obj, true, false); json.textOrdinal = makeNumber_1(obj, true, true); res.push(json); }); if (n !== null) { return res[n]; } return res; }, /** two of what? */ units: function units() { var m = this.lookAhead('(#Unit|#Noun)+'); m = m.splitAfter('@hasComma').first(); m = m.not('#Pronoun'); return m.first(); }, /** return only ordinal numbers */ isOrdinal: function isOrdinal() { return this["if"]('#Ordinal'); }, /** return only cardinal numbers*/ isCardinal: function isCardinal() { return this["if"]('#Cardinal'); }, /** convert to numeric form like '8' or '8th' */ toNumber: function toNumber() { this.forEach(function (val) { var obj = parse$1(val); if (obj.num === null) { return; } var str = makeNumber_1(obj, false, val.has('#Ordinal')); val.replaceWith(str, true); val.tag('NumericValue'); }); return this; }, /** add commas, or nicer formatting for numbers */ toLocaleString: function toLocaleString() { this.forEach(function (val) { var obj = parse$1(val); if (obj.num === null) { return; } obj.num = obj.num.toLocaleString(); var str = makeNumber_1(obj, false, val.has('#Ordinal')); val.replaceWith(str, true); }); return this; }, /** convert to text form - like 'eight' or 'eigth'*/ toText: function toText() { this.forEach(function (val) { var obj = parse$1(val); if (obj.num === null) { return; } var str = makeNumber_1(obj, true, val.has('#Ordinal')); val.replaceWith(str, true); val.tag('TextValue'); }); return this; }, /** convert to cardinal form, like 'eight', or '8' */ toCardinal: function toCardinal(agree) { var m = this["if"]('#Ordinal'); m.forEach(function (val) { var obj = parse$1(val); if (obj.num === null) { return; } var str = makeNumber_1(obj, val.has('#TextValue'), false); // a hack for number-ranges if (val.has('#NumberRange')) { var t = val.termList()[0]; if (t.text && t.post === '') { t.post = ' '; } } // change the number text val.replaceWith(str, true); val.tag('Cardinal'); // turn unit into plural -> 'seven beers' _agreeUnits(agree, val, obj); }); return this; }, /** convert to ordinal form, like 'eighth', or '8th' */ toOrdinal: function toOrdinal() { var _this = this; var m = this["if"]('#Cardinal'); m.forEach(function (val) { var obj = parse$1(val); if (obj.num === null) { return; } var str = makeNumber_1(obj, val.has('#TextValue'), true); // a hack for number-ranges if (val.has('#NumberRange')) { var t = val.termList()[0]; if (t.text && t.post === '') { t.post = ' '; } } // change the number text val.replaceWith(str, true); val.tag('Ordinal'); // turn unit into singular -> 'seventh beer' var unit = _this.lookAhead('^#Plural'); if (unit.found) { unit.nouns().toSingular(); } }); return this; }, /** return only numbers that are == n */ isEqual: function isEqual(n) { return this.filter(function (val) { var num = parse$1(val).num; return num === n; }); }, /** return only numbers that are > n*/ greaterThan: function greaterThan(n) { return this.filter(function (val) { var num = parse$1(val).num; return num > n; }); }, /** return only numbers that are < n*/ lessThan: function lessThan(n) { return this.filter(function (val) { var num = parse$1(val).num; return num < n; }); }, /** return only numbers > min and < max */ between: function between(min, max) { return this.filter(function (val) { var num = parse$1(val).num; return num > min && num < max; }); }, /** set these number to n */ set: function set(n, agree) { if (n === undefined) { return this; // don't bother } if (typeof n === 'string') { n = toNumber(n); } this.forEach(function (val) { var obj = parse$1(val); obj.num = n; if (obj.num === null) { return; } var str = makeNumber_1(obj, val.has('#TextValue'), val.has('#Ordinal')); val = val.not('#Currency'); val.replaceWith(str, true); // handle plural/singular unit _agreeUnits(agree, val, obj); }); return this; }, add: function add(n, agree) { if (!n) { return this; // don't bother } if (typeof n === 'string') { n = toNumber(n); } this.forEach(function (val) { var obj = parse$1(val); if (obj.num === null) { return; } obj.num += n; var str = makeNumber_1(obj, val.has('#TextValue'), val.has('#Ordinal')); val = val.not('#Currency'); val.replaceWith(str, true); // handle plural/singular unit _agreeUnits(agree, val, obj); }); return this; }, /** decrease each number by n*/ subtract: function subtract(n, agree) { return this.add(n * -1, agree); }, /** increase each number by 1 */ increment: function increment(agree) { this.add(1, agree); return this; }, /** decrease each number by 1 */ decrement: function decrement(agree) { this.add(-1, agree); return this; }, /** return things like CCXX*/ romanNumerals: function romanNumerals(n) { var m = this.match('#RomanNumeral').numbers(); if (typeof n === 'number') { m = m.get(n); } return m; }, /** split-apart suffix and number */ normalize: function normalize() { var keep = { '%': true }; this.forEach(function (val) { var obj = parse$1(val); if (obj.num !== null && obj.suffix && keep[obj.suffix] !== true) { var prefix = obj.prefix || ''; val = val.replaceWith(prefix + obj.num + ' ' + obj.suffix); return; } }); return this; }, /** retrieve the parsed number */ get: function get(n) { var arr = []; this.forEach(function (doc) { arr.push(parse$1(doc).num); }); if (n !== undefined) { return arr[n]; } return arr; } }; // aliases methods.toNice = methods.toLocaleString; methods.isBetween = methods.between; methods.minus = methods.subtract; methods.plus = methods.add; methods.equals = methods.isEqual; var methods_1 = methods; //from wikipedia's {{infobox currency}}, Dec 2020 var currencies = [{ dem: 'american', name: 'dollar', iso: 'usd', sub: 'cent', sym: ['$', 'US$', 'U$'] }, { name: 'euro', iso: 'eur', sub: 'cent', sym: ['€'] }, { dem: 'british', name: 'pound', iso: 'gbp', sub: 'penny', alias: { sterling: true }, sym: ['£'] }, { name: 'renminbi', iso: 'cny', sub: 'yuán', plural: 'yuán', alias: { yuan: true }, sym: ['元'] //'¥' }, { dem: 'japanese', name: 'yen', iso: 'jpy', sub: 'sen', sym: ['¥', '円', '圓'] }, // kr { dem: 'swedish', name: 'krona', iso: 'sek', sub: 'öre', alias: { ore: true, kronor: true }, sym: ['kr'] }, { dem: 'estonian', name: 'kroon', iso: 'eek', sub: 'sent', sym: ['kr'] }, { dem: 'norwegian', name: 'krone', iso: 'nok', sub: 'øre', sym: ['kr'] }, { dem: 'icelandic', name: 'króna', iso: 'isk', sym: ['kr'] }, { dem: 'danish', name: 'krone', iso: 'dkk', sub: 'øre', sym: ['kr.'] }, // { // dem: 'scandinavian', // name: 'Monetary Union', // sub: 'øre', // sym: ['kr.'], // }, // 'k' { dem: 'zambian', name: 'kwacha', iso: 'zmw', sub: 'ngwee', sym: ['K'] }, { dem: 'malawian', name: 'kwacha', iso: 'mwk', sub: 'tambala', sym: ['K'] }, // misc { dem: 'greek', name: 'drachma', iso: 'grd', sub: 'leptοn', sym: ['Δρχ.', 'Δρ.', '₯'] }, { dem: 'eastern caribbean', name: 'dollar', iso: 'xcd', sub: 'cent', sym: ['$'] }, { dem: 'finnish', name: 'markka', iso: 'fim', sub: 'penni', sym: ['mk'] }, { dem: 'polish', name: 'złoty', iso: 'pln', sub: 'grosz', sym: ['zł'] }, { dem: 'slovenian', name: 'tolar', iso: 'sit', sub: 'stotin', sym: [] }, { dem: 'australian', name: 'dollar', iso: 'aud', sub: 'cent', sym: ['$', 'A$', 'AU$'] }, { dem: 'deutsche', name: 'mark', iso: 'dem', sub: 'pfennig', sym: ['DM'] }, { dem: 'thai', name: 'baht', iso: 'thb', sub: 'satang', sym: ['฿'] }, { dem: 'canadian', name: 'dollar', iso: 'cad', sub: 'cent', sym: ['$', 'Can$', 'C$', 'CA$', 'CAD'] }, { dem: 'mexican', name: 'peso', iso: 'mxn', sub: 'centavo', sym: ['$', 'Mex$'] }, { dem: 'spanish', name: 'peseta', iso: 'esp', sub: 'céntimo', sym: ['Pta'] }, { dem: 'new zealand', name: 'dollar', iso: 'nzd', sub: 'cent', sym: ['$', 'NZ$'] }, { dem: 'chilean', name: 'peso', iso: 'clp', sub: 'Centavo', sym: ['Cifrão', '$'] }, { dem: 'nigerian', name: 'naira', iso: 'ngn', sub: 'kobo', sym: ['₦'] }, { dem: 'austrian', name: 'schilling', iso: 'ats', sub: 'groschen', sym: ['S', 'öS'] }, { dem: 'guatemalan', name: 'quetzal', iso: 'gtq', sub: 'centavo', sym: ['Q'] }, { dem: 'philippine', name: 'peso', iso: 'php', sub: 'sentimo', sym: ['₱'] }, { dem: 'hungarian', name: 'forint', iso: 'huf', sub: 'fillér', sym: ['Ft'] }, { dem: 'russian', name: 'ruble', iso: 'rub', sub: 'kopeyka', sym: ['₽', 'руб', 'р.'] }, { dem: 'kuwaiti', name: 'dinar', iso: 'kwd', sub: 'fils', sym: ['د.ك', 'KD'] }, { dem: 'israeli', name: 'new shekel', iso: 'ils', sub: 'agora', sym: ['₪'] }, { dem: 'latvian', name: 'lats', iso: 'lvl', sub: 'santīms', sym: ['Ls'] }, { dem: 'kazakhstani', name: 'tenge', iso: 'kzt', sub: 'tıyn', sym: ['₸'] }, { dem: 'iraqi', name: 'dinar', iso: 'iqd', sub: 'fils', sym: ['د.ع'] }, { dem: 'bahamian', name: 'dollar', iso: 'bsd', sub: 'cent', sym: ['$', 'B$'] }, { dem: 'seychellois', name: 'rupee', iso: 'scr', sub: 'cent', sym: ['SCR', 'SR'] }, { dem: 'albanian', name: 'lek', iso: 'all', sub: 'qindarkë', sym: ['L'] }, { dem: 'bulgarian', name: 'lev', iso: 'bgn', sub: 'stotinka', sym: ['лв.'] }, { dem: 'irish', name: 'pound', iso: 'iep', sym: ['£', 'IR£'] }, { name: 'cfp franc', iso: 'xpf', sym: ['f'] }, { dem: 'south african', name: 'rand', iso: 'zar', sub: 'cent', sym: ['R'] }, { dem: 'south korean', name: 'won', iso: 'krw', sub: 'jeon', plural: 'won', sym: ['₩'] }, { dem: 'north korean', name: 'won', iso: 'kpw', sub: 'chon', plural: 'won', sym: ['₩'] }, { dem: 'portuguese', name: 'escudo', iso: 'pte', sub: 'centavo', sym: [] }, { dem: 'ghanaian', name: 'cedi', iso: 'ghs', sub: 'pesewa', sym: ['GH₵'] }, { dem: 'hong kong', name: 'dollar', iso: 'hkd', sub: '毫', sym: ['$'] }, { dem: 'new taiwan', name: 'dollar', iso: 'twd', sub: 'dime', sym: ['NT$'] }, { dem: 'east german', name: 'mark', iso: 'ddm', sub: 'pfennig', sym: ['M'] }, { dem: 'namibian', name: 'dollar', iso: 'nad', sub: 'cent', sym: ['$'] }, { dem: 'malaysian', name: 'ringgit', iso: 'myr', sub: 'sen', sym: ['RM'] }, { dem: 'swiss', name: 'franc', iso: 'chf', sym: ['Rp.'] }, { dem: 'panamanian', name: 'balboa', iso: 'pab', sub: 'centésimo', sym: ['B/.'] }, { dem: 'indonesian', name: 'rupiah', iso: 'idr', sub: 'sen', sym: ['Rp'] }, { dem: 'brunei', name: 'dollar', iso: 'bnd', sub: 'sen', sym: ['$', 'B$'] }, { dem: 'venezuelan', name: 'bolívar', iso: 'vef', sub: 'céntimo', sym: ['Bs.F', 'Bs.'] }, { dem: 'macedonian', name: 'denar', iso: 'mkd', sub: 'deni', sym: ['den'] }, { dem: 'mauritanian', name: 'ouguiya', iso: 'mru', sub: 'khoums', sym: ['UM'] }, { dem: 'argentine', name: 'peso', iso: 'ars', sub: 'centavo', sym: ['$'] }, { dem: 'libyan', name: 'dinar', iso: 'lyd', sub: 'dirham', sym: ['LD', 'ل.د'] }, { dem: 'jordanian', name: 'dinar', iso: 'jod', sub: 'dirham', sym: ['د.أ'] }, { dem: 'french', name: 'franc', iso: 'frf', sub: 'centime', sym: ['F', 'Fr', 'FF', '₣'] }, { dem: 'syrian', name: 'pound', iso: 'syp', sub: 'piastre', sym: ['LS', '£S'] }, { dem: 'belize', name: 'dollar', iso: 'bzd', sub: 'cent', sym: ['$'] }, { dem: 'saudi', name: 'riyal', iso: 'sar', sub: 'halalah', sym: ['SAR', 'ر.س', ' ﷼'] }, { dem: 'surinamese', name: 'dollar', iso: 'srd', sub: 'cent', sym: ['$'] }, { dem: 'singapore', name: 'dollar', iso: 'sgd', sub: 'cent', sym: ['S$', '$'] }, { dem: 'nepalese', name: 'rupee', iso: 'npr', sub: 'Paisa', sym: ['रु ₨', 'Re'] }, { dem: 'macanese', name: 'pataca', iso: 'mop', sub: 'ho', sym: ['MOP$'] }, { dem: 'nicaraguan', name: 'córdoba', iso: 'nio', sub: 'centavo', sym: ['C$'] }, { dem: 'bangladeshi', name: 'taka', iso: 'bdt', sub: 'poysha', sym: ['৳'] }, { dem: 'indian', name: 'rupee', iso: 'inr', sub: 'paisa', sym: ['₹'] }, { dem: 'maldivian', name: 'rufiyaa', iso: 'mvr', sub: 'laari', sym: ['Rf', 'MRf', 'MVR', '.ރ '] }, { dem: 'sri lankan', name: 'rupee', iso: 'lkr', sub: 'Cents', sym: ['Rs', 'රු', 'ரூ'] }, { dem: 'bhutanese', name: 'ngultrum', iso: 'btn', sub: 'chhertum', sym: ['Nu.'] }, { dem: 'turkish', name: 'lira', iso: 'try', sub: 'new kuruş', sym: ['YTL'] }, { dem: 'serbian', name: 'dinar', iso: 'rsd', sub: 'para', sym: ['din', 'дин'] }, { dem: 'bosnia and herzegovina', name: 'convertible mark', iso: 'bam', sub: 'Fening/Pfenig', sym: ['KM'] }, { dem: 'botswana', name: 'pula', iso: 'bwp', sub: 'thebe', sym: ['p'] }, { dem: 'swazi', name: 'lilangeni', iso: 'szl', sub: 'cent', sym: ['L', 'E'] }, { dem: 'lithuanian', name: 'litas', iso: 'ltl', sub: 'centas', sym: ['Lt', 'ct'] }, { dem: 'mauritian', name: 'rupee', iso: 'mur', sub: 'cent', sym: ['₨'] }, { dem: 'pakistani', name: 'rupee', iso: 'pkr', sub: 'Paisa', sym: ['₨'] }, { dem: 'maltese', name: 'lira', iso: 'mtl', sub: 'cent', sym: ['₤', 'Lm'] }, { dem: 'cypriot', name: 'pound', iso: 'cyp', sub: 'cent', sym: ['£'] }, { dem: 'moldovan', name: 'leu', iso: 'mdl', sub: 'ban', sym: ['l'] }, { dem: 'croatian', name: 'kuna', iso: 'hrk', sub: 'lipa', sym: ['kn'] }, { dem: 'afghan', name: 'afghani', iso: 'afn', sub: 'pul', sym: ['؋', 'Af', 'Afs'] }, { dem: 'ecuadorian', name: 'sucre', iso: 'ecs', sub: 'centavo', sym: ['S/.'] }, { dem: 'sierra leonean', name: 'leone', iso: 'sll', sub: 'cent', sym: ['Le'] } // { // // name: 'European Currency Unit', // iso: 'xeu', // sym: ['₠'], // }, // { // // name: 'Special drawing rights', // iso: 'xdr', // sym: ['SDR'], // }, // { // // name: 'Unidad de Valor Constante', // iso: 'ecv', // }, ]; var symbols = {}; currencies.forEach(function (o) { o.sym.forEach(function (str) { symbols[str] = symbols[str] || o.iso; }); symbols[o.iso] = symbols[o.iso] || o.iso; }); // parse 'australian dollars' var getNamedCurrency = function getNamedCurrency(doc) { var m = doc.match('#Currency+'); m.nouns().toSingular(); // 'dollars'➔'dollar' var str = m.text('reduced'); return currencies.find(function (o) { // 'mexcan peso' if (str === "".concat(o.dem, " ").concat(o.name)) { return o; } // 'CAD' if (str === o.iso) { return o; } // 'cent' if (str === o.sub) { return o; } // 'peso' if (str === o.name) { return o; } // any other alt names if (o.alias && o.alias[str] === true) { return o; } return false; }); }; // turn '£' into GBP var getBySymbol = function getBySymbol(obj) { // do suffix first, for '$50CAD' if (obj.suffix && symbols.hasOwnProperty(obj.suffix)) { return currencies.find(function (o) { return o.iso === symbols[obj.suffix]; }); } // parse prefix for '£50' if (obj.prefix && symbols.hasOwnProperty(obj.prefix)) { return currencies.find(function (o) { return o.iso === symbols[obj.prefix]; }); } return null; }; var parseMoney = function parseMoney(doc) { var res = parse$1(doc); var found = getBySymbol(res) || getNamedCurrency(doc) || {}; var sym = ''; if (found && found.sym) { sym = found.sym[0]; } return { num: res.num, iso: found.iso, demonym: found.dem, currency: found.name, plural: found.plural, symbol: sym }; }; var parse$2 = parseMoney; var titleCase = function titleCase() { var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; return str.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); }; var moneyMethods = { /** which currency is this money in? */ currency: function currency(n) { var arr = []; this.forEach(function (doc) { var found = parse$2(doc); if (found) { arr.push(found); } }); if (typeof n === 'number') { return arr[n]; } return arr; }, /** overloaded json method with additional number information */ json: function json(options) { var n = null; if (typeof options === 'number') { n = options; options = null; } options = options || { text: true, normal: true, trim: true, terms: true }; var res = []; this.forEach(function (doc) { var json = doc.json(options)[0]; var obj = parse$2(doc); json.number = obj.num; if (obj.iso) { json.iso = obj.iso.toUpperCase(); json.symbol = obj.symbol; json.currency = titleCase(obj.demonym) + ' ' + titleCase(obj.currency); } // 'thirty pounds' json.textFmt = makeNumber_1(obj, true, false); if (obj.currency) { var str = obj.currency; if (obj.num !== 1) { str = obj.plural || str + 's'; } json.textFmt += ' ' + str; } res.push(json); }); if (n !== null) { return res[n] || {}; } return res; } }; var methods$1 = moneyMethods; var endS = /s$/; var slashForm = function slashForm(m) { var str = m.text('reduced'); var found = str.match(/^([-+]?[0-9]+)\/([-+]?[0-9]+)(st|nd|rd|th)?s?$/); if (found && found[1] && found[0]) { return { numerator: Number(found[1]), denominator: Number(found[2]) }; } return null; }; // parse '4 out of 4' var textForm1 = function textForm1(m) { var found = m.match('[#Value+] out of every? [#Value+]'); if (found.found !== true) { return null; } var _found$groups = found.groups(), num = _found$groups.num, den = _found$groups.den; num = num.numbers().get(0); den = den.numbers().get(0); if (typeof num === 'number' && typeof den === 'number') { return { numerator: num, denominator: den }; } return null; }; // parse 'a third' var textForm2 = function textForm2(m) { var found = m.match('[(#Cardinal|a)+] [#Ordinal+]'); if (found.found !== true) { return null; } var _found$groups2 = found.groups(), num = _found$groups2.num, den = _found$groups2.den; // quick-support for 'a third' if (num.has('a')) { num = 1; } else { num = num.numbers().get(0); } // turn 'thirds' into third var str = den.text('reduced'); if (endS.test(str)) { str = str.replace(endS, ''); den.replaceWith(str); } // support 'one half' as '1/2' if (den.has('half')) { den = 2; } else { den = den.numbers().get(0); } if (typeof num === 'number' && typeof den === 'number') { return { numerator: num, denominator: den }; } return null; }; var parseFraction = function parseFraction(m) { return slashForm(m) || textForm1(m) || textForm2(m) || null; }; var parse$3 = parseFraction; var methods$2 = { /** overloaded json method with additional number information */ json: function json(options) { var n = null; if (typeof options === 'number') { n = options; options = null; } options = options || { text: true, normal: true, trim: true, terms: true }; var res = []; this.forEach(function (m) { var json = m.json(options)[0]; var found = parse$3(m) || {}; json.numerator = found.numerator; json.denominator = found.denominator; res.push(json); }); if (n !== null) { return res[n] || {}; } return res; }, /** change 'four out of 10' to 4/10 */ normalize: function normalize() { var _this = this; this.forEach(function (m) { var found = parse$3(m); if (found && typeof found.numerator === 'number' && typeof found.denominator === 'number') { var str = "".concat(found.numerator, "/").concat(found.denominator); _this.replace(m, str); } }); return this; } }; var methods_1$1 = methods$2; var here = 'number-tag'; var multiples = '(hundred|thousand|million|billion|trillion|quadrillion|quintillion|sextillion|septillion)'; //support 'two thirds' // (do this conservatively) var ordinals = ['half', 'third', 'fourth', 'quarter', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth', 'hundredth', 'thousandth', 'millionth']; // add plural forms var len = ordinals.length; for (var i = 0; i < len; i += 1) { ordinals.push(ordinals[i] + 's'); } ordinals = "(".concat(ordinals.join('|'), ")"); // improved tagging for numbers var tagger = function tagger(doc) { doc.match(multiples).tag('#Multiple', here); // in the 400s doc.match('the [/[0-9]+s$/]').tag('#Plural', here); //half a million doc.match('half a? #Value').tag('Value', 'half-a-value'); //(quarter not ready) //five and a half doc.match('#Value and a (half|quarter)').tag('Value', 'value-and-a-half'); //one hundred and seven dollars doc.match('#Money and #Money #Currency?').tag('Money', 'money-and-money'); // $5.032 is invalid money doc.match('#Money').not('#TextValue').match('/\\.[0-9]{3}$/').unTag('#Money', 'three-decimal money'); // cleanup currency false-positives doc.ifNo('#Value').match('#Currency #Verb').unTag('Currency', 'no-currency'); // 6 dollars and 5 cents doc.match('#Value #Currency [and] #Value (cents|ore|centavos|sens)', 0).tag('Money', here); // maybe currencies var m = doc.match('[#Value] [(mark|rand|won|rub|ore)]'); m.group('num').tag('Money', here); m.group('currency').tag('Currency', here); // fraction - '3 out of 5' doc.match('#Cardinal+ out of every? #Cardinal').tag('Fraction', here); // fraction - 'a third of a slice' m = doc.match("[(#Cardinal|a) ".concat(ordinals, "] of (a|an|the)"), 0).tag('Fraction', here); // tag 'thirds' as a ordinal m.match('.$').tag('Ordinal', 'plural-ordinal'); }; var tagger_1 = tagger; var tags = { Fraction: { isA: ['Value', 'NumericValue'] }, Multiple: { isA: 'Value' } }; var ambig = { mark: true, sucre: true, leone: true, afghani: true, rand: true, "try": true, mop: true, won: true, all: true, rub: true, eek: true, sit: true, bam: true, npr: true, leu: true }; var lex = { kronor: 'Currency' }; currencies.forEach(function (o) { if (o.iso && !ambig[o.iso]) { lex[o.iso] = ['Acronym', 'Currency']; } var name = o.name; if (name && !ambig[name]) { lex[name] = 'Currency'; lex[name + 's'] = 'Currency'; } if (o.dem) { var dem = o.dem; lex["".concat(dem, " ").concat(name)] = 'Currency'; lex["".concat(dem, " ").concat(name, "s")] = 'Currency'; } }); var lexicon = lex; /** adds .numbers() method */ var plugin = function plugin(Doc, world) { // add money words to our lexicon world.addWords(lexicon); // add tags to our tagset world.addTags(tags); // additional tagging before running the number-parser world.postProcess(tagger_1); /** a list of number values, and their units */ var Numbers = /*#__PURE__*/function (_Doc) { _inherits(Numbers, _Doc); var _super = _createSuper(Numbers); function Numbers() { _classCallCheck(this, Numbers); return _super.apply(this, arguments); } return Numbers; }(Doc); Object.assign(Numbers.prototype, methods_1); /** a number and a currency */ var Money = /*#__PURE__*/function (_Numbers) { _inherits(Money, _Numbers); var _super2 = _createSuper(Money); function Money() { _classCallCheck(this, Money); return _super2.apply(this, arguments); } return Money; }(Numbers); Object.assign(Money.prototype, methods$1); var Fraction = /*#__PURE__*/function (_Numbers2) { _inherits(Fraction, _Numbers2); var _super3 = _createSuper(Fraction); function Fraction() { _classCallCheck(this, Fraction); return _super3.apply(this, arguments); } return Fraction; }(Numbers); Object.assign(Fraction.prototype, methods_1$1); var docMethods = { /** find all numbers and values */ numbers: function numbers(n) { var m = find(this, n); return new Numbers(m.list, this, this.world); }, /** return '4%' or 'four percent' etc*/ percentages: function percentages(n) { var m = this.match('#Percent+'); m = m.concat(this.match('[#Cardinal] percent', 0)); if (typeof n === 'number') { m = m.eq(n); } return new Numbers(m.list, this, this.world); }, /** return '3 out of 5' or '3/5' etc**/ fractions: function fractions(n) { var m = this.match('#Fraction+'); if (typeof n === 'number') { m = m.eq(n); } return new Fraction(m.list, this, this.world); }, /** number + currency pair */ money: function money() { var m = this.splitOn('(#Money|#Currency)+'); m = m["if"]('#Money')["if"]('#Value'); return new Money(m.list, this, this.world); } }; // aliases docMethods.values = docMethods.numbers; docMethods.percents = docMethods.percentages; Object.assign(Doc.prototype, docMethods); return Doc; }; var src = plugin; return src; }))); //# sourceMappingURL=compromise-numbers.js.map