@schukai/monster 3.35.4 → 3.37.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/package.json +1 -1
- package/source/dom/util.mjs +46 -1
- package/source/text/bracketed-key-value-hash.mjs +245 -0
- package/source/text/generate-range-comparison-expression.mjs +93 -0
- package/source/text/util.mjs +1 -85
- package/source/types/version.mjs +1 -1
- package/test/cases/dom/util.mjs +60 -0
- package/test/cases/monster.mjs +1 -1
- package/test/cases/text/bracketed-key-value-hash.mjs +214 -0
- package/test/util/jsdom.mjs +1 -1
- package/test/web/test.html +2 -2
- package/test/web/tests.js +214 -50
package/package.json
CHANGED
package/source/dom/util.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { getGlobal } from "../types/global.mjs";
|
|
9
9
|
import { validateString } from "../types/validate.mjs";
|
|
10
10
|
|
|
11
|
-
export { getDocument, getWindow, getDocumentFragmentFromString, findElementWithIdUpwards };
|
|
11
|
+
export { getDocument, getWindow, getDocumentFragmentFromString, findElementWithIdUpwards,getContainingDocument };
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* This method fetches the document object
|
|
@@ -199,3 +199,48 @@ function findElementWithIdUpwards(element, targetId) {
|
|
|
199
199
|
// Otherwise, search the current element's parent
|
|
200
200
|
return findElementWithIdUpwards(element.parentElement, targetId);
|
|
201
201
|
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @private
|
|
205
|
+
* @param {HTMLElement} element
|
|
206
|
+
* @returns {HTMLElement|null}
|
|
207
|
+
*/
|
|
208
|
+
function traverseShadowRoots(element) {
|
|
209
|
+
let currentRoot = element.shadowRoot;
|
|
210
|
+
let currentParent = element.parentNode;
|
|
211
|
+
|
|
212
|
+
while (currentParent && currentParent.nodeType !== Node.DOCUMENT_NODE && currentParent.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
|
|
213
|
+
if (currentRoot && currentRoot.parentNode) {
|
|
214
|
+
currentParent = currentRoot.parentNode;
|
|
215
|
+
currentRoot = currentParent.shadowRoot;
|
|
216
|
+
} else if (currentParent.parentNode) {
|
|
217
|
+
currentParent = currentParent.parentNode;
|
|
218
|
+
currentRoot = null;
|
|
219
|
+
} else if (currentRoot && currentRoot.host && currentRoot.host.nodeType === Node.DOCUMENT_NODE) {
|
|
220
|
+
currentParent = currentRoot.host;
|
|
221
|
+
currentRoot = null;
|
|
222
|
+
} else {
|
|
223
|
+
currentParent = null;
|
|
224
|
+
currentRoot = null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return currentParent;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Recursively searches upwards from a given element to find an ancestor element
|
|
233
|
+
*
|
|
234
|
+
* @param {HTMLElement} element
|
|
235
|
+
* @returns {*}
|
|
236
|
+
* @throws {Error} Invalid argument. Expected an HTMLElement.
|
|
237
|
+
* @memberOf Monster.DOM
|
|
238
|
+
* @since 3.36.0
|
|
239
|
+
*/
|
|
240
|
+
function getContainingDocument(element) {
|
|
241
|
+
if (!element || !(element instanceof HTMLElement || element instanceof element.ownerDocument.defaultView.HTMLElement)) {
|
|
242
|
+
throw new Error('Invalid argument. Expected an HTMLElement.');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return traverseShadowRoots(element) || null;
|
|
246
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright schukai GmbH and contributors 2023. All Rights Reserved.
|
|
3
|
+
* Node module: @schukai/monster
|
|
4
|
+
* This file is licensed under the AGPLv3 License.
|
|
5
|
+
* License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {parseBracketedKeyValueHash, createBracketedKeyValueHash}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parses a string containing bracketed key-value pairs and returns an object representing the parsed result.
|
|
12
|
+
*
|
|
13
|
+
* - The string starts with a hash symbol #.
|
|
14
|
+
* - After the hash symbol, there are one or more selector strings, separated by a semicolon ;.
|
|
15
|
+
* - Each selector string has the format selectorName(key1=value1,key2=value2,...).
|
|
16
|
+
* - The selector name is a string of one or more alphanumeric characters.
|
|
17
|
+
* - The key-value pairs are separated by commas , and are of the form key=value.
|
|
18
|
+
* - The key is a string of one or more alphanumeric characters.
|
|
19
|
+
* - The value can be an empty string or a string of one or more characters.
|
|
20
|
+
* - If the value contains commas, it must be enclosed in double quotes ".
|
|
21
|
+
* - The entire key-value pair must be URL-encoded.
|
|
22
|
+
* - The closing parenthesis ) for each selector must be present, even if there are no key-value pairs.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
*
|
|
26
|
+
* ```javascript
|
|
27
|
+
* // Example 1:
|
|
28
|
+
* const hashString = '#selector1(key1=value1,key2=value2);selector2(key3=value3)';
|
|
29
|
+
* const result = parseBracketedKeyValueHash(hashString);
|
|
30
|
+
* // result => { selector1: { key1: "value1", key2: "value2" }, selector2: { key3: "value3" } }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
*
|
|
35
|
+
* ```javascript
|
|
36
|
+
* // Example 2:
|
|
37
|
+
* const hashString = '#selector1(key1=value1,key2=value2);selector2(';
|
|
38
|
+
* const result = parseBracketedKeyValueHash(hashString);
|
|
39
|
+
* // result => {}
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @since 3.37.0
|
|
43
|
+
* @param {string} hashString - The string to parse, containing bracketed key-value pairs.
|
|
44
|
+
* @returns {Object} - An object representing the parsed result, with keys representing the selectors and values representing the key-value pairs associated with each selector.
|
|
45
|
+
* - Returns an empty object if there was an error during parsing. */
|
|
46
|
+
function parseBracketedKeyValueHash(hashString) {
|
|
47
|
+
const selectors = {};
|
|
48
|
+
//const selectorStack = [];
|
|
49
|
+
//const keyValueStack = [];
|
|
50
|
+
|
|
51
|
+
const trimmedHashString = hashString.trim();
|
|
52
|
+
const cleanedHashString = trimmedHashString.charAt(0) === '#' ? trimmedHashString.slice(1) : trimmedHashString;
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
//const selectors = (keyValueStack.length > 0) ? result[selectorStack[selectorStack.length - 1]] : result;
|
|
56
|
+
let currentSelector = "";
|
|
57
|
+
|
|
58
|
+
function addToResult(key, value) {
|
|
59
|
+
if (currentSelector && key) {
|
|
60
|
+
if (!selectors[currentSelector]) {
|
|
61
|
+
selectors[currentSelector] = {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
selectors[currentSelector][key] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let currentKey = '';
|
|
69
|
+
let currentValue = '';
|
|
70
|
+
let inKey = true;
|
|
71
|
+
let inValue = false;
|
|
72
|
+
let inQuotedValue = false;
|
|
73
|
+
let inSelector = true;
|
|
74
|
+
let escaped = false;
|
|
75
|
+
let quotedValueStartChar = '';
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < cleanedHashString.length; i++) {
|
|
78
|
+
const c = cleanedHashString[i];
|
|
79
|
+
const nextChar = cleanedHashString?.[i + 1];
|
|
80
|
+
|
|
81
|
+
if (c === '\\' && !escaped) {
|
|
82
|
+
escaped = true;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (escaped) {
|
|
87
|
+
if (inSelector) {
|
|
88
|
+
currentSelector += c;
|
|
89
|
+
} else if (inKey) {
|
|
90
|
+
currentKey += c;
|
|
91
|
+
} else if (inValue) {
|
|
92
|
+
currentValue += c;
|
|
93
|
+
}
|
|
94
|
+
escaped = false;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (inQuotedValue && quotedValueStartChar !== c) {
|
|
99
|
+
|
|
100
|
+
if (inSelector) {
|
|
101
|
+
currentSelector += c;
|
|
102
|
+
} else if (inKey) {
|
|
103
|
+
currentKey += c;
|
|
104
|
+
} else if (inValue) {
|
|
105
|
+
currentValue += c;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (c === ';' && inSelector) {
|
|
112
|
+
inSelector = true;
|
|
113
|
+
currentSelector = "";
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if (inSelector === true && c !== '(') {
|
|
119
|
+
currentSelector += c;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (c === '(' && inSelector) {
|
|
124
|
+
inSelector = false;
|
|
125
|
+
inKey = true;
|
|
126
|
+
|
|
127
|
+
currentKey = "";
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (inKey === true && c !== '=') {
|
|
132
|
+
currentKey += c;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (c === '=' && inKey) {
|
|
137
|
+
|
|
138
|
+
inKey = false;
|
|
139
|
+
inValue = true;
|
|
140
|
+
|
|
141
|
+
if (nextChar === '"' || nextChar === "'") {
|
|
142
|
+
inQuotedValue = true;
|
|
143
|
+
quotedValueStartChar = nextChar;
|
|
144
|
+
i++;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
currentValue = "";
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (inValue === true) {
|
|
153
|
+
if (inQuotedValue) {
|
|
154
|
+
if (c === quotedValueStartChar) {
|
|
155
|
+
inQuotedValue = false;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
currentValue += c;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (c === ',') {
|
|
164
|
+
inValue = false;
|
|
165
|
+
inKey = true;
|
|
166
|
+
const decodedCurrentValue = decodeURIComponent(currentValue);
|
|
167
|
+
addToResult(currentKey, decodedCurrentValue);
|
|
168
|
+
currentKey = "";
|
|
169
|
+
currentValue = "";
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (c === ')') {
|
|
174
|
+
inValue = false;
|
|
175
|
+
//inKey = true;
|
|
176
|
+
inSelector = true;
|
|
177
|
+
|
|
178
|
+
const decodedCurrentValue = decodeURIComponent(currentValue);
|
|
179
|
+
addToResult(currentKey, decodedCurrentValue);
|
|
180
|
+
currentKey = "";
|
|
181
|
+
currentValue = "";
|
|
182
|
+
currentSelector = "";
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
currentValue += c;
|
|
187
|
+
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if (inSelector) {
|
|
194
|
+
return selectors;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
return {};
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Creates a hash selector string from an object.
|
|
204
|
+
*
|
|
205
|
+
* @param {Object} object - The object containing selectors and key-value pairs.
|
|
206
|
+
* @param {boolean} addHashPrefix - Whether to add the hash prefix # to the beginning of the string.
|
|
207
|
+
* @returns {string} The hash selector string.
|
|
208
|
+
* @since 3.37.0
|
|
209
|
+
*/
|
|
210
|
+
function createBracketedKeyValueHash(object, addHashPrefix = true) {
|
|
211
|
+
|
|
212
|
+
if (!object) {
|
|
213
|
+
return addHashPrefix ? '#' : '';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let hashString = '';
|
|
217
|
+
|
|
218
|
+
function encodeKeyValue(key, value) {
|
|
219
|
+
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const selector in object) {
|
|
223
|
+
if (object.hasOwnProperty(selector)) {
|
|
224
|
+
const keyValuePairs = object[selector];
|
|
225
|
+
let selectorString = selector;
|
|
226
|
+
let keyValueString = '';
|
|
227
|
+
|
|
228
|
+
for (const key in keyValuePairs) {
|
|
229
|
+
if (keyValuePairs.hasOwnProperty(key)) {
|
|
230
|
+
const value = keyValuePairs[key];
|
|
231
|
+
keyValueString += keyValueString.length === 0 ? '' : ',';
|
|
232
|
+
keyValueString += encodeKeyValue(key, value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (keyValueString.length > 0) {
|
|
237
|
+
selectorString += '(' + keyValueString + ')';
|
|
238
|
+
hashString += hashString.length === 0 ? '' : ';';
|
|
239
|
+
hashString += selectorString;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return addHashPrefix ? '#' + hashString : hashString;
|
|
245
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright schukai GmbH and contributors 2023. All Rights Reserved.
|
|
3
|
+
* Node module: @schukai/monster
|
|
4
|
+
* This file is licensed under the AGPLv3 License.
|
|
5
|
+
* License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { generateRangeComparisonExpression };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The `generateRangeComparisonExpression()` function is function that generates a string representation
|
|
12
|
+
* of a comparison expression based on a range of values. It takes three arguments:
|
|
13
|
+
*
|
|
14
|
+
* - expression (required): a string representation of a range of values in the format of start1-end1,start2-end2,value3....
|
|
15
|
+
* - valueName (required): a string representing the name of the value that is being compared to the range of values.
|
|
16
|
+
* - options (optional): an object containing additional options to customize the comparison expression.
|
|
17
|
+
*
|
|
18
|
+
* The generateRangeComparisonExpression() function returns a string representation of the comparison expression.
|
|
19
|
+
*
|
|
20
|
+
* ## Options
|
|
21
|
+
* The options parameter is an object that can have the following properties:
|
|
22
|
+
*
|
|
23
|
+
* urlEncode (boolean, default: false): if set to true, URL encodes the comparison operators.
|
|
24
|
+
* andOp (string, default: '&&'): the logical AND operator to use in the expression.
|
|
25
|
+
* orOp (string, default: '||'): the logical OR operator to use in the expression.
|
|
26
|
+
* eqOp (string, default: '=='): the equality operator to use in the expression.
|
|
27
|
+
* geOp (string, default: '>='): the greater than or equal to operator to use in the expression.
|
|
28
|
+
* leOp (string, default: '<='): the less than or equal to operator to use in the expression.
|
|
29
|
+
*
|
|
30
|
+
* Examples
|
|
31
|
+
*
|
|
32
|
+
* ```javascript
|
|
33
|
+
* const expression = '0-10,20-30';
|
|
34
|
+
* const valueName = 'age';
|
|
35
|
+
* const options = { urlEncode: true, andOp: 'and', orOp: 'or', eqOp: '=', geOp: '>=', leOp: '<=' };
|
|
36
|
+
* const comparisonExpression = generateRangeComparisonExpression(expression, valueName, options);
|
|
37
|
+
*
|
|
38
|
+
* console.log(comparisonExpression); // age%3E%3D0%20and%20age%3C%3D10%20or%20age%3E%3D20%20and%20age%3C%3D30
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* In this example, the generateRangeComparisonExpression() function generates a string representation of the comparison
|
|
42
|
+
* expression for the expression and valueName parameters with the specified options. The resulting comparison
|
|
43
|
+
* expression is 'age>=0 and age<=10 or age>=20 and age<=30', URL encoded according to the urlEncode option.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} expression - The string expression to generate the comparison for.
|
|
46
|
+
* @param {string} valueName - The name of the value to compare against.
|
|
47
|
+
* @param {Object} [options] - The optional parameters.
|
|
48
|
+
* @param {boolean} [options.urlEncode=false] - Whether to encode comparison operators for use in a URL.
|
|
49
|
+
* @param {string} [options.andOp='&&'] - The logical AND operator to use.
|
|
50
|
+
* @param {string} [options.orOp='||'] - The logical OR operator to use.
|
|
51
|
+
* @param {string} [options.eqOp='=='] - The comparison operator for equality to use.
|
|
52
|
+
* @param {string} [options.geOp='>='] - The comparison operator for greater than or equal to to use.
|
|
53
|
+
* @param {string} [options.leOp='<='] - The comparison operator for less than or equal to to use.
|
|
54
|
+
* @returns {string} The generated comparison expression.
|
|
55
|
+
* @throws {Error} If the input is invalid.
|
|
56
|
+
* @memberOf Monster.Text
|
|
57
|
+
* @summary Generates a comparison expression based on a range of values.
|
|
58
|
+
*/
|
|
59
|
+
function generateRangeComparisonExpression(expression, valueName, options = {}) {
|
|
60
|
+
const { urlEncode = false, andOp = "&&", orOp = "||", eqOp = "==", geOp = ">=", leOp = "<=" } = options;
|
|
61
|
+
const ranges = expression.split(",");
|
|
62
|
+
let comparison = "";
|
|
63
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
64
|
+
const range = ranges[i].trim();
|
|
65
|
+
if (range === "") {
|
|
66
|
+
throw new Error(`Invalid range '${range}'`);
|
|
67
|
+
} else if (range.includes("-")) {
|
|
68
|
+
const [start, end] = range.split("-").map((s) => (s === "" ? null : parseFloat(s)));
|
|
69
|
+
if ((start !== null && isNaN(start)) || (end !== null && isNaN(end))) {
|
|
70
|
+
throw new Error(`Invalid value in range '${range}'`);
|
|
71
|
+
}
|
|
72
|
+
if (start !== null && end !== null && start > end) {
|
|
73
|
+
throw new Error(`Invalid range '${range}'`);
|
|
74
|
+
}
|
|
75
|
+
const compStart =
|
|
76
|
+
start !== null ? `${valueName}${urlEncode ? encodeURIComponent(geOp) : geOp}${start}` : "";
|
|
77
|
+
const compEnd = end !== null ? `${valueName}${urlEncode ? encodeURIComponent(leOp) : leOp}${end}` : "";
|
|
78
|
+
const compRange = `${compStart}${compStart && compEnd ? ` ${andOp} ` : ""}${compEnd}`;
|
|
79
|
+
comparison += ranges.length > 1 ? `(${compRange})` : compRange;
|
|
80
|
+
} else {
|
|
81
|
+
const value = parseFloat(range);
|
|
82
|
+
if (isNaN(value)) {
|
|
83
|
+
throw new Error(`Invalid value '${range}'`);
|
|
84
|
+
}
|
|
85
|
+
const compValue = `${valueName}${urlEncode ? encodeURIComponent(eqOp) : eqOp}${value}`;
|
|
86
|
+
comparison += ranges.length > 1 ? `(${compValue})` : compValue;
|
|
87
|
+
}
|
|
88
|
+
if (i < ranges.length - 1) {
|
|
89
|
+
comparison += ` ${orOp} `;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return comparison;
|
|
93
|
+
}
|
package/source/text/util.mjs
CHANGED
|
@@ -1,86 +1,2 @@
|
|
|
1
|
-
export { generateRangeComparisonExpression }
|
|
1
|
+
export { generateRangeComparisonExpression } from "./generate-range-comparison-expression.mjs"
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* The `generateRangeComparisonExpression()` function is function that generates a string representation
|
|
5
|
-
* of a comparison expression based on a range of values. It takes three arguments:
|
|
6
|
-
*
|
|
7
|
-
* - expression (required): a string representation of a range of values in the format of start1-end1,start2-end2,value3....
|
|
8
|
-
* - valueName (required): a string representing the name of the value that is being compared to the range of values.
|
|
9
|
-
* - options (optional): an object containing additional options to customize the comparison expression.
|
|
10
|
-
*
|
|
11
|
-
* The generateRangeComparisonExpression() function returns a string representation of the comparison expression.
|
|
12
|
-
*
|
|
13
|
-
* ## Options
|
|
14
|
-
* The options parameter is an object that can have the following properties:
|
|
15
|
-
*
|
|
16
|
-
* urlEncode (boolean, default: false): if set to true, URL encodes the comparison operators.
|
|
17
|
-
* andOp (string, default: '&&'): the logical AND operator to use in the expression.
|
|
18
|
-
* orOp (string, default: '||'): the logical OR operator to use in the expression.
|
|
19
|
-
* eqOp (string, default: '=='): the equality operator to use in the expression.
|
|
20
|
-
* geOp (string, default: '>='): the greater than or equal to operator to use in the expression.
|
|
21
|
-
* leOp (string, default: '<='): the less than or equal to operator to use in the expression.
|
|
22
|
-
*
|
|
23
|
-
* Examples
|
|
24
|
-
*
|
|
25
|
-
* ```javascript
|
|
26
|
-
* const expression = '0-10,20-30';
|
|
27
|
-
* const valueName = 'age';
|
|
28
|
-
* const options = { urlEncode: true, andOp: 'and', orOp: 'or', eqOp: '=', geOp: '>=', leOp: '<=' };
|
|
29
|
-
* const comparisonExpression = generateRangeComparisonExpression(expression, valueName, options);
|
|
30
|
-
*
|
|
31
|
-
* console.log(comparisonExpression); // age%3E%3D0%20and%20age%3C%3D10%20or%20age%3E%3D20%20and%20age%3C%3D30
|
|
32
|
-
* ```
|
|
33
|
-
*
|
|
34
|
-
* In this example, the generateRangeComparisonExpression() function generates a string representation of the comparison
|
|
35
|
-
* expression for the expression and valueName parameters with the specified options. The resulting comparison
|
|
36
|
-
* expression is 'age>=0 and age<=10 or age>=20 and age<=30', URL encoded according to the urlEncode option.
|
|
37
|
-
*
|
|
38
|
-
* @param {string} expression - The string expression to generate the comparison for.
|
|
39
|
-
* @param {string} valueName - The name of the value to compare against.
|
|
40
|
-
* @param {Object} [options] - The optional parameters.
|
|
41
|
-
* @param {boolean} [options.urlEncode=false] - Whether to encode comparison operators for use in a URL.
|
|
42
|
-
* @param {string} [options.andOp='&&'] - The logical AND operator to use.
|
|
43
|
-
* @param {string} [options.orOp='||'] - The logical OR operator to use.
|
|
44
|
-
* @param {string} [options.eqOp='=='] - The comparison operator for equality to use.
|
|
45
|
-
* @param {string} [options.geOp='>='] - The comparison operator for greater than or equal to to use.
|
|
46
|
-
* @param {string} [options.leOp='<='] - The comparison operator for less than or equal to to use.
|
|
47
|
-
* @returns {string} The generated comparison expression.
|
|
48
|
-
* @throws {Error} If the input is invalid.
|
|
49
|
-
* @memberOf Monster.Text
|
|
50
|
-
* @summary Generates a comparison expression based on a range of values.
|
|
51
|
-
*/
|
|
52
|
-
function generateRangeComparisonExpression(expression, valueName, options = {}) {
|
|
53
|
-
const { urlEncode = false, andOp = "&&", orOp = "||", eqOp = "==", geOp = ">=", leOp = "<=" } = options;
|
|
54
|
-
const ranges = expression.split(",");
|
|
55
|
-
let comparison = "";
|
|
56
|
-
for (let i = 0; i < ranges.length; i++) {
|
|
57
|
-
const range = ranges[i].trim();
|
|
58
|
-
if (range === "") {
|
|
59
|
-
throw new Error(`Invalid range '${range}'`);
|
|
60
|
-
} else if (range.includes("-")) {
|
|
61
|
-
const [start, end] = range.split("-").map((s) => (s === "" ? null : parseFloat(s)));
|
|
62
|
-
if ((start !== null && isNaN(start)) || (end !== null && isNaN(end))) {
|
|
63
|
-
throw new Error(`Invalid value in range '${range}'`);
|
|
64
|
-
}
|
|
65
|
-
if (start !== null && end !== null && start > end) {
|
|
66
|
-
throw new Error(`Invalid range '${range}'`);
|
|
67
|
-
}
|
|
68
|
-
const compStart =
|
|
69
|
-
start !== null ? `${valueName}${urlEncode ? encodeURIComponent(geOp) : geOp}${start}` : "";
|
|
70
|
-
const compEnd = end !== null ? `${valueName}${urlEncode ? encodeURIComponent(leOp) : leOp}${end}` : "";
|
|
71
|
-
const compRange = `${compStart}${compStart && compEnd ? ` ${andOp} ` : ""}${compEnd}`;
|
|
72
|
-
comparison += ranges.length > 1 ? `(${compRange})` : compRange;
|
|
73
|
-
} else {
|
|
74
|
-
const value = parseFloat(range);
|
|
75
|
-
if (isNaN(value)) {
|
|
76
|
-
throw new Error(`Invalid value '${range}'`);
|
|
77
|
-
}
|
|
78
|
-
const compValue = `${valueName}${urlEncode ? encodeURIComponent(eqOp) : eqOp}${value}`;
|
|
79
|
-
comparison += ranges.length > 1 ? `(${compValue})` : compValue;
|
|
80
|
-
}
|
|
81
|
-
if (i < ranges.length - 1) {
|
|
82
|
-
comparison += ` ${orOp} `;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return comparison;
|
|
86
|
-
}
|
package/source/types/version.mjs
CHANGED
package/test/cases/dom/util.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
getDocument, getWindow, getDocumentFragmentFromString
|
|
3
3
|
} from "../../../../application/source/dom/util.mjs";
|
|
4
4
|
|
|
5
|
+
import {getContainingDocument} from "../../../../application/source/dom/util.mjs";
|
|
5
6
|
|
|
6
7
|
import {initJSDOM} from "../../util/jsdom.mjs";
|
|
7
8
|
|
|
@@ -52,4 +53,63 @@ describe('DOM', function () {
|
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
});
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
describe('getContainingDocument', () => {
|
|
59
|
+
let jsDomDocument;
|
|
60
|
+
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
jsDomDocument = getDocument();
|
|
63
|
+
});
|
|
64
|
+
//
|
|
65
|
+
// afterEach(() => {
|
|
66
|
+
// dom.window.close();
|
|
67
|
+
// });
|
|
68
|
+
|
|
69
|
+
it('should throw an error when called with an invalid argument', () => {
|
|
70
|
+
expect(() => getContainingDocument(null)).to.throw('Invalid argument. Expected an HTMLElement.');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should return the correct containing document for an element in the main document', () => {
|
|
74
|
+
const element = jsDomDocument.createElement('div');
|
|
75
|
+
const containingDocument = getContainingDocument(element);
|
|
76
|
+
|
|
77
|
+
expect(containingDocument).to.null;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should return the correct containing document for an element inside a shadow root', () => {
|
|
81
|
+
const host = jsDomDocument.createElement('div');
|
|
82
|
+
const shadowRoot = host.attachShadow({ mode: 'open' });
|
|
83
|
+
const element = jsDomDocument.createElement('span');
|
|
84
|
+
shadowRoot.appendChild(element);
|
|
85
|
+
|
|
86
|
+
const containingDocument = getContainingDocument(element);
|
|
87
|
+
expect(containingDocument).to.not.null;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should return the correct containing document for an element inside a nested shadow root', () => {
|
|
91
|
+
const outerHost = jsDomDocument.createElement('div');
|
|
92
|
+
const outerShadowRoot = outerHost.attachShadow({ mode: 'open' });
|
|
93
|
+
|
|
94
|
+
const innerHost = jsDomDocument.createElement('div');
|
|
95
|
+
outerShadowRoot.appendChild(innerHost);
|
|
96
|
+
|
|
97
|
+
const innerShadowRoot = innerHost.attachShadow({ mode: 'open' });
|
|
98
|
+
|
|
99
|
+
const element = jsDomDocument.createElement('span');
|
|
100
|
+
innerShadowRoot.appendChild(element);
|
|
101
|
+
|
|
102
|
+
const containingDocument = getContainingDocument(element);
|
|
103
|
+
expect(containingDocument).to.not.null;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should return null when the element is not attached to any document', () => {
|
|
107
|
+
const detachedElement = jsDomDocument.createElement('div');
|
|
108
|
+
detachedElement.remove();
|
|
109
|
+
|
|
110
|
+
const containingDocument = getContainingDocument(detachedElement);
|
|
111
|
+
expect(containingDocument).to.be.null;
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
55
115
|
});
|
package/test/cases/monster.mjs
CHANGED