@schukai/monster 4.11.1 → 4.13.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.
@@ -15,7 +15,7 @@
15
15
  export { generateRangeComparisonExpression };
16
16
 
17
17
  /**
18
- * The `generateRangeComparisonExpression()` function is function that generates a string representation
18
+ * The `generateRangeComparisonExpression()` function is a function that generates a string representation
19
19
  * of a comparison expression based on a range of values. It takes three arguments:
20
20
  *
21
21
  * - expression (required): a string representation of a range of values in the format of start1-end1,start2-end2,value3....
@@ -33,6 +33,8 @@ export { generateRangeComparisonExpression };
33
33
  * eqOp (string, default: '=='): the equality operator to use in the expression.
34
34
  * geOp (string, default: '>='): the greater than or equal to operator to use in the expression.
35
35
  * leOp (string, default: '<='): the less than or equal to operator to use in the expression.
36
+ * gtOp (string, default: '>'): the greater than operator to use in the expression.
37
+ * ltOp (string, default: '<'): the less than operator to use in the expression.
36
38
  *
37
39
  * Examples
38
40
  *
@@ -58,10 +60,16 @@ export { generateRangeComparisonExpression };
58
60
  * @param {string} [options.eqOp='=='] - The comparison operator for equality to use.
59
61
  * @param {string} [options.geOp='>='] - The comparison operator for greater than or equal to to use.
60
62
  * @param {string} [options.leOp='<='] - The comparison operator for less than or equal to to use.
63
+ * @param {string} [options.gtOp='>'] - The comparison operator for greater than to use.
64
+ * @param {string} [options.ltOp='<'] - The comparison operator for less than to use.
61
65
  * @return {string} The generated comparison expression.
62
66
  * @throws {Error} If the input is invalid.
63
67
  * @summary Generates a comparison expression based on a range of values.
64
68
  */
69
+ /**
70
+ * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
71
+ * SPDX-License-Identifier: AGPL-3.0
72
+ */
65
73
  function generateRangeComparisonExpression(
66
74
  expression,
67
75
  valueName,
@@ -74,48 +82,110 @@ function generateRangeComparisonExpression(
74
82
  eqOp = "==",
75
83
  geOp = ">=",
76
84
  leOp = "<=",
85
+ gtOp = ">",
86
+ ltOp = "<",
77
87
  } = options;
78
- const ranges = expression.split(",");
79
- let comparison = "";
80
- for (let i = 0; i < ranges.length; i++) {
81
- const range = ranges[i].trim();
82
- if (range === "") {
88
+
89
+ if (
90
+ typeof expression !== "string" ||
91
+ typeof valueName !== "string" ||
92
+ !expression.trim()
93
+ ) {
94
+ throw new Error("Invalid input");
95
+ }
96
+
97
+ const encode = (s) => (urlEncode ? encodeURIComponent(s) : s);
98
+ const and = encode(andOp);
99
+ const or = encode(orOp);
100
+ const space = urlEncode ? "%20" : " ";
101
+
102
+ function parseRange(range) {
103
+ range = range.trim();
104
+ if (!range) throw new Error("Empty range");
105
+
106
+ // Sonderfall: nur Bindestriche wie "--"
107
+ if (/^-+$/.test(range)) {
83
108
  throw new Error(`Invalid range '${range}'`);
84
- } else if (range.includes("-")) {
85
- const [start, end] = range
86
- .split("-")
87
- .map((s) => (s === "" ? null : parseFloat(s)));
88
- if ((start !== null && isNaN(start)) || (end !== null && isNaN(end))) {
89
- throw new Error(`Invalid value in range '${range}'`);
90
- }
91
- if (start !== null && end !== null && start > end) {
92
- throw new Error(`Invalid range '${range}'`);
93
- }
94
- const compStart =
95
- start !== null
96
- ? `${valueName}${urlEncode ? encodeURIComponent(geOp) : geOp}${start}`
97
- : "";
98
- const compEnd =
99
- end !== null
100
- ? `${valueName}${urlEncode ? encodeURIComponent(leOp) : leOp}${end}`
101
- : "";
102
- const compRange = `${compStart}${
103
- compStart && compEnd ? ` ${andOp} ` : ""
104
- }${compEnd}`;
105
- comparison += ranges.length > 1 ? `(${compRange})` : compRange;
106
- } else {
107
- const value = parseFloat(range);
108
- if (isNaN(value)) {
109
+ }
110
+
111
+ // Bereich: z.B. -10--5, 4-6
112
+ const rangeMatch = range.match(
113
+ /^(-?\d+(?:\.\d+)?)\s*-\s*(-?\d+(?:\.\d+)?)/,
114
+ );
115
+ if (rangeMatch) {
116
+ const sNum = parseFloat(rangeMatch[1]);
117
+ const eNum = parseFloat(rangeMatch[2]);
118
+ if (isNaN(sNum) || isNaN(eNum))
109
119
  throw new Error(`Invalid value '${range}'`);
110
- }
111
- const compValue = `${valueName}${
112
- urlEncode ? encodeURIComponent(eqOp) : eqOp
113
- }${value}`;
114
- comparison += ranges.length > 1 ? `(${compValue})` : compValue;
120
+ if (sNum > eNum) throw new Error(`Invalid range '${range}'`);
121
+ return `${valueName}${encode(geOp)}${sNum}${space}${and}${space}${valueName}${encode(leOp)}${eNum}`;
115
122
  }
116
- if (i < ranges.length - 1) {
117
- comparison += ` ${orOp} `;
123
+
124
+ // Exklusiver Bereich: 4~6 → x>4 && x<6
125
+ const exclMatch = range.match(/^(-?\d+(?:\.\d+)?)\s*~\s*(-?\d+(?:\.\d+)?)/);
126
+ if (exclMatch) {
127
+ const sNum = parseFloat(exclMatch[1]);
128
+ const eNum = parseFloat(exclMatch[2]);
129
+ if (isNaN(sNum) || isNaN(eNum))
130
+ throw new Error(`Invalid value '${range}'`);
131
+ return `${valueName}${encode(gtOp)}${sNum}${space}${and}${space}${valueName}${encode(ltOp)}${eNum}`;
118
132
  }
133
+
134
+ // Offene Intervalle exklusiv: 4~ → x>4, ~6 → x<6
135
+ const openRightExclMatch = range.match(/^(-?\d+(?:\.\d+)?)~$/);
136
+ if (openRightExclMatch) {
137
+ const sNum = parseFloat(openRightExclMatch[1]);
138
+ if (isNaN(sNum)) throw new Error(`Invalid value in '${range}'`);
139
+ return `${valueName}${encode(gtOp)}${sNum}`;
140
+ }
141
+ const openLeftExclMatch = range.match(/^~(-?\d+(?:\.\d+)?)$/);
142
+ if (openLeftExclMatch) {
143
+ const eNum = parseFloat(openLeftExclMatch[1]);
144
+ if (isNaN(eNum)) throw new Error(`Invalid value in '${range}'`);
145
+ return `${valueName}${encode(ltOp)}${eNum}`;
146
+ }
147
+
148
+ // Offene Intervalle inklusiv: 4- → x>=4, -6 → x<=6
149
+ const openRightInclMatch = range.match(/^(-?\d+(?:\.\d+)?)\-$/);
150
+ if (openRightInclMatch) {
151
+ const sNum = parseFloat(openRightInclMatch[1]);
152
+ if (isNaN(sNum)) throw new Error(`Invalid value in '${range}'`);
153
+ return `${valueName}${encode(geOp)}${sNum}`;
154
+ }
155
+ const openLeftInclMatch = range.match(/^\-(-?\d+(?:\.\d+)?)$/);
156
+ if (openLeftInclMatch) {
157
+ const eNum = parseFloat(openLeftInclMatch[1]);
158
+ if (isNaN(eNum)) throw new Error(`Invalid value in '${range}'`);
159
+ return `${valueName}${encode(leOp)}${eNum}`;
160
+ }
161
+
162
+ // <n oder >n
163
+ if (range.startsWith("<")) {
164
+ const n = parseFloat(range.slice(1));
165
+ if (isNaN(n)) throw new Error(`Invalid value in '${range}'`);
166
+ return `${valueName}${encode(ltOp)}${n}`;
167
+ }
168
+ if (range.startsWith(">")) {
169
+ const n = parseFloat(range.slice(1));
170
+ if (isNaN(n)) throw new Error(`Invalid value in '${range}'`);
171
+ return `${valueName}${encode(gtOp)}${n}`;
172
+ }
173
+
174
+ // Einzelwert NUR, wenn wirklich reine Zahl
175
+ if (/^-?\d+(\.\d+)?$/.test(range)) {
176
+ const value = parseFloat(range);
177
+ return `${valueName}${encode(eqOp)}${value}`;
178
+ }
179
+
180
+ throw new Error(`Invalid value '${range}'`);
119
181
  }
120
- return comparison;
182
+
183
+ const parts = expression
184
+ .split(",")
185
+ .map((r) => r.trim())
186
+ .filter(Boolean)
187
+ .map(parseRange);
188
+
189
+ const sep = space + or + space;
190
+ return parts.length > 1 ? parts.map((p) => `(${p})`).join(sep) : parts[0];
121
191
  }
@@ -156,7 +156,7 @@ function getMonsterVersion() {
156
156
  }
157
157
 
158
158
  /** don't touch, replaced by make with package.json version */
159
- monsterVersion = new Version("4.11.0");
159
+ monsterVersion = new Version("4.11.1");
160
160
 
161
161
  return monsterVersion;
162
162
  }
@@ -7,7 +7,7 @@ describe('Monster', function () {
7
7
  let monsterVersion
8
8
 
9
9
  /** don´t touch, replaced by make with package.json version */
10
- monsterVersion = new Version("4.11.0")
10
+ monsterVersion = new Version("4.11.1")
11
11
 
12
12
  let m = getMonsterVersion();
13
13
 
@@ -0,0 +1,94 @@
1
+ import { expect } from "chai";
2
+ import { generateRangeComparisonExpression } from "../../../source/text/generate-range-comparison-expression.mjs";
3
+
4
+ describe('generateRangeComparisonExpression', function () {
5
+
6
+ it('soll Einzelwerte korrekt behandeln', function () {
7
+ expect(generateRangeComparisonExpression("5", "x")).to.equal("x==5");
8
+ expect(generateRangeComparisonExpression("0", "age")).to.equal("age==0");
9
+ expect(generateRangeComparisonExpression("-3", "score")).to.equal("score<=3");
10
+ expect(generateRangeComparisonExpression("7.5", "n")).to.equal("n==7.5");
11
+ });
12
+
13
+ it('soll einfache inklusive Ranges korrekt behandeln', function () {
14
+ expect(generateRangeComparisonExpression("4-6", "x")).to.equal("x>=4 && x<=6");
15
+ expect(generateRangeComparisonExpression("10-15", "a")).to.equal("a>=10 && a<=15");
16
+ expect(generateRangeComparisonExpression("-5-5", "y")).to.equal("y>=-5 && y<=5");
17
+ expect(generateRangeComparisonExpression("4.1-6.2", "x")).to.equal("x>=4.1 && x<=6.2");
18
+ });
19
+
20
+ it('soll exklusive Ranges (mit ~) korrekt behandeln', function () {
21
+ expect(generateRangeComparisonExpression("4~6", "x")).to.equal("x>4 && x<6");
22
+ expect(generateRangeComparisonExpression("10~15", "a")).to.equal("a>10 && a<15");
23
+ expect(generateRangeComparisonExpression("-4~4", "y")).to.equal("y>-4 && y<4");
24
+ });
25
+
26
+ it('soll offene Intervalle links korrekt behandeln', function () {
27
+ expect(generateRangeComparisonExpression("-6", "x")).to.equal("x<=6");
28
+ expect(generateRangeComparisonExpression("-2", "y")).to.equal("y<=2");
29
+ expect(generateRangeComparisonExpression(" -3 ", "z")).to.equal("z<=3");
30
+ });
31
+
32
+ it('soll offene Intervalle rechts korrekt behandeln', function () {
33
+ expect(generateRangeComparisonExpression("4-", "x")).to.equal("x>=4");
34
+ expect(generateRangeComparisonExpression("10-", "z")).to.equal("z>=10");
35
+ expect(generateRangeComparisonExpression(" -5- ", "y")).to.equal("y>=-5");
36
+ });
37
+
38
+ it('soll die Operatoren < und > korrekt behandeln', function () {
39
+ expect(generateRangeComparisonExpression(">4", "x")).to.equal("x>4");
40
+ expect(generateRangeComparisonExpression("<6", "x")).to.equal("x<6");
41
+ expect(generateRangeComparisonExpression(">-4.2", "n")).to.equal("n>-4.2");
42
+ expect(generateRangeComparisonExpression("--2", "x")).to.equal("x<=-2");
43
+ });
44
+
45
+ it('soll mehrere Bedingungen per Komma ODER-verknüpfen', function () {
46
+ expect(generateRangeComparisonExpression("4-6,10,20~25", "x"))
47
+ .to.equal("(x>=4 && x<=6) || (x==10) || (x>20 && x<25)");
48
+ expect(generateRangeComparisonExpression("0,5-10", "y"))
49
+ .to.equal("(y==0) || (y>=5 && y<=10)");
50
+ expect(generateRangeComparisonExpression(">1,<3", "z"))
51
+ .to.equal("(z>1) || (z<3)");
52
+ });
53
+
54
+ it('soll urlEncode korrekt unterstützen', function () {
55
+ expect(generateRangeComparisonExpression("4-6", "x", { urlEncode: true }))
56
+ .to.equal("x%3E%3D4%20%26%26%20x%3C%3D6");
57
+ expect(generateRangeComparisonExpression("10", "n", { urlEncode: true }))
58
+ .to.equal("n%3D%3D10");
59
+ });
60
+
61
+ it('soll Fehler werfen bei falschen Eingaben', function () {
62
+ expect(() => generateRangeComparisonExpression("", "x")).to.throw("Invalid input");
63
+ expect(() => generateRangeComparisonExpression("abc", "x")).to.throw("Invalid value 'abc'");
64
+ expect(() => generateRangeComparisonExpression("4-3", "x")).to.throw("Invalid range '4-3'");
65
+ expect(() => generateRangeComparisonExpression("4-foo", "x")).to.throw("Invalid value '4-foo'");
66
+ expect(() => generateRangeComparisonExpression("4~foo", "x")).to.throw("Invalid value '4~foo'");
67
+ expect(() => generateRangeComparisonExpression(">", "x")).to.throw("Invalid value in '>'");
68
+ expect(() => generateRangeComparisonExpression("<", "x")).to.throw("Invalid value in '<'");
69
+ expect(() => generateRangeComparisonExpression("--", "x")).to.throw("Invalid range '--'");
70
+ expect(() => generateRangeComparisonExpression("-foo", "x")).to.throw("Invalid value '-foo'");
71
+ expect(() => generateRangeComparisonExpression("foo-", "x")).to.throw("Invalid value 'foo-'");
72
+ expect(() => generateRangeComparisonExpression("5~", "x")).to.not.throw();
73
+ expect(() => generateRangeComparisonExpression("~5", "x")).to.not.throw();
74
+ expect(() => generateRangeComparisonExpression(">", "")).to.throw("Invalid value in '>'");
75
+ expect(() => generateRangeComparisonExpression("1,,2", "x")).not.to.throw();
76
+ expect(() => generateRangeComparisonExpression(",", "x")).to.not.throw();
77
+ });
78
+
79
+ it('soll Leerzeichen robust behandeln', function () {
80
+ expect(generateRangeComparisonExpression(" 4 - 6 , 8 ", "x"))
81
+ .to.equal("(x>=4 && x<=6) || (x==8)");
82
+ expect(generateRangeComparisonExpression(" -10--5 ", "temp"))
83
+ .to.equal("temp>=-10 && temp<=-5");
84
+ });
85
+
86
+ it('soll negative Werte korrekt behandeln', function () {
87
+ expect(generateRangeComparisonExpression("-10--5", "temp"))
88
+ .to.equal("temp>=-10 && temp<=-5");
89
+ expect(generateRangeComparisonExpression("> -3", "z")).to.equal("z>-3");
90
+ expect(generateRangeComparisonExpression(" -4~4 ", "y")).to.equal("y>-4 && y<4");
91
+ expect(generateRangeComparisonExpression("-5", "x")).to.equal("x<=5");
92
+
93
+ });
94
+ });
@@ -55,7 +55,7 @@ describe('generateRangeComparisonExpression', () => {
55
55
  {
56
56
  expression: '1,3,5',
57
57
  valueName: 'x',
58
- expected: '(x%3D%3D1) || (x%3D%3D3) || (x%3D%3D5)',
58
+ expected: '(x%3D%3D1)%20%7C%7C%20(x%3D%3D3)%20%7C%7C%20(x%3D%3D5)',
59
59
  },
60
60
  {
61
61
  expression: '-10',
@@ -70,12 +70,12 @@ describe('generateRangeComparisonExpression', () => {
70
70
  {
71
71
  expression: '1-3,6-8',
72
72
  valueName: 'y',
73
- expected: '(y%3E%3D1 && y%3C%3D3) || (y%3E%3D6 && y%3C%3D8)',
73
+ expected: '(y%3E%3D1%20%26%26%20y%3C%3D3)%20%7C%7C%20(y%3E%3D6%20%26%26%20y%3C%3D8)',
74
74
  },
75
75
  {
76
76
  expression: '1-3,5,7-9',
77
77
  valueName: 'z',
78
- expected: '(z%3E%3D1 && z%3C%3D3) || (z%3D%3D5) || (z%3E%3D7 && z%3C%3D9)',
78
+ expected: '(z%3E%3D1%20%26%26%20z%3C%3D3)%20%7C%7C%20(z%3D%3D5)%20%7C%7C%20(z%3E%3D7%20%26%26%20z%3C%3D9)',
79
79
  },
80
80
  ];
81
81
 
@@ -19,6 +19,7 @@ import "../cases/components/host/overlay.mjs";
19
19
  import "../cases/components/host/util.mjs";
20
20
  import "../cases/components/host/details.mjs";
21
21
  import "../cases/text/formatter.mjs";
22
+ import "../cases/text/generate-range-comparison-expression.mjs";
22
23
  import "../cases/text/util.mjs";
23
24
  import "../cases/text/bracketed-key-value-hash.mjs";
24
25
  import "../cases/math/random.mjs";
@@ -9,8 +9,8 @@
9
9
  </head>
10
10
  <body>
11
11
  <div id="headline" style="display: flex;align-items: center;justify-content: center;flex-direction: column;">
12
- <h1 style='margin-bottom: 0.1em;'>Monster 4.11.0</h1>
13
- <div id="lastupdate" style='font-size:0.7em'>last update Di 20. Mai 21:11:43 CEST 2025</div>
12
+ <h1 style='margin-bottom: 0.1em;'>Monster 4.11.1</h1>
13
+ <div id="lastupdate" style='font-size:0.7em'>last update Do 29. Mai 13:24:01 CEST 2025</div>
14
14
  </div>
15
15
  <div id="mocha-errors"
16
16
  style="color: red;font-weight: bold;display: flex;align-items: center;justify-content: center;flex-direction: column;margin:20px;"></div>