jtlt 0.7.3 → 0.9.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/.c8rc.json +26 -0
- package/CHANGES.md +10 -0
- package/dist/AbstractJoiningTransformer.d.ts +55 -0
- package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
- package/dist/DOMJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONJoiningTransformer.d.ts +2 -2
- package/dist/JSONJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformerContext.d.ts +34 -7
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/StringJoiningTransformer.d.ts +2 -2
- package/dist/StringJoiningTransformer.d.ts.map +1 -1
- package/dist/XPathTransformer.d.ts.map +1 -1
- package/dist/XPathTransformerContext.d.ts +28 -3
- 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 +2 -2
- package/eslint.config.js +2 -1
- package/package.json +1 -1
- package/src/AbstractJoiningTransformer.js +95 -0
- package/src/DOMJoiningTransformer.js +16 -12
- package/src/JSONJoiningTransformer.js +15 -10
- package/src/JSONPathTransformer.js +7 -0
- package/src/JSONPathTransformerContext.js +134 -6
- package/src/StringJoiningTransformer.js +33 -22
- package/src/XPathTransformer.js +4 -0
- package/src/XPathTransformerContext.js +148 -6
- 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').
|
|
@@ -1459,13 +1587,27 @@ class XPathTransformerContext {
|
|
|
1459
1587
|
return this;
|
|
1460
1588
|
}
|
|
1461
1589
|
|
|
1590
|
+
/**
|
|
1591
|
+
* @param {string} stylesheetPrefix
|
|
1592
|
+
* @param {string} resultPrefix
|
|
1593
|
+
* @returns {this}
|
|
1594
|
+
*/
|
|
1595
|
+
namespaceAlias (stylesheetPrefix, resultPrefix) {
|
|
1596
|
+
this._getJoiningTransformer().namespaceAlias(
|
|
1597
|
+
stylesheetPrefix, resultPrefix
|
|
1598
|
+
);
|
|
1599
|
+
return this;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1462
1602
|
/**
|
|
1463
1603
|
* Append element.
|
|
1464
1604
|
* @param {string} name Tag name
|
|
1465
1605
|
* @param {Record<string, string>|any[]|
|
|
1466
|
-
* ((this: XPathTransformerContext)=>void)} [atts] Attributes
|
|
1467
|
-
*
|
|
1468
|
-
*
|
|
1606
|
+
* ((this: XPathTransformerContext)=>void)} [atts] Attributes,
|
|
1607
|
+
* children, or callback
|
|
1608
|
+
* @param {any[]|
|
|
1609
|
+
* ((this: XPathTransformerContext)=>void)} [children]
|
|
1610
|
+
* Children or callback
|
|
1469
1611
|
* @param {(this: XPathTransformerContext)=>void} [cb] Callback
|
|
1470
1612
|
* @param {string[]} [useAttributeSets] - Attribute set names to apply
|
|
1471
1613
|
* @returns {XPathTransformerContext}
|
|
@@ -1895,7 +2037,7 @@ class XPathTransformerContext {
|
|
|
1895
2037
|
* @returns {void}
|
|
1896
2038
|
*/
|
|
1897
2039
|
template (node, cfg) {
|
|
1898
|
-
this.applyTemplates('
|
|
2040
|
+
this.applyTemplates('node()', cfg.mode);
|
|
1899
2041
|
}
|
|
1900
2042
|
},
|
|
1901
2043
|
transformElements: {
|
|
@@ -1906,7 +2048,7 @@ class XPathTransformerContext {
|
|
|
1906
2048
|
* @returns {void}
|
|
1907
2049
|
*/
|
|
1908
2050
|
template (node, cfg) {
|
|
1909
|
-
this.applyTemplates('
|
|
2051
|
+
this.applyTemplates('node()', cfg.mode);
|
|
1910
2052
|
}
|
|
1911
2053
|
},
|
|
1912
2054
|
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
|