@flolegal-it/numbers 1.0.0
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 +96 -0
- package/lib/cjs/formula.d.ts +12 -0
- package/lib/cjs/formula.js +60 -0
- package/lib/cjs/index.d.ts +3 -0
- package/lib/cjs/index.js +9 -0
- package/lib/cjs/package.json +1 -0
- package/lib/cjs/reg-exp.d.ts +25 -0
- package/lib/cjs/reg-exp.js +108 -0
- package/lib/esm/formula.d.ts +12 -0
- package/lib/esm/formula.js +57 -0
- package/lib/esm/index.d.ts +3 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/package.json +1 -0
- package/lib/esm/reg-exp.d.ts +25 -0
- package/lib/esm/reg-exp.js +105 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# numbers
|
|
2
|
+
|
|
3
|
+
TypeScript utility library for number handling.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @flo-legal-it/numbers
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Regex utilities
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { getNumberRegExp } from 'numbers';
|
|
15
|
+
|
|
16
|
+
// English format (1,000.25)
|
|
17
|
+
const enRegex = getNumberRegExp('en');
|
|
18
|
+
enRegex.test('1,000.25'); // true
|
|
19
|
+
enRegex.test('0.5'); // true
|
|
20
|
+
|
|
21
|
+
// Dutch/French format (1.000,25)
|
|
22
|
+
const nlRegex = getNumberRegExp('nl');
|
|
23
|
+
nlRegex.test('1.000,25'); // true
|
|
24
|
+
nlRegex.test('0,5'); // true
|
|
25
|
+
|
|
26
|
+
// Allow negative numbers
|
|
27
|
+
const negRegex = getNumberRegExp('en', true);
|
|
28
|
+
negRegex.test('-1,000.25'); // true
|
|
29
|
+
|
|
30
|
+
// Disallow empty string
|
|
31
|
+
const strictRegex = getNumberRegExp('en', false, false);
|
|
32
|
+
strictRegex.test(''); // false
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Formula utilities
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { validateFormula, getUsedVars, evalFormula } from '@flo-legal-it/numbers';
|
|
39
|
+
|
|
40
|
+
// Validate a formula against allowed variables
|
|
41
|
+
const errors = validateFormula('a + b * 2', ['a', 'b']);
|
|
42
|
+
// [] (no errors)
|
|
43
|
+
|
|
44
|
+
// Get variables used in a formula
|
|
45
|
+
const vars = getUsedVars('a + b * 2');
|
|
46
|
+
// ['a', 'b']
|
|
47
|
+
|
|
48
|
+
// Evaluate a formula with a variable scope
|
|
49
|
+
const result = evalFormula('a + b * 2', { a: 1, b: 3 });
|
|
50
|
+
// 7
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
### `getNumberRegExp(language, includeNegativeNumbers?, allowEmpty?)`
|
|
56
|
+
|
|
57
|
+
Returns a `RegExp` that validates number strings for the given locale.
|
|
58
|
+
|
|
59
|
+
| Parameter | Type | Default | Description |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| `language` | `'nl' \| 'en' \| 'fr'` | — | Locale for number formatting |
|
|
62
|
+
| `includeNegativeNumbers` | `boolean` | `false` | Allow negative numbers |
|
|
63
|
+
| `allowEmpty` | `boolean` | `true` | Allow empty string |
|
|
64
|
+
|
|
65
|
+
### `validateFormula(expr, allowedVars)`
|
|
66
|
+
|
|
67
|
+
Returns an array of error messages. Empty array means the formula is valid.
|
|
68
|
+
|
|
69
|
+
| Parameter | Type | Description |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `expr` | `string` | Math expression to validate |
|
|
72
|
+
| `allowedVars` | `string[]` | Variable names allowed in the expression |
|
|
73
|
+
|
|
74
|
+
### `getUsedVars(expr)`
|
|
75
|
+
|
|
76
|
+
Returns an array of variable names found in the expression, or an empty array if parsing fails.
|
|
77
|
+
|
|
78
|
+
### `evalFormula(expression, scope)`
|
|
79
|
+
|
|
80
|
+
Evaluates a math expression with the given variable scope and returns the numeric result. Validate the formula first with `validateFormula`.
|
|
81
|
+
|
|
82
|
+
## Module formats
|
|
83
|
+
|
|
84
|
+
This package ships both CommonJS and ESM builds. The correct format is selected automatically based on your project's module system via the `exports` field in `package.json`.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Notes for internal contributers
|
|
89
|
+
This repo is published as a *public* npm package. When publishing a new version, use `npm publish --access public`.
|
|
90
|
+
Don't forget to bump the version number!
|
|
91
|
+
```
|
|
92
|
+
npm version patch # 1.0.0 → 1.0.1
|
|
93
|
+
npm version minor # 1.0.0 → 1.1.0
|
|
94
|
+
npm version major # 1.0.0 → 2.0.0
|
|
95
|
+
|
|
96
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function validateFormula(expr: string, allowedVars: string[]): string[];
|
|
2
|
+
/**
|
|
3
|
+
* Returns list of currently used vars in formula, or null when formula cannot
|
|
4
|
+
* be parsed
|
|
5
|
+
* @param expr
|
|
6
|
+
*/
|
|
7
|
+
export declare function getUsedVars(expr: string): string[];
|
|
8
|
+
/**
|
|
9
|
+
* Evaluate a math expression with a fixed variable scope
|
|
10
|
+
* nb. use validateFormula first
|
|
11
|
+
*/
|
|
12
|
+
export declare function evalFormula(expression: string, scope: Record<string, number>): number;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateFormula = validateFormula;
|
|
4
|
+
exports.getUsedVars = getUsedVars;
|
|
5
|
+
exports.evalFormula = evalFormula;
|
|
6
|
+
const number_1 = require("mathjs/number");
|
|
7
|
+
// Create a minimal math.js instance
|
|
8
|
+
const math = (0, number_1.create)({
|
|
9
|
+
evaluateDependencies: number_1.evaluateDependencies,
|
|
10
|
+
addDependencies: number_1.addDependencies,
|
|
11
|
+
subtractDependencies: number_1.subtractDependencies,
|
|
12
|
+
multiplyDependencies: number_1.multiplyDependencies,
|
|
13
|
+
divideDependencies: number_1.divideDependencies,
|
|
14
|
+
});
|
|
15
|
+
function validateFormula(expr, allowedVars) {
|
|
16
|
+
const errorMessages = [];
|
|
17
|
+
try {
|
|
18
|
+
const vars = new Set(allowedVars);
|
|
19
|
+
const node = math.parse(expr);
|
|
20
|
+
node.traverse((n) => {
|
|
21
|
+
if (n.isSymbolNode && !vars.has(n.name)) {
|
|
22
|
+
errorMessages.push(`'${n.name}' is geen geldige variable`);
|
|
23
|
+
}
|
|
24
|
+
if (n.isOperatorNode && !["+", "-", "*", "/"].includes(n.op)) {
|
|
25
|
+
errorMessages.push(`'${n.op}' is geen geldige operator`);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
errorMessages.push(e.message);
|
|
31
|
+
}
|
|
32
|
+
return errorMessages;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Returns list of currently used vars in formula, or null when formula cannot
|
|
36
|
+
* be parsed
|
|
37
|
+
* @param expr
|
|
38
|
+
*/
|
|
39
|
+
function getUsedVars(expr) {
|
|
40
|
+
try {
|
|
41
|
+
const node = math.parse(expr);
|
|
42
|
+
const vars = [];
|
|
43
|
+
node.traverse((n) => {
|
|
44
|
+
if (n.isSymbolNode) {
|
|
45
|
+
vars.push(n.name);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return vars;
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Evaluate a math expression with a fixed variable scope
|
|
56
|
+
* nb. use validateFormula first
|
|
57
|
+
*/
|
|
58
|
+
function evalFormula(expression, scope) {
|
|
59
|
+
return math.evaluate(expression, scope);
|
|
60
|
+
}
|
package/lib/cjs/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evalFormula = exports.getUsedVars = exports.validateFormula = exports.getNumberRegExp = void 0;
|
|
4
|
+
var reg_exp_1 = require("./reg-exp");
|
|
5
|
+
Object.defineProperty(exports, "getNumberRegExp", { enumerable: true, get: function () { return reg_exp_1.getNumberRegExp; } });
|
|
6
|
+
var formula_1 = require("./formula");
|
|
7
|
+
Object.defineProperty(exports, "validateFormula", { enumerable: true, get: function () { return formula_1.validateFormula; } });
|
|
8
|
+
Object.defineProperty(exports, "getUsedVars", { enumerable: true, get: function () { return formula_1.getUsedVars; } });
|
|
9
|
+
Object.defineProperty(exports, "evalFormula", { enumerable: true, get: function () { return formula_1.evalFormula; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface NumberRegExpOptions {
|
|
2
|
+
useThousandsSeparator: boolean;
|
|
3
|
+
allowDecimalSeparator: boolean;
|
|
4
|
+
thousandsSeparator?: ',' | '.' | ' ';
|
|
5
|
+
decimalSeparator?: '.' | ',';
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Returns a regex that accepts any possible string representation of a positive floating
|
|
9
|
+
* point number for the given language including '0'.
|
|
10
|
+
* The regex does not accept power notations.
|
|
11
|
+
* Negative numbers are optional.
|
|
12
|
+
*
|
|
13
|
+
* Examples NL:
|
|
14
|
+
* 0
|
|
15
|
+
* 0,5
|
|
16
|
+
* 1.000
|
|
17
|
+
* 1.000,25
|
|
18
|
+
*
|
|
19
|
+
* Examples EN:
|
|
20
|
+
* 0
|
|
21
|
+
* 0.5
|
|
22
|
+
* 1,000
|
|
23
|
+
* 1,000.25
|
|
24
|
+
*/
|
|
25
|
+
export declare function getNumberRegExp(language: 'nl' | 'en' | 'fr', includeNegativeNumbers?: boolean, allowEmpty?: boolean): RegExp;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getNumberRegExp = getNumberRegExp;
|
|
4
|
+
/**
|
|
5
|
+
* Returns a regex that accepts any possible string representation of a positive floating
|
|
6
|
+
* point number for the given language including '0'.
|
|
7
|
+
* The regex does not accept power notations.
|
|
8
|
+
* Negative numbers are optional.
|
|
9
|
+
*
|
|
10
|
+
* Examples NL:
|
|
11
|
+
* 0
|
|
12
|
+
* 0,5
|
|
13
|
+
* 1.000
|
|
14
|
+
* 1.000,25
|
|
15
|
+
*
|
|
16
|
+
* Examples EN:
|
|
17
|
+
* 0
|
|
18
|
+
* 0.5
|
|
19
|
+
* 1,000
|
|
20
|
+
* 1,000.25
|
|
21
|
+
*/
|
|
22
|
+
function getNumberRegExp(language, includeNegativeNumbers = false, allowEmpty = true) {
|
|
23
|
+
const variants = [];
|
|
24
|
+
const baseOptions = {
|
|
25
|
+
useThousandsSeparator: true,
|
|
26
|
+
allowDecimalSeparator: true
|
|
27
|
+
};
|
|
28
|
+
if (language === 'en') {
|
|
29
|
+
// English formatting (1,000.50)
|
|
30
|
+
variants.push(buildNumberPattern({ ...baseOptions, thousandsSeparator: ',', decimalSeparator: '.' }));
|
|
31
|
+
// International formatting with space (1 000.50)
|
|
32
|
+
variants.push(buildNumberPattern({ ...baseOptions, thousandsSeparator: ' ', decimalSeparator: '.' }));
|
|
33
|
+
// Without thousands separator (1000.50)
|
|
34
|
+
variants.push(buildNumberPattern({ ...baseOptions, useThousandsSeparator: false, decimalSeparator: '.' }));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Dutch / French formatting (1.000,50)
|
|
38
|
+
variants.push(buildNumberPattern({ ...baseOptions, thousandsSeparator: '.', decimalSeparator: ',' }));
|
|
39
|
+
// International formatting with space (1 000,50)
|
|
40
|
+
variants.push(buildNumberPattern({ ...baseOptions, thousandsSeparator: ' ', decimalSeparator: ',' }));
|
|
41
|
+
// Without thousands separator (1000,50)
|
|
42
|
+
variants.push(buildNumberPattern({ ...baseOptions, useThousandsSeparator: false, decimalSeparator: ',' }));
|
|
43
|
+
}
|
|
44
|
+
// Combine all allowed variants
|
|
45
|
+
let pattern = variants.join('|');
|
|
46
|
+
// Optionally allow empty string
|
|
47
|
+
if (allowEmpty) {
|
|
48
|
+
pattern += '|';
|
|
49
|
+
}
|
|
50
|
+
// Optionally allow negative numbers
|
|
51
|
+
if (includeNegativeNumbers) {
|
|
52
|
+
pattern = `-?(?:${pattern})`;
|
|
53
|
+
}
|
|
54
|
+
// Add start and end anchors
|
|
55
|
+
return new RegExp(`^(?:${pattern})$`);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Builds a regex fragment for a single number formatting configuration.
|
|
59
|
+
*
|
|
60
|
+
* Integer part:
|
|
61
|
+
* - Allows '0'
|
|
62
|
+
* - Prevents leading zeros like '01'
|
|
63
|
+
* - Allows properly grouped thousands if enabled
|
|
64
|
+
*
|
|
65
|
+
* Fraction part:
|
|
66
|
+
* - Optional
|
|
67
|
+
* - Decimal separator followed by one or more digits
|
|
68
|
+
*/
|
|
69
|
+
function buildNumberPattern(options) {
|
|
70
|
+
let integerPart = '';
|
|
71
|
+
if (options.useThousandsSeparator && options.thousandsSeparator) {
|
|
72
|
+
// first digit is 1-9 (unless number is exactly 0)
|
|
73
|
+
// then maximum two digits (first group)
|
|
74
|
+
// followed by one or more groups of 3 digits
|
|
75
|
+
// preceded by the thousands separator
|
|
76
|
+
switch (options.thousandsSeparator) {
|
|
77
|
+
case '.':
|
|
78
|
+
integerPart = '(0|[1-9]\\d{0,2}(?:\\.\\d{3})+|[1-9]\\d*)';
|
|
79
|
+
break;
|
|
80
|
+
case ',':
|
|
81
|
+
integerPart = '(0|[1-9]\\d{0,2}(?:,\\d{3})+|[1-9]\\d*)';
|
|
82
|
+
break;
|
|
83
|
+
case ' ':
|
|
84
|
+
integerPart = '(0|[1-9]\\d{0,2}(?:\\s\\d{3})+|[1-9]\\d*)';
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// no thousands separator
|
|
90
|
+
// first position cannot be 0 unless number is exactly 0
|
|
91
|
+
// remaining positions can be any digit
|
|
92
|
+
integerPart = '(0|[1-9]\\d*)';
|
|
93
|
+
}
|
|
94
|
+
let fractionPart = '';
|
|
95
|
+
if (options.allowDecimalSeparator && options.decimalSeparator) {
|
|
96
|
+
// optional fraction group
|
|
97
|
+
// decimal separator followed by one or more digits
|
|
98
|
+
switch (options.decimalSeparator) {
|
|
99
|
+
case '.':
|
|
100
|
+
fractionPart = '(?:\\.\\d+)?';
|
|
101
|
+
break;
|
|
102
|
+
case ',':
|
|
103
|
+
fractionPart = '(?:,\\d+)?';
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return integerPart + fractionPart;
|
|
108
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function validateFormula(expr: string, allowedVars: string[]): string[];
|
|
2
|
+
/**
|
|
3
|
+
* Returns list of currently used vars in formula, or null when formula cannot
|
|
4
|
+
* be parsed
|
|
5
|
+
* @param expr
|
|
6
|
+
*/
|
|
7
|
+
export declare function getUsedVars(expr: string): string[];
|
|
8
|
+
/**
|
|
9
|
+
* Evaluate a math expression with a fixed variable scope
|
|
10
|
+
* nb. use validateFormula first
|
|
11
|
+
*/
|
|
12
|
+
export declare function evalFormula(expression: string, scope: Record<string, number>): number;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { create, evaluateDependencies, addDependencies, subtractDependencies, multiplyDependencies, divideDependencies,
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
} from 'mathjs/number';
|
|
4
|
+
// Create a minimal math.js instance
|
|
5
|
+
const math = create({
|
|
6
|
+
evaluateDependencies,
|
|
7
|
+
addDependencies,
|
|
8
|
+
subtractDependencies,
|
|
9
|
+
multiplyDependencies,
|
|
10
|
+
divideDependencies,
|
|
11
|
+
});
|
|
12
|
+
export function validateFormula(expr, allowedVars) {
|
|
13
|
+
const errorMessages = [];
|
|
14
|
+
try {
|
|
15
|
+
const vars = new Set(allowedVars);
|
|
16
|
+
const node = math.parse(expr);
|
|
17
|
+
node.traverse((n) => {
|
|
18
|
+
if (n.isSymbolNode && !vars.has(n.name)) {
|
|
19
|
+
errorMessages.push(`'${n.name}' is geen geldige variable`);
|
|
20
|
+
}
|
|
21
|
+
if (n.isOperatorNode && !["+", "-", "*", "/"].includes(n.op)) {
|
|
22
|
+
errorMessages.push(`'${n.op}' is geen geldige operator`);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
errorMessages.push(e.message);
|
|
28
|
+
}
|
|
29
|
+
return errorMessages;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns list of currently used vars in formula, or null when formula cannot
|
|
33
|
+
* be parsed
|
|
34
|
+
* @param expr
|
|
35
|
+
*/
|
|
36
|
+
export function getUsedVars(expr) {
|
|
37
|
+
try {
|
|
38
|
+
const node = math.parse(expr);
|
|
39
|
+
const vars = [];
|
|
40
|
+
node.traverse((n) => {
|
|
41
|
+
if (n.isSymbolNode) {
|
|
42
|
+
vars.push(n.name);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return vars;
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Evaluate a math expression with a fixed variable scope
|
|
53
|
+
* nb. use validateFormula first
|
|
54
|
+
*/
|
|
55
|
+
export function evalFormula(expression, scope) {
|
|
56
|
+
return math.evaluate(expression, scope);
|
|
57
|
+
}
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface NumberRegExpOptions {
|
|
2
|
+
useThousandsSeparator: boolean;
|
|
3
|
+
allowDecimalSeparator: boolean;
|
|
4
|
+
thousandsSeparator?: ',' | '.' | ' ';
|
|
5
|
+
decimalSeparator?: '.' | ',';
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Returns a regex that accepts any possible string representation of a positive floating
|
|
9
|
+
* point number for the given language including '0'.
|
|
10
|
+
* The regex does not accept power notations.
|
|
11
|
+
* Negative numbers are optional.
|
|
12
|
+
*
|
|
13
|
+
* Examples NL:
|
|
14
|
+
* 0
|
|
15
|
+
* 0,5
|
|
16
|
+
* 1.000
|
|
17
|
+
* 1.000,25
|
|
18
|
+
*
|
|
19
|
+
* Examples EN:
|
|
20
|
+
* 0
|
|
21
|
+
* 0.5
|
|
22
|
+
* 1,000
|
|
23
|
+
* 1,000.25
|
|
24
|
+
*/
|
|
25
|
+
export declare function getNumberRegExp(language: 'nl' | 'en' | 'fr', includeNegativeNumbers?: boolean, allowEmpty?: boolean): RegExp;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a regex that accepts any possible string representation of a positive floating
|
|
3
|
+
* point number for the given language including '0'.
|
|
4
|
+
* The regex does not accept power notations.
|
|
5
|
+
* Negative numbers are optional.
|
|
6
|
+
*
|
|
7
|
+
* Examples NL:
|
|
8
|
+
* 0
|
|
9
|
+
* 0,5
|
|
10
|
+
* 1.000
|
|
11
|
+
* 1.000,25
|
|
12
|
+
*
|
|
13
|
+
* Examples EN:
|
|
14
|
+
* 0
|
|
15
|
+
* 0.5
|
|
16
|
+
* 1,000
|
|
17
|
+
* 1,000.25
|
|
18
|
+
*/
|
|
19
|
+
export function getNumberRegExp(language, includeNegativeNumbers = false, allowEmpty = true) {
|
|
20
|
+
const variants = [];
|
|
21
|
+
const baseOptions = {
|
|
22
|
+
useThousandsSeparator: true,
|
|
23
|
+
allowDecimalSeparator: true
|
|
24
|
+
};
|
|
25
|
+
if (language === 'en') {
|
|
26
|
+
// English formatting (1,000.50)
|
|
27
|
+
variants.push(buildNumberPattern({ ...baseOptions, thousandsSeparator: ',', decimalSeparator: '.' }));
|
|
28
|
+
// International formatting with space (1 000.50)
|
|
29
|
+
variants.push(buildNumberPattern({ ...baseOptions, thousandsSeparator: ' ', decimalSeparator: '.' }));
|
|
30
|
+
// Without thousands separator (1000.50)
|
|
31
|
+
variants.push(buildNumberPattern({ ...baseOptions, useThousandsSeparator: false, decimalSeparator: '.' }));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Dutch / French formatting (1.000,50)
|
|
35
|
+
variants.push(buildNumberPattern({ ...baseOptions, thousandsSeparator: '.', decimalSeparator: ',' }));
|
|
36
|
+
// International formatting with space (1 000,50)
|
|
37
|
+
variants.push(buildNumberPattern({ ...baseOptions, thousandsSeparator: ' ', decimalSeparator: ',' }));
|
|
38
|
+
// Without thousands separator (1000,50)
|
|
39
|
+
variants.push(buildNumberPattern({ ...baseOptions, useThousandsSeparator: false, decimalSeparator: ',' }));
|
|
40
|
+
}
|
|
41
|
+
// Combine all allowed variants
|
|
42
|
+
let pattern = variants.join('|');
|
|
43
|
+
// Optionally allow empty string
|
|
44
|
+
if (allowEmpty) {
|
|
45
|
+
pattern += '|';
|
|
46
|
+
}
|
|
47
|
+
// Optionally allow negative numbers
|
|
48
|
+
if (includeNegativeNumbers) {
|
|
49
|
+
pattern = `-?(?:${pattern})`;
|
|
50
|
+
}
|
|
51
|
+
// Add start and end anchors
|
|
52
|
+
return new RegExp(`^(?:${pattern})$`);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Builds a regex fragment for a single number formatting configuration.
|
|
56
|
+
*
|
|
57
|
+
* Integer part:
|
|
58
|
+
* - Allows '0'
|
|
59
|
+
* - Prevents leading zeros like '01'
|
|
60
|
+
* - Allows properly grouped thousands if enabled
|
|
61
|
+
*
|
|
62
|
+
* Fraction part:
|
|
63
|
+
* - Optional
|
|
64
|
+
* - Decimal separator followed by one or more digits
|
|
65
|
+
*/
|
|
66
|
+
function buildNumberPattern(options) {
|
|
67
|
+
let integerPart = '';
|
|
68
|
+
if (options.useThousandsSeparator && options.thousandsSeparator) {
|
|
69
|
+
// first digit is 1-9 (unless number is exactly 0)
|
|
70
|
+
// then maximum two digits (first group)
|
|
71
|
+
// followed by one or more groups of 3 digits
|
|
72
|
+
// preceded by the thousands separator
|
|
73
|
+
switch (options.thousandsSeparator) {
|
|
74
|
+
case '.':
|
|
75
|
+
integerPart = '(0|[1-9]\\d{0,2}(?:\\.\\d{3})+|[1-9]\\d*)';
|
|
76
|
+
break;
|
|
77
|
+
case ',':
|
|
78
|
+
integerPart = '(0|[1-9]\\d{0,2}(?:,\\d{3})+|[1-9]\\d*)';
|
|
79
|
+
break;
|
|
80
|
+
case ' ':
|
|
81
|
+
integerPart = '(0|[1-9]\\d{0,2}(?:\\s\\d{3})+|[1-9]\\d*)';
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// no thousands separator
|
|
87
|
+
// first position cannot be 0 unless number is exactly 0
|
|
88
|
+
// remaining positions can be any digit
|
|
89
|
+
integerPart = '(0|[1-9]\\d*)';
|
|
90
|
+
}
|
|
91
|
+
let fractionPart = '';
|
|
92
|
+
if (options.allowDecimalSeparator && options.decimalSeparator) {
|
|
93
|
+
// optional fraction group
|
|
94
|
+
// decimal separator followed by one or more digits
|
|
95
|
+
switch (options.decimalSeparator) {
|
|
96
|
+
case '.':
|
|
97
|
+
fractionPart = '(?:\\.\\d+)?';
|
|
98
|
+
break;
|
|
99
|
+
case ',':
|
|
100
|
+
fractionPart = '(?:,\\d+)?';
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return integerPart + fractionPart;
|
|
105
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flolegal-it/numbers",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Contains utility functions for working with numbers",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/FLO-Legal-IT/numbers.git"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"number",
|
|
11
|
+
"regex"
|
|
12
|
+
],
|
|
13
|
+
"author": "FloLegal IT",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"main": "lib/cjs/index.js",
|
|
16
|
+
"module": "lib/esm/index.js",
|
|
17
|
+
"types": "lib/cjs/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./lib/cjs/index.d.ts",
|
|
21
|
+
"import": "./lib/esm/index.js",
|
|
22
|
+
"require": "./lib/cjs/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"lib"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "rm -rf lib && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && echo '{\"type\":\"commonjs\"}' > lib/cjs/package.json && echo '{\"type\":\"module\"}' > lib/esm/package.json",
|
|
30
|
+
"test": "ts-node node_modules/.bin/tape 'src/**/*.test.ts'",
|
|
31
|
+
"prepublishOnly": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"mathjs": "^15.1.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/tape": "^5.8.1",
|
|
38
|
+
"tape": "^5.9.0",
|
|
39
|
+
"ts-node": "^10.9.2",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
}
|
|
42
|
+
}
|