jtlt 0.5.0 → 0.7.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/CHANGES.md +11 -0
- package/dist/AbstractJoiningTransformer.d.ts +8 -0
- package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
- package/dist/DOMJoiningTransformer.d.ts +25 -0
- package/dist/DOMJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONJoiningTransformer.d.ts +20 -1
- package/dist/JSONJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformerContext.d.ts +55 -1
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/StringJoiningTransformer.d.ts +18 -1
- package/dist/StringJoiningTransformer.d.ts.map +1 -1
- package/dist/XPathTransformerContext.d.ts +76 -1
- package/dist/XPathTransformerContext.d.ts.map +1 -1
- package/docs/TO-DO.md +36 -36
- package/package.json +1 -1
- package/src/AbstractJoiningTransformer.js +11 -0
- package/src/DOMJoiningTransformer.js +85 -2
- package/src/JSONJoiningTransformer.js +39 -1
- package/src/JSONPathTransformerContext.js +98 -2
- package/src/StringJoiningTransformer.js +35 -1
- package/src/XPathTransformerContext.js +189 -9
|
@@ -59,6 +59,10 @@ class XPathTransformerContext {
|
|
|
59
59
|
this._currPath = undefined; // XPath string of current context
|
|
60
60
|
/** @type {Record<string, any> | undefined} */
|
|
61
61
|
this._params = undefined;
|
|
62
|
+
/** @type {string[]} */
|
|
63
|
+
this._preserveSpaceElements = [];
|
|
64
|
+
/** @type {string[]} */
|
|
65
|
+
this._stripSpaceElements = [];
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
/** @returns {import('./index.js').JoiningTransformer} */
|
|
@@ -66,6 +70,92 @@ class XPathTransformerContext {
|
|
|
66
70
|
return this._config.joiningTransformer;
|
|
67
71
|
}
|
|
68
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Check if whitespace should be stripped for a given element.
|
|
75
|
+
* @param {Node} node - The node to check
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
_shouldStripSpace (node) {
|
|
79
|
+
if (node.nodeType !== 1) {
|
|
80
|
+
return false; // Only elements
|
|
81
|
+
}
|
|
82
|
+
const elementName = /** @type {Element} */ (node).localName;
|
|
83
|
+
// Check if in preserve list (takes precedence)
|
|
84
|
+
if (this._preserveSpaceElements.some((pattern) => {
|
|
85
|
+
return pattern === '*' || pattern === elementName;
|
|
86
|
+
})) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
// Check if in strip list
|
|
90
|
+
return this._stripSpaceElements.some((pattern) => {
|
|
91
|
+
return pattern === '*' || pattern === elementName;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Clone the DOM and strip whitespace-only text nodes from elements
|
|
97
|
+
* marked for stripping.
|
|
98
|
+
* @param {Node} node - The node to clone
|
|
99
|
+
* @returns {Node} The cloned node with whitespace stripped
|
|
100
|
+
*/
|
|
101
|
+
_cloneAndStripWhitespace (node) {
|
|
102
|
+
// Deep clone the node
|
|
103
|
+
const cloned = node.cloneNode(true);
|
|
104
|
+
|
|
105
|
+
// Only process if we have strip-space declarations
|
|
106
|
+
if (this._stripSpaceElements.length === 0) {
|
|
107
|
+
return cloned;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Recursively strip whitespace-only text nodes
|
|
111
|
+
const stripWhitespace = (/** @type {Node} */ n) => {
|
|
112
|
+
if (n.nodeType === 1) { // Element node
|
|
113
|
+
if (this._shouldStripSpace(n)) {
|
|
114
|
+
// Remove whitespace-only text node children
|
|
115
|
+
const childNodes = [...n.childNodes];
|
|
116
|
+
for (const child of childNodes) {
|
|
117
|
+
if (child.nodeType === 3) { // Text node
|
|
118
|
+
/* c8 ignore next 2 -- Branch inside loop already tested by
|
|
119
|
+
integration tests; c8 artifact */
|
|
120
|
+
const text = child.nodeValue || '';
|
|
121
|
+
// Check if text is whitespace-only
|
|
122
|
+
if (text.trim() === '') {
|
|
123
|
+
child.remove();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Recurse into child elements
|
|
129
|
+
const children = [...n.childNodes];
|
|
130
|
+
for (const child of children) {
|
|
131
|
+
stripWhitespace(child);
|
|
132
|
+
}
|
|
133
|
+
} else if (n.nodeType === 9) { // Document node
|
|
134
|
+
// Process document's children (typically documentElement)
|
|
135
|
+
const children = [...n.childNodes];
|
|
136
|
+
for (const child of children) {
|
|
137
|
+
stripWhitespace(child);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
stripWhitespace(cloned);
|
|
143
|
+
return cloned;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Apply whitespace stripping to the context node based on strip-space
|
|
148
|
+
* declarations. This clones the DOM and updates the context.
|
|
149
|
+
* @returns {this}
|
|
150
|
+
*/
|
|
151
|
+
applyWhitespaceStripping () {
|
|
152
|
+
if (this._stripSpaceElements.length > 0) {
|
|
153
|
+
const stripped = this._cloneAndStripWhitespace(this._origNode);
|
|
154
|
+
this._contextNode = stripped;
|
|
155
|
+
}
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
|
|
69
159
|
/**
|
|
70
160
|
* Evaluate an XPath expression against the current context node.
|
|
71
161
|
* @param {string} expr - XPath expression
|
|
@@ -76,12 +166,16 @@ class XPathTransformerContext {
|
|
|
76
166
|
if (!expr) {
|
|
77
167
|
return this._contextNode;
|
|
78
168
|
}
|
|
169
|
+
|
|
170
|
+
// Ensure we're using the stripped DOM if strip-space is declared
|
|
171
|
+
const contextNode = this._contextNode;
|
|
172
|
+
|
|
79
173
|
const version = this._config.xpathVersion ?? 1;
|
|
80
174
|
if (version === 1) {
|
|
81
175
|
// Use native XPath (browser-like); rely on DOM doc if available.
|
|
82
|
-
const doc =
|
|
83
|
-
?
|
|
84
|
-
: (
|
|
176
|
+
const doc = contextNode && contextNode.ownerDocument
|
|
177
|
+
? contextNode.ownerDocument
|
|
178
|
+
: (contextNode.nodeType === 9 ? contextNode : undefined);
|
|
85
179
|
if (!doc || doc.nodeType !== 9) {
|
|
86
180
|
throw new Error(
|
|
87
181
|
'Native XPath unavailable for xpathVersion=1'
|
|
@@ -105,7 +199,7 @@ class XPathTransformerContext {
|
|
|
105
199
|
);
|
|
106
200
|
/* c8 ignore stop */
|
|
107
201
|
const resultObj = docTyped.evaluate(
|
|
108
|
-
expr,
|
|
202
|
+
expr, contextNode, resolver, type, null
|
|
109
203
|
);
|
|
110
204
|
if (asNodes) {
|
|
111
205
|
/** @type {Node[]} */
|
|
@@ -150,7 +244,7 @@ class XPathTransformerContext {
|
|
|
150
244
|
}
|
|
151
245
|
if (version === 2) {
|
|
152
246
|
// Version 2: xpath2.js
|
|
153
|
-
const result = xpath2.evaluate(expr,
|
|
247
|
+
const result = xpath2.evaluate(expr, contextNode);
|
|
154
248
|
if (asNodes) {
|
|
155
249
|
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
156
250
|
/* c8 ignore next -- array wrap/identity branch counted in other tests */
|
|
@@ -163,7 +257,7 @@ class XPathTransformerContext {
|
|
|
163
257
|
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
164
258
|
// eslint-disable-next-line import/no-named-as-default-member -- Only as default
|
|
165
259
|
const result = fontoxpath.evaluateXPath(
|
|
166
|
-
expr,
|
|
260
|
+
expr, contextNode, undefined, undefined,
|
|
167
261
|
// Non-deprecated, predictable all results
|
|
168
262
|
14 // ReturnType.ALL_RESULTS
|
|
169
263
|
);
|
|
@@ -1233,6 +1327,43 @@ class XPathTransformerContext {
|
|
|
1233
1327
|
this._getJoiningTransformer().propValue(prop, val);
|
|
1234
1328
|
return this;
|
|
1235
1329
|
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Alias for propValue(). Append a key-value pair to the current map/object.
|
|
1332
|
+
* @param {string} prop Property name
|
|
1333
|
+
* @param {any} val Value
|
|
1334
|
+
* @returns {XPathTransformerContext}
|
|
1335
|
+
*/
|
|
1336
|
+
mapEntry (prop, val) {
|
|
1337
|
+
return this.propValue(prop, val);
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Declare elements for which whitespace-only text nodes should be preserved.
|
|
1341
|
+
* Equivalent to xsl:preserve-space.
|
|
1342
|
+
* @param {string|string[]} elements - Element name(s) or patterns
|
|
1343
|
+
* @returns {XPathTransformerContext}
|
|
1344
|
+
*/
|
|
1345
|
+
preserveSpace (elements) {
|
|
1346
|
+
const elemArray = Array.isArray(elements) ? elements : [elements];
|
|
1347
|
+
this._preserveSpaceElements.push(...elemArray);
|
|
1348
|
+
return this;
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Declare elements for which whitespace-only text nodes should be stripped.
|
|
1352
|
+
* Equivalent to xsl:strip-space.
|
|
1353
|
+
* This automatically clones the DOM and removes whitespace-only text nodes.
|
|
1354
|
+
* @param {string|string[]} elements - Element name(s) or patterns
|
|
1355
|
+
* @returns {XPathTransformerContext}
|
|
1356
|
+
*/
|
|
1357
|
+
stripSpace (elements) {
|
|
1358
|
+
const elemArray = Array.isArray(elements) ? elements : [elements];
|
|
1359
|
+
const wasEmpty = this._stripSpaceElements.length === 0;
|
|
1360
|
+
this._stripSpaceElements.push(...elemArray);
|
|
1361
|
+
// Apply stripping if this is the first strip-space declaration
|
|
1362
|
+
if (wasEmpty && this._stripSpaceElements.length > 0) {
|
|
1363
|
+
this.applyWhitespaceStripping();
|
|
1364
|
+
}
|
|
1365
|
+
return this;
|
|
1366
|
+
}
|
|
1236
1367
|
/**
|
|
1237
1368
|
* Append object.
|
|
1238
1369
|
* @param {Record<string, unknown>|
|
|
@@ -1253,6 +1384,23 @@ class XPathTransformerContext {
|
|
|
1253
1384
|
propSets);
|
|
1254
1385
|
return this;
|
|
1255
1386
|
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Alias for object(). Append an object/map.
|
|
1389
|
+
* @param {Record<string, unknown>|
|
|
1390
|
+
* ((this: XPathTransformerContext) => void)} objOrCb Object or callback
|
|
1391
|
+
* @param {((this: XPathTransformerContext) => void)|
|
|
1392
|
+
* any[]} [cbOrUsePropertySets] Callback or property sets
|
|
1393
|
+
* @param {any[]|
|
|
1394
|
+
* Record<string, unknown>} [usePropertySetsOrPropSets]
|
|
1395
|
+
* Property sets or props
|
|
1396
|
+
* @param {Record<string, unknown>} [propSets] Additional property sets
|
|
1397
|
+
* @returns {XPathTransformerContext}
|
|
1398
|
+
*/
|
|
1399
|
+
map (objOrCb, cbOrUsePropertySets, usePropertySetsOrPropSets, propSets) {
|
|
1400
|
+
return this.object(
|
|
1401
|
+
objOrCb, cbOrUsePropertySets, usePropertySetsOrPropSets, propSets
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1256
1404
|
/**
|
|
1257
1405
|
* Append array.
|
|
1258
1406
|
* @param {any[]|
|
|
@@ -1290,6 +1438,16 @@ class XPathTransformerContext {
|
|
|
1290
1438
|
return this;
|
|
1291
1439
|
}
|
|
1292
1440
|
|
|
1441
|
+
/**
|
|
1442
|
+
* @param {string} name
|
|
1443
|
+
* @param {Record<string, string>} attributes
|
|
1444
|
+
* @returns {this}
|
|
1445
|
+
*/
|
|
1446
|
+
attributeSet (name, attributes) {
|
|
1447
|
+
this._getJoiningTransformer().attributeSet(name, attributes);
|
|
1448
|
+
return this;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1293
1451
|
/**
|
|
1294
1452
|
* Append element.
|
|
1295
1453
|
* @param {string} name Tag name
|
|
@@ -1298,11 +1456,13 @@ class XPathTransformerContext {
|
|
|
1298
1456
|
* @param {any[]|((this: XPathTransformerContext)=>void)} [children]
|
|
1299
1457
|
* Children
|
|
1300
1458
|
* @param {(this: XPathTransformerContext)=>void} [cb] Callback
|
|
1459
|
+
* @param {string[]} [useAttributeSets] - Attribute set names to apply
|
|
1301
1460
|
* @returns {XPathTransformerContext}
|
|
1302
1461
|
*/
|
|
1303
|
-
element (name, atts, children, cb) {
|
|
1304
|
-
|
|
1305
|
-
|
|
1462
|
+
element (name, atts, children, cb, useAttributeSets) {
|
|
1463
|
+
/** @type {any} */ (this._getJoiningTransformer()).element(
|
|
1464
|
+
name, atts, children, cb, useAttributeSets
|
|
1465
|
+
);
|
|
1306
1466
|
return this;
|
|
1307
1467
|
}
|
|
1308
1468
|
|
|
@@ -1556,6 +1716,26 @@ class XPathTransformerContext {
|
|
|
1556
1716
|
return this;
|
|
1557
1717
|
}
|
|
1558
1718
|
|
|
1719
|
+
/**
|
|
1720
|
+
* Assert that a test condition is true, throwing an error if it fails.
|
|
1721
|
+
* Equivalent to xsl:assert. Evaluates an XPath expression using the
|
|
1722
|
+
* same truthiness rules as if() and choose().
|
|
1723
|
+
* @param {string} test - XPath expression to test
|
|
1724
|
+
* @param {string} [message] - Optional error message to include
|
|
1725
|
+
* @returns {XPathTransformerContext}
|
|
1726
|
+
* @throws {Error} When the test expression evaluates to false
|
|
1727
|
+
*/
|
|
1728
|
+
assert (test, message) {
|
|
1729
|
+
const passes = this._passesIf(test);
|
|
1730
|
+
if (!passes) {
|
|
1731
|
+
const errorMsg = message
|
|
1732
|
+
? `Assertion failed: ${message}`
|
|
1733
|
+
: `Assertion failed: ${test}`;
|
|
1734
|
+
throw new Error(errorMsg);
|
|
1735
|
+
}
|
|
1736
|
+
return this;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1559
1739
|
/**
|
|
1560
1740
|
* Analyze a string with a regular expression, equivalent to
|
|
1561
1741
|
* xsl:analyze-string. Processes matching and non-matching substrings
|