jtlt 0.8.0 → 0.10.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 -1
- package/README.md +2 -4
- package/dist/AbstractJoiningTransformer.d.ts +92 -0
- package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
- package/dist/DOMJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformerContext.d.ts +39 -0
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/StringJoiningTransformer.d.ts.map +1 -1
- package/dist/XPathTransformer.d.ts.map +1 -1
- package/dist/XPathTransformerContext.d.ts +39 -0
- package/dist/XPathTransformerContext.d.ts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/docs/API.expanded.md +33 -0
- package/docs/API.md +22 -0
- package/docs/TO-DO.md +1 -1
- package/eslint.config.js +2 -1
- package/package.json +1 -1
- package/src/AbstractJoiningTransformer.js +113 -0
- package/src/DOMJoiningTransformer.js +39 -1
- package/src/JSONJoiningTransformer.js +53 -2
- package/src/JSONPathTransformer.js +4 -0
- package/src/JSONPathTransformerContext.js +138 -1
- package/src/StringJoiningTransformer.js +38 -1
- package/src/XPathTransformer.js +4 -0
- package/src/XPathTransformerContext.js +156 -3
- package/src/index.js +1 -0
|
@@ -359,6 +359,66 @@ class XPathTransformerContext {
|
|
|
359
359
|
});
|
|
360
360
|
let templateObj;
|
|
361
361
|
if (!pathMatchedTemplates.length) { // default template rule branches
|
|
362
|
+
// Check mode config for no-match behavior
|
|
363
|
+
const joiner = this._getJoiningTransformer();
|
|
364
|
+
const modeConfig = joiner._modeConfig;
|
|
365
|
+
if (modeConfig) {
|
|
366
|
+
const onNoMatch = modeConfig.onNoMatch ?? 'text-only-copy';
|
|
367
|
+
if (modeConfig.warningOnNoMatch) {
|
|
368
|
+
// eslint-disable-next-line no-console -- Warning as specified
|
|
369
|
+
console.warn(
|
|
370
|
+
'Warning: No template matches. ' +
|
|
371
|
+
'Mode is configured with warningOnNoMatch=true.'
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (onNoMatch === 'fail') {
|
|
375
|
+
throw new Error(
|
|
376
|
+
'No template matches. Mode is configured with onNoMatch="fail".'
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
if (onNoMatch === 'deep-skip') {
|
|
380
|
+
// Skip this node entirely
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (onNoMatch === 'shallow-copy') {
|
|
384
|
+
// Output the node without processing children
|
|
385
|
+
if (node.nodeType === 1 && node.ownerDocument?.defaultView) {
|
|
386
|
+
const serializer =
|
|
387
|
+
new node.ownerDocument.defaultView.XMLSerializer();
|
|
388
|
+
// Clone node without children for shallow copy
|
|
389
|
+
const clone = node.cloneNode(false);
|
|
390
|
+
joiner.rawAppend(serializer.serializeToString(clone));
|
|
391
|
+
} else if (node.nodeType === 3 && node.nodeValue) { // Text
|
|
392
|
+
joiner.text(node.nodeValue);
|
|
393
|
+
}
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (onNoMatch === 'deep-copy') {
|
|
397
|
+
// Output the node and all descendants
|
|
398
|
+
if (node.nodeType === 1 && node.ownerDocument?.defaultView) {
|
|
399
|
+
const serializer =
|
|
400
|
+
new node.ownerDocument.defaultView.XMLSerializer();
|
|
401
|
+
joiner.rawAppend(serializer.serializeToString(node));
|
|
402
|
+
} else if (node.nodeType === 3 && node.nodeValue) { // Text
|
|
403
|
+
joiner.text(node.nodeValue);
|
|
404
|
+
}
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (onNoMatch === 'text-only-copy') {
|
|
408
|
+
// Output only text content
|
|
409
|
+
if (node.nodeType === 3 && node.nodeValue) { // Text node
|
|
410
|
+
joiner.text(node.nodeValue);
|
|
411
|
+
} else if (node.nodeType === 1) { // Element - get text content
|
|
412
|
+
const {textContent} = node;
|
|
413
|
+
if (textContent) {
|
|
414
|
+
joiner.text(textContent);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
// 'apply-templates', 'shallow-skip', or other:
|
|
420
|
+
// use default template rules
|
|
421
|
+
}
|
|
362
422
|
// Default template rules (simplified compared to JSON version)
|
|
363
423
|
const DTR = XPathTransformerContext.DefaultTemplateRules;
|
|
364
424
|
// Treat Document (9) like Element (1) so the default root rule
|
|
@@ -391,6 +451,50 @@ class XPathTransformerContext {
|
|
|
391
451
|
}
|
|
392
452
|
return aPr > bPr ? -1 : 1;
|
|
393
453
|
});
|
|
454
|
+
|
|
455
|
+
// Check for multiple matches with same priority when mode is configured
|
|
456
|
+
const joiner = this._getJoiningTransformer();
|
|
457
|
+
const modeConfig = joiner._modeConfig;
|
|
458
|
+
if (modeConfig && pathMatchedTemplates.length > 1) {
|
|
459
|
+
// Check if top two templates have equal priority
|
|
460
|
+
const topPriority =
|
|
461
|
+
typeof pathMatchedTemplates[0].priority === 'number'
|
|
462
|
+
? pathMatchedTemplates[0].priority
|
|
463
|
+
: (this._config.specificityPriorityResolver &&
|
|
464
|
+
pathMatchedTemplates[0].path
|
|
465
|
+
? this._config.specificityPriorityResolver(
|
|
466
|
+
pathMatchedTemplates[0].path
|
|
467
|
+
)
|
|
468
|
+
/* c8 ignore next 2 -- defensive, templates without paths
|
|
469
|
+
are already filtered at line 334 */
|
|
470
|
+
: 0);
|
|
471
|
+
const secondPriority =
|
|
472
|
+
typeof pathMatchedTemplates[1].priority === 'number'
|
|
473
|
+
? pathMatchedTemplates[1].priority
|
|
474
|
+
: (this._config.specificityPriorityResolver &&
|
|
475
|
+
pathMatchedTemplates[1].path
|
|
476
|
+
? this._config.specificityPriorityResolver(
|
|
477
|
+
pathMatchedTemplates[1].path
|
|
478
|
+
)
|
|
479
|
+
/* c8 ignore next 2 -- defensive, templates without paths
|
|
480
|
+
are already filtered at line 334 */
|
|
481
|
+
: 0);
|
|
482
|
+
if (topPriority === secondPriority) {
|
|
483
|
+
if (modeConfig.onMultipleMatch === 'fail') {
|
|
484
|
+
throw new Error(
|
|
485
|
+
'Multiple templates match with equal priority. ' +
|
|
486
|
+
'Mode is configured with onMultipleMatch="fail".'
|
|
487
|
+
);
|
|
488
|
+
} else if (modeConfig.warningOnMultipleMatch !== false) {
|
|
489
|
+
// eslint-disable-next-line no-console -- Warning as specified
|
|
490
|
+
console.warn(
|
|
491
|
+
'Warning: Multiple templates match with equal priority. ' +
|
|
492
|
+
'Mode is configured with warningOnMultipleMatch=true.'
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
394
498
|
templateObj =
|
|
395
499
|
/**
|
|
396
500
|
* @type {import('./index.js').XPathTemplateObject<any>}
|
|
@@ -410,7 +514,15 @@ class XPathTransformerContext {
|
|
|
410
514
|
this._params = prevTemplateParams;
|
|
411
515
|
|
|
412
516
|
if (typeof ret !== 'undefined') {
|
|
413
|
-
this._getJoiningTransformer()
|
|
517
|
+
const joiner = this._getJoiningTransformer();
|
|
518
|
+
// Close any open tag before appending template return value
|
|
519
|
+
// @ts-expect-error -- _openTagState only on StringJoiningTransformer
|
|
520
|
+
if (joiner._openTagState) {
|
|
521
|
+
joiner.append('>');
|
|
522
|
+
// @ts-expect-error -- _openTagState only on StringJoiningTransformer
|
|
523
|
+
joiner._openTagState = false;
|
|
524
|
+
}
|
|
525
|
+
joiner.append(ret);
|
|
414
526
|
}
|
|
415
527
|
this._contextNode = node; // Restore (placeholder for more complex state)
|
|
416
528
|
}
|
|
@@ -1438,6 +1550,22 @@ class XPathTransformerContext {
|
|
|
1438
1550
|
return this;
|
|
1439
1551
|
}
|
|
1440
1552
|
|
|
1553
|
+
/**
|
|
1554
|
+
* Configure mode behavior (similar to xsl:mode).
|
|
1555
|
+
* @param {{
|
|
1556
|
+
* onMultipleMatch?: "use-last"|"fail",
|
|
1557
|
+
* warningOnMultipleMatch?: boolean,
|
|
1558
|
+
* onNoMatch?: "shallow-copy"|"deep-copy"|"fail"|"apply-templates"|
|
|
1559
|
+
* "shallow-skip"|"deep-skip"|"text-only-copy",
|
|
1560
|
+
* warningOnNoMatch?: boolean
|
|
1561
|
+
* }} cfg - Mode configuration
|
|
1562
|
+
* @returns {this}
|
|
1563
|
+
*/
|
|
1564
|
+
mode (cfg) {
|
|
1565
|
+
this._getJoiningTransformer().mode(cfg);
|
|
1566
|
+
return this;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1441
1569
|
/**
|
|
1442
1570
|
* @param {string} name
|
|
1443
1571
|
* @param {import('./AbstractJoiningTransformer.js').
|
|
@@ -1471,6 +1599,31 @@ class XPathTransformerContext {
|
|
|
1471
1599
|
return this;
|
|
1472
1600
|
}
|
|
1473
1601
|
|
|
1602
|
+
/**
|
|
1603
|
+
* Configure stylesheet behavior (similar to xsl:stylesheet).
|
|
1604
|
+
* Unlike xsl:stylesheet, this is a directive method and does not contain
|
|
1605
|
+
* nested content.
|
|
1606
|
+
* @param {{
|
|
1607
|
+
* excludeResultPrefixes?: string[]
|
|
1608
|
+
* }} cfg - Stylesheet configuration
|
|
1609
|
+
* @returns {this}
|
|
1610
|
+
*/
|
|
1611
|
+
stylesheet (cfg) {
|
|
1612
|
+
this._getJoiningTransformer().stylesheet(cfg);
|
|
1613
|
+
return this;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/**
|
|
1617
|
+
* Alias for stylesheet() method (XSLT compatibility).
|
|
1618
|
+
* @param {{
|
|
1619
|
+
* excludeResultPrefixes?: string[]
|
|
1620
|
+
* }} cfg - Stylesheet configuration
|
|
1621
|
+
* @returns {this}
|
|
1622
|
+
*/
|
|
1623
|
+
transform (cfg) {
|
|
1624
|
+
return this.stylesheet(cfg);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1474
1627
|
/**
|
|
1475
1628
|
* Append element.
|
|
1476
1629
|
* @param {string} name Tag name
|
|
@@ -1909,7 +2062,7 @@ class XPathTransformerContext {
|
|
|
1909
2062
|
* @returns {void}
|
|
1910
2063
|
*/
|
|
1911
2064
|
template (node, cfg) {
|
|
1912
|
-
this.applyTemplates('
|
|
2065
|
+
this.applyTemplates('node()', cfg.mode);
|
|
1913
2066
|
}
|
|
1914
2067
|
},
|
|
1915
2068
|
transformElements: {
|
|
@@ -1920,7 +2073,7 @@ class XPathTransformerContext {
|
|
|
1920
2073
|
* @returns {void}
|
|
1921
2074
|
*/
|
|
1922
2075
|
template (node, cfg) {
|
|
1923
|
-
this.applyTemplates('
|
|
2076
|
+
this.applyTemplates('node()', cfg.mode);
|
|
1924
2077
|
}
|
|
1925
2078
|
},
|
|
1926
2079
|
transformTextNodes: {
|
package/src/index.js
CHANGED
|
@@ -34,6 +34,7 @@ export const setWindow = (win) => {
|
|
|
34
34
|
* @template TCtx
|
|
35
35
|
* @typedef {object} TemplateObject
|
|
36
36
|
* @property {string} [path] - JSONPath or XPath selector for matching nodes
|
|
37
|
+
* @property {string} [match] - Alias for 'path' (XSLT compatibility)
|
|
37
38
|
* @property {string} [name] - Optional name for calling via callTemplate
|
|
38
39
|
* @property {string} [mode] - Optional mode for template matching
|
|
39
40
|
* @property {number} [priority] - Priority for template selection
|