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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jtlt",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "author": "Brett Zamir",
6
6
  "contributors": [],
@@ -72,6 +72,8 @@ class AbstractJoiningTransformer {
72
72
  this._cfg = cfg ?? /** @type {JoiningTransformerConfig<T>} */ ({});
73
73
  /** @type {Record<string, OutputCharacters>} */
74
74
  this._characterMap = {};
75
+ /** @type {Record<string, Record<string, string>>} */
76
+ this._attributeSet = {};
75
77
  }
76
78
 
77
79
  /**
@@ -105,6 +107,15 @@ class AbstractJoiningTransformer {
105
107
  this._characterMap[name] = outputCharacters;
106
108
  }
107
109
 
110
+ /**
111
+ * @param {string} name
112
+ * @param {Record<string, string>} attributes
113
+ * @returns {void}
114
+ */
115
+ attributeSet (name, attributes) {
116
+ this._attributeSet[name] = attributes;
117
+ }
118
+
108
119
  /**
109
120
  * @param {string} str
110
121
  * @returns {string}
@@ -29,6 +29,8 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
29
29
  this._docs = [];
30
30
  /** @type {Array<{href: string, document: XMLDocument, format?: string}>} */
31
31
  this._resultDocuments = [];
32
+ /** @type {Record<string, unknown>} */
33
+ this.propertySets = {};
32
34
  }
33
35
 
34
36
  /**
@@ -67,6 +69,16 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
67
69
  this._dom[prop] = val;
68
70
  }
69
71
 
72
+ /**
73
+ * Alias for propValue(). Set a key-value pair in the current map/object.
74
+ * @param {string} prop - Property name
75
+ * @param {any} val - Property value
76
+ * @returns {void}
77
+ */
78
+ mapEntry (prop, val) {
79
+ return this.propValue(prop, val);
80
+ }
81
+
70
82
  /**
71
83
  * @param {Record<string, unknown>} obj - Object to serialize
72
84
  * @param {(this: DOMJoiningTransformer) => void} [cb] - Callback function.
@@ -75,9 +87,20 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
75
87
  * @returns {DOMJoiningTransformer}
76
88
  */
77
89
  object (obj, cb, usePropertySets, propSets) {
90
+ // eslint-disable-next-line unicorn/no-this-assignment -- Temporary
91
+ const that = this;
78
92
  this._requireSameChildren('dom', 'object');
79
- if (this._cfg.JHTMLForJSON) {
93
+
94
+ if (usePropertySets !== undefined) {
95
+ obj = usePropertySets.reduce(function (o, psName) {
96
+ return that._usePropertySets(o, psName);
97
+ }, obj);
98
+ }
99
+ if (propSets !== undefined) {
80
100
  Object.assign(obj, propSets);
101
+ }
102
+
103
+ if (this._cfg.JHTMLForJSON) {
81
104
  this.append(JHTML.toJHTMLDOM(/** @type {any} */ (obj)));
82
105
  } else {
83
106
  // Todo: set current position and deal with children
@@ -86,6 +109,18 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
86
109
  return this;
87
110
  }
88
111
 
112
+ /**
113
+ * Alias for object(). Build an object/map.
114
+ * @param {Record<string, unknown>} obj - Object to serialize
115
+ * @param {(this: DOMJoiningTransformer) => void} [cb] - Callback function.
116
+ * @param {any[]} [usePropertySets] - Property sets to use
117
+ * @param {Record<string, unknown>} [propSets] - Additional property sets
118
+ * @returns {DOMJoiningTransformer}
119
+ */
120
+ map (obj, cb, usePropertySets, propSets) {
121
+ return this.object(obj, cb, usePropertySets, propSets);
122
+ }
123
+
89
124
  /**
90
125
  * @param {any[]|Element} arr
91
126
  * @param {(this: DOMJoiningTransformer) => void} [cb] - Callback function
@@ -241,9 +276,10 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
241
276
  * @param {(Node|string)[]|((this: DOMJoiningTransformer) => void)
242
277
  * } [childNodes] - Child nodes or callback
243
278
  * @param {(this: DOMJoiningTransformer) => void} [cb] - Callback
279
+ * @param {string[]} [useAttributeSets] - Attribute set names to apply
244
280
  * @returns {DOMJoiningTransformer}
245
281
  */
246
- element (elName, atts, childNodes, cb) {
282
+ element (elName, atts, childNodes, cb, useAttributeSets) {
247
283
  // Handle argument overloading like other transformers
248
284
  if (Array.isArray(atts)) {
249
285
  cb = /** @type {(this: DOMJoiningTransformer) => void} */ (
@@ -328,6 +364,21 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
328
364
  // Use the document's root element
329
365
  const el = doc.documentElement;
330
366
 
367
+ // Apply attribute sets if specified
368
+ if (useAttributeSets && useAttributeSets.length) {
369
+ useAttributeSets.forEach((setName) => {
370
+ if (this._attributeSet[setName]) {
371
+ for (const att in this._attributeSet[setName]) {
372
+ if (Object.hasOwn(this._attributeSet[setName], att)) {
373
+ el.setAttribute(att, this._replaceCharacterMaps(
374
+ this._attributeSet[setName][att]
375
+ ));
376
+ }
377
+ }
378
+ }
379
+ });
380
+ }
381
+
331
382
  for (const att in atts) {
332
383
  if (Object.hasOwn(atts, att)) {
333
384
  el.setAttribute(att, this._replaceCharacterMaps(atts[att]));
@@ -363,6 +414,21 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
363
414
  this._cfg.document.createElement(elName)
364
415
  );
365
416
 
417
+ // Apply attribute sets if specified
418
+ if (useAttributeSets && useAttributeSets.length) {
419
+ useAttributeSets.forEach((setName) => {
420
+ if (this._attributeSet[setName]) {
421
+ for (const att in this._attributeSet[setName]) {
422
+ if (Object.hasOwn(this._attributeSet[setName], att)) {
423
+ el.setAttribute(att, this._replaceCharacterMaps(
424
+ this._attributeSet[setName][att]
425
+ ));
426
+ }
427
+ }
428
+ }
429
+ });
430
+ }
431
+
366
432
  for (const att in atts) {
367
433
  if (Object.hasOwn(atts, att)) {
368
434
  el.setAttribute(att, this._replaceCharacterMaps(atts[att]));
@@ -571,6 +637,23 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
571
637
 
572
638
  return this;
573
639
  }
640
+
641
+ /**
642
+ * Helper method to use property sets.
643
+ * @param {Record<string, unknown>} obj - Object to apply property set to
644
+ * @param {string} psName - Property set name
645
+ * @returns {Record<string, unknown>}
646
+ */
647
+ _usePropertySets (obj, psName) {
648
+ // Merge named property set from this.propertySets into obj
649
+ if (this.propertySets && this.propertySets[psName]) {
650
+ return {
651
+ ...obj,
652
+ ...this.propertySets[psName]
653
+ };
654
+ }
655
+ return obj;
656
+ }
574
657
  }
575
658
 
576
659
  export default DOMJoiningTransformer;
@@ -142,6 +142,16 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
142
142
  (/** @type {Record<string, any>} */ (this._obj))[prop] = val;
143
143
  }
144
144
 
145
+ /**
146
+ * Alias for propValue(). Set a key-value pair in the current map/object.
147
+ * @param {string} prop - Property name
148
+ * @param {any} val - Property value
149
+ * @returns {void}
150
+ */
151
+ mapEntry (prop, val) {
152
+ return this.propValue(prop, val);
153
+ }
154
+
145
155
  /* c8 ignore next 13 -- JSDoc block incorrectly counted as coverable by c8 */
146
156
  /**
147
157
  * @param {Record<string, unknown>|ObjectCallback} [objOrCb]
@@ -207,6 +217,22 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
207
217
  return this;
208
218
  }
209
219
 
220
+ /**
221
+ * Alias for object(). Build an object/map.
222
+ * @param {Record<string, unknown>|ObjectCallback} [objOrCb]
223
+ * Seed object or callback.
224
+ * @param {ObjectCallback|any[]} [cbOrUsePropertySets] Callback or sets.
225
+ * @param {any[]|Record<string, unknown>} [usePropertySetsOrPropSets]
226
+ * Sets or prop sets.
227
+ * @param {Record<string, unknown>} [propSets] Key-value pairs to add.
228
+ * @returns {JSONJoiningTransformer}
229
+ */
230
+ map (objOrCb, cbOrUsePropertySets, usePropertySetsOrPropSets, propSets) {
231
+ return this.object(
232
+ objOrCb, cbOrUsePropertySets, usePropertySetsOrPropSets, propSets
233
+ );
234
+ }
235
+
210
236
  /**
211
237
  * Creates a new array and executes a callback in its context.
212
238
  * @param {any[]|ArrayCallback} [arrOrCb] Seed array or callback.
@@ -339,9 +365,10 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
339
365
  * Attrs, children, or cb.
340
366
  * @param {any[]|SimpleCallback} [childNodes] Children or cb.
341
367
  * @param {SimpleCallback} [cb] Builder callback.
368
+ * @param {string[]} [useAttributeSets] - Attribute set names to apply
342
369
  * @returns {JSONJoiningTransformer}
343
370
  */
344
- element (elName, atts, childNodes, cb) {
371
+ element (elName, atts, childNodes, cb, useAttributeSets) {
345
372
  this._requireSameChildren('json', 'element');
346
373
  const isRoot = !this.root;
347
374
  if (isRoot) {
@@ -379,6 +406,17 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
379
406
  /** @type {import('jamilih').JamilihChildren} */
380
407
  const jmlChildren = [];
381
408
 
409
+ // Apply attribute sets if specified
410
+ if (useAttributeSets && useAttributeSets.length) {
411
+ const mergedAtts = {};
412
+ useAttributeSets.forEach((setName) => {
413
+ if (this._attributeSet[setName]) {
414
+ Object.assign(mergedAtts, this._attributeSet[setName]);
415
+ }
416
+ });
417
+ attsObj = Object.assign(mergedAtts, attsObj);
418
+ }
419
+
382
420
  // Preprocess special attribute helpers present directly on attsObj
383
421
  if (attsObj.dataset && typeof attsObj.dataset === 'object' &&
384
422
  !Array.isArray(attsObj.dataset)
@@ -105,6 +105,10 @@ class JSONPathTransformerContext {
105
105
  this._currPath = undefined;
106
106
  /** @type {Record<string, any> | undefined} */
107
107
  this._params = undefined;
108
+ /** @type {string[]} */
109
+ this._preserveSpaceElements = [];
110
+ /** @type {string[]} */
111
+ this._stripSpaceElements = [];
108
112
  }
109
113
 
110
114
  /**
@@ -120,6 +124,24 @@ class JSONPathTransformerContext {
120
124
  }
121
125
  }
122
126
 
127
+ /**
128
+ * Check if whitespace should be stripped for a given element name.
129
+ * @param {string} elementName - The element name to check
130
+ * @returns {boolean}
131
+ */
132
+ _shouldStripSpace (elementName) {
133
+ // Check if in preserve list (takes precedence)
134
+ if (this._preserveSpaceElements.some((pattern) => {
135
+ return pattern === '*' || pattern === elementName;
136
+ })) {
137
+ return false;
138
+ }
139
+ // Check if in strip list
140
+ return this._stripSpaceElements.some((pattern) => {
141
+ return pattern === '*' || pattern === elementName;
142
+ });
143
+ }
144
+
123
145
  /**
124
146
  * Gets the joining transformer from config.
125
147
  * @returns {T extends "json" ? import('./JSONJoiningTransformer.js').
@@ -1654,6 +1676,40 @@ class JSONPathTransformerContext {
1654
1676
  return this;
1655
1677
  }
1656
1678
 
1679
+ /**
1680
+ * Alias for propValue(). Set a key-value pair in the current map/object.
1681
+ * @param {string} prop - Property name
1682
+ * @param {any} val - Property value
1683
+ * @returns {this}
1684
+ */
1685
+ mapEntry (prop, val) {
1686
+ return this.propValue(prop, val);
1687
+ }
1688
+
1689
+ /**
1690
+ * Declare elements for which whitespace-only text nodes should be preserved.
1691
+ * Equivalent to xsl:preserve-space.
1692
+ * @param {string|string[]} elements - Element name(s) or patterns
1693
+ * @returns {this}
1694
+ */
1695
+ preserveSpace (elements) {
1696
+ const elemArray = Array.isArray(elements) ? elements : [elements];
1697
+ this._preserveSpaceElements.push(...elemArray);
1698
+ return this;
1699
+ }
1700
+
1701
+ /**
1702
+ * Declare elements for which whitespace-only text nodes should be stripped.
1703
+ * Equivalent to xsl:strip-space.
1704
+ * @param {string|string[]} elements - Element name(s) or patterns
1705
+ * @returns {this}
1706
+ */
1707
+ stripSpace (elements) {
1708
+ const elemArray = Array.isArray(elements) ? elements : [elements];
1709
+ this._stripSpaceElements.push(...elemArray);
1710
+ return this;
1711
+ }
1712
+
1657
1713
  /**
1658
1714
  * Build an object. Mirrors the joining transformer API. All joiners now
1659
1715
  * support both signatures: (obj, cb, usePropertySets, propSets) with seed
@@ -1666,6 +1722,15 @@ class JSONPathTransformerContext {
1666
1722
  return this;
1667
1723
  }
1668
1724
 
1725
+ /**
1726
+ * Alias for object(). Build an object/map.
1727
+ * @param {...any} args - Arguments to pass to joiner
1728
+ * @returns {this}
1729
+ */
1730
+ map (...args) {
1731
+ return this.object(...args);
1732
+ }
1733
+
1669
1734
  /**
1670
1735
  * Build an array. Mirrors the joining transformer API. All joiners now
1671
1736
  * support both signatures: (arr, cb) with seed array or (cb) without.
@@ -1698,6 +1763,16 @@ class JSONPathTransformerContext {
1698
1763
  return this;
1699
1764
  }
1700
1765
 
1766
+ /**
1767
+ * @param {string} name
1768
+ * @param {Record<string, string>} attributes
1769
+ * @returns {this}
1770
+ */
1771
+ attributeSet (name, attributes) {
1772
+ this._getJoiningTransformer().attributeSet(name, attributes);
1773
+ return this;
1774
+ }
1775
+
1701
1776
  /**
1702
1777
  * Create an element. Mirrors the joining transformer API so templates can
1703
1778
  * call `this.element()`.
@@ -1706,11 +1781,12 @@ class JSONPathTransformerContext {
1706
1781
  * @param {any[]} [children] - Child nodes
1707
1782
  * @param {import('./JSONJoiningTransformer.js').
1708
1783
  * SimpleCallback<T>} [cb] - Callback function
1784
+ * @param {string[]} [useAttributeSets] - Attribute set names to apply
1709
1785
  * @returns {this}
1710
1786
  */
1711
- element (name, atts, children, cb) {
1787
+ element (name, atts, children, cb, useAttributeSets) {
1712
1788
  /** @type {any} */ (this._getJoiningTransformer()).element(
1713
- name, atts, children, cb
1789
+ name, atts, children, cb, useAttributeSets
1714
1790
  );
1715
1791
  return this;
1716
1792
  }
@@ -1937,6 +2013,26 @@ class JSONPathTransformerContext {
1937
2013
  }
1938
2014
  return this;
1939
2015
  }
2016
+
2017
+ /**
2018
+ * Assert that a test condition is true, throwing an error if it fails.
2019
+ * Equivalent to xsl:assert. Evaluates a JSONPath expression using the
2020
+ * same truthiness rules as if() and choose().
2021
+ * @param {string} test - JSONPath expression to test
2022
+ * @param {string} [message] - Optional error message to include
2023
+ * @returns {this}
2024
+ * @throws {Error} When the test expression evaluates to false
2025
+ */
2026
+ assert (test, message) {
2027
+ const passes = this._passesIf(test);
2028
+ if (!passes) {
2029
+ const errorMsg = message
2030
+ ? `Assertion failed: ${message}`
2031
+ : `Assertion failed: ${test}`;
2032
+ throw new Error(errorMsg);
2033
+ }
2034
+ return this;
2035
+ }
1940
2036
  }
1941
2037
 
1942
2038
  export default JSONPathTransformerContext;
@@ -173,6 +173,16 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
173
173
  return this;
174
174
  }
175
175
 
176
+ /**
177
+ * Alias for propValue(). Set a key-value pair in the current map/object.
178
+ * @param {string} prop - Property name
179
+ * @param {any} val - Property value
180
+ * @returns {StringJoiningTransformer}
181
+ */
182
+ mapEntry (prop, val) {
183
+ return this.propValue(prop, val);
184
+ }
185
+
176
186
  /**
177
187
  * @param {string} prop - Property name
178
188
  * @param {(this: StringJoiningTransformer) => void} cb - Callback function
@@ -258,6 +268,18 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
258
268
  return this;
259
269
  }
260
270
 
271
+ /**
272
+ * Alias for object(). Build an object/map.
273
+ * @param {Record<string, unknown>|Element} obj - Object to serialize
274
+ * @param {(this: StringJoiningTransformer) => void} cb - Callback function
275
+ * @param {any[]} [usePropertySets] - Property sets to use
276
+ * @param {Record<string, unknown>} [propSets] - Additional property sets
277
+ * @returns {StringJoiningTransformer}
278
+ */
279
+ map (obj, cb, usePropertySets, propSets) {
280
+ return this.object(obj, cb, usePropertySets, propSets);
281
+ }
282
+
261
283
  /**
262
284
  * @param {any[]|Element} [arr] - Array to serialize
263
285
  * @param {(this: StringJoiningTransformer) => void} [cb] - Callback function
@@ -440,9 +462,10 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
440
462
  * @param {ElementAttributes} [atts] - Element attributes
441
463
  * @param {any[]} [childNodes] - Child nodes
442
464
  * @param {(this: StringJoiningTransformer) => void} [cb] - Callback function
465
+ * @param {string[]} [useAttributeSets] - Attribute set names to apply
443
466
  * @returns {StringJoiningTransformer}
444
467
  */
445
- element (elName, atts, childNodes, cb) {
468
+ element (elName, atts, childNodes, cb, useAttributeSets) {
446
469
  // If a parent element's start tag is still open, close it before
447
470
  // starting a new element to ensure valid nesting.
448
471
  if (this._openTagState) {
@@ -541,6 +564,17 @@ class StringJoiningTransformer extends AbstractJoiningTransformer {
541
564
  elName = elObj.nodeName;
542
565
  }
543
566
 
567
+ // Apply attribute sets if specified
568
+ if (useAttributeSets && useAttributeSets.length) {
569
+ const mergedAtts = {};
570
+ useAttributeSets.forEach((setName) => {
571
+ if (this._attributeSet[setName]) {
572
+ Object.assign(mergedAtts, this._attributeSet[setName]);
573
+ }
574
+ });
575
+ atts = Object.assign(mergedAtts, atts);
576
+ }
577
+
544
578
  this.append('<' + elName);
545
579
  /** @type {any} */
546
580
  const oldTagState = this._openTagState;