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.
- package/CHANGES.md +18 -0
- package/README.md +16 -87
- package/demo/calltemplate-params-demo.js +138 -0
- package/demo/index.html +31 -0
- package/demo/index.js +30 -0
- package/demo/xpath2-placeholder.js +1 -0
- package/dist/AbstractJoiningTransformer.d.ts +83 -9
- package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
- package/dist/DOMJoiningTransformer.d.ts +85 -25
- package/dist/DOMJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONJoiningTransformer.d.ts +159 -51
- package/dist/JSONJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformer.d.ts +37 -38
- package/dist/JSONPathTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformerContext.d.ts +247 -121
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/StringJoiningTransformer.d.ts +132 -41
- package/dist/StringJoiningTransformer.d.ts.map +1 -1
- package/dist/XPathTransformer.d.ts +35 -20
- package/dist/XPathTransformer.d.ts.map +1 -1
- package/dist/XPathTransformerContext.d.ts +191 -99
- package/dist/XPathTransformerContext.d.ts.map +1 -1
- package/dist/index-browser.d.ts +4 -0
- package/dist/index-browser.d.ts.map +1 -0
- package/dist/index-node.d.ts +4 -0
- package/dist/index-node.d.ts.map +1 -0
- package/dist/index.d.ts +330 -57
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +204 -0
- package/dist/types.d.ts.map +1 -0
- package/docs/API.expanded.md +167 -2
- package/docs/API.md +91 -1
- package/docs/TO-DO.md +144 -0
- package/docs/calltemplate-params.md +251 -0
- package/eslint.config.js +9 -5
- package/package.json +13 -7
- package/pnpm-workspace.yaml +1 -0
- package/src/AbstractJoiningTransformer.js +54 -15
- package/src/DOMJoiningTransformer.js +275 -28
- package/src/JSONJoiningTransformer.js +351 -70
- package/src/JSONPathTransformer.js +48 -30
- package/src/JSONPathTransformerContext.js +308 -104
- package/src/StringJoiningTransformer.js +311 -57
- package/src/XPathTransformer.js +27 -12
- package/src/XPathTransformerContext.js +467 -89
- package/src/index-browser.js +5 -0
- package/src/index-node.js +7 -0
- package/src/index.js +498 -97
- package/typings/xpath2-js.d.ts +40 -1
- package/src/types/xpath2-js.d.ts +0 -2
|
@@ -10,6 +10,43 @@ function _makeDatasetAttribute (n0) {
|
|
|
10
10
|
return n0.charAt(0) + '-' + n0.charAt(1).toLowerCase();
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @callback ObjectCallback
|
|
15
|
+
* @this {JSONJoiningTransformer}
|
|
16
|
+
* @param {Record<string, unknown>} obj
|
|
17
|
+
* @returns {void}
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* @callback ArrayCallback
|
|
21
|
+
* @this {JSONJoiningTransformer}
|
|
22
|
+
* @param {any[]} arr
|
|
23
|
+
* @returns {void}
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* @template [T = "json"]
|
|
27
|
+
* @callback SimpleCallback
|
|
28
|
+
* @this {T extends "json" ? JSONJoiningTransformer :
|
|
29
|
+
* T extends "string" ? import('./StringJoiningTransformer.js').default
|
|
30
|
+
* : import('./DOMJoiningTransformer.js').default}
|
|
31
|
+
* @returns {void}
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Attributes object for element() allowing standard string attributes
|
|
36
|
+
* plus special helpers: dataset (object) and $a (ordered attribute array).
|
|
37
|
+
* @typedef {Record<string, unknown> & {
|
|
38
|
+
* dataset?: Record<string, string>,
|
|
39
|
+
* $a?: Array<[string, string]>
|
|
40
|
+
* }} ElementAttributes
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @typedef {{
|
|
45
|
+
* attsObj: Record<string, unknown>,
|
|
46
|
+
* jmlChildren: unknown[]
|
|
47
|
+
* }} ElementInfo
|
|
48
|
+
*/
|
|
49
|
+
|
|
13
50
|
/**
|
|
14
51
|
* JSON-based joining transformer for building JSON/JavaScript objects.
|
|
15
52
|
*
|
|
@@ -17,27 +54,35 @@ function _makeDatasetAttribute (n0) {
|
|
|
17
54
|
* append() will push to arrays or shallow-merge into objects; string/number/
|
|
18
55
|
* boolean/null add primitives accordingly. It does not perform HTML escaping
|
|
19
56
|
* or string serialization; it builds real JS values.
|
|
57
|
+
* @extends {AbstractJoiningTransformer<"json">}
|
|
20
58
|
*/
|
|
21
59
|
class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
22
60
|
/**
|
|
23
|
-
* @param {any[]|
|
|
24
|
-
* @param {
|
|
61
|
+
* @param {any[]|Record<string, unknown>} [o] - Initial object or array
|
|
62
|
+
* @param {import('./AbstractJoiningTransformer.js').
|
|
63
|
+
* JSONJoiningTransformerConfig} [cfg] - Configuration object
|
|
25
64
|
*/
|
|
26
65
|
constructor (o, cfg) {
|
|
27
66
|
super(cfg);
|
|
28
|
-
/** @type {any[]|
|
|
67
|
+
/** @type {any[]|Record<string, unknown>} */
|
|
29
68
|
this._obj = o || [];
|
|
30
69
|
/** @type {boolean | undefined} */
|
|
31
70
|
this._objPropState = undefined;
|
|
32
71
|
/** @type {boolean | undefined} */
|
|
33
72
|
this._arrItemState = undefined;
|
|
34
|
-
/** @type {
|
|
73
|
+
/** @type {ElementInfo[]} */
|
|
35
74
|
this._elementStack = [];
|
|
75
|
+
/** @type {Record<string, unknown>} */
|
|
76
|
+
this.propertySets = {};
|
|
77
|
+
/** @type {any[]} */
|
|
78
|
+
this._docs = [];
|
|
79
|
+
/** @type {Array<{href: string, document: any, format?: string}>} */
|
|
80
|
+
this._resultDocuments = [];
|
|
36
81
|
}
|
|
37
82
|
|
|
38
83
|
/**
|
|
39
84
|
* Directly appends an item to the internal array without checks.
|
|
40
|
-
* @param {
|
|
85
|
+
* @param {any} item - Item to append
|
|
41
86
|
* @returns {void}
|
|
42
87
|
*/
|
|
43
88
|
rawAppend (item) {
|
|
@@ -46,7 +91,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
46
91
|
|
|
47
92
|
/**
|
|
48
93
|
* Appends an item to the current object or array.
|
|
49
|
-
* @param {
|
|
94
|
+
* @param {any} item - Item to append
|
|
50
95
|
* @returns {JSONJoiningTransformer}
|
|
51
96
|
*/
|
|
52
97
|
append (item) {
|
|
@@ -66,11 +111,16 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
66
111
|
* Gets the current object or array. If unwrapSingleResult config option is
|
|
67
112
|
* enabled and the root array contains exactly one element, returns that
|
|
68
113
|
* element directly (unwrapped).
|
|
69
|
-
* @returns {any[]|
|
|
114
|
+
* @returns {any[]|Record<string, unknown>|any}
|
|
70
115
|
*/
|
|
71
116
|
get () {
|
|
117
|
+
// If exposeDocuments is set, return the array of documents
|
|
118
|
+
if (this._cfg.exposeDocuments) {
|
|
119
|
+
return this._docs;
|
|
120
|
+
}
|
|
121
|
+
// Removed this._doc logic; use this._docs only
|
|
72
122
|
// Unwrap single-element arrays at the root level if configured
|
|
73
|
-
if (this._cfg
|
|
123
|
+
if (this._cfg.unwrapSingleResult &&
|
|
74
124
|
Array.isArray(this._obj) && this._obj.length === 1) {
|
|
75
125
|
return this._obj[0];
|
|
76
126
|
}
|
|
@@ -80,7 +130,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
80
130
|
/**
|
|
81
131
|
* Sets a property value on the current object.
|
|
82
132
|
* @param {string} prop - Property name
|
|
83
|
-
* @param {
|
|
133
|
+
* @param {any} val - Property value
|
|
84
134
|
* @returns {void}
|
|
85
135
|
*/
|
|
86
136
|
propValue (prop, val) {
|
|
@@ -94,16 +144,12 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
94
144
|
|
|
95
145
|
/* c8 ignore next 13 -- JSDoc block incorrectly counted as coverable by c8 */
|
|
96
146
|
/**
|
|
97
|
-
* @param {
|
|
98
|
-
*
|
|
99
|
-
* @param {
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* @param {
|
|
103
|
-
* property set names to copy onto the new object, or propSets if second
|
|
104
|
-
* arg was a callback
|
|
105
|
-
* @param {object} [propSets] - An object of key-value pairs to copy onto
|
|
106
|
-
* the new object
|
|
147
|
+
* @param {Record<string, unknown>|ObjectCallback} [objOrCb]
|
|
148
|
+
* Seed object or callback.
|
|
149
|
+
* @param {ObjectCallback|any[]} [cbOrUsePropertySets] Callback or sets.
|
|
150
|
+
* @param {any[]|Record<string, unknown>} [usePropertySetsOrPropSets]
|
|
151
|
+
* Sets or prop sets.
|
|
152
|
+
* @param {Record<string, unknown>} [propSets] Key-value pairs to add.
|
|
107
153
|
* @returns {JSONJoiningTransformer}
|
|
108
154
|
*/
|
|
109
155
|
object (objOrCb, cbOrUsePropertySets, usePropertySetsOrPropSets, propSets) {
|
|
@@ -124,12 +170,14 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
124
170
|
obj = {};
|
|
125
171
|
cb = objOrCb;
|
|
126
172
|
usePropertySets = /** @type {any[]} */ (cbOrUsePropertySets);
|
|
127
|
-
propSetsToUse = /** @type {
|
|
173
|
+
propSetsToUse = /** @type {Record<string, unknown>} */ (
|
|
174
|
+
usePropertySetsOrPropSets
|
|
175
|
+
);
|
|
128
176
|
} else {
|
|
129
177
|
// Seed object provided: object(obj, cb, usePropertySets, propSets)
|
|
130
178
|
// Clone seed object to avoid mutating the original
|
|
131
179
|
obj = objOrCb ? {...objOrCb} : {};
|
|
132
|
-
cb = /** @type {
|
|
180
|
+
cb = /** @type {ObjectCallback} */ (cbOrUsePropertySets);
|
|
133
181
|
usePropertySets = /** @type {any[]} */ (usePropertySetsOrPropSets);
|
|
134
182
|
propSetsToUse = propSets;
|
|
135
183
|
}
|
|
@@ -144,7 +192,6 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
144
192
|
Object.assign(obj, propSetsToUse);
|
|
145
193
|
}
|
|
146
194
|
|
|
147
|
-
/** @type {any} */
|
|
148
195
|
const oldObjPropState = this._objPropState;
|
|
149
196
|
this._objPropState = true;
|
|
150
197
|
this._obj = obj; // Set current object so propValue() works
|
|
@@ -162,9 +209,8 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
162
209
|
|
|
163
210
|
/**
|
|
164
211
|
* Creates a new array and executes a callback in its context.
|
|
165
|
-
* @param {any[]|
|
|
166
|
-
*
|
|
167
|
-
* @param {Function} [cb] - Callback function (if first arg was a seed array)
|
|
212
|
+
* @param {any[]|ArrayCallback} [arrOrCb] Seed array or callback.
|
|
213
|
+
* @param {ArrayCallback} [cb] Callback when first arg was array.
|
|
168
214
|
* @returns {JSONJoiningTransformer}
|
|
169
215
|
*/
|
|
170
216
|
array (arrOrCb, cb) {
|
|
@@ -200,8 +246,8 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
200
246
|
|
|
201
247
|
/**
|
|
202
248
|
* Appends a string value.
|
|
203
|
-
* @param {string} str
|
|
204
|
-
|
|
249
|
+
* @param {string} str String value.
|
|
250
|
+
* @param {SimpleCallback} [cb] Unused callback.
|
|
205
251
|
* @returns {JSONJoiningTransformer}
|
|
206
252
|
*/
|
|
207
253
|
string (str, cb) {
|
|
@@ -244,7 +290,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
244
290
|
* @returns {JSONJoiningTransformer}
|
|
245
291
|
*/
|
|
246
292
|
undefined () {
|
|
247
|
-
if (this._cfg
|
|
293
|
+
if (this._cfg.mode !== 'JavaScript') {
|
|
248
294
|
throw new Error(
|
|
249
295
|
'undefined is not allowed unless added in JavaScript mode'
|
|
250
296
|
);
|
|
@@ -259,7 +305,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
259
305
|
* @returns {JSONJoiningTransformer}
|
|
260
306
|
*/
|
|
261
307
|
nonfiniteNumber (num) {
|
|
262
|
-
if (this._cfg
|
|
308
|
+
if (this._cfg.mode !== 'JavaScript') {
|
|
263
309
|
throw new Error(
|
|
264
310
|
'Non-finite numbers are not allowed unless added in JavaScript mode'
|
|
265
311
|
);
|
|
@@ -270,11 +316,11 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
270
316
|
|
|
271
317
|
/**
|
|
272
318
|
* Appends a function value (JavaScript mode only).
|
|
273
|
-
* @param {
|
|
319
|
+
* @param {(...args: any[]) => any} func Function to append.
|
|
274
320
|
* @returns {JSONJoiningTransformer}
|
|
275
321
|
*/
|
|
276
322
|
function (func) {
|
|
277
|
-
if (this._cfg
|
|
323
|
+
if (this._cfg.mode !== 'JavaScript') {
|
|
278
324
|
throw new Error(
|
|
279
325
|
'function is not allowed unless added in JavaScript mode'
|
|
280
326
|
);
|
|
@@ -284,49 +330,67 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
284
330
|
}
|
|
285
331
|
|
|
286
332
|
/**
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
333
|
+
* @param {import('./StringJoiningTransformer.js').OutputConfig} cfg
|
|
334
|
+
* @returns {JSONJoiningTransformer}
|
|
335
|
+
*/
|
|
336
|
+
output (cfg) {
|
|
337
|
+
// We wait until first element is set in `element()` to add
|
|
338
|
+
// XML declaration and DOCTYPE as latter depends on root element
|
|
339
|
+
this._outputConfig = cfg;
|
|
340
|
+
|
|
341
|
+
// Use for file extension if making downloadable?
|
|
342
|
+
this.mediaType = cfg.mediaType;
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Build a Jamilih-style element JSON array and append to current container.
|
|
348
|
+
* Result form: ['tag', {attr: 'val'}, child1, child2, ...]
|
|
349
|
+
* Helpers: dataset -> data-*; $a -> ordered attributes.
|
|
350
|
+
* Supported signatures mirror StringJoiningTransformer.element.
|
|
351
|
+
* @param {string|Element} elName Element name or Element-like.
|
|
352
|
+
* @param {ElementAttributes|any[]|SimpleCallback} [atts]
|
|
353
|
+
* Attrs, children, or cb.
|
|
354
|
+
* @param {any[]|SimpleCallback} [childNodes] Children or cb.
|
|
355
|
+
* @param {SimpleCallback} [cb] Builder callback.
|
|
295
356
|
* @returns {JSONJoiningTransformer}
|
|
296
357
|
*/
|
|
297
358
|
element (elName, atts, childNodes, cb) {
|
|
298
359
|
this._requireSameChildren('json', 'element');
|
|
360
|
+
const isRoot = !this.root;
|
|
361
|
+
if (isRoot) {
|
|
362
|
+
this.root = elName;
|
|
363
|
+
}
|
|
299
364
|
// Normalize arguments similarly to StringJoiningTransformer.element
|
|
300
365
|
if (Array.isArray(atts)) {
|
|
301
|
-
cb = /** @type {
|
|
366
|
+
cb = /** @type {SimpleCallback} */ (childNodes);
|
|
302
367
|
childNodes = atts;
|
|
303
368
|
atts = {};
|
|
304
369
|
} else if (typeof atts === 'function') {
|
|
305
|
-
cb = /** @type {
|
|
370
|
+
cb = /** @type {SimpleCallback} */ (atts);
|
|
306
371
|
childNodes = [];
|
|
307
372
|
atts = {};
|
|
308
373
|
}
|
|
309
374
|
if (typeof childNodes === 'function') {
|
|
310
|
-
cb = /** @type {
|
|
375
|
+
cb = /** @type {SimpleCallback} */ (childNodes);
|
|
311
376
|
childNodes = [];
|
|
312
377
|
}
|
|
313
378
|
|
|
379
|
+
const elementName = typeof elName === 'string'
|
|
380
|
+
? elName
|
|
381
|
+
: elName.localName;
|
|
314
382
|
// Element-like object (DOM Element) -> extract attributes
|
|
315
383
|
if (typeof elName === 'object' && elName && 'attributes' in elName) {
|
|
316
|
-
/** @type {Record<string,
|
|
384
|
+
/** @type {Record<string, string>} */
|
|
317
385
|
const objAtts = {};
|
|
318
|
-
// @ts-ignore - treat elName as Element-like
|
|
319
386
|
[...elName.attributes].forEach((att) => {
|
|
320
387
|
objAtts[att.name] = att.value;
|
|
321
388
|
});
|
|
322
389
|
atts = Object.assign(objAtts, atts);
|
|
323
|
-
// @ts-ignore
|
|
324
|
-
elName = /** @type {any} */ (elName).nodeName;
|
|
325
390
|
}
|
|
326
391
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
/** @type {any[]} */
|
|
392
|
+
let attsObj = atts || {};
|
|
393
|
+
/** @type {import('jamilih').JamilihChildren} */
|
|
330
394
|
const jmlChildren = [];
|
|
331
395
|
|
|
332
396
|
// Preprocess special attribute helpers present directly on attsObj
|
|
@@ -343,9 +407,9 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
343
407
|
delete attsObj.dataset;
|
|
344
408
|
}
|
|
345
409
|
if (Array.isArray(attsObj.$a)) {
|
|
346
|
-
attsObj.$a.forEach((pair) => {
|
|
410
|
+
(/** @type {unknown[][]} */ (attsObj.$a)).forEach((pair) => {
|
|
347
411
|
if (Array.isArray(pair) && pair.length > 1) {
|
|
348
|
-
attsObj[pair[0]] = pair[1];
|
|
412
|
+
attsObj[String(pair[0])] = pair[1];
|
|
349
413
|
}
|
|
350
414
|
});
|
|
351
415
|
delete attsObj.$a;
|
|
@@ -361,7 +425,7 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
361
425
|
// Push current state onto a stack
|
|
362
426
|
this._elementStack.push({attsObj, jmlChildren});
|
|
363
427
|
cb.call(this);
|
|
364
|
-
const state = /** @type {
|
|
428
|
+
const state = /** @type {ElementInfo} */ (this._elementStack.pop());
|
|
365
429
|
({attsObj} = state);
|
|
366
430
|
// Children may have been mutated by nested element()/text();
|
|
367
431
|
// already in jmlChildren
|
|
@@ -369,15 +433,56 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
369
433
|
|
|
370
434
|
// Build Jamilih array
|
|
371
435
|
/** @type {any[]} */
|
|
372
|
-
const jmlEl = [
|
|
436
|
+
const jmlEl = [elementName];
|
|
373
437
|
if (Object.keys(attsObj).length) {
|
|
374
438
|
jmlEl.push(attsObj);
|
|
375
439
|
}
|
|
376
440
|
jmlEl.push(...jmlChildren);
|
|
377
441
|
|
|
442
|
+
if (isRoot) {
|
|
443
|
+
// todo: indent, cdataSectionElements
|
|
444
|
+
const {
|
|
445
|
+
omitXmlDeclaration, doctypePublic, doctypeSystem, method
|
|
446
|
+
} = this._outputConfig ?? {};
|
|
447
|
+
|
|
448
|
+
const dtd = {$DOCTYPE: {
|
|
449
|
+
name: elementName,
|
|
450
|
+
publicId: doctypePublic ?? null, // Public ID (optional)
|
|
451
|
+
systemId: doctypeSystem ?? null // System ID (optional)
|
|
452
|
+
}};
|
|
453
|
+
|
|
454
|
+
let xmlDeclaration;
|
|
455
|
+
/* c8 ignore start -- third OR condition short-circuits */
|
|
456
|
+
if (!omitXmlDeclaration && (
|
|
457
|
+
method === 'xml' || method === 'xhtml' || omitXmlDeclaration === false)
|
|
458
|
+
) {
|
|
459
|
+
const {version, encoding, standalone} = this._outputConfig ?? {};
|
|
460
|
+
|
|
461
|
+
xmlDeclaration = {
|
|
462
|
+
version,
|
|
463
|
+
encoding,
|
|
464
|
+
standalone
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
/* c8 ignore stop */
|
|
468
|
+
|
|
469
|
+
const doc = {$document: {
|
|
470
|
+
...(xmlDeclaration ? {xmlDeclaration} : {}),
|
|
471
|
+
childNodes: [
|
|
472
|
+
...(method === 'xml' || method === 'xhtml' ? [dtd] : []),
|
|
473
|
+
jmlEl
|
|
474
|
+
]
|
|
475
|
+
}};
|
|
476
|
+
|
|
477
|
+
// Removed this._doc; use this._docs only
|
|
478
|
+
if (this._cfg.exposeDocuments) {
|
|
479
|
+
this._docs.push(doc);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
378
483
|
// If inside a parent element, append as its child; otherwise append to root
|
|
379
484
|
if (this._elementStack.length) {
|
|
380
|
-
const top = /** @type {
|
|
485
|
+
const top = /** @type {ElementInfo} */ (this._elementStack.at(-1));
|
|
381
486
|
top.jmlChildren.push(jmlEl);
|
|
382
487
|
} else {
|
|
383
488
|
this.append(jmlEl);
|
|
@@ -390,7 +495,8 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
390
495
|
* a callback-driven element(). When not in an element callback context,
|
|
391
496
|
* throws. Supports the same dataset/$a helpers as string joiner.
|
|
392
497
|
* @param {string} name - Attribute name (or helper: dataset, $a)
|
|
393
|
-
* @param {string|
|
|
498
|
+
* @param {string|Record<string, unknown>|unknown[]} val
|
|
499
|
+
* Attribute value or helper object
|
|
394
500
|
* @returns {JSONJoiningTransformer}
|
|
395
501
|
*/
|
|
396
502
|
attribute (name, val) {
|
|
@@ -398,23 +504,24 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
398
504
|
// No-op outside an element() callback (JSON joiner semantics)
|
|
399
505
|
return this;
|
|
400
506
|
}
|
|
401
|
-
const top = /** @type {
|
|
507
|
+
const top = /** @type {ElementInfo} */ (this._elementStack.at(-1));
|
|
402
508
|
const {attsObj} = top;
|
|
403
509
|
if (name === 'dataset' && val && typeof val === 'object' &&
|
|
404
510
|
!Array.isArray(val)
|
|
405
511
|
) {
|
|
406
|
-
|
|
407
|
-
|
|
512
|
+
const datasetObj = /** @type {Record<string, unknown>} */ (val);
|
|
513
|
+
for (const k in datasetObj) {
|
|
514
|
+
if (Object.hasOwn(datasetObj, k)) {
|
|
408
515
|
const dashed = k.replaceAll(camelCase, _makeDatasetAttribute);
|
|
409
|
-
attsObj['data-' + dashed] =
|
|
516
|
+
attsObj['data-' + dashed] = datasetObj[k];
|
|
410
517
|
}
|
|
411
518
|
}
|
|
412
519
|
return this;
|
|
413
520
|
}
|
|
414
521
|
if (name === '$a' && Array.isArray(val)) {
|
|
415
|
-
val.forEach((pair) => {
|
|
522
|
+
(/** @type {unknown[][]} */ (val)).forEach((pair) => {
|
|
416
523
|
if (Array.isArray(pair) && pair.length > 1) {
|
|
417
|
-
attsObj[pair[0]] = pair[1];
|
|
524
|
+
attsObj[String(pair[0])] = pair[1];
|
|
418
525
|
}
|
|
419
526
|
});
|
|
420
527
|
return this;
|
|
@@ -423,6 +530,43 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
423
530
|
return this;
|
|
424
531
|
}
|
|
425
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Adds a comment node (string) as a child within the current
|
|
535
|
+
* element() callback context. Outside of an element callback,
|
|
536
|
+
* simply appends the comment to the current array/object like string().
|
|
537
|
+
* @param {string} txt - Comment content
|
|
538
|
+
* @returns {JSONJoiningTransformer}
|
|
539
|
+
*/
|
|
540
|
+
comment (txt) {
|
|
541
|
+
if (this._elementStack.length) {
|
|
542
|
+
const top = /** @type {ElementInfo} */ (this._elementStack.at(-1));
|
|
543
|
+
const {jmlChildren} = top;
|
|
544
|
+
jmlChildren.push(['!', txt]);
|
|
545
|
+
return this;
|
|
546
|
+
}
|
|
547
|
+
// No-op outside element context in JSON joiner
|
|
548
|
+
return this;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Adds a comment node (string) as a child within the current
|
|
553
|
+
* element() callback context. Outside of an element callback,
|
|
554
|
+
* simply appends the comment to the current array/object like string().
|
|
555
|
+
* @param {string} target - processing instruction content
|
|
556
|
+
* @param {string} data - processing instruction content
|
|
557
|
+
* @returns {JSONJoiningTransformer}
|
|
558
|
+
*/
|
|
559
|
+
processingInstruction (target, data) {
|
|
560
|
+
if (this._elementStack.length) {
|
|
561
|
+
const top = /** @type {ElementInfo} */ (this._elementStack.at(-1));
|
|
562
|
+
const {jmlChildren} = top;
|
|
563
|
+
jmlChildren.push(['?', target, data]);
|
|
564
|
+
return this;
|
|
565
|
+
}
|
|
566
|
+
// No-op outside element context in JSON joiner
|
|
567
|
+
return this;
|
|
568
|
+
}
|
|
569
|
+
|
|
426
570
|
/**
|
|
427
571
|
* Adds a text node (string) as a child within the current element() callback
|
|
428
572
|
* context. Outside of an element callback, simply appends the text to the
|
|
@@ -432,9 +576,9 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
432
576
|
*/
|
|
433
577
|
text (txt) {
|
|
434
578
|
if (this._elementStack.length) {
|
|
435
|
-
const top = /** @type {
|
|
579
|
+
const top = /** @type {ElementInfo} */ (this._elementStack.at(-1));
|
|
436
580
|
const {jmlChildren} = top;
|
|
437
|
-
jmlChildren.push(txt);
|
|
581
|
+
jmlChildren.push(['!', txt]);
|
|
438
582
|
return this;
|
|
439
583
|
}
|
|
440
584
|
// No-op outside element context in JSON joiner
|
|
@@ -452,17 +596,154 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
452
596
|
}
|
|
453
597
|
|
|
454
598
|
/**
|
|
455
|
-
*
|
|
456
|
-
*
|
|
599
|
+
* Creates a new JSON document and executes a callback in its context.
|
|
600
|
+
* Similar to XSLT's xsl:document, this allows templates to generate
|
|
601
|
+
* multiple output documents. The created document is pushed to this._docs
|
|
602
|
+
* and will be included in the result when exposeDocuments is true.
|
|
603
|
+
*
|
|
604
|
+
* @param {(this: JSONJoiningTransformer) => void} cb
|
|
605
|
+
* Callback that builds the document content
|
|
606
|
+
* @param {import('./StringJoiningTransformer.js').OutputConfig} [cfg]
|
|
607
|
+
* Output configuration for the document (encoding, doctype, etc.)
|
|
608
|
+
* @returns {JSONJoiningTransformer}
|
|
609
|
+
*/
|
|
610
|
+
document (cb, cfg) {
|
|
611
|
+
// Save current state
|
|
612
|
+
/** @type {any} */
|
|
613
|
+
const oldRoot = this.root;
|
|
614
|
+
/** @type {any} */
|
|
615
|
+
const oldOutputConfig = this._outputConfig;
|
|
616
|
+
const oldObj = this._obj;
|
|
617
|
+
const oldElementStack = this._elementStack;
|
|
618
|
+
|
|
619
|
+
// Reset state for new document
|
|
620
|
+
this.root = undefined;
|
|
621
|
+
/** @type {any} */
|
|
622
|
+
this._outputConfig = cfg;
|
|
623
|
+
this._obj = [];
|
|
624
|
+
this._elementStack = [];
|
|
625
|
+
|
|
626
|
+
// Execute callback to build document content
|
|
627
|
+
cb.call(this);
|
|
628
|
+
|
|
629
|
+
// Restore previous state
|
|
630
|
+
this.root = oldRoot;
|
|
631
|
+
this._outputConfig = oldOutputConfig;
|
|
632
|
+
this._obj = oldObj;
|
|
633
|
+
this._elementStack = oldElementStack;
|
|
634
|
+
|
|
635
|
+
return this;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Creates a new result document with metadata (href, format).
|
|
640
|
+
* Similar to XSLT's xsl:result-document, this allows templates to generate
|
|
641
|
+
* multiple output documents with associated metadata like URIs. The created
|
|
642
|
+
* document is stored in this._resultDocuments with the provided href.
|
|
643
|
+
*
|
|
644
|
+
* @param {string} href - URI/path for the result document
|
|
645
|
+
* @param {(this: JSONJoiningTransformer) => void} cb
|
|
646
|
+
* Callback that builds the document content
|
|
647
|
+
* @param {import('./StringJoiningTransformer.js').OutputConfig} [cfg]
|
|
648
|
+
* Output configuration for the document (encoding, doctype, format, etc.)
|
|
649
|
+
* @returns {JSONJoiningTransformer}
|
|
650
|
+
*/
|
|
651
|
+
resultDocument (href, cb, cfg) {
|
|
652
|
+
// Save current state
|
|
653
|
+
/** @type {any} */
|
|
654
|
+
const oldRoot = this.root;
|
|
655
|
+
/** @type {any} */
|
|
656
|
+
const oldOutputConfig = this._outputConfig;
|
|
657
|
+
const oldObj = this._obj;
|
|
658
|
+
const oldElementStack = this._elementStack;
|
|
659
|
+
|
|
660
|
+
// Reset state for new document
|
|
661
|
+
this.root = undefined;
|
|
662
|
+
/** @type {any} */
|
|
663
|
+
this._outputConfig = cfg;
|
|
664
|
+
this._obj = [];
|
|
665
|
+
this._elementStack = [];
|
|
666
|
+
|
|
667
|
+
// Execute callback to build document content
|
|
668
|
+
cb.call(this);
|
|
669
|
+
|
|
670
|
+
// Get the created document from _docs or construct from current state
|
|
671
|
+
let resultDoc;
|
|
672
|
+
if (this._docs.length > 0) {
|
|
673
|
+
// Document was created with exposeDocuments flag
|
|
674
|
+
resultDoc = this._docs.at(-1);
|
|
675
|
+
} else if (this._outputConfig) {
|
|
676
|
+
// We have output config but document wasn't pushed to _docs
|
|
677
|
+
// Create the document wrapper manually
|
|
678
|
+
const {
|
|
679
|
+
omitXmlDeclaration, doctypePublic, doctypeSystem, method
|
|
680
|
+
} = this._outputConfig;
|
|
681
|
+
|
|
682
|
+
const elementData = Array.isArray(this._obj) && this._obj.length > 0
|
|
683
|
+
? this._obj[0]
|
|
684
|
+
: this._obj;
|
|
685
|
+
|
|
686
|
+
const elementName = Array.isArray(elementData) ? elementData[0] : 'root';
|
|
687
|
+
|
|
688
|
+
const dtd = {$DOCTYPE: {
|
|
689
|
+
name: elementName,
|
|
690
|
+
publicId: doctypePublic ?? null,
|
|
691
|
+
systemId: doctypeSystem ?? null
|
|
692
|
+
}};
|
|
693
|
+
|
|
694
|
+
let xmlDeclaration;
|
|
695
|
+
/* c8 ignore start -- third OR condition short-circuits */
|
|
696
|
+
if (!omitXmlDeclaration && (
|
|
697
|
+
method === 'xml' || method === 'xhtml' || omitXmlDeclaration === false)
|
|
698
|
+
) {
|
|
699
|
+
const {version, encoding, standalone} = this._outputConfig;
|
|
700
|
+
xmlDeclaration = {
|
|
701
|
+
version,
|
|
702
|
+
encoding,
|
|
703
|
+
standalone
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
/* c8 ignore stop */
|
|
707
|
+
|
|
708
|
+
resultDoc = {$document: {
|
|
709
|
+
...(xmlDeclaration ? {xmlDeclaration} : {}),
|
|
710
|
+
childNodes: [
|
|
711
|
+
...(method === 'xml' || method === 'xhtml' ? [dtd] : []),
|
|
712
|
+
elementData
|
|
713
|
+
]
|
|
714
|
+
}};
|
|
715
|
+
} else {
|
|
716
|
+
// No output config, just use the raw object/array
|
|
717
|
+
resultDoc = this._obj;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Store with metadata, using the output config that was set during callback
|
|
721
|
+
this._resultDocuments.push({
|
|
722
|
+
href,
|
|
723
|
+
document: resultDoc,
|
|
724
|
+
format: this._outputConfig?.method || cfg?.method
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// Restore previous state
|
|
728
|
+
this.root = oldRoot;
|
|
729
|
+
this._outputConfig = oldOutputConfig;
|
|
730
|
+
this._obj = oldObj;
|
|
731
|
+
this._elementStack = oldElementStack;
|
|
732
|
+
|
|
733
|
+
return this;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Helper method to use property sets.
|
|
738
|
+
* @param {Record<string, unknown>} obj - Object to which to apply
|
|
739
|
+
* property set
|
|
457
740
|
* @param {string} psName - Property set name
|
|
458
|
-
* @returns {
|
|
741
|
+
* @returns {Record<string, unknown>}
|
|
459
742
|
*/
|
|
460
743
|
_usePropertySets (obj, psName) {
|
|
461
744
|
// Merge the named property set (if present) into the provided object
|
|
462
|
-
if (this
|
|
463
|
-
|
|
464
|
-
) {
|
|
465
|
-
return Object.assign(obj, /** @type {any} */ (this).propertySets[psName]);
|
|
745
|
+
if (this.propertySets[psName]) {
|
|
746
|
+
return Object.assign(obj, this.propertySets[psName]);
|
|
466
747
|
}
|
|
467
748
|
return obj;
|
|
468
749
|
}
|