numeric-quantity 2.0.0-beta.1 → 2.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm][badge-npm]](https://www.npmjs.com/package/numeric-quantity)
4
4
  ![workflow status](https://github.com/jakeboone02/numeric-quantity/actions/workflows/main.yml/badge.svg)
5
- [![codecov.io](https://codecov.io/github/jakeboone02/numeric-quantity/coverage.svg?branch=master)](https://codecov.io/github/jakeboone02/numeric-quantity?branch=main)
5
+ [![codecov.io](https://codecov.io/github/jakeboone02/numeric-quantity/coverage.svg?branch=main)](https://codecov.io/github/jakeboone02/numeric-quantity?branch=main)
6
6
  [![downloads](https://img.shields.io/npm/dm/numeric-quantity.svg)](http://npm-stat.com/charts.html?package=numeric-quantity&from=2015-08-01)
7
7
  [![MIT License](https://img.shields.io/npm/l/numeric-quantity.svg)](http://opensource.org/licenses/MIT)
8
8
 
@@ -10,6 +10,8 @@ Converts a string to a number, like an enhanced version of `parseFloat`.
10
10
 
11
11
  In addition to plain integers and decimals, `numeric-quantity` can parse numbers with comma or underscore separators (`'1,000'` or `'1_000'`), mixed numbers (`'1 2/3'`), vulgar fractions (`'1⅖'`), the fraction slash character (`'1 2⁄3'`), and Roman numerals (`'MCCXIV'` or `'Ⅻ'`). The return value will be `NaN` if the provided string does not resemble a number.
12
12
 
13
+ To allow and ignore trailing invalid characters _à la_ `parseFloat`, pass `{ allowTrailingInvalid: true }` as the second argument.
14
+
13
15
  > _For the inverse operation—converting a number to an imperial measurement—check out [format-quantity](https://www.npmjs.com/package/format-quantity)._
14
16
  >
15
17
  > _For a more complete solution to parsing recipe ingredients, try [parse-ingredient](https://www.npmjs.com/package/parse-ingredient)._
@@ -48,19 +50,21 @@ As UMD (all exports are properties of the global object `NumericQuantity`):
48
50
 
49
51
  ## Other exports
50
52
 
51
- | Name | Type | Description |
52
- | ------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------- |
53
- | `numericRegex` | `RegExp` | Regular expression matching a string that resembles a number (using Arabic numerals) in its entirety |
54
- | `VulgarFraction` | `type` | Union type of all unicode vulgar fraction code points |
55
- | `vulgarFractionsRegex` | `RegExp` | Regular expression matching the first unicode vulgar fraction code point |
56
- | `vulgarFractionToAsciiMap` | `object` | Mapping of each vulgar fraction to its traditional ASCII representation (e.g., `'½'` to `'1/2'`) |
57
- | `parseRomanNumerals` | `function` | Same function signature as `numericQuantity`, but only for Roman numerals (used internally) |
58
- | `romanNumeralRegex` | `RegExp` | Regular expression matching valid Roman numeral sequences (uses modern, strict rules) |
59
- | `romanNumeralUnicodeRegex` | `RegExp` | Regular expression matching any unicode Roman numeral code point |
60
- | `romanNumeralUnicodeToAsciiMap` | `object` | Mapping of each Roman numeral to its traditional ASCII representation (e.g., `'Ⅻ'` to `'XII'`) |
61
- | `romanNumeralValues` | `object` | Mapping of each valid Roman numeral sequence fragment to its numeric value |
62
- | `RomanNumeralAscii` | `type` | Union type of allowable Roman numeral characters (uppercase only) |
63
- | `RomanNumeralUnicode` | `type` | Union type of all Unicode Roman numeral characters (representing 1-12, 50, 100, 500, and 1000) |
64
- | `RomanNumeral` | `type` | Union type of `RomanNumeralAscii` and `RomanNumeralUnicode` |
53
+ | Name | Type | Description |
54
+ | --------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------- |
55
+ | `numericRegex` | `RegExp` | Regular expression matching a string that resembles a number (using Arabic numerals) in its entirety |
56
+ | `numericRegexWithTrailingInvalid` | `RegExp` | Same as `numericRegex`, but allows/ignores trailing invalid characters. |
57
+ | `VulgarFraction` | `type` | Union type of all unicode vulgar fraction code points |
58
+ | `vulgarFractionsRegex` | `RegExp` | Regular expression matching the first unicode vulgar fraction code point |
59
+ | `vulgarFractionToAsciiMap` | `object` | Mapping of each vulgar fraction to its traditional ASCII representation (e.g., `'½'` to `'1/2'`) |
60
+ | `parseRomanNumerals` | `function` | Same function signature as `numericQuantity`, but only for Roman numerals (used internally) |
61
+ | `romanNumeralRegex` | `RegExp` | Regular expression matching valid Roman numeral sequences (uses modern, strict rules) |
62
+ | `romanNumeralUnicodeRegex` | `RegExp` | Regular expression matching any unicode Roman numeral code point |
63
+ | `romanNumeralUnicodeToAsciiMap` | `object` | Mapping of each Roman numeral to its traditional ASCII representation (e.g., `'Ⅻ'` to `'XII'`) |
64
+ | `romanNumeralValues` | `object` | Mapping of each valid Roman numeral sequence fragment to its numeric value |
65
+ | `NumericQuantityOptions` | `interface` | Shape of the (optional) second argument to `numericQuantity` |
66
+ | `RomanNumeralAscii` | `type` | Union type of allowable Roman numeral characters (uppercase only) |
67
+ | `RomanNumeralUnicode` | `type` | Union type of all Unicode Roman numeral characters (representing 1-12, 50, 100, 500, and 1000) |
68
+ | `RomanNumeral` | `type` | Union type of `RomanNumeralAscii` and `RomanNumeralUnicode` |
65
69
 
66
70
  [badge-npm]: https://img.shields.io/npm/v/numeric-quantity.svg?cacheSeconds=3600&logo=npm
@@ -22,6 +22,7 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  numericQuantity: () => numericQuantity,
24
24
  numericRegex: () => numericRegex,
25
+ numericRegexWithTrailingInvalid: () => numericRegexWithTrailingInvalid,
25
26
  parseRomanNumerals: () => parseRomanNumerals,
26
27
  romanNumeralRegex: () => romanNumeralRegex,
27
28
  romanNumeralUnicodeRegex: () => romanNumeralUnicodeRegex,
@@ -54,7 +55,8 @@ var vulgarFractionToAsciiMap = {
54
55
  "\u215E": "7/8",
55
56
  "\u215F": "1/"
56
57
  };
57
- var numericRegex = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/;
58
+ var numericRegex = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?$/;
59
+ var numericRegexWithTrailingInvalid = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/;
58
60
  var vulgarFractionsRegex = new RegExp(
59
61
  `(${Object.keys(vulgarFractionToAsciiMap).join("|")})`
60
62
  );
@@ -168,6 +170,7 @@ var romanNumeralRegex = /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(
168
170
 
169
171
  // src/parseRomanNumerals.ts
170
172
  var parseRomanNumerals = (romanNumerals) => {
173
+ var _a, _b, _c, _d;
171
174
  const normalized = `${romanNumerals}`.replace(
172
175
  romanNumeralUnicodeRegex,
173
176
  (_m, rn) => romanNumeralUnicodeToAsciiMap[rn]
@@ -177,12 +180,12 @@ var parseRomanNumerals = (romanNumerals) => {
177
180
  return NaN;
178
181
  }
179
182
  const [, thousands, hundreds, tens, ones] = regexResult;
180
- return (romanNumeralValues[thousands] || 0) + (romanNumeralValues[hundreds] || 0) + (romanNumeralValues[tens] || 0) + (romanNumeralValues[ones] || 0);
183
+ return ((_a = romanNumeralValues[thousands]) != null ? _a : 0) + ((_b = romanNumeralValues[hundreds]) != null ? _b : 0) + ((_c = romanNumeralValues[tens]) != null ? _c : 0) + ((_d = romanNumeralValues[ones]) != null ? _d : 0);
181
184
  };
182
185
 
183
186
  // src/numericQuantity.ts
184
187
  var spaceThenSlashRegex = /^\s*\//;
185
- var numericQuantity = (quantity) => {
188
+ var numericQuantity = (quantity, options = { allowTrailingInvalid: false }) => {
186
189
  if (typeof quantity === "number" || typeof quantity === "bigint") {
187
190
  return quantity;
188
191
  }
@@ -194,7 +197,7 @@ var numericQuantity = (quantity) => {
194
197
  if (quantityAsString.length === 0) {
195
198
  return NaN;
196
199
  }
197
- const regexResult = numericRegex.exec(quantityAsString);
200
+ const regexResult = ((options == null ? void 0 : options.allowTrailingInvalid) ? numericRegexWithTrailingInvalid : numericRegex).exec(quantityAsString);
198
201
  if (!regexResult) {
199
202
  return parseRomanNumerals(quantityAsString);
200
203
  }
@@ -227,6 +230,7 @@ var numericQuantity = (quantity) => {
227
230
  0 && (module.exports = {
228
231
  numericQuantity,
229
232
  numericRegex,
233
+ numericRegexWithTrailingInvalid,
230
234
  parseRomanNumerals,
231
235
  romanNumeralRegex,
232
236
  romanNumeralUnicodeRegex,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/constants.ts","../../src/parseRomanNumerals.ts","../../src/numericQuantity.ts"],"sourcesContent":["export * from './constants';\nexport * from './numericQuantity';\nexport * from './parseRomanNumerals';\nexport * from './types';\n","import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] || 0) +\n (romanNumeralValues[hundreds as RNV] || 0) +\n (romanNumeralValues[tens as RNV] || 0) +\n (romanNumeralValues[ones as RNV] || 0)\n );\n};\n","import {\n numericRegex,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (quantity: string | number) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = numericRegex.exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/index.ts","../../src/constants.ts","../../src/parseRomanNumerals.ts","../../src/numericQuantity.ts"],"sourcesContent":["export * from './constants';\nexport * from './numericQuantity';\nexport * from './parseRomanNumerals';\nexport * from './types';\n","import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?$/;\n/**\n * Same as `numericRegex`, but allows/ignores trailing invalid characters.\n */\nexport const numericRegexWithTrailingInvalid =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\n/**\n * Converts a string of Roman numerals to a number, like `parseInt`\n * for Roman numerals. Uses modern, strict rules (only 1 to 3999).\n *\n * The string can include ASCII representations of Roman numerals\n * or Unicode Roman numeral code points (`U+2160` through `U+217F`).\n */\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n // Convert Unicode Roman numerals to ASCII\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n // Normalize to uppercase (more common for Roman numerals)\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] ?? 0) +\n (romanNumeralValues[hundreds as RNV] ?? 0) +\n (romanNumeralValues[tens as RNV] ?? 0) +\n (romanNumeralValues[ones as RNV] ?? 0)\n );\n};\n","import {\n numericRegex,\n numericRegexWithTrailingInvalid,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\nimport { NumericQuantityOptions } from './types';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (\n quantity: string | number,\n options: NumericQuantityOptions = { allowTrailingInvalid: false }\n) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = (\n options?.allowTrailingInvalid\n ? numericRegexWithTrailingInvalid\n : numericRegex\n ).exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":[]}
@@ -1,2 +1,2 @@
1
- "use strict";var N=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var y=(r,e)=>{for(var o in e)N(r,o,{get:e[o],enumerable:!0})},$=(r,e,o,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of M(e))!A.call(r,a)&&a!==o&&N(r,a,{get:()=>e[a],enumerable:!(t=C(e,a))||t.enumerable});return r};var h=r=>$(N({},"__esModule",{value:!0}),r);var L={};y(L,{numericQuantity:()=>F,numericRegex:()=>R,parseRomanNumerals:()=>V,romanNumeralRegex:()=>f,romanNumeralUnicodeRegex:()=>g,romanNumeralUnicodeToAsciiMap:()=>I,romanNumeralValues:()=>m,vulgarFractionToAsciiMap:()=>l,vulgarFractionsRegex:()=>d});module.exports=h(L);var l={"\xBC":"1/4","\xBD":"1/2","\xBE":"3/4","\u2150":"1/7","\u2151":"1/9","\u2152":"1/10","\u2153":"1/3","\u2154":"2/3","\u2155":"1/5","\u2156":"2/5","\u2157":"3/5","\u2158":"4/5","\u2159":"1/6","\u215A":"5/6","\u215B":"1/8","\u215C":"3/8","\u215D":"5/8","\u215E":"7/8","\u215F":"1/"},R=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/,d=new RegExp(`(${Object.keys(l).join("|")})`),m={MMM:3e3,MM:2e3,M:1e3,CM:900,DCCC:800,DCC:700,DC:600,D:500,CD:400,CCC:300,CC:200,C:100,XC:90,LXXX:80,LXX:70,LX:60,L:50,XL:40,XXX:30,XX:20,XII:12,XI:11,X:10,IX:9,VIII:8,VII:7,VI:6,V:5,IV:4,III:3,II:2,I:1},I={"\u2160":"I","\u2161":"II","\u2162":"III","\u2163":"IV","\u2164":"V","\u2165":"VI","\u2166":"VII","\u2167":"VIII","\u2168":"IX","\u2169":"X","\u216A":"XI","\u216B":"XII","\u216C":"L","\u216D":"C","\u216E":"D","\u216F":"M","\u2170":"I","\u2171":"II","\u2172":"III","\u2173":"IV","\u2174":"V","\u2175":"VI","\u2176":"VII","\u2177":"VIII","\u2178":"IX","\u2179":"X","\u217A":"XI","\u217B":"XII","\u217C":"L","\u217D":"C","\u217E":"D","\u217F":"M"},g=new RegExp(`(${Object.keys(I).join("|")})`,"gi"),f=/^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;var V=r=>{let e=`${r}`.replace(g,(u,n)=>I[n]).toUpperCase(),o=f.exec(e);if(!o)return NaN;let[,t,a,p,i]=o;return(m[t]||0)+(m[a]||0)+(m[p]||0)+(m[i]||0)};var D=/^\s*\//,F=r=>{if(typeof r=="number"||typeof r=="bigint")return r;let e=NaN,o=`${r}`.replace(d,(s,c)=>` ${l[c]}`).replace("\u2044","/").trim();if(o.length===0)return NaN;let t=R.exec(o);if(!t)return V(o);let[,a,p,i]=t,u=p.replace(/[,_]/g,""),n=i==null?void 0:i.replace(/[,_]/g,"");if(!u&&n&&n.startsWith(".")?e=0:e=parseInt(u),!n)return a?e*-1:e;if(n.startsWith(".")){let s=parseFloat(n);e+=Math.round(s*1e3)/1e3}else if(D.test(n)){let s=parseInt(u),c=parseInt(n.replace("/",""));e=Math.round(s*1e3/c)/1e3}else{let s=n.split("/"),[c,X]=s.map(x=>parseInt(x));e+=Math.round(c*1e3/X)/1e3}return a?e*-1:e};0&&(module.exports={numericQuantity,numericRegex,parseRomanNumerals,romanNumeralRegex,romanNumeralUnicodeRegex,romanNumeralUnicodeToAsciiMap,romanNumeralValues,vulgarFractionToAsciiMap,vulgarFractionsRegex});
1
+ "use strict";var R=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var h=(a,r)=>{for(var e in r)R(a,e,{get:r[e],enumerable:!0})},_=(a,r,e,s)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of y(r))!$.call(a,n)&&n!==e&&R(a,n,{get:()=>r[n],enumerable:!(s=A(r,n))||s.enumerable});return a};var T=a=>_(R({},"__esModule",{value:!0}),a);var L={};h(L,{numericQuantity:()=>F,numericRegex:()=>g,numericRegexWithTrailingInvalid:()=>f,parseRomanNumerals:()=>C,romanNumeralRegex:()=>X,romanNumeralUnicodeRegex:()=>V,romanNumeralUnicodeToAsciiMap:()=>p,romanNumeralValues:()=>u,vulgarFractionToAsciiMap:()=>I,vulgarFractionsRegex:()=>x});module.exports=T(L);var I={"\xBC":"1/4","\xBD":"1/2","\xBE":"3/4","\u2150":"1/7","\u2151":"1/9","\u2152":"1/10","\u2153":"1/3","\u2154":"2/3","\u2155":"1/5","\u2156":"2/5","\u2157":"3/5","\u2158":"4/5","\u2159":"1/6","\u215A":"5/6","\u215B":"1/8","\u215C":"3/8","\u215D":"5/8","\u215E":"7/8","\u215F":"1/"},g=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?$/,f=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/,x=new RegExp(`(${Object.keys(I).join("|")})`),u={MMM:3e3,MM:2e3,M:1e3,CM:900,DCCC:800,DCC:700,DC:600,D:500,CD:400,CCC:300,CC:200,C:100,XC:90,LXXX:80,LXX:70,LX:60,L:50,XL:40,XXX:30,XX:20,XII:12,XI:11,X:10,IX:9,VIII:8,VII:7,VI:6,V:5,IV:4,III:3,II:2,I:1},p={"\u2160":"I","\u2161":"II","\u2162":"III","\u2163":"IV","\u2164":"V","\u2165":"VI","\u2166":"VII","\u2167":"VIII","\u2168":"IX","\u2169":"X","\u216A":"XI","\u216B":"XII","\u216C":"L","\u216D":"C","\u216E":"D","\u216F":"M","\u2170":"I","\u2171":"II","\u2172":"III","\u2173":"IV","\u2174":"V","\u2175":"VI","\u2176":"VII","\u2177":"VIII","\u2178":"IX","\u2179":"X","\u217A":"XI","\u217B":"XII","\u217C":"L","\u217D":"C","\u217E":"D","\u217F":"M"},V=new RegExp(`(${Object.keys(p).join("|")})`,"gi"),X=/^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;var C=a=>{var i,m,o,t;let r=`${a}`.replace(V,(c,N)=>p[N]).toUpperCase(),e=X.exec(r);if(!e)return NaN;let[,s,n,l,d]=e;return((i=u[s])!=null?i:0)+((m=u[n])!=null?m:0)+((o=u[l])!=null?o:0)+((t=u[d])!=null?t:0)};var D=/^\s*\//,F=(a,r={allowTrailingInvalid:!1})=>{if(typeof a=="number"||typeof a=="bigint")return a;let e=NaN,s=`${a}`.replace(x,(t,c)=>` ${I[c]}`).replace("\u2044","/").trim();if(s.length===0)return NaN;let n=(r!=null&&r.allowTrailingInvalid?f:g).exec(s);if(!n)return C(s);let[,l,d,i]=n,m=d.replace(/[,_]/g,""),o=i==null?void 0:i.replace(/[,_]/g,"");if(!m&&o&&o.startsWith(".")?e=0:e=parseInt(m),!o)return l?e*-1:e;if(o.startsWith(".")){let t=parseFloat(o);e+=Math.round(t*1e3)/1e3}else if(D.test(o)){let t=parseInt(m),c=parseInt(o.replace("/",""));e=Math.round(t*1e3/c)/1e3}else{let t=o.split("/"),[c,N]=t.map(M=>parseInt(M));e+=Math.round(c*1e3/N)/1e3}return l?e*-1:e};0&&(module.exports={numericQuantity,numericRegex,numericRegexWithTrailingInvalid,parseRomanNumerals,romanNumeralRegex,romanNumeralUnicodeRegex,romanNumeralUnicodeToAsciiMap,romanNumeralValues,vulgarFractionToAsciiMap,vulgarFractionsRegex});
2
2
  //# sourceMappingURL=numeric-quantity.cjs.production.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/constants.ts","../../src/parseRomanNumerals.ts","../../src/numericQuantity.ts"],"sourcesContent":["export * from './constants';\nexport * from './numericQuantity';\nexport * from './parseRomanNumerals';\nexport * from './types';\n","import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] || 0) +\n (romanNumeralValues[hundreds as RNV] || 0) +\n (romanNumeralValues[tens as RNV] || 0) +\n (romanNumeralValues[ones as RNV] || 0)\n );\n};\n","import {\n numericRegex,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (quantity: string | number) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = numericRegex.exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":["src_exports","__export","numericQuantity","numericRegex","parseRomanNumerals","romanNumeralRegex","romanNumeralUnicodeRegex","romanNumeralUnicodeToAsciiMap","romanNumeralValues","vulgarFractionToAsciiMap","vulgarFractionsRegex","__toCommonJS","vulgarFractionToAsciiMap","numericRegex","vulgarFractionsRegex","romanNumeralValues","romanNumeralUnicodeToAsciiMap","romanNumeralUnicodeRegex","romanNumeralRegex","parseRomanNumerals","romanNumerals","normalized","romanNumeralUnicodeRegex","_m","rn","romanNumeralUnicodeToAsciiMap","regexResult","romanNumeralRegex","thousands","hundreds","tens","ones","romanNumeralValues","spaceThenSlashRegex","numericQuantity","quantity","finalResult","quantityAsString","vulgarFractionsRegex","_m","vf","vulgarFractionToAsciiMap","regexResult","numericRegex","parseRomanNumerals","dash","ng1temp","ng2temp","numberGroup1","numberGroup2","numerator","denominator","fractionArray","v"]}
1
+ {"version":3,"sources":["../../src/index.ts","../../src/constants.ts","../../src/parseRomanNumerals.ts","../../src/numericQuantity.ts"],"sourcesContent":["export * from './constants';\nexport * from './numericQuantity';\nexport * from './parseRomanNumerals';\nexport * from './types';\n","import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?$/;\n/**\n * Same as `numericRegex`, but allows/ignores trailing invalid characters.\n */\nexport const numericRegexWithTrailingInvalid =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\n/**\n * Converts a string of Roman numerals to a number, like `parseInt`\n * for Roman numerals. Uses modern, strict rules (only 1 to 3999).\n *\n * The string can include ASCII representations of Roman numerals\n * or Unicode Roman numeral code points (`U+2160` through `U+217F`).\n */\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n // Convert Unicode Roman numerals to ASCII\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n // Normalize to uppercase (more common for Roman numerals)\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] ?? 0) +\n (romanNumeralValues[hundreds as RNV] ?? 0) +\n (romanNumeralValues[tens as RNV] ?? 0) +\n (romanNumeralValues[ones as RNV] ?? 0)\n );\n};\n","import {\n numericRegex,\n numericRegexWithTrailingInvalid,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\nimport { NumericQuantityOptions } from './types';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (\n quantity: string | number,\n options: NumericQuantityOptions = { allowTrailingInvalid: false }\n) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = (\n options?.allowTrailingInvalid\n ? numericRegexWithTrailingInvalid\n : numericRegex\n ).exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":["src_exports","__export","numericQuantity","numericRegex","numericRegexWithTrailingInvalid","parseRomanNumerals","romanNumeralRegex","romanNumeralUnicodeRegex","romanNumeralUnicodeToAsciiMap","romanNumeralValues","vulgarFractionToAsciiMap","vulgarFractionsRegex","__toCommonJS","vulgarFractionToAsciiMap","numericRegex","numericRegexWithTrailingInvalid","vulgarFractionsRegex","romanNumeralValues","romanNumeralUnicodeToAsciiMap","romanNumeralUnicodeRegex","romanNumeralRegex","parseRomanNumerals","romanNumerals","_a","_b","_c","_d","normalized","romanNumeralUnicodeRegex","_m","rn","romanNumeralUnicodeToAsciiMap","regexResult","romanNumeralRegex","thousands","hundreds","tens","ones","romanNumeralValues","spaceThenSlashRegex","numericQuantity","quantity","options","finalResult","quantityAsString","vulgarFractionsRegex","_m","vf","vulgarFractionToAsciiMap","regexResult","numericRegexWithTrailingInvalid","numericRegex","parseRomanNumerals","dash","ng1temp","ng2temp","numberGroup1","numberGroup2","numerator","denominator","fractionArray","v"]}
@@ -1,3 +1,9 @@
1
+ interface NumericQuantityOptions {
2
+ /**
3
+ * Allow and ignore trailing invalid characters _à la_ `parseFloat`.
4
+ */
5
+ allowTrailingInvalid?: boolean;
6
+ }
1
7
  /**
2
8
  * Unicode vulgar fraction code points
3
9
  */
@@ -53,6 +59,10 @@ declare const vulgarFractionToAsciiMap: Record<VulgarFraction, string>;
53
59
  * numericRegex.exec("2 / 3") // [ "2 / 3", "2", "/ 3", null ]
54
60
  */
55
61
  declare const numericRegex: RegExp;
62
+ /**
63
+ * Same as `numericRegex`, but allows/ignores trailing invalid characters.
64
+ */
65
+ declare const numericRegexWithTrailingInvalid: RegExp;
56
66
  /**
57
67
  * Captures any Unicode vulgar fractions
58
68
  */
@@ -130,8 +140,15 @@ declare const romanNumeralRegex: RegExp;
130
140
  *
131
141
  * The string can include mixed numbers, vulgar fractions, or Roman numerals.
132
142
  */
133
- declare const numericQuantity: (quantity: string | number) => number;
143
+ declare const numericQuantity: (quantity: string | number, options?: NumericQuantityOptions) => number;
134
144
 
145
+ /**
146
+ * Converts a string of Roman numerals to a number, like `parseInt`
147
+ * for Roman numerals. Uses modern, strict rules (only 1 to 3999).
148
+ *
149
+ * The string can include ASCII representations of Roman numerals
150
+ * or Unicode Roman numeral code points (`U+2160` through `U+217F`).
151
+ */
135
152
  declare const parseRomanNumerals: (romanNumerals: string) => number;
136
153
 
137
- export { RomanNumeral, RomanNumeralAscii, RomanNumeralUnicode, VulgarFraction, numericQuantity, numericRegex, parseRomanNumerals, romanNumeralRegex, romanNumeralUnicodeRegex, romanNumeralUnicodeToAsciiMap, romanNumeralValues, vulgarFractionToAsciiMap, vulgarFractionsRegex };
154
+ export { NumericQuantityOptions, RomanNumeral, RomanNumeralAscii, RomanNumeralUnicode, VulgarFraction, numericQuantity, numericRegex, numericRegexWithTrailingInvalid, parseRomanNumerals, romanNumeralRegex, romanNumeralUnicodeRegex, romanNumeralUnicodeToAsciiMap, romanNumeralValues, vulgarFractionToAsciiMap, vulgarFractionsRegex };
@@ -20,7 +20,8 @@ var vulgarFractionToAsciiMap = {
20
20
  "\u215E": "7/8",
21
21
  "\u215F": "1/"
22
22
  };
23
- var numericRegex = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/;
23
+ var numericRegex = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?$/;
24
+ var numericRegexWithTrailingInvalid = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/;
24
25
  var vulgarFractionsRegex = new RegExp(
25
26
  `(${Object.keys(vulgarFractionToAsciiMap).join("|")})`
26
27
  );
@@ -134,6 +135,7 @@ var romanNumeralRegex = /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(
134
135
 
135
136
  // src/parseRomanNumerals.ts
136
137
  var parseRomanNumerals = (romanNumerals) => {
138
+ var _a, _b, _c, _d;
137
139
  const normalized = `${romanNumerals}`.replace(
138
140
  romanNumeralUnicodeRegex,
139
141
  (_m, rn) => romanNumeralUnicodeToAsciiMap[rn]
@@ -143,12 +145,12 @@ var parseRomanNumerals = (romanNumerals) => {
143
145
  return NaN;
144
146
  }
145
147
  const [, thousands, hundreds, tens, ones] = regexResult;
146
- return (romanNumeralValues[thousands] || 0) + (romanNumeralValues[hundreds] || 0) + (romanNumeralValues[tens] || 0) + (romanNumeralValues[ones] || 0);
148
+ return ((_a = romanNumeralValues[thousands]) != null ? _a : 0) + ((_b = romanNumeralValues[hundreds]) != null ? _b : 0) + ((_c = romanNumeralValues[tens]) != null ? _c : 0) + ((_d = romanNumeralValues[ones]) != null ? _d : 0);
147
149
  };
148
150
 
149
151
  // src/numericQuantity.ts
150
152
  var spaceThenSlashRegex = /^\s*\//;
151
- var numericQuantity = (quantity) => {
153
+ var numericQuantity = (quantity, options = { allowTrailingInvalid: false }) => {
152
154
  if (typeof quantity === "number" || typeof quantity === "bigint") {
153
155
  return quantity;
154
156
  }
@@ -160,7 +162,7 @@ var numericQuantity = (quantity) => {
160
162
  if (quantityAsString.length === 0) {
161
163
  return NaN;
162
164
  }
163
- const regexResult = numericRegex.exec(quantityAsString);
165
+ const regexResult = ((options == null ? void 0 : options.allowTrailingInvalid) ? numericRegexWithTrailingInvalid : numericRegex).exec(quantityAsString);
164
166
  if (!regexResult) {
165
167
  return parseRomanNumerals(quantityAsString);
166
168
  }
@@ -192,6 +194,7 @@ var numericQuantity = (quantity) => {
192
194
  export {
193
195
  numericQuantity,
194
196
  numericRegex,
197
+ numericRegexWithTrailingInvalid,
195
198
  parseRomanNumerals,
196
199
  romanNumeralRegex,
197
200
  romanNumeralUnicodeRegex,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] || 0) +\n (romanNumeralValues[hundreds as RNV] || 0) +\n (romanNumeralValues[tens as RNV] || 0) +\n (romanNumeralValues[ones as RNV] || 0)\n );\n};\n","import {\n numericRegex,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (quantity: string | number) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = numericRegex.exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?$/;\n/**\n * Same as `numericRegex`, but allows/ignores trailing invalid characters.\n */\nexport const numericRegexWithTrailingInvalid =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\n/**\n * Converts a string of Roman numerals to a number, like `parseInt`\n * for Roman numerals. Uses modern, strict rules (only 1 to 3999).\n *\n * The string can include ASCII representations of Roman numerals\n * or Unicode Roman numeral code points (`U+2160` through `U+217F`).\n */\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n // Convert Unicode Roman numerals to ASCII\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n // Normalize to uppercase (more common for Roman numerals)\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] ?? 0) +\n (romanNumeralValues[hundreds as RNV] ?? 0) +\n (romanNumeralValues[tens as RNV] ?? 0) +\n (romanNumeralValues[ones as RNV] ?? 0)\n );\n};\n","import {\n numericRegex,\n numericRegexWithTrailingInvalid,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\nimport { NumericQuantityOptions } from './types';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (\n quantity: string | number,\n options: NumericQuantityOptions = { allowTrailingInvalid: false }\n) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = (\n options?.allowTrailingInvalid\n ? numericRegexWithTrailingInvalid\n : numericRegex\n ).exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":[]}
@@ -20,7 +20,8 @@ var vulgarFractionToAsciiMap = {
20
20
  "\u215E": "7/8",
21
21
  "\u215F": "1/"
22
22
  };
23
- var numericRegex = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/;
23
+ var numericRegex = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?$/;
24
+ var numericRegexWithTrailingInvalid = /^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/;
24
25
  var vulgarFractionsRegex = new RegExp(
25
26
  `(${Object.keys(vulgarFractionToAsciiMap).join("|")})`
26
27
  );
@@ -134,6 +135,7 @@ var romanNumeralRegex = /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(
134
135
 
135
136
  // src/parseRomanNumerals.ts
136
137
  var parseRomanNumerals = (romanNumerals) => {
138
+ var _a, _b, _c, _d;
137
139
  const normalized = `${romanNumerals}`.replace(
138
140
  romanNumeralUnicodeRegex,
139
141
  (_m, rn) => romanNumeralUnicodeToAsciiMap[rn]
@@ -143,12 +145,12 @@ var parseRomanNumerals = (romanNumerals) => {
143
145
  return NaN;
144
146
  }
145
147
  const [, thousands, hundreds, tens, ones] = regexResult;
146
- return (romanNumeralValues[thousands] || 0) + (romanNumeralValues[hundreds] || 0) + (romanNumeralValues[tens] || 0) + (romanNumeralValues[ones] || 0);
148
+ return ((_a = romanNumeralValues[thousands]) != null ? _a : 0) + ((_b = romanNumeralValues[hundreds]) != null ? _b : 0) + ((_c = romanNumeralValues[tens]) != null ? _c : 0) + ((_d = romanNumeralValues[ones]) != null ? _d : 0);
147
149
  };
148
150
 
149
151
  // src/numericQuantity.ts
150
152
  var spaceThenSlashRegex = /^\s*\//;
151
- var numericQuantity = (quantity) => {
153
+ var numericQuantity = (quantity, options = { allowTrailingInvalid: false }) => {
152
154
  if (typeof quantity === "number" || typeof quantity === "bigint") {
153
155
  return quantity;
154
156
  }
@@ -160,7 +162,7 @@ var numericQuantity = (quantity) => {
160
162
  if (quantityAsString.length === 0) {
161
163
  return NaN;
162
164
  }
163
- const regexResult = numericRegex.exec(quantityAsString);
165
+ const regexResult = ((options == null ? void 0 : options.allowTrailingInvalid) ? numericRegexWithTrailingInvalid : numericRegex).exec(quantityAsString);
164
166
  if (!regexResult) {
165
167
  return parseRomanNumerals(quantityAsString);
166
168
  }
@@ -192,6 +194,7 @@ var numericQuantity = (quantity) => {
192
194
  export {
193
195
  numericQuantity,
194
196
  numericRegex,
197
+ numericRegexWithTrailingInvalid,
195
198
  parseRomanNumerals,
196
199
  romanNumeralRegex,
197
200
  romanNumeralUnicodeRegex,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] || 0) +\n (romanNumeralValues[hundreds as RNV] || 0) +\n (romanNumeralValues[tens as RNV] || 0) +\n (romanNumeralValues[ones as RNV] || 0)\n );\n};\n","import {\n numericRegex,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (quantity: string | number) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = numericRegex.exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?$/;\n/**\n * Same as `numericRegex`, but allows/ignores trailing invalid characters.\n */\nexport const numericRegexWithTrailingInvalid =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\n/**\n * Converts a string of Roman numerals to a number, like `parseInt`\n * for Roman numerals. Uses modern, strict rules (only 1 to 3999).\n *\n * The string can include ASCII representations of Roman numerals\n * or Unicode Roman numeral code points (`U+2160` through `U+217F`).\n */\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n // Convert Unicode Roman numerals to ASCII\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n // Normalize to uppercase (more common for Roman numerals)\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] ?? 0) +\n (romanNumeralValues[hundreds as RNV] ?? 0) +\n (romanNumeralValues[tens as RNV] ?? 0) +\n (romanNumeralValues[ones as RNV] ?? 0)\n );\n};\n","import {\n numericRegex,\n numericRegexWithTrailingInvalid,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\nimport { NumericQuantityOptions } from './types';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (\n quantity: string | number,\n options: NumericQuantityOptions = { allowTrailingInvalid: false }\n) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = (\n options?.allowTrailingInvalid\n ? numericRegexWithTrailingInvalid\n : numericRegex\n ).exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":[]}
@@ -1,2 +1,2 @@
1
- var I={"\xBC":"1/4","\xBD":"1/2","\xBE":"3/4","\u2150":"1/7","\u2151":"1/9","\u2152":"1/10","\u2153":"1/3","\u2154":"2/3","\u2155":"1/5","\u2156":"2/5","\u2157":"3/5","\u2158":"4/5","\u2159":"1/6","\u215A":"5/6","\u215B":"1/8","\u215C":"3/8","\u215D":"5/8","\u215E":"7/8","\u215F":"1/"},N=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/,R=new RegExp(`(${Object.keys(I).join("|")})`),m={MMM:3e3,MM:2e3,M:1e3,CM:900,DCCC:800,DCC:700,DC:600,D:500,CD:400,CCC:300,CC:200,C:100,XC:90,LXXX:80,LXX:70,LX:60,L:50,XL:40,XXX:30,XX:20,XII:12,XI:11,X:10,IX:9,VIII:8,VII:7,VI:6,V:5,IV:4,III:3,II:2,I:1},p={"\u2160":"I","\u2161":"II","\u2162":"III","\u2163":"IV","\u2164":"V","\u2165":"VI","\u2166":"VII","\u2167":"VIII","\u2168":"IX","\u2169":"X","\u216A":"XI","\u216B":"XII","\u216C":"L","\u216D":"C","\u216E":"D","\u216F":"M","\u2170":"I","\u2171":"II","\u2172":"III","\u2173":"IV","\u2174":"V","\u2175":"VI","\u2176":"VII","\u2177":"VIII","\u2178":"IX","\u2179":"X","\u217A":"XI","\u217B":"XII","\u217C":"L","\u217D":"C","\u217E":"D","\u217F":"M"},d=new RegExp(`(${Object.keys(p).join("|")})`,"gi"),g=/^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;var f=o=>{let e=`${o}`.replace(d,(u,r)=>p[r]).toUpperCase(),a=g.exec(e);if(!a)return NaN;let[,i,c,l,t]=a;return(m[i]||0)+(m[c]||0)+(m[l]||0)+(m[t]||0)};var x=/^\s*\//,h=o=>{if(typeof o=="number"||typeof o=="bigint")return o;let e=NaN,a=`${o}`.replace(R,(n,s)=>` ${I[s]}`).replace("\u2044","/").trim();if(a.length===0)return NaN;let i=N.exec(a);if(!i)return f(a);let[,c,l,t]=i,u=l.replace(/[,_]/g,""),r=t==null?void 0:t.replace(/[,_]/g,"");if(!u&&r&&r.startsWith(".")?e=0:e=parseInt(u),!r)return c?e*-1:e;if(r.startsWith(".")){let n=parseFloat(r);e+=Math.round(n*1e3)/1e3}else if(x.test(r)){let n=parseInt(u),s=parseInt(r.replace("/",""));e=Math.round(n*1e3/s)/1e3}else{let n=r.split("/"),[s,V]=n.map(X=>parseInt(X));e+=Math.round(s*1e3/V)/1e3}return c?e*-1:e};export{h as numericQuantity,N as numericRegex,f as parseRomanNumerals,g as romanNumeralRegex,d as romanNumeralUnicodeRegex,p as romanNumeralUnicodeToAsciiMap,m as romanNumeralValues,I as vulgarFractionToAsciiMap,R as vulgarFractionsRegex};
1
+ var d={"\xBC":"1/4","\xBD":"1/2","\xBE":"3/4","\u2150":"1/7","\u2151":"1/9","\u2152":"1/10","\u2153":"1/3","\u2154":"2/3","\u2155":"1/5","\u2156":"2/5","\u2157":"3/5","\u2158":"4/5","\u2159":"1/6","\u215A":"5/6","\u215B":"1/8","\u215C":"3/8","\u215D":"5/8","\u215E":"7/8","\u215F":"1/"},R=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?$/,g=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/,f=new RegExp(`(${Object.keys(d).join("|")})`),c={MMM:3e3,MM:2e3,M:1e3,CM:900,DCCC:800,DCC:700,DC:600,D:500,CD:400,CCC:300,CC:200,C:100,XC:90,LXXX:80,LXX:70,LX:60,L:50,XL:40,XXX:30,XX:20,XII:12,XI:11,X:10,IX:9,VIII:8,VII:7,VI:6,V:5,IV:4,III:3,II:2,I:1},N={"\u2160":"I","\u2161":"II","\u2162":"III","\u2163":"IV","\u2164":"V","\u2165":"VI","\u2166":"VII","\u2167":"VIII","\u2168":"IX","\u2169":"X","\u216A":"XI","\u216B":"XII","\u216C":"L","\u216D":"C","\u216E":"D","\u216F":"M","\u2170":"I","\u2171":"II","\u2172":"III","\u2173":"IV","\u2174":"V","\u2175":"VI","\u2176":"VII","\u2177":"VIII","\u2178":"IX","\u2179":"X","\u217A":"XI","\u217B":"XII","\u217C":"L","\u217D":"C","\u217E":"D","\u217F":"M"},x=new RegExp(`(${Object.keys(N).join("|")})`,"gi"),V=/^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;var X=o=>{var n,t,r,a;let i=`${o}`.replace(x,(s,p)=>N[p]).toUpperCase(),e=V.exec(i);if(!e)return NaN;let[,m,u,l,I]=e;return((n=c[m])!=null?n:0)+((t=c[u])!=null?t:0)+((r=c[l])!=null?r:0)+((a=c[I])!=null?a:0)};var M=/^\s*\//,T=(o,i={allowTrailingInvalid:!1})=>{if(typeof o=="number"||typeof o=="bigint")return o;let e=NaN,m=`${o}`.replace(f,(a,s)=>` ${d[s]}`).replace("\u2044","/").trim();if(m.length===0)return NaN;let u=(i!=null&&i.allowTrailingInvalid?g:R).exec(m);if(!u)return X(m);let[,l,I,n]=u,t=I.replace(/[,_]/g,""),r=n==null?void 0:n.replace(/[,_]/g,"");if(!t&&r&&r.startsWith(".")?e=0:e=parseInt(t),!r)return l?e*-1:e;if(r.startsWith(".")){let a=parseFloat(r);e+=Math.round(a*1e3)/1e3}else if(M.test(r)){let a=parseInt(t),s=parseInt(r.replace("/",""));e=Math.round(a*1e3/s)/1e3}else{let a=r.split("/"),[s,p]=a.map(C=>parseInt(C));e+=Math.round(s*1e3/p)/1e3}return l?e*-1:e};export{T as numericQuantity,R as numericRegex,g as numericRegexWithTrailingInvalid,X as parseRomanNumerals,V as romanNumeralRegex,x as romanNumeralUnicodeRegex,N as romanNumeralUnicodeToAsciiMap,c as romanNumeralValues,d as vulgarFractionToAsciiMap,f as vulgarFractionsRegex};
2
2
  //# sourceMappingURL=numeric-quantity.production.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] || 0) +\n (romanNumeralValues[hundreds as RNV] || 0) +\n (romanNumeralValues[tens as RNV] || 0) +\n (romanNumeralValues[ones as RNV] || 0)\n );\n};\n","import {\n numericRegex,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (quantity: string | number) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = numericRegex.exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":["vulgarFractionToAsciiMap","numericRegex","vulgarFractionsRegex","romanNumeralValues","romanNumeralUnicodeToAsciiMap","romanNumeralUnicodeRegex","romanNumeralRegex","parseRomanNumerals","romanNumerals","normalized","romanNumeralUnicodeRegex","_m","rn","romanNumeralUnicodeToAsciiMap","regexResult","romanNumeralRegex","thousands","hundreds","tens","ones","romanNumeralValues","spaceThenSlashRegex","numericQuantity","quantity","finalResult","quantityAsString","vulgarFractionsRegex","_m","vf","vulgarFractionToAsciiMap","regexResult","numericRegex","parseRomanNumerals","dash","ng1temp","ng2temp","numberGroup1","numberGroup2","numerator","denominator","fractionArray","v"]}
1
+ {"version":3,"sources":["../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?$/;\n/**\n * Same as `numericRegex`, but allows/ignores trailing invalid characters.\n */\nexport const numericRegexWithTrailingInvalid =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\n/**\n * Converts a string of Roman numerals to a number, like `parseInt`\n * for Roman numerals. Uses modern, strict rules (only 1 to 3999).\n *\n * The string can include ASCII representations of Roman numerals\n * or Unicode Roman numeral code points (`U+2160` through `U+217F`).\n */\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n // Convert Unicode Roman numerals to ASCII\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n // Normalize to uppercase (more common for Roman numerals)\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] ?? 0) +\n (romanNumeralValues[hundreds as RNV] ?? 0) +\n (romanNumeralValues[tens as RNV] ?? 0) +\n (romanNumeralValues[ones as RNV] ?? 0)\n );\n};\n","import {\n numericRegex,\n numericRegexWithTrailingInvalid,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\nimport { NumericQuantityOptions } from './types';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (\n quantity: string | number,\n options: NumericQuantityOptions = { allowTrailingInvalid: false }\n) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = (\n options?.allowTrailingInvalid\n ? numericRegexWithTrailingInvalid\n : numericRegex\n ).exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":["vulgarFractionToAsciiMap","numericRegex","numericRegexWithTrailingInvalid","vulgarFractionsRegex","romanNumeralValues","romanNumeralUnicodeToAsciiMap","romanNumeralUnicodeRegex","romanNumeralRegex","parseRomanNumerals","romanNumerals","_a","_b","_c","_d","normalized","romanNumeralUnicodeRegex","_m","rn","romanNumeralUnicodeToAsciiMap","regexResult","romanNumeralRegex","thousands","hundreds","tens","ones","romanNumeralValues","spaceThenSlashRegex","numericQuantity","quantity","options","finalResult","quantityAsString","vulgarFractionsRegex","_m","vf","vulgarFractionToAsciiMap","regexResult","numericRegexWithTrailingInvalid","numericRegex","parseRomanNumerals","dash","ng1temp","ng2temp","numberGroup1","numberGroup2","numerator","denominator","fractionArray","v"]}
@@ -1,2 +1,2 @@
1
- "use strict";var NumericQuantity=(()=>{var N=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var y=(r,e)=>{for(var o in e)N(r,o,{get:e[o],enumerable:!0})},$=(r,e,o,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of M(e))!A.call(r,a)&&a!==o&&N(r,a,{get:()=>e[a],enumerable:!(t=C(e,a))||t.enumerable});return r};var h=r=>$(N({},"__esModule",{value:!0}),r);var L={};y(L,{numericQuantity:()=>F,numericRegex:()=>R,parseRomanNumerals:()=>V,romanNumeralRegex:()=>f,romanNumeralUnicodeRegex:()=>g,romanNumeralUnicodeToAsciiMap:()=>I,romanNumeralValues:()=>m,vulgarFractionToAsciiMap:()=>l,vulgarFractionsRegex:()=>d});var l={"\xBC":"1/4","\xBD":"1/2","\xBE":"3/4","\u2150":"1/7","\u2151":"1/9","\u2152":"1/10","\u2153":"1/3","\u2154":"2/3","\u2155":"1/5","\u2156":"2/5","\u2157":"3/5","\u2158":"4/5","\u2159":"1/6","\u215A":"5/6","\u215B":"1/8","\u215C":"3/8","\u215D":"5/8","\u215E":"7/8","\u215F":"1/"},R=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/,d=new RegExp(`(${Object.keys(l).join("|")})`),m={MMM:3e3,MM:2e3,M:1e3,CM:900,DCCC:800,DCC:700,DC:600,D:500,CD:400,CCC:300,CC:200,C:100,XC:90,LXXX:80,LXX:70,LX:60,L:50,XL:40,XXX:30,XX:20,XII:12,XI:11,X:10,IX:9,VIII:8,VII:7,VI:6,V:5,IV:4,III:3,II:2,I:1},I={"\u2160":"I","\u2161":"II","\u2162":"III","\u2163":"IV","\u2164":"V","\u2165":"VI","\u2166":"VII","\u2167":"VIII","\u2168":"IX","\u2169":"X","\u216A":"XI","\u216B":"XII","\u216C":"L","\u216D":"C","\u216E":"D","\u216F":"M","\u2170":"I","\u2171":"II","\u2172":"III","\u2173":"IV","\u2174":"V","\u2175":"VI","\u2176":"VII","\u2177":"VIII","\u2178":"IX","\u2179":"X","\u217A":"XI","\u217B":"XII","\u217C":"L","\u217D":"C","\u217E":"D","\u217F":"M"},g=new RegExp(`(${Object.keys(I).join("|")})`,"gi"),f=/^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;var V=r=>{let e=`${r}`.replace(g,(u,n)=>I[n]).toUpperCase(),o=f.exec(e);if(!o)return NaN;let[,t,a,p,i]=o;return(m[t]||0)+(m[a]||0)+(m[p]||0)+(m[i]||0)};var D=/^\s*\//,F=r=>{if(typeof r=="number"||typeof r=="bigint")return r;let e=NaN,o=`${r}`.replace(d,(s,c)=>` ${l[c]}`).replace("\u2044","/").trim();if(o.length===0)return NaN;let t=R.exec(o);if(!t)return V(o);let[,a,p,i]=t,u=p.replace(/[,_]/g,""),n=i==null?void 0:i.replace(/[,_]/g,"");if(!u&&n&&n.startsWith(".")?e=0:e=parseInt(u),!n)return a?e*-1:e;if(n.startsWith(".")){let s=parseFloat(n);e+=Math.round(s*1e3)/1e3}else if(D.test(n)){let s=parseInt(u),c=parseInt(n.replace("/",""));e=Math.round(s*1e3/c)/1e3}else{let s=n.split("/"),[c,X]=s.map(x=>parseInt(x));e+=Math.round(c*1e3/X)/1e3}return a?e*-1:e};return h(L);})();
1
+ "use strict";var NumericQuantity=(()=>{var R=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var h=(a,r)=>{for(var e in r)R(a,e,{get:r[e],enumerable:!0})},_=(a,r,e,s)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of y(r))!$.call(a,n)&&n!==e&&R(a,n,{get:()=>r[n],enumerable:!(s=A(r,n))||s.enumerable});return a};var T=a=>_(R({},"__esModule",{value:!0}),a);var L={};h(L,{numericQuantity:()=>F,numericRegex:()=>g,numericRegexWithTrailingInvalid:()=>f,parseRomanNumerals:()=>C,romanNumeralRegex:()=>X,romanNumeralUnicodeRegex:()=>V,romanNumeralUnicodeToAsciiMap:()=>p,romanNumeralValues:()=>u,vulgarFractionToAsciiMap:()=>I,vulgarFractionsRegex:()=>x});var I={"\xBC":"1/4","\xBD":"1/2","\xBE":"3/4","\u2150":"1/7","\u2151":"1/9","\u2152":"1/10","\u2153":"1/3","\u2154":"2/3","\u2155":"1/5","\u2156":"2/5","\u2157":"3/5","\u2158":"4/5","\u2159":"1/6","\u215A":"5/6","\u215B":"1/8","\u215C":"3/8","\u215D":"5/8","\u215E":"7/8","\u215F":"1/"},g=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?$/,f=/^(?=-?\s*\.\d|-?\s*\d)(-)?\s*((?:\d(?:[\d,_]*\d)?)*)(\.\d(?:[\d,_]*\d)?|(\s+\d(?:[\d,_]*\d)?\s*)?\s*\/\s*\d(?:[\d,_]*\d)?)?(?:\s*[^\.\d\/].*)?/,x=new RegExp(`(${Object.keys(I).join("|")})`),u={MMM:3e3,MM:2e3,M:1e3,CM:900,DCCC:800,DCC:700,DC:600,D:500,CD:400,CCC:300,CC:200,C:100,XC:90,LXXX:80,LXX:70,LX:60,L:50,XL:40,XXX:30,XX:20,XII:12,XI:11,X:10,IX:9,VIII:8,VII:7,VI:6,V:5,IV:4,III:3,II:2,I:1},p={"\u2160":"I","\u2161":"II","\u2162":"III","\u2163":"IV","\u2164":"V","\u2165":"VI","\u2166":"VII","\u2167":"VIII","\u2168":"IX","\u2169":"X","\u216A":"XI","\u216B":"XII","\u216C":"L","\u216D":"C","\u216E":"D","\u216F":"M","\u2170":"I","\u2171":"II","\u2172":"III","\u2173":"IV","\u2174":"V","\u2175":"VI","\u2176":"VII","\u2177":"VIII","\u2178":"IX","\u2179":"X","\u217A":"XI","\u217B":"XII","\u217C":"L","\u217D":"C","\u217E":"D","\u217F":"M"},V=new RegExp(`(${Object.keys(p).join("|")})`,"gi"),X=/^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;var C=a=>{var i,m,o,t;let r=`${a}`.replace(V,(c,N)=>p[N]).toUpperCase(),e=X.exec(r);if(!e)return NaN;let[,s,n,l,d]=e;return((i=u[s])!=null?i:0)+((m=u[n])!=null?m:0)+((o=u[l])!=null?o:0)+((t=u[d])!=null?t:0)};var D=/^\s*\//,F=(a,r={allowTrailingInvalid:!1})=>{if(typeof a=="number"||typeof a=="bigint")return a;let e=NaN,s=`${a}`.replace(x,(t,c)=>` ${I[c]}`).replace("\u2044","/").trim();if(s.length===0)return NaN;let n=(r!=null&&r.allowTrailingInvalid?f:g).exec(s);if(!n)return C(s);let[,l,d,i]=n,m=d.replace(/[,_]/g,""),o=i==null?void 0:i.replace(/[,_]/g,"");if(!m&&o&&o.startsWith(".")?e=0:e=parseInt(m),!o)return l?e*-1:e;if(o.startsWith(".")){let t=parseFloat(o);e+=Math.round(t*1e3)/1e3}else if(D.test(o)){let t=parseInt(m),c=parseInt(o.replace("/",""));e=Math.round(t*1e3/c)/1e3}else{let t=o.split("/"),[c,N]=t.map(M=>parseInt(M));e+=Math.round(c*1e3/N)/1e3}return l?e*-1:e};return T(L);})();
2
2
  //# sourceMappingURL=numeric-quantity.umd.min.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["export * from './constants';\nexport * from './numericQuantity';\nexport * from './parseRomanNumerals';\nexport * from './types';\n","import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] || 0) +\n (romanNumeralValues[hundreds as RNV] || 0) +\n (romanNumeralValues[tens as RNV] || 0) +\n (romanNumeralValues[ones as RNV] || 0)\n );\n};\n","import {\n numericRegex,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (quantity: string | number) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = numericRegex.exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"mcAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,qBAAAE,EAAA,iBAAAC,EAAA,uBAAAC,EAAA,sBAAAC,EAAA,6BAAAC,EAAA,kCAAAC,EAAA,uBAAAC,EAAA,6BAAAC,EAAA,yBAAAC,ICUO,IAAMC,EAA2D,CACtE,OAAK,MACL,OAAK,MACL,OAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,OACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,MACL,SAAK,IACP,EAkCaC,EACX,iJAKWC,EAAuB,IAAI,OACtC,IAAI,OAAO,KAAKF,CAAwB,EAAE,KAAK,GAAG,IACpD,EAUaG,EAAqB,CAChC,IAAK,IACL,GAAI,IACJ,EAAG,IACH,GAAI,IACJ,KAAM,IACN,IAAK,IACL,GAAI,IACJ,EAAG,IACH,GAAI,IACJ,IAAK,IACL,GAAI,IACJ,EAAG,IACH,GAAI,GACJ,KAAM,GACN,IAAK,GACL,GAAI,GACJ,EAAG,GACH,GAAI,GACJ,IAAK,GACL,GAAI,GAEJ,IAAK,GAEL,GAAI,GACJ,EAAG,GACH,GAAI,EACJ,KAAM,EACN,IAAK,EACL,GAAI,EACJ,EAAG,EACH,GAAI,EACJ,IAAK,EACL,GAAI,EACJ,EAAG,CACL,EAKaC,EAGT,CAEF,SAAG,IAEH,SAAG,KAEH,SAAG,MAEH,SAAG,KAEH,SAAG,IAEH,SAAG,KAEH,SAAG,MAEH,SAAG,OAEH,SAAG,KAEH,SAAG,IAEH,SAAG,KAEH,SAAG,MAEH,SAAG,IAEH,SAAG,IAEH,SAAG,IAEH,SAAG,IAEH,SAAG,IAEH,SAAG,KAEH,SAAG,MAEH,SAAG,KAEH,SAAG,IAEH,SAAG,KAEH,SAAG,MAEH,SAAG,OAEH,SAAG,KAEH,SAAG,IAEH,SAAG,KAEH,SAAG,MAEH,SAAG,IAEH,SAAG,IAEH,SAAG,IAEH,SAAG,GACL,EAKaC,EAA2B,IAAI,OAC1C,IAAI,OAAO,KAAKD,CAA6B,EAAE,KAAK,GAAG,KACvD,IACF,EA0BaE,EACX,2ECvNK,IAAMC,EAAsBC,GAA0B,CAC3D,IAAMC,EAAa,GAAGD,IACnB,QACCE,EACA,CAACC,EAAIC,IACHC,EAA8BD,CAAE,CACpC,EACC,YAAY,EAETE,EAAcC,EAAkB,KAAKN,CAAU,EAErD,GAAI,CAACK,EACH,MAAO,KAGT,GAAM,CAAC,CAAEE,EAAWC,EAAUC,EAAMC,CAAI,EAAIL,EAE5C,OACGM,EAAmBJ,CAAgB,GAAK,IACxCI,EAAmBH,CAAe,GAAK,IACvCG,EAAmBF,CAAW,GAAK,IACnCE,EAAmBD,CAAW,GAAK,EAExC,EC1BA,IAAME,EAAsB,SAOfC,EAAmBC,GAA8B,CAC5D,GAAI,OAAOA,GAAa,UAAY,OAAOA,GAAa,SACtD,OAAOA,EAGT,IAAIC,EAAc,IAGZC,EAAmB,GAAGF,IAGzB,QACCG,EACA,CAACC,EAAIC,IACH,IAAIC,EAAyBD,CAAE,GACnC,EAEC,QAAQ,SAAK,GAAG,EAChB,KAAK,EAGR,GAAIH,EAAiB,SAAW,EAC9B,MAAO,KAGT,IAAMK,EAAcC,EAAa,KAAKN,CAAgB,EAGtD,GAAI,CAACK,EACH,OAAOE,EAAmBP,CAAgB,EAG5C,GAAM,CAAC,CAAEQ,EAAMC,EAASC,CAAO,EAAIL,EAC7BM,EAAeF,EAAQ,QAAQ,QAAS,EAAE,EAC1CG,EAAeF,GAAA,YAAAA,EAAS,QAAQ,QAAS,IAW/C,GARI,CAACC,GAAgBC,GAAgBA,EAAa,WAAW,GAAG,EAC9Db,EAAc,EAEdA,EAAc,SAASY,CAAY,EAKjC,CAACC,EACH,OAAOJ,EAAOT,EAAc,GAAKA,EAGnC,GAAIa,EAAa,WAAW,GAAG,EAAG,CAEhC,IAAMC,EAAY,WAAWD,CAAY,EACzCb,GAAe,KAAK,MAAMc,EAAY,GAAI,EAAI,YACrCjB,EAAoB,KAAKgB,CAAY,EAAG,CAEjD,IAAMC,EAAY,SAASF,CAAY,EACjCG,EAAc,SAASF,EAAa,QAAQ,IAAK,EAAE,CAAC,EAC1Db,EAAc,KAAK,MAAOc,EAAY,IAAQC,CAAW,EAAI,QACxD,CAEL,IAAMC,EAAgBH,EAAa,MAAM,GAAG,EACtC,CAACC,EAAWC,CAAW,EAAIC,EAAc,IAAIC,GAAK,SAASA,CAAC,CAAC,EACnEjB,GAAe,KAAK,MAAOc,EAAY,IAAQC,CAAW,EAAI,IAGhE,OAAON,EAAOT,EAAc,GAAKA,CACnC","names":["src_exports","__export","numericQuantity","numericRegex","parseRomanNumerals","romanNumeralRegex","romanNumeralUnicodeRegex","romanNumeralUnicodeToAsciiMap","romanNumeralValues","vulgarFractionToAsciiMap","vulgarFractionsRegex","vulgarFractionToAsciiMap","numericRegex","vulgarFractionsRegex","romanNumeralValues","romanNumeralUnicodeToAsciiMap","romanNumeralUnicodeRegex","romanNumeralRegex","parseRomanNumerals","romanNumerals","normalized","romanNumeralUnicodeRegex","_m","rn","romanNumeralUnicodeToAsciiMap","regexResult","romanNumeralRegex","thousands","hundreds","tens","ones","romanNumeralValues","spaceThenSlashRegex","numericQuantity","quantity","finalResult","quantityAsString","vulgarFractionsRegex","_m","vf","vulgarFractionToAsciiMap","regexResult","numericRegex","parseRomanNumerals","dash","ng1temp","ng2temp","numberGroup1","numberGroup2","numerator","denominator","fractionArray","v"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/parseRomanNumerals.ts","../src/numericQuantity.ts"],"sourcesContent":["export * from './constants';\nexport * from './numericQuantity';\nexport * from './parseRomanNumerals';\nexport * from './types';\n","import type {\n RomanNumeralAscii,\n RomanNumeralUnicode,\n VulgarFraction,\n} from './types';\n\n// #region Arabic numerals\n/**\n * Map of Unicode fraction code points to their ASCII equivalents\n */\nexport const vulgarFractionToAsciiMap: Record<VulgarFraction, string> = {\n '¼': '1/4',\n '½': '1/2',\n '¾': '3/4',\n '⅐': '1/7',\n '⅑': '1/9',\n '⅒': '1/10',\n '⅓': '1/3',\n '⅔': '2/3',\n '⅕': '1/5',\n '⅖': '2/5',\n '⅗': '3/5',\n '⅘': '4/5',\n '⅙': '1/6',\n '⅚': '5/6',\n '⅛': '1/8',\n '⅜': '3/8',\n '⅝': '5/8',\n '⅞': '7/8',\n '⅟': '1/',\n};\n\n/**\n * Captures the individual elements of a numeric string.\n *\n * Capture groups:\n *\n * +=====+====================+========================+\n * | # | Description | Example |\n * +=====+====================+========================+\n * | 0 | entire string | \"2 1/3\" from \"2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 1 | \"negative\" dash | \"-\" from \"-2 1/3\" |\n * +-----+--------------------+------------------------+\n * | 2 | the whole number | \"2\" from \"2 1/3\" |\n * | | - OR - | |\n * | | the numerator | \"1\" from \"1/3\" |\n * | + + |\n * | (This may include comma/underscore separators) |\n * +-----+--------------------+------------------------+\n * | 3 | entire fraction | \"1/3\" from \"2 1/3\" |\n * | | - OR - | |\n * | | decimal portion | \".33\" from \"2.33\" |\n * | | - OR - | |\n * | | denominator | \"/3\" from \"1/3\" |\n * +=====+====================+========================+\n *\n * @example\n * numericRegex.exec(\"1\") // [ \"1\", \"1\", null, null ]\n * numericRegex.exec(\"1.23\") // [ \"1.23\", \"1\", \".23\", null ]\n * numericRegex.exec(\"1 2/3\") // [ \"1 2/3\", \"1\", \" 2/3\", \" 2\" ]\n * numericRegex.exec(\"2/3\") // [ \"2/3\", \"2\", \"/3\", null ]\n * numericRegex.exec(\"2 / 3\") // [ \"2 / 3\", \"2\", \"/ 3\", null ]\n */\nexport const numericRegex =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?$/;\n/**\n * Same as `numericRegex`, but allows/ignores trailing invalid characters.\n */\nexport const numericRegexWithTrailingInvalid =\n /^(?=-?\\s*\\.\\d|-?\\s*\\d)(-)?\\s*((?:\\d(?:[\\d,_]*\\d)?)*)(\\.\\d(?:[\\d,_]*\\d)?|(\\s+\\d(?:[\\d,_]*\\d)?\\s*)?\\s*\\/\\s*\\d(?:[\\d,_]*\\d)?)?(?:\\s*[^\\.\\d\\/].*)?/;\n\n/**\n * Captures any Unicode vulgar fractions\n */\nexport const vulgarFractionsRegex = new RegExp(\n `(${Object.keys(vulgarFractionToAsciiMap).join('|')})`\n);\n// #endregion\n\n// #region Roman numerals\ntype RomanNumeralSequenceFragment =\n | `${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`\n | `${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}${RomanNumeralAscii}`;\n\nexport const romanNumeralValues = {\n MMM: 3000,\n MM: 2000,\n M: 1000,\n CM: 900,\n DCCC: 800,\n DCC: 700,\n DC: 600,\n D: 500,\n CD: 400,\n CCC: 300,\n CC: 200,\n C: 100,\n XC: 90,\n LXXX: 80,\n LXX: 70,\n LX: 60,\n L: 50,\n XL: 40,\n XXX: 30,\n XX: 20,\n // Twelve is only here for tests; not used in practice\n XII: 12,\n // Eleven is only here for tests; not used in practice\n XI: 11,\n X: 10,\n IX: 9,\n VIII: 8,\n VII: 7,\n VI: 6,\n V: 5,\n IV: 4,\n III: 3,\n II: 2,\n I: 1,\n} satisfies { [k in RomanNumeralSequenceFragment]?: number };\n\n/**\n * Map of Unicode Roman numeral code points to their ASCII equivalents\n */\nexport const romanNumeralUnicodeToAsciiMap: Record<\n RomanNumeralUnicode,\n keyof typeof romanNumeralValues\n> = {\n // Roman Numeral One (U+2160)\n Ⅰ: 'I',\n // Roman Numeral Two (U+2161)\n Ⅱ: 'II',\n // Roman Numeral Three (U+2162)\n Ⅲ: 'III',\n // Roman Numeral Four (U+2163)\n Ⅳ: 'IV',\n // Roman Numeral Five (U+2164)\n Ⅴ: 'V',\n // Roman Numeral Six (U+2165)\n Ⅵ: 'VI',\n // Roman Numeral Seven (U+2166)\n Ⅶ: 'VII',\n // Roman Numeral Eight (U+2167)\n Ⅷ: 'VIII',\n // Roman Numeral Nine (U+2168)\n Ⅸ: 'IX',\n // Roman Numeral Ten (U+2169)\n Ⅹ: 'X',\n // Roman Numeral Eleven (U+216A)\n Ⅺ: 'XI',\n // Roman Numeral Twelve (U+216B)\n Ⅻ: 'XII',\n // Roman Numeral Fifty (U+216C)\n Ⅼ: 'L',\n // Roman Numeral One Hundred (U+216D)\n Ⅽ: 'C',\n // Roman Numeral Five Hundred (U+216E)\n Ⅾ: 'D',\n // Roman Numeral One Thousand (U+216F)\n Ⅿ: 'M',\n // Small Roman Numeral One (U+2170)\n ⅰ: 'I',\n // Small Roman Numeral Two (U+2171)\n ⅱ: 'II',\n // Small Roman Numeral Three (U+2172)\n ⅲ: 'III',\n // Small Roman Numeral Four (U+2173)\n ⅳ: 'IV',\n // Small Roman Numeral Five (U+2174)\n ⅴ: 'V',\n // Small Roman Numeral Six (U+2175)\n ⅵ: 'VI',\n // Small Roman Numeral Seven (U+2176)\n ⅶ: 'VII',\n // Small Roman Numeral Eight (U+2177)\n ⅷ: 'VIII',\n // Small Roman Numeral Nine (U+2178)\n ⅸ: 'IX',\n // Small Roman Numeral Ten (U+2179)\n ⅹ: 'X',\n // Small Roman Numeral Eleven (U+217A)\n ⅺ: 'XI',\n // Small Roman Numeral Twelve (U+217B)\n ⅻ: 'XII',\n // Small Roman Numeral Fifty (U+217C)\n ⅼ: 'L',\n // Small Roman Numeral One Hundred (U+217D)\n ⅽ: 'C',\n // Small Roman Numeral Five Hundred (U+217E)\n ⅾ: 'D',\n // Small Roman Numeral One Thousand (U+217F)\n ⅿ: 'M',\n};\n\n/**\n * Captures all Unicode Roman numeral code points\n */\nexport const romanNumeralUnicodeRegex = new RegExp(\n `(${Object.keys(romanNumeralUnicodeToAsciiMap).join('|')})`,\n 'gi'\n);\n\n/**\n * Captures a valid Roman numeral sequence\n *\n * Capture groups:\n *\n * +=====+=================+==========================+\n * | # | Description | Example |\n * +=====+=================+==========================+\n * | 0 | Entire string | \"MCCXIV\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 1 | Thousands | \"M\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 2 | Hundreds | \"CC\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 3 | Tens | \"X\" from \"MCCXIV\" |\n * +-----+-----------------+--------------------------+\n * | 4 | Ones | \"IV\" from \"MCCXIV\" |\n * +=====+=================+==========================+\n *\n * @example\n * romanNumeralRegex.exec(\"M\") // [ \"M\", \"M\", \"\", \"\", \"\" ]\n * romanNumeralRegex.exec(\"XII\") // [ \"XII\", \"\", \"\", \"X\", \"II\" ]\n * romanNumeralRegex.exec(\"MCCXIV\") // [ \"MCCXIV\", \"M\", \"CC\", \"X\", \"IV\" ]\n */\nexport const romanNumeralRegex =\n /^(?=[MDCLXVI])(M{0,3})(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$/i;\n// #endregion\n","import {\n romanNumeralRegex,\n romanNumeralUnicodeRegex,\n romanNumeralUnicodeToAsciiMap,\n romanNumeralValues,\n} from './constants';\n\n// Just a shorthand type alias\ntype RNV = keyof typeof romanNumeralValues;\n\n/**\n * Converts a string of Roman numerals to a number, like `parseInt`\n * for Roman numerals. Uses modern, strict rules (only 1 to 3999).\n *\n * The string can include ASCII representations of Roman numerals\n * or Unicode Roman numeral code points (`U+2160` through `U+217F`).\n */\nexport const parseRomanNumerals = (romanNumerals: string) => {\n const normalized = `${romanNumerals}`\n // Convert Unicode Roman numerals to ASCII\n .replace(\n romanNumeralUnicodeRegex,\n (_m, rn: keyof typeof romanNumeralUnicodeToAsciiMap) =>\n romanNumeralUnicodeToAsciiMap[rn]\n )\n // Normalize to uppercase (more common for Roman numerals)\n .toUpperCase();\n\n const regexResult = romanNumeralRegex.exec(normalized);\n\n if (!regexResult) {\n return NaN;\n }\n\n const [, thousands, hundreds, tens, ones] = regexResult;\n\n return (\n (romanNumeralValues[thousands as RNV] ?? 0) +\n (romanNumeralValues[hundreds as RNV] ?? 0) +\n (romanNumeralValues[tens as RNV] ?? 0) +\n (romanNumeralValues[ones as RNV] ?? 0)\n );\n};\n","import {\n numericRegex,\n numericRegexWithTrailingInvalid,\n vulgarFractionToAsciiMap,\n vulgarFractionsRegex,\n} from './constants';\nimport { parseRomanNumerals } from './parseRomanNumerals';\nimport { NumericQuantityOptions } from './types';\n\nconst spaceThenSlashRegex = /^\\s*\\//;\n\n/**\n * Converts a string to a number, like an enhanced version of `parseFloat`.\n *\n * The string can include mixed numbers, vulgar fractions, or Roman numerals.\n */\nexport const numericQuantity = (\n quantity: string | number,\n options: NumericQuantityOptions = { allowTrailingInvalid: false }\n) => {\n if (typeof quantity === 'number' || typeof quantity === 'bigint') {\n return quantity;\n }\n\n let finalResult = NaN;\n\n // Coerce to string in case qty is a number\n const quantityAsString = `${quantity}`\n // Convert vulgar fractions to ASCII, with a leading space\n // to keep the whole number and the fraction separate\n .replace(\n vulgarFractionsRegex,\n (_m, vf: keyof typeof vulgarFractionToAsciiMap) =>\n ` ${vulgarFractionToAsciiMap[vf]}`\n )\n // Convert fraction slash to standard slash\n .replace('⁄', '/')\n .trim();\n\n // Bail out if the string was only white space\n if (quantityAsString.length === 0) {\n return NaN;\n }\n\n const regexResult = (\n options?.allowTrailingInvalid\n ? numericRegexWithTrailingInvalid\n : numericRegex\n ).exec(quantityAsString);\n\n // If the Arabic numeral regex fails, try Roman numerals\n if (!regexResult) {\n return parseRomanNumerals(quantityAsString);\n }\n\n const [, dash, ng1temp, ng2temp] = regexResult;\n const numberGroup1 = ng1temp.replace(/[,_]/g, '');\n const numberGroup2 = ng2temp?.replace(/[,_]/g, '');\n\n // Numerify capture group 1\n if (!numberGroup1 && numberGroup2 && numberGroup2.startsWith('.')) {\n finalResult = 0;\n } else {\n finalResult = parseInt(numberGroup1);\n }\n\n // If capture group 2 is null, then we're dealing with an integer\n // and there is nothing left to process\n if (!numberGroup2) {\n return dash ? finalResult * -1 : finalResult;\n }\n\n if (numberGroup2.startsWith('.')) {\n // If first char is \".\" it's a decimal so just trim to 3 decimal places\n const numerator = parseFloat(numberGroup2);\n finalResult += Math.round(numerator * 1000) / 1000;\n } else if (spaceThenSlashRegex.test(numberGroup2)) {\n // If the first non-space char is \"/\" it's a pure fraction (e.g. \"1/2\")\n const numerator = parseInt(numberGroup1);\n const denominator = parseInt(numberGroup2.replace('/', ''));\n finalResult = Math.round((numerator * 1000) / denominator) / 1000;\n } else {\n // Otherwise it's a mixed fraction (e.g. \"1 2/3\")\n const fractionArray = numberGroup2.split('/');\n const [numerator, denominator] = fractionArray.map(v => parseInt(v));\n finalResult += Math.round((numerator * 1000) / denominator) / 1000;\n }\n\n return dash ? finalResult * -1 : finalResult;\n};\n"],"mappings":"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","names":["src_exports","__export","numericQuantity","numericRegex","numericRegexWithTrailingInvalid","parseRomanNumerals","romanNumeralRegex","romanNumeralUnicodeRegex","romanNumeralUnicodeToAsciiMap","romanNumeralValues","vulgarFractionToAsciiMap","vulgarFractionsRegex","vulgarFractionToAsciiMap","numericRegex","numericRegexWithTrailingInvalid","vulgarFractionsRegex","romanNumeralValues","romanNumeralUnicodeToAsciiMap","romanNumeralUnicodeRegex","romanNumeralRegex","parseRomanNumerals","romanNumerals","_a","_b","_c","_d","normalized","romanNumeralUnicodeRegex","_m","rn","romanNumeralUnicodeToAsciiMap","regexResult","romanNumeralRegex","thousands","hundreds","tens","ones","romanNumeralValues","spaceThenSlashRegex","numericQuantity","quantity","options","finalResult","quantityAsString","vulgarFractionsRegex","_m","vf","vulgarFractionToAsciiMap","regexResult","numericRegexWithTrailingInvalid","numericRegex","parseRomanNumerals","dash","ng1temp","ng2temp","numberGroup1","numberGroup2","numerator","denominator","fractionArray","v"]}
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "2.0.0-beta.1",
2
+ "version": "2.0.0-beta.2",
3
3
  "license": "MIT",
4
4
  "name": "numeric-quantity",
5
5
  "author": "Jake Boone <jakeboone02@gmail.com>",
6
- "description": "Number parser with support for mixed numbers and vulgar fractions",
6
+ "description": "Number parser with support for mixed numbers, vulgar fractions, and Roman numerals",
7
7
  "files": [
8
8
  "dist"
9
9
  ],
@@ -28,10 +28,13 @@
28
28
  "url": "https://github.com/jakeboone02/numeric-quantity.git"
29
29
  },
30
30
  "keywords": [
31
- "convert",
31
+ "parse",
32
32
  "number",
33
+ "convert",
33
34
  "fraction",
34
- "decimal"
35
+ "decimal",
36
+ "roman",
37
+ "numerals"
35
38
  ],
36
39
  "scripts": {
37
40
  "start": "vite",
@@ -42,9 +45,6 @@
42
45
  "pretty-print": "prettier --write *.{mjs,js,ts,json} src/*.*"
43
46
  },
44
47
  "devDependencies": {
45
- "@babel/core": "^7.22.5",
46
- "@babel/preset-env": "^7.22.5",
47
- "@babel/preset-typescript": "^7.22.5",
48
48
  "@types/jest": "^29.5.2",
49
49
  "@types/node": "^20.3.0",
50
50
  "gh-pages": "^5.0.0",
@@ -52,6 +52,7 @@
52
52
  "np": "^8.0.3",
53
53
  "prettier": "^2.8.8",
54
54
  "prettier-plugin-organize-imports": "^3.2.2",
55
+ "ts-jest": "^29.1.0",
55
56
  "tsup": "^6.7.0",
56
57
  "typescript": "^5.1.3",
57
58
  "vite": "^4.3.9"