jtlt 0.2.0 → 0.3.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.
Files changed (50) hide show
  1. package/CHANGES.md +18 -0
  2. package/README.md +16 -87
  3. package/demo/calltemplate-params-demo.js +138 -0
  4. package/demo/index.html +31 -0
  5. package/demo/index.js +30 -0
  6. package/demo/xpath2-placeholder.js +1 -0
  7. package/dist/AbstractJoiningTransformer.d.ts +83 -9
  8. package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
  9. package/dist/DOMJoiningTransformer.d.ts +85 -25
  10. package/dist/DOMJoiningTransformer.d.ts.map +1 -1
  11. package/dist/JSONJoiningTransformer.d.ts +159 -51
  12. package/dist/JSONJoiningTransformer.d.ts.map +1 -1
  13. package/dist/JSONPathTransformer.d.ts +37 -38
  14. package/dist/JSONPathTransformer.d.ts.map +1 -1
  15. package/dist/JSONPathTransformerContext.d.ts +247 -121
  16. package/dist/JSONPathTransformerContext.d.ts.map +1 -1
  17. package/dist/StringJoiningTransformer.d.ts +132 -41
  18. package/dist/StringJoiningTransformer.d.ts.map +1 -1
  19. package/dist/XPathTransformer.d.ts +35 -20
  20. package/dist/XPathTransformer.d.ts.map +1 -1
  21. package/dist/XPathTransformerContext.d.ts +191 -99
  22. package/dist/XPathTransformerContext.d.ts.map +1 -1
  23. package/dist/index-browser.d.ts +4 -0
  24. package/dist/index-browser.d.ts.map +1 -0
  25. package/dist/index-node.d.ts +4 -0
  26. package/dist/index-node.d.ts.map +1 -0
  27. package/dist/index.d.ts +330 -57
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/types.d.ts +204 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/docs/API.expanded.md +167 -2
  32. package/docs/API.md +91 -1
  33. package/docs/TO-DO.md +144 -0
  34. package/docs/calltemplate-params.md +251 -0
  35. package/eslint.config.js +9 -5
  36. package/package.json +13 -7
  37. package/pnpm-workspace.yaml +1 -0
  38. package/src/AbstractJoiningTransformer.js +54 -15
  39. package/src/DOMJoiningTransformer.js +275 -28
  40. package/src/JSONJoiningTransformer.js +351 -70
  41. package/src/JSONPathTransformer.js +48 -30
  42. package/src/JSONPathTransformerContext.js +308 -104
  43. package/src/StringJoiningTransformer.js +311 -57
  44. package/src/XPathTransformer.js +27 -12
  45. package/src/XPathTransformerContext.js +467 -89
  46. package/src/index-browser.js +5 -0
  47. package/src/index-node.js +7 -0
  48. package/src/index.js +498 -97
  49. package/typings/xpath2-js.d.ts +40 -1
  50. package/src/types/xpath2-js.d.ts +0 -2
@@ -1,6 +1,12 @@
1
1
  import * as JHTML from 'jhtml';
2
2
  import AbstractJoiningTransformer from './AbstractJoiningTransformer.js';
3
3
 
4
+ /**
5
+ * @callback SimpleCallback
6
+ * @this {DOMJoiningTransformer}
7
+ * @returns {void}
8
+ */
9
+
4
10
  /**
5
11
  * Joining transformer that accumulates into a DOM tree.
6
12
  *
@@ -8,17 +14,21 @@ import AbstractJoiningTransformer from './AbstractJoiningTransformer.js';
8
14
  * It expects templates to build DOM nodes explicitly (e.g., via element(),
9
15
  * attribute(), and text()), though string/number/boolean will append text
10
16
  * nodes for convenience.
17
+ * @extends {AbstractJoiningTransformer<"dom">}
11
18
  */
12
19
  class DOMJoiningTransformer extends AbstractJoiningTransformer {
13
20
  /**
14
21
  * @param {DocumentFragment|Element} o - Initial DOM node
15
- * @param {object} cfg - Configuration object
16
- * @param {object} [cfg.document] - Document object
22
+ * @param {import('./AbstractJoiningTransformer.js').
23
+ * DOMJoiningTransformerConfig} cfg - Configuration object
17
24
  */
18
25
  constructor (o, cfg) {
19
- super(cfg); // Include this in any subclass of AbstractJoiningTransformer
20
- this._dom = o ||
21
- /** @type {any} */ (cfg).document.createDocumentFragment();
26
+ super(cfg);
27
+ this._dom = o || cfg.document.createDocumentFragment();
28
+ /** @type {XMLDocument[]} */
29
+ this._docs = [];
30
+ /** @type {Array<{href: string, document: XMLDocument, format?: string}>} */
31
+ this._resultDocuments = [];
22
32
  }
23
33
 
24
34
  /**
@@ -38,32 +48,36 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
38
48
  }
39
49
 
40
50
  /**
41
- * @returns {DocumentFragment|Element}
51
+ * @returns {DocumentFragment|Element|XMLDocument[]}
42
52
  */
43
53
  get () {
54
+ if (this._cfg.exposeDocuments) {
55
+ return this._docs;
56
+ }
44
57
  return this._dom;
45
58
  }
46
59
 
47
60
  /**
48
61
  * @param {string} prop - Property name
49
- * @param {*} val - Property value
62
+ * @param {any} val - Property value
50
63
  * @returns {void}
51
64
  */
52
- // eslint-disable-next-line class-methods-use-this -- Incomplete?
53
65
  propValue (prop, val) {
54
- //
66
+ // @ts-expect-error Ok
67
+ this._dom[prop] = val;
55
68
  }
56
69
 
57
70
  /**
58
- * @param {object} obj - Object to serialize
59
- * @param {Function} [cb] - Callback function.
71
+ * @param {Record<string, unknown>} obj - Object to serialize
72
+ * @param {(this: DOMJoiningTransformer) => void} [cb] - Callback function.
60
73
  * @param {any[]} [usePropertySets] - Property sets to use
61
- * @param {object} [propSets] - Additional property sets
74
+ * @param {Record<string, unknown>} [propSets] - Additional property sets
62
75
  * @returns {DOMJoiningTransformer}
63
76
  */
64
77
  object (obj, cb, usePropertySets, propSets) {
65
78
  this._requireSameChildren('dom', 'object');
66
- if (this._cfg && /** @type {any} */ (this._cfg).JHTMLForJSON) {
79
+ if (this._cfg.JHTMLForJSON) {
80
+ Object.assign(obj, propSets);
67
81
  this.append(JHTML.toJHTMLDOM(/** @type {any} */ (obj)));
68
82
  } else {
69
83
  // Todo: set current position and deal with children
@@ -74,12 +88,12 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
74
88
 
75
89
  /**
76
90
  * @param {any[]|Element} arr
77
- * @param {Function} [cb] - Callback function
91
+ * @param {(this: DOMJoiningTransformer) => void} [cb] - Callback function
78
92
  * @returns {DOMJoiningTransformer}
79
93
  */
80
94
  array (arr, cb) {
81
95
  this._requireSameChildren('dom', 'array');
82
- if (this._cfg && /** @type {any} */ (this._cfg).JHTMLForJSON) {
96
+ if (this._cfg.JHTMLForJSON) {
83
97
  this.append(JHTML.toJHTMLDOM(/** @type {any} */ (arr)));
84
98
  } else {
85
99
  // Todo: set current position and deal with children
@@ -90,7 +104,8 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
90
104
 
91
105
  /**
92
106
  * @param {string} str - String value
93
- * @param {Function} cb - Callback function (unused)
107
+ * @param {(this: DOMJoiningTransformer) => void} [cb] - Callback
108
+ * function (unused)
94
109
  * @returns {DOMJoiningTransformer}
95
110
  */
96
111
  string (str, cb) {
@@ -129,7 +144,7 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
129
144
  * @returns {DOMJoiningTransformer}
130
145
  */
131
146
  undefined () {
132
- if (this._cfg && /** @type {any} */ (this._cfg).mode !== 'JavaScript') {
147
+ if (this._cfg.mode !== 'JavaScript') {
133
148
  throw new Error(
134
149
  'undefined is not allowed unless added in JavaScript mode'
135
150
  );
@@ -143,7 +158,7 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
143
158
  * @returns {DOMJoiningTransformer}
144
159
  */
145
160
  nonfiniteNumber (num) {
146
- if (this._cfg && /** @type {any} */ (this._cfg).mode !== 'JavaScript') {
161
+ if (this._cfg.mode !== 'JavaScript') {
147
162
  throw new Error(
148
163
  'Non-finite numbers are not allowed unless added in JavaScript mode'
149
164
  );
@@ -153,11 +168,11 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
153
168
  }
154
169
 
155
170
  /**
156
- * @param {Function} func - Function to stringify
171
+ * @param {(...args: any[]) => any} func - Function to stringify
157
172
  * @returns {DOMJoiningTransformer}
158
173
  */
159
174
  function (func) {
160
- if (this._cfg && /** @type {any} */ (this._cfg).mode !== 'JavaScript') {
175
+ if (this._cfg.mode !== 'JavaScript') {
161
176
  throw new Error(
162
177
  'function is not allowed unless added in JavaScript mode'
163
178
  );
@@ -167,9 +182,23 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
167
182
  }
168
183
 
169
184
  /**
170
- * @param {string} elName - Element name
171
- * @param {object} [atts] - Attributes object
172
- * @param {Function} [cb] - Callback function
185
+ * @param {import('./StringJoiningTransformer.js').OutputConfig} cfg
186
+ * @returns {DOMJoiningTransformer}
187
+ */
188
+ output (cfg) {
189
+ // We wait until first element is set in `element()` to add
190
+ // XML declaration and DOCTYPE as latter depends on root element
191
+ this._outputConfig = cfg;
192
+
193
+ // Use for file extension if making downloadable?
194
+ this.mediaType = cfg.mediaType;
195
+ return this;
196
+ }
197
+
198
+ /**
199
+ * @param {Element|string} elName - Element name
200
+ * @param {Record<string, string>} [atts] - Attributes object
201
+ * @param {(this: DOMJoiningTransformer) => void} [cb] - Callback function
173
202
  * @returns {DOMJoiningTransformer}
174
203
  */
175
204
  element (elName, atts, cb) {
@@ -179,13 +208,100 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
179
208
  // Todo: allow separate XML DOM one with XML String and hXML conversions
180
209
  // (HTML to XHTML is inevitably safe?)
181
210
 
182
- const el = this._cfg &&
183
- /** @type {any} */ (this._cfg).document.createElement(elName);
211
+ if (!this.root && this._outputConfig) {
212
+ this.root = elName;
213
+
214
+ const elementName = typeof elName === 'string'
215
+ ? elName
216
+ : elName.localName;
217
+
218
+ const {
219
+ omitXmlDeclaration, doctypePublic, doctypeSystem, method
220
+ /* c8 ignore start -- outputConfig ?? attribution issue */
221
+ } = this._outputConfig ?? {};
222
+ /* c8 ignore stop */
223
+
224
+ /* c8 ignore start -- namespace/prefix detection branches */
225
+ const dtd = this._cfg.document.implementation.createDocumentType(
226
+ elementName,
227
+ doctypePublic ?? '',
228
+ doctypeSystem ?? ''
229
+ );
230
+
231
+ let xmlns;
232
+ if (elementName.includes(':')) {
233
+ const prefix = elementName.slice(0, elementName.indexOf(':'));
234
+ xmlns = atts?.[prefix];
235
+ } else {
236
+ ({xmlns} = atts ?? {});
237
+ }
238
+
239
+ const doc = /** @type {XMLDocument} */ (
240
+ this._cfg.document.implementation.createDocument(
241
+ xmlns ?? null,
242
+ elementName,
243
+ dtd
244
+ )
245
+ );
246
+ /* c8 ignore stop */
247
+
248
+ /* c8 ignore start -- third OR condition short-circuits */
249
+ if (!omitXmlDeclaration && (
250
+ method === 'xml' || method === 'xhtml' || omitXmlDeclaration === false)
251
+ ) {
252
+ const {version, encoding, standalone} = this._outputConfig ?? {};
253
+
254
+ const xmlDeclarationData = `${
255
+ version ? ` version="${version}"` : ''
256
+ }${
257
+ encoding ? ` encoding="${encoding}"` : ''
258
+ }${
259
+ standalone ? ` standalone="yes"` : ''
260
+ }`.slice(1);
261
+
262
+ const xmlDecl = doc.createProcessingInstruction(
263
+ 'xml', xmlDeclarationData
264
+ );
265
+ doc.insertBefore(
266
+ xmlDecl,
267
+ doc.firstChild
268
+ );
269
+ }
270
+ /* c8 ignore stop */
271
+
272
+ // Push the document to _docs
273
+ this._docs.push(doc);
274
+
275
+ // Use the document's root element
276
+ const el = doc.documentElement;
277
+
278
+ for (const att in atts) {
279
+ if (Object.hasOwn(atts, att)) {
280
+ el.setAttribute(att, atts[att]);
281
+ }
282
+ }
283
+
284
+ const oldDOM = this._dom;
285
+ this._dom = el;
286
+
287
+ if (cb) {
288
+ cb.call(this);
289
+ }
290
+ this._dom = oldDOM;
291
+
292
+ return this;
293
+ }
294
+
295
+ // Non-root elements
296
+ const el = elName && typeof elName === 'object'
297
+ ? elName
298
+ : /** @type {Element} */ (
299
+ this._cfg.document.createElement(elName)
300
+ );
301
+
184
302
  for (const att in atts) {
185
303
  if (Object.hasOwn(atts, att)) {
186
- /** @type {Record<string, any>} */
187
- const attsObj = /** @type {any} */ (atts);
188
- el.setAttribute(att, attsObj[att]);
304
+ el.setAttribute(att, atts[att]);
189
305
  }
190
306
  }
191
307
  this.append(el);
@@ -193,6 +309,7 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
193
309
  const oldDOM = this._dom;
194
310
 
195
311
  this._dom = el;
312
+
196
313
  if (cb) {
197
314
  cb.call(this);
198
315
  }
@@ -224,6 +341,40 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
224
341
  return this;
225
342
  }
226
343
 
344
+ /**
345
+ * @param {string} text
346
+ * @returns {DOMJoiningTransformer}}
347
+ */
348
+ comment (text) {
349
+ if (!this._dom || typeof this._dom !== 'object' ||
350
+ (![1, 9, 11].includes(this._dom.nodeType))) {
351
+ throw new Error(
352
+ 'You may only set a comment on a document, fragment, or element'
353
+ );
354
+ }
355
+ this._dom.append((this._dom.ownerDocument).createComment(text));
356
+ return this;
357
+ }
358
+
359
+ /**
360
+ * @param {string} target
361
+ * @param {string} data
362
+ * @returns {DOMJoiningTransformer}}
363
+ */
364
+ processingInstruction (target, data) {
365
+ if (!this._dom || typeof this._dom !== 'object' ||
366
+ (![1, 9, 11].includes(this._dom.nodeType))) {
367
+ throw new Error(
368
+ 'You may only set a processing instruction on a ' +
369
+ 'document, fragment, or element'
370
+ );
371
+ }
372
+ this._dom.append((this._dom.ownerDocument).createProcessingInstruction(
373
+ target, data
374
+ ));
375
+ return this;
376
+ }
377
+
227
378
  /**
228
379
  * @param {string} str
229
380
  * @returns {DOMJoiningTransformer}
@@ -232,6 +383,102 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
232
383
  this.text(str);
233
384
  return this;
234
385
  }
386
+
387
+ /**
388
+ * Creates a new XML document and executes a callback in its context.
389
+ * Similar to XSLT's xsl:document, this allows templates to generate
390
+ * multiple output documents. The created document is pushed to this._docs
391
+ * and will be included in the result when exposeDocuments is true.
392
+ *
393
+ * @param {(this: DOMJoiningTransformer) => void} cb
394
+ * Callback that builds the document content
395
+ * @param {import('./StringJoiningTransformer.js').OutputConfig} [cfg]
396
+ * Output configuration for the document (encoding, doctype, etc.)
397
+ * @returns {DOMJoiningTransformer}
398
+ */
399
+ document (cb, cfg) {
400
+ // Save current state
401
+ /** @type {any} */
402
+ const oldRoot = this.root;
403
+ /** @type {any} */
404
+ const oldOutputConfig = this._outputConfig;
405
+ const oldDOM = this._dom;
406
+
407
+ // Reset state for new document
408
+ this.root = undefined;
409
+ /** @type {any} */
410
+ this._outputConfig = cfg;
411
+
412
+ // Create a new document fragment as the working context
413
+ const fragment = this._cfg.document.createDocumentFragment();
414
+ this._dom = fragment;
415
+
416
+ // Execute callback to build document content
417
+ cb.call(this);
418
+
419
+ // Restore previous state
420
+ this.root = oldRoot;
421
+ this._outputConfig = oldOutputConfig;
422
+ this._dom = oldDOM;
423
+
424
+ return this;
425
+ }
426
+
427
+ /**
428
+ * Creates a new result document with metadata (href, format).
429
+ * Similar to XSLT's xsl:result-document, this allows templates to generate
430
+ * multiple output documents with associated metadata like URIs. The created
431
+ * document is stored in this._resultDocuments with the provided href.
432
+ *
433
+ * @param {string} href - URI/path for the result document
434
+ * @param {(this: DOMJoiningTransformer) => void} cb
435
+ * Callback that builds the document content
436
+ * @param {import('./StringJoiningTransformer.js').OutputConfig} [cfg]
437
+ * Output configuration for the document (encoding, doctype, format, etc.)
438
+ * @returns {DOMJoiningTransformer}
439
+ */
440
+ resultDocument (href, cb, cfg) {
441
+ // Save current state
442
+ /** @type {any} */
443
+ const oldRoot = this.root;
444
+ /** @type {any} */
445
+ const oldOutputConfig = this._outputConfig;
446
+ const oldDOM = this._dom;
447
+
448
+ // Reset state for new document
449
+ this.root = undefined;
450
+ /** @type {any} */
451
+ this._outputConfig = cfg;
452
+
453
+ // Create a new document fragment as the working context
454
+ const fragment = this._cfg.document.createDocumentFragment();
455
+ this._dom = fragment;
456
+
457
+ // Execute callback to build document content
458
+ cb.call(this);
459
+
460
+ // Get the created document from _docs (document() will have pushed it)
461
+ // or extract from the current DOM state
462
+ const resultDoc = this._docs.length > 0
463
+ ? /** @type {XMLDocument} */ (this._docs.at(-1))
464
+ : /** @type {XMLDocument} */ (
465
+ this._cfg.document.implementation.createDocument(null, 'root', null)
466
+ );
467
+
468
+ // Store with metadata, using the output config that was set during callback
469
+ this._resultDocuments.push({
470
+ href,
471
+ document: resultDoc,
472
+ format: this._outputConfig?.method || cfg?.method
473
+ });
474
+
475
+ // Restore previous state
476
+ this.root = oldRoot;
477
+ this._outputConfig = oldOutputConfig;
478
+ this._dom = oldDOM;
479
+
480
+ return this;
481
+ }
235
482
  }
236
483
 
237
484
  export default DOMJoiningTransformer;