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.
@@ -74,6 +74,20 @@ class AbstractJoiningTransformer {
74
74
  this._characterMap = {};
75
75
  /** @type {Record<string, Record<string, string>>} */
76
76
  this._attributeSet = {};
77
+
78
+ /** @type {Map<string, string>} */
79
+ this._namespaceAliases = new Map();
80
+
81
+ /**
82
+ * @type {{
83
+ * onMultipleMatch?: "use-last"|"fail",
84
+ * warningOnMultipleMatch?: boolean,
85
+ * onNoMatch?: "shallow-copy"|"deep-copy"|"fail"|"apply-templates"|
86
+ * "shallow-skip"|"deep-skip"|"text-only-copy",
87
+ * warningOnNoMatch?: boolean
88
+ * }|undefined}
89
+ */
90
+ this._modeConfig = undefined;
77
91
  }
78
92
 
79
93
  /**
@@ -98,6 +112,22 @@ class AbstractJoiningTransformer {
98
112
  return this;
99
113
  }
100
114
 
115
+ /**
116
+ * Configure mode behavior (similar to xsl:mode).
117
+ * @param {{
118
+ * onMultipleMatch?: "use-last"|"fail",
119
+ * warningOnMultipleMatch?: boolean,
120
+ * onNoMatch?: "shallow-copy"|"deep-copy"|"fail"|"apply-templates"|
121
+ * "shallow-skip"|"deep-skip"|"text-only-copy",
122
+ * warningOnNoMatch?: boolean
123
+ * }} cfg - Mode configuration
124
+ * @returns {this}
125
+ */
126
+ mode (cfg) {
127
+ this._modeConfig = cfg;
128
+ return this;
129
+ }
130
+
101
131
  /**
102
132
  * @param {string} name
103
133
  * @param {OutputCharacters} outputCharacters
@@ -116,6 +146,71 @@ class AbstractJoiningTransformer {
116
146
  this._attributeSet[name] = attributes;
117
147
  }
118
148
 
149
+ /**
150
+ * @param {string} stylesheetPrefix
151
+ * @param {string} resultPrefix
152
+ * @returns {void}
153
+ */
154
+ namespaceAlias (stylesheetPrefix, resultPrefix) {
155
+ // Convert empty string to '#default' to match _getNamespaceAlias behavior
156
+ const key = stylesheetPrefix || '#default';
157
+ this._namespaceAliases.set(key, resultPrefix);
158
+ }
159
+
160
+ /**
161
+ * @param {string} prefix
162
+ * @returns {string}
163
+ */
164
+ _getNamespaceAlias (prefix) {
165
+ // eslint-disable-next-line @stylistic/max-len -- Long
166
+ // eslint-disable-next-line unicorn/prefer-default-parameters -- Empty string fallback
167
+ const lookupPrefix = prefix || '#default';
168
+ return this._namespaceAliases.get(lookupPrefix) ?? (
169
+ lookupPrefix
170
+ );
171
+ }
172
+
173
+ /**
174
+ * @param {string} attName
175
+ * @returns {string}
176
+ */
177
+ _replaceNamespaceAliasInNamespaceDeclaration (attName) {
178
+ if (!attName.startsWith('xmlns')) {
179
+ return attName;
180
+ }
181
+ const namespacedAtt = attName.startsWith('xmlns:');
182
+
183
+ const prefix = namespacedAtt ? attName.slice(0, 6) : '#default';
184
+ const alias = this._getNamespaceAlias(prefix);
185
+
186
+ return alias === '#default'
187
+ ? 'xmlns'
188
+ : 'xmlns:' + alias;
189
+ }
190
+
191
+ /**
192
+ * @param {string} elemName
193
+ * @returns {string}
194
+ */
195
+ _replaceNamespaceAliasInElement (elemName) {
196
+ const colonIdx = elemName.indexOf(':');
197
+
198
+ const prefix = colonIdx === -1 ? '#default' : elemName.slice(0, colonIdx);
199
+ const alias = this._getNamespaceAlias(prefix);
200
+
201
+ if (colonIdx === -1) {
202
+ if (alias === '#default') {
203
+ return elemName;
204
+ }
205
+ return alias + ':' + elemName;
206
+ }
207
+
208
+ return (alias === '#default'
209
+ ? ''
210
+ : alias + ':'
211
+ ) + elemName.slice(colonIdx + 1);
212
+ }
213
+
119
214
  /**
120
215
  * @param {string} str
121
216
  * @returns {string}
@@ -281,7 +281,7 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
281
281
  * @returns {DOMJoiningTransformer}
282
282
  */
283
283
  /**
284
- * @param {Element|string} elName - Element name
284
+ * @param {Element|string} elem - Element name
285
285
  * @param {Record<string, string>|(Node|string)[
286
286
  * ]|((this: DOMJoiningTransformer) => void)} [atts] - Attributes,
287
287
  * childNodes, or callback
@@ -291,7 +291,7 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
291
291
  * @param {string[]} [useAttributeSets] - Attribute set names to apply
292
292
  * @returns {DOMJoiningTransformer}
293
293
  */
294
- element (elName, atts, childNodes, cb, useAttributeSets) {
294
+ element (elem, atts, childNodes, cb, useAttributeSets) {
295
295
  // Handle argument overloading like other transformers
296
296
  if (Array.isArray(atts)) {
297
297
  cb = /** @type {(this: DOMJoiningTransformer) => void} */ (
@@ -309,12 +309,14 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
309
309
  childNodes = [];
310
310
  }
311
311
 
312
- if (!this.root && this._outputConfig) {
313
- this.root = elName;
312
+ const elementName = this._replaceNamespaceAliasInElement(
313
+ typeof elem === 'string'
314
+ ? elem
315
+ : elem.localName
316
+ );
314
317
 
315
- const elementName = typeof elName === 'string'
316
- ? elName
317
- : elName.localName;
318
+ if (!this.root && this._outputConfig) {
319
+ this.root = elementName;
318
320
 
319
321
  const {
320
322
  omitXmlDeclaration, doctypePublic, doctypeSystem, method
@@ -420,10 +422,10 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
420
422
  }
421
423
 
422
424
  // Non-root elements
423
- const el = elName && typeof elName === 'object'
424
- ? elName
425
+ const el = elem && typeof elem === 'object'
426
+ ? elem
425
427
  : /** @type {Element} */ (
426
- this._cfg.document.createElement(elName)
428
+ this._cfg.document.createElement(elem)
427
429
  );
428
430
 
429
431
  // Apply attribute sets if specified
@@ -477,10 +479,11 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
477
479
  * @returns {DOMJoiningTransformer}
478
480
  */
479
481
  namespace (prefix, namespaceURI) {
482
+ const alias = this._getNamespaceAlias(prefix);
480
483
  /** @type {Element} */
481
484
  (this._dom).setAttributeNS(
482
485
  'http://www.w3.org/2000/xmlns/',
483
- 'xmlns:' + prefix,
486
+ alias === '#default' ? 'xmlns' : 'xmlns:' + alias,
484
487
  this._replaceCharacterMaps(namespaceURI)
485
488
  );
486
489
  return this;
@@ -497,7 +500,8 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
497
500
  throw new Error('You may only set an attribute on an element');
498
501
  }
499
502
  (/** @type {Element} */ (this._dom)).setAttribute(
500
- name, this._replaceCharacterMaps(val)
503
+ this._replaceNamespaceAliasInNamespaceDeclaration(name),
504
+ this._replaceCharacterMaps(val)
501
505
  );
502
506
  return this;
503
507
  }
@@ -372,7 +372,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
372
372
  * Result form: ['tag', {attr: 'val'}, child1, child2, ...]
373
373
  * Helpers: dataset -> data-*; $a -> ordered attributes.
374
374
  * Supported signatures mirror StringJoiningTransformer.element.
375
- * @param {string|Element} elName Element name or Element-like.
375
+ * @param {string|Element} elem Element name or Element-like.
376
376
  * @param {ElementAttributes|any[]|SimpleCallback} [atts]
377
377
  * Attrs, children, or cb.
378
378
  * @param {any[]|SimpleCallback} [childNodes] Children or cb.
@@ -380,11 +380,16 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
380
380
  * @param {string[]} [useAttributeSets] - Attribute set names to apply
381
381
  * @returns {JSONJoiningTransformer}
382
382
  */
383
- element (elName, atts, childNodes, cb, useAttributeSets) {
383
+ element (elem, atts, childNodes, cb, useAttributeSets) {
384
384
  this._requireSameChildren('json', 'element');
385
+ const elementName = this._replaceNamespaceAliasInElement(
386
+ typeof elem === 'string'
387
+ ? elem
388
+ : elem.localName
389
+ );
385
390
  const isRoot = !this.root;
386
391
  if (isRoot) {
387
- this.root = elName;
392
+ this.root = elementName;
388
393
  }
389
394
  // Normalize arguments similarly to StringJoiningTransformer.element
390
395
  if (Array.isArray(atts)) {
@@ -401,14 +406,11 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
401
406
  childNodes = [];
402
407
  }
403
408
 
404
- const elementName = typeof elName === 'string'
405
- ? elName
406
- : elName.localName;
407
409
  // Element-like object (DOM Element) -> extract attributes
408
- if (typeof elName === 'object' && elName && 'attributes' in elName) {
410
+ if (typeof elem === 'object' && elem && 'attributes' in elem) {
409
411
  /** @type {Record<string, string>} */
410
412
  const objAtts = {};
411
- [...elName.attributes].forEach((att) => {
413
+ [...elem.attributes].forEach((att) => {
412
414
  objAtts[att.name] = att.value;
413
415
  });
414
416
  atts = Object.assign(objAtts, atts);
@@ -556,8 +558,10 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
556
558
  attsObj.xmlns = {};
557
559
  }
558
560
 
561
+ const alias = this._getNamespaceAlias(prefix);
559
562
  /** @type {Record<string, string>} */
560
- (attsObj.xmlns)[prefix] = this._replaceCharacterMaps(namespaceURI);
563
+ (attsObj.xmlns)[alias === '#default' ? '' : alias] =
564
+ this._replaceCharacterMaps(namespaceURI);
561
565
 
562
566
  return this;
563
567
  }
@@ -600,7 +604,8 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
600
604
  });
601
605
  return this;
602
606
  }
603
- attsObj[name] = this._replaceCharacterMaps(/** @type {string} */ (val));
607
+ attsObj[this._replaceNamespaceAliasInNamespaceDeclaration(name)] =
608
+ this._replaceCharacterMaps(/** @type {string} */ (val));
604
609
  return this;
605
610
  }
606
611
 
@@ -19,6 +19,9 @@ class JSONPathTransformer {
19
19
  this._config = config;
20
20
  /** @type {import('./index.js').JSONPathTemplateObject<T>[]} */
21
21
  this.rootTemplates = [];
22
+ if (!config.templates) {
23
+ throw new TypeError('config.templates is required');
24
+ }
22
25
  this.templates = config.templates.map(function (template) {
23
26
  if (Array.isArray(template)) {
24
27
  // Todo: We could allow a third argument (at beginning or
@@ -27,6 +30,10 @@ class JSONPathTransformer {
27
30
  {path: template[0], template: template[1]}
28
31
  );
29
32
  }
33
+ // Normalize 'match' to 'path' for XSLT compatibility
34
+ if (template.match && !template.path) {
35
+ return {...template, path: template.match};
36
+ }
30
37
  return template;
31
38
  });
32
39
  this.templates.forEach((template, i, templates) => {
@@ -62,7 +62,7 @@ import JSONPathTransformer from './JSONPathTransformer.js';
62
62
  * JSONPath
63
63
  * @property {(path: string) => number} [specificityPriorityResolver]
64
64
  * Priority resolver function
65
- * @property {import('./index.js').JSONPathTemplateObject<T>[]} templates
65
+ * @property {import('./index.js').JSONPathTemplateObject<T>[]} [templates]
66
66
  */
67
67
 
68
68
  /**
@@ -408,6 +408,48 @@ class JSONPathTransformerContext {
408
408
 
409
409
  let templateObj;
410
410
  if (!pathMatchedTemplates.length) {
411
+ // Check mode config for no-match behavior
412
+ const joiner = that._getJoiningTransformer();
413
+ const modeConfig = joiner._modeConfig;
414
+ if (modeConfig) {
415
+ const onNoMatch = modeConfig.onNoMatch ?? 'text-only-copy';
416
+ if (modeConfig.warningOnNoMatch) {
417
+ // eslint-disable-next-line no-console -- Warning as specified
418
+ console.warn(
419
+ 'Warning: No template matches. ' +
420
+ 'Mode is configured with warningOnNoMatch=true.'
421
+ );
422
+ }
423
+ if (onNoMatch === 'fail') {
424
+ throw new Error(
425
+ 'No template matches. Mode is configured with onNoMatch="fail".'
426
+ );
427
+ }
428
+ if (onNoMatch === 'deep-skip') {
429
+ // Skip this node and its descendants entirely
430
+ continue;
431
+ }
432
+ if (onNoMatch === 'shallow-copy') {
433
+ // Output the value as-is without processing children
434
+ joiner.append(value);
435
+ continue;
436
+ }
437
+ if (onNoMatch === 'deep-copy') {
438
+ // Output the value and all descendants as-is
439
+ joiner.append(JSON.stringify(value));
440
+ continue;
441
+ }
442
+ if (onNoMatch === 'text-only-copy') {
443
+ // Output only text content (primitives)
444
+ if (typeof value === 'string' || typeof value === 'number' ||
445
+ typeof value === 'boolean') {
446
+ joiner.append(String(value));
447
+ }
448
+ continue;
449
+ }
450
+ // 'apply-templates', 'shallow-skip', or other:
451
+ // use default template rules
452
+ }
411
453
  const dtr = JSONPathTransformer.DefaultTemplateRules;
412
454
  if (propertyNamesMode) {
413
455
  templateObj = dtr.transformPropertyNames;
@@ -446,6 +488,49 @@ class JSONPathTransformerContext {
446
488
  return (aPriority > bPriority) ? -1 : 1;
447
489
  });
448
490
 
491
+ // Check for multiple matches with same priority when mode is configured
492
+ const joiner = that._getJoiningTransformer();
493
+ const modeConfig = joiner._modeConfig;
494
+ if (modeConfig && pathMatchedTemplates.length > 1) {
495
+ // Check if top two templates have equal priority
496
+ const topPriority =
497
+ typeof pathMatchedTemplates[0].priority === 'number'
498
+ ? pathMatchedTemplates[0].priority
499
+ : (that._config.specificityPriorityResolver &&
500
+ pathMatchedTemplates[0].path
501
+ ? that._config.specificityPriorityResolver(
502
+ pathMatchedTemplates[0].path
503
+ )
504
+ /* c8 ignore next 2 -- defensive, templates without paths
505
+ are already filtered at line 388 */
506
+ : 0);
507
+ const secondPriority =
508
+ typeof pathMatchedTemplates[1].priority === 'number'
509
+ ? pathMatchedTemplates[1].priority
510
+ : (that._config.specificityPriorityResolver &&
511
+ pathMatchedTemplates[1].path
512
+ ? that._config.specificityPriorityResolver(
513
+ pathMatchedTemplates[1].path
514
+ )
515
+ /* c8 ignore next 2 -- defensive, templates without paths
516
+ are already filtered at line 388 */
517
+ : 0);
518
+ if (topPriority === secondPriority) {
519
+ if (modeConfig.onMultipleMatch === 'fail') {
520
+ throw new Error(
521
+ 'Multiple templates match with equal priority. ' +
522
+ 'Mode is configured with onMultipleMatch="fail".'
523
+ );
524
+ } else if (modeConfig.warningOnMultipleMatch !== false) {
525
+ // eslint-disable-next-line no-console -- Warning as specified
526
+ console.warn(
527
+ 'Warning: Multiple templates match with equal priority. ' +
528
+ 'Mode is configured with warningOnMultipleMatch=true.'
529
+ );
530
+ }
531
+ }
532
+ }
533
+
449
534
  templateObj =
450
535
  /** @type {import('./index.js').JSONPathTemplateObject<T>} */ (
451
536
  pathMatchedTemplates.shift()
@@ -471,7 +556,18 @@ class JSONPathTransformerContext {
471
556
  that._params = prevTemplateParams;
472
557
  if (typeof ret !== 'undefined') {
473
558
  // After the undefined check, ret is ResultType<T>
474
- that._getJoiningTransformer().append(
559
+ const joiner = that._getJoiningTransformer();
560
+ // Close any open tag before appending template return value
561
+ /* c8 ignore start -- _openTagState only on StringJoiningTransformer,
562
+ defensive check for string output mode */
563
+ // @ts-expect-error -- _openTagState only on StringJoiningTransformer
564
+ if (joiner._openTagState) {
565
+ joiner.append('>');
566
+ // @ts-expect-error -- _openTagState only on StringJoiningTransformer
567
+ joiner._openTagState = false;
568
+ }
569
+ /* c8 ignore stop */
570
+ joiner.append(
475
571
  /** @type {string|Node|*} */ (ret)
476
572
  );
477
573
  }
@@ -1757,6 +1853,22 @@ class JSONPathTransformerContext {
1757
1853
  return this;
1758
1854
  }
1759
1855
 
1856
+ /**
1857
+ * Configure mode behavior (similar to xsl:mode).
1858
+ * @param {{
1859
+ * onMultipleMatch?: "use-last"|"fail",
1860
+ * warningOnMultipleMatch?: boolean,
1861
+ * onNoMatch?: "shallow-copy"|"deep-copy"|"fail"|"apply-templates"|
1862
+ * "shallow-skip"|"deep-skip"|"text-only-copy",
1863
+ * warningOnNoMatch?: boolean
1864
+ * }} cfg - Mode configuration
1865
+ * @returns {this}
1866
+ */
1867
+ mode (cfg) {
1868
+ this._getJoiningTransformer().mode(cfg);
1869
+ return this;
1870
+ }
1871
+
1760
1872
  /**
1761
1873
  * @param {string} name
1762
1874
  * @param {import('./AbstractJoiningTransformer.js').
@@ -1778,14 +1890,30 @@ class JSONPathTransformerContext {
1778
1890
  return this;
1779
1891
  }
1780
1892
 
1893
+ /**
1894
+ * @param {string} stylesheetPrefix
1895
+ * @param {string} resultPrefix
1896
+ * @returns {this}
1897
+ */
1898
+ namespaceAlias (stylesheetPrefix, resultPrefix) {
1899
+ this._getJoiningTransformer().namespaceAlias(
1900
+ stylesheetPrefix, resultPrefix
1901
+ );
1902
+ return this;
1903
+ }
1904
+
1781
1905
  /**
1782
1906
  * Create an element. Mirrors the joining transformer API so templates can
1783
1907
  * call `this.element()`.
1784
1908
  * @param {string} name - Element name
1785
- * @param {Record<string, string>} [atts] - Attributes object
1786
- * @param {any[]} [children] - Child nodes
1787
- * @param {import('./JSONJoiningTransformer.js').
1788
- * SimpleCallback<T>} [cb] - Callback function
1909
+ * @param {Record<string, string>|any[]|
1910
+ * ((this: JSONPathTransformerContext<T>) => void)} [atts] -
1911
+ * Attributes object or children or callback
1912
+ * @param {any[]|
1913
+ * ((this: JSONPathTransformerContext<T>) => void)} [children] -
1914
+ * Child nodes or callback
1915
+ * @param {(this: JSONPathTransformerContext<T>) => void} [cb] -
1916
+ * Callback function
1789
1917
  * @param {string[]} [useAttributeSets] - Attribute set names to apply
1790
1918
  * @returns {this}
1791
1919
  */
@@ -471,14 +471,14 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
471
471
  }
472
472
 
473
473
  /**
474
- * @param {string|Element} elName - Element name or element object
474
+ * @param {string|Element} elem - Element name or element object
475
475
  * @param {ElementAttributes} [atts] - Element attributes
476
476
  * @param {any[]} [childNodes] - Child nodes
477
477
  * @param {(this: StringJoiningTransformer) => void} [cb] - Callback function
478
478
  * @param {string[]} [useAttributeSets] - Attribute set names to apply
479
479
  * @returns {StringJoiningTransformer}
480
480
  */
481
- element (elName, atts, childNodes, cb, useAttributeSets) {
481
+ element (elem, atts, childNodes, cb, useAttributeSets) {
482
482
  // If a parent element's start tag is still open, close it before
483
483
  // starting a new element to ensure valid nesting.
484
484
  if (this._openTagState) {
@@ -486,6 +486,24 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
486
486
  this._openTagState = false;
487
487
  }
488
488
 
489
+ /** @type {string} */
490
+ let elName;
491
+ if (typeof elem === 'object') {
492
+ /** @type {Record<string, string>} */
493
+ const objAtts = {};
494
+ /** @type {any} */
495
+ const elObj = elem;
496
+ [...elObj.attributes].forEach(function (att, i) {
497
+ objAtts[att.name] = att.value;
498
+ });
499
+ atts = Object.assign(objAtts, atts);
500
+ elName = elObj.nodeName;
501
+ } else {
502
+ elName = elem;
503
+ }
504
+
505
+ elName = this._replaceNamespaceAliasInElement(elName);
506
+
489
507
  if (!this.root) {
490
508
  this.root = elName;
491
509
 
@@ -565,18 +583,6 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
565
583
  return this;
566
584
  }
567
585
 
568
- if (typeof elName === 'object') {
569
- /** @type {Record<string, string>} */
570
- const objAtts = {};
571
- /** @type {any} */
572
- const elObj = elName;
573
- [...elObj.attributes].forEach(function (att, i) {
574
- objAtts[att.name] = att.value;
575
- });
576
- atts = Object.assign(objAtts, atts);
577
- elName = elObj.nodeName;
578
- }
579
-
580
586
  // Apply attribute sets if specified
581
587
  if (useAttributeSets && useAttributeSets.length) {
582
588
  const mergedAtts = {};
@@ -627,7 +633,10 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
627
633
  * @returns {StringJoiningTransformer}
628
634
  */
629
635
  namespace (prefix, namespaceURI) {
630
- this.append(' xmlns:' + prefix + '="' +
636
+ const alias = this._getNamespaceAlias(prefix);
637
+ this.append(' ' + (
638
+ alias === '#default' ? 'xmlns' : 'xmlns:' + alias
639
+ ) + '="' +
631
640
  this._replaceCharacterMaps(namespaceURI) + '"');
632
641
  return this;
633
642
  }
@@ -644,8 +653,7 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
644
653
  // Adds an attribute to the most recently opened start tag. Supports
645
654
  // special objects for dataset and ordered attributes ($a). Escapes '&'
646
655
  // and '"' unless cfg.preEscapedAttributes or avoidAttEscape are set.
647
- // eslint-disable-next-line unicorn/no-this-assignment -- Temporary
648
- const that = this;
656
+
649
657
  if (!this._openTagState) {
650
658
  throw new Error(
651
659
  'An attribute cannot be added after an opening tag has been closed ' +
@@ -659,8 +667,8 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
659
667
  const valObj = /** @type {any} */ (val);
660
668
  switch (name) {
661
669
  case 'dataset': {
662
- Object.keys(valObj).forEach(function (att) {
663
- that.attribute(
670
+ Object.keys(valObj).forEach((att) => {
671
+ this.attribute(
664
672
  'data-' + att.replaceAll(
665
673
  camelCase, _makeDatasetAttribute
666
674
  ),
@@ -673,8 +681,8 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
673
681
  case '$a': { // Ordered attributes
674
682
  /** @type {unknown[][]} */
675
683
  const valArr = /** @type {any} */ (val);
676
- valArr.forEach(function (attArr) {
677
- that.attribute(
684
+ valArr.forEach((attArr) => {
685
+ this.attribute(
678
686
  String(attArr[0]),
679
687
  /** @type {string|Record<string, unknown>} */ (attArr[1]),
680
688
  false
@@ -695,7 +703,10 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
695
703
  val = (this._cfg.preEscapedAttributes || avoidAttEscape)
696
704
  ? valStr
697
705
  : valStr.replaceAll('&', '&amp;').replaceAll('"', '&quot;');
698
- this.append(' ' + name + '="' + this._replaceCharacterMaps(val) + '"');
706
+ this.append(
707
+ ' ' + this._replaceNamespaceAliasInNamespaceDeclaration(name) + '="' +
708
+ this._replaceCharacterMaps(val) + '"'
709
+ );
699
710
  return this;
700
711
  }
701
712
 
@@ -34,6 +34,10 @@ class XPathTransformer {
34
34
  if (Array.isArray(template)) {
35
35
  return {path: template[0], template: template[1]};
36
36
  }
37
+ // Normalize 'match' to 'path' for XSLT compatibility
38
+ if (template.match && !template.path) {
39
+ return {...template, path: template.match};
40
+ }
37
41
  return template;
38
42
  });
39
43
  this.templates.forEach((template) => {