jtlt 0.17.0 → 0.19.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/.gitignore +3 -0
- package/.npmignore +3 -0
- package/CHANGES.md +14 -0
- package/README.md +76 -0
- package/demo/vendor/jamilih/dist/jml.mjs +898 -25
- package/dist/DOMJoiningTransformer.d.ts +73 -25
- package/dist/DOMJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONJoiningTransformer.d.ts +5 -3
- package/dist/JSONJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformerContext.d.ts +106 -19
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/StringJoiningTransformer.d.ts +8 -3
- package/dist/StringJoiningTransformer.d.ts.map +1 -1
- package/dist/XPathTransformerContext.d.ts +44 -0
- package/dist/XPathTransformerContext.d.ts.map +1 -1
- package/dist/index.d.ts +104 -261
- package/dist/index.d.ts.map +1 -1
- package/dist/jsonTemplate.d.ts +37 -0
- package/dist/jsonTemplate.d.ts.map +1 -0
- package/docs/API.expanded.md +75 -1
- package/docs/API.md +20 -0
- package/docs/TO-DO.md +10 -4
- package/package.json +6 -6
- package/pnpm-workspace.yaml +4 -1
- package/src/DOMJoiningTransformer.js +37 -11
- package/src/JSONJoiningTransformer.js +94 -67
- package/src/JSONPathTransformer.js +40 -10
- package/src/JSONPathTransformerContext.js +337 -47
- package/src/StringJoiningTransformer.js +35 -9
- package/src/XPathTransformerContext.js +140 -2
- package/src/index.js +115 -4
- package/src/jsonTemplate.js +410 -0
package/docs/API.expanded.md
CHANGED
|
@@ -59,8 +59,9 @@ Common to both entry points unless noted.
|
|
|
59
59
|
| ------ | ---- | ----------- |
|
|
60
60
|
| `data` | object \| primitive | Root JSON/JS value (or DOM `Document`/`Element` for the XPath engine). Required unless `ajaxData` provided. |
|
|
61
61
|
| `ajaxData` | string | URL to fetch JSON (async start). |
|
|
62
|
-
| `templates` | Array<TemplateObject> | Template declarations; see below. |
|
|
62
|
+
| `templates` | Array<TemplateObject> \| TemplateObject \| [path, Function\|Array] | Template declarations; see below. A bare `TemplateObject` or `[path, template]` tuple (not wrapped in an outer array) is also accepted directly. |
|
|
63
63
|
| `template` | Function \| TemplateObject | Single root template convenience. |
|
|
64
|
+
| `defaultTemplateFormat` | 'json' \| 'javascript' | JSONPath engine only. Jamilih validation strictness applied to a declarative (`Array`) `template` when its own `format` isn't set. Default `'json'`. See "Declarative (jamilih-shaped) templates" below. |
|
|
64
65
|
| `query` | Function | Root template convenience (wrapped as `path: '$'`). |
|
|
65
66
|
| `forQuery` | [select, cb] | One-off query (like FLWOR `for`). Auto-wrapped as root template. |
|
|
66
67
|
| `success` | Function(result) | **`JTLT` only** — required callback; also the return of `.transform()`. Not accepted by `jtlt()`. |
|
|
@@ -97,6 +98,79 @@ Edge cases:
|
|
|
97
98
|
- For XPath: `//item`, `/root/item`, `//*[@id='x']`.
|
|
98
99
|
- Root template: path `$` (JSONPath) or `/` (XPath).
|
|
99
100
|
|
|
101
|
+
## Declarative (jamilih-shaped) templates
|
|
102
|
+
|
|
103
|
+
JSONPath engine only. A `TemplateObject.template` may be an `Array` of
|
|
104
|
+
declarative nodes — modeled on [jamilih](https://github.com/brettz9/jamilih)'s
|
|
105
|
+
own JSON/JS-array HTML/XML construction syntax — instead of a `Function`.
|
|
106
|
+
`compileJSONTemplate()` compiles it once, up front, into an ordinary
|
|
107
|
+
`TemplateFunction`; every other API (`applyTemplates`, `callTemplate`, modes,
|
|
108
|
+
priority, …) works the same regardless of which form a matched template uses.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
const out = await jtlt({
|
|
112
|
+
data: {name: 'Ada'},
|
|
113
|
+
outputType: 'string',
|
|
114
|
+
templates: {
|
|
115
|
+
path: '$',
|
|
116
|
+
template: [
|
|
117
|
+
['h1', ['Hello']],
|
|
118
|
+
[{$valueOf: '$.name'}]
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
// -> <h1>Hello</h1>Ada
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A node is one of:
|
|
126
|
+
|
|
127
|
+
| Node | Emits |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `"text"` (a plain string) | a text node |
|
|
130
|
+
| `[name, attrs?, children?]` | an element — `attrs` is omitted when there are none, in which case the second item is the children array directly |
|
|
131
|
+
| `[{$text: value}]` | `this.text(value)` — jamilih's own native text-node form; bare only, no `$select` |
|
|
132
|
+
| `[{$jtltText: value, $select?: sel}]` | `this.text(value)` (no `$select`) or `this.valueOf(sel)` (with `$select`) — the general/canonical form of `$text` |
|
|
133
|
+
| `[{$string: value, $select?: sel}]` | `this.string(value)` or, with `$select`, the selected value stringified |
|
|
134
|
+
| `[{$valueOf: sel}]` | `this.valueOf(sel)` |
|
|
135
|
+
| `[{$applyTemplates: sel, $jtltMode?: mode, $sort?: sort}]` | `this.applyTemplates(sel, mode, sort)` |
|
|
136
|
+
| `[{$if: sel}, thenNodes, elseNodes?]` | `this.if(sel, cb)` or, with an else, `this.choose(sel, whenCb, otherwiseCb)` |
|
|
137
|
+
| `[{$forEach: sel, $sort?: sort}, childNodes]` | `this.forEach(sel, cb, sort)` |
|
|
138
|
+
| `[{$variable: name, $select: sel}]` | `this.variable(name, sel)`, readable back later via a bare `$name` reference |
|
|
139
|
+
| `[{$indexedDB: {db, store, options?}, $as?: name}, childNodes?]` | fetches rows; with `$as`, binds them via `this.variable(name, {value: rows})`; without it, swaps the data context (`$`) to the rows for `childNodes` |
|
|
140
|
+
| `[{$renderDefault: true}]` | `this.appendOutput(await this.renderDefault())` (an `extensions.renderDefault` your config supplies) |
|
|
141
|
+
|
|
142
|
+
Every operation node's leading object is entirely `$`-prefixed — jamilih's
|
|
143
|
+
own validator requires this of any first-position plain object. `$jtltMode`/
|
|
144
|
+
`$jtltText` are namespaced because jamilih itself reserves `$mode` outright
|
|
145
|
+
and gives `$text` its own (bare-only) native meaning; see the README's
|
|
146
|
+
["Differences between an exact equivalence with
|
|
147
|
+
XSLT"](../README.md#differences-between-an-exact-equivalence-with-xslt)
|
|
148
|
+
section for why `template`, an `$if`/`$forEach` body, and an operation
|
|
149
|
+
node's own trailing arguments are each *arrays* — including the
|
|
150
|
+
easy-to-miss case of a single node or a single sibling, which still needs
|
|
151
|
+
its wrapping array.
|
|
152
|
+
|
|
153
|
+
`$indexedDB`/`$renderDefault` may appear nested anywhere a node is
|
|
154
|
+
allowed — inside an element's children, or a `$if`/`$forEach` body — not
|
|
155
|
+
just at a template's top level; see the README section above for why.
|
|
156
|
+
|
|
157
|
+
**Validation**: `format: 'json'` (the default) rejects an embedded live
|
|
158
|
+
function/DOM node anywhere in the tree — the intent is a safely
|
|
159
|
+
serializable structure (e.g. round-tripped through IndexedDB), not
|
|
160
|
+
arbitrary JavaScript. Pass `format: 'javascript'` on the `TemplateObject`
|
|
161
|
+
(or set `defaultTemplateFormat: 'javascript'` on the config to relax every
|
|
162
|
+
entry lacking its own `format`) to allow one.
|
|
163
|
+
|
|
164
|
+
**Exports** (from `'jtlt'`):
|
|
165
|
+
- `compileJSONTemplate(nodes, {format?}) => TemplateFunction` — throws a
|
|
166
|
+
`TypeError` with every problem found if `nodes` is invalid.
|
|
167
|
+
- `validateJSONTemplate(nodes, {format?}) => {valid, errors}` — the same
|
|
168
|
+
checks, non-executing and non-throwing; for a "validate before save"
|
|
169
|
+
editor workflow.
|
|
170
|
+
- `isJSONTemplateNodeArray(x) => boolean` — `Array.isArray`, exported for
|
|
171
|
+
callers that need to detect the declarative form themselves (e.g. before
|
|
172
|
+
deciding whether to call `compileJSONTemplate`).
|
|
173
|
+
|
|
100
174
|
## Engines
|
|
101
175
|
|
|
102
176
|
### `JSONPathTransformer`
|
package/docs/API.md
CHANGED
|
@@ -181,6 +181,26 @@ this.applyTemplates('$.items[*]');
|
|
|
181
181
|
|
|
182
182
|
This is useful for strict template matching where ambiguity should be an error rather than silently choosing a template, or for development where warnings help identify potential issues.
|
|
183
183
|
|
|
184
|
+
## Declarative (jamilih-shaped) templates
|
|
185
|
+
|
|
186
|
+
JSONPath engine only. `template` may be an `Array` of jamilih-shaped nodes
|
|
187
|
+
instead of a `Function` — compiled once via `compileJSONTemplate()`
|
|
188
|
+
(also exported, along with `validateJSONTemplate()` for non-executing
|
|
189
|
+
validation and `isJSONTemplateNodeArray()`):
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
const templates = {path: '$', template: [
|
|
193
|
+
['h1', ['Hello']],
|
|
194
|
+
[{$valueOf: '$.name'}]
|
|
195
|
+
]};
|
|
196
|
+
// -> <h1>Hello</h1>Ada
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`format: 'json'` (default) rejects an embedded live function/DOM node
|
|
200
|
+
anywhere in the tree; `format: 'javascript'` (per-entry, or
|
|
201
|
+
`defaultTemplateFormat` on the config) allows one. See
|
|
202
|
+
`docs/API.expanded.md` for the full node vocabulary.
|
|
203
|
+
|
|
184
204
|
## Sorting
|
|
185
205
|
|
|
186
206
|
Path string (ascending text), comparator function, object spec
|
package/docs/TO-DO.md
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
1. Document
|
|
6
6
|
|
|
7
|
+
1. Write a specification for declarative jamilih / jtlt?
|
|
8
|
+
|
|
7
9
|
1. Implement and demo equivalent to applying and calling templates, and
|
|
8
10
|
root template
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
1. Demo chaining of methods, including [equivalents](https://www.saxonica.com/papers/XTech2005/mhkpaper.html#S4.)
|
|
11
13
|
to XQuery's FLWOR expressions (see also Promises to-do), perhaps
|
|
12
14
|
even making aliases so that XQuery's friendlier terms can be used
|
|
13
15
|
instead of XSLT's.
|
|
@@ -41,10 +43,12 @@
|
|
|
41
43
|
through [HTTPQuery](https://github.com/brettz9/httpquery) (and also
|
|
42
44
|
supply to JSONEditor, etc.). Utilize updating by reference.
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
## Possible to-dos
|
|
47
|
+
|
|
48
|
+
1. Demo narrowing to subset of JavaScript (as with `jsep`) to make
|
|
45
49
|
JTLT truly "declarative" as far as freedom from scripting
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
1. Support XPath and Declarative Jamilih
|
|
48
52
|
|
|
49
53
|
1. Make schema-aware so that templates could target types. Most reusable
|
|
50
54
|
application may be having a type-driven view of a JSON Schema instance
|
|
@@ -112,6 +116,7 @@ We'd otherwise have to refactor a great deal to maintain a buffer.
|
|
|
112
116
|
xsl:break
|
|
113
117
|
xsl:catch
|
|
114
118
|
xsl:context-item
|
|
119
|
+
~~"xsl:document",~~ (not yet exposed)
|
|
115
120
|
xsl:evaluate
|
|
116
121
|
xsl:expose
|
|
117
122
|
"xsl:fallback",
|
|
@@ -139,6 +144,8 @@ We'd otherwise have to refactor a great deal to maintain a buffer.
|
|
|
139
144
|
xsl:use-package
|
|
140
145
|
xsl:where-populated
|
|
141
146
|
|
|
147
|
+
// Todo: Some of these may be implemented but not yet exposed
|
|
148
|
+
|
|
142
149
|
~~"xsl:analyze-string",~~
|
|
143
150
|
~~"xsl:apply-templates",~~
|
|
144
151
|
~~xsl:assert~~
|
|
@@ -151,7 +158,6 @@ We'd otherwise have to refactor a great deal to maintain a buffer.
|
|
|
151
158
|
~~"xsl:copy",~~
|
|
152
159
|
~~"xsl:copy-of",~~
|
|
153
160
|
~~"xsl:decimal-format",~~
|
|
154
|
-
~~"xsl:document",~~
|
|
155
161
|
~~"xsl:element",~~
|
|
156
162
|
~~"xsl:for-each",~~
|
|
157
163
|
~~"xsl:for-each-group",~~
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jtlt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Brett Zamir",
|
|
6
6
|
"contributors": [],
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"codemirror": "^6.0.2",
|
|
40
40
|
"fontoxpath": "^3.34.0",
|
|
41
41
|
"idb": "^8.0.3",
|
|
42
|
-
"jamilih": "0.
|
|
42
|
+
"jamilih": "0.70.0",
|
|
43
43
|
"jhtml": "0.7.3",
|
|
44
44
|
"jsdom": "30.0.1",
|
|
45
45
|
"jsonpath-plus": "10.4.0",
|
|
@@ -51,17 +51,17 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
54
|
-
"@node-static/node-static": "^0.9.
|
|
54
|
+
"@node-static/node-static": "^0.9.2",
|
|
55
55
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
56
56
|
"@types/chai": "^5.2.3",
|
|
57
57
|
"@types/jsdom": "^30.0.0",
|
|
58
58
|
"@types/mocha": "^10.0.10",
|
|
59
|
-
"baseline-browser-mapping": "^2.11.
|
|
59
|
+
"baseline-browser-mapping": "^2.11.22",
|
|
60
60
|
"c8": "^12.0.0",
|
|
61
61
|
"chai": "^6.2.2",
|
|
62
62
|
"eslint": "^10.10.0",
|
|
63
|
-
"eslint-config-ash-nazg": "^43.1.
|
|
64
|
-
"indexeddbshim": "^19.0.
|
|
63
|
+
"eslint-config-ash-nazg": "^43.1.8",
|
|
64
|
+
"indexeddbshim": "^19.0.2",
|
|
65
65
|
"mocha": "^12.0.0",
|
|
66
66
|
"open": "^11.0.2",
|
|
67
67
|
"rollup": "^4.63.1",
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -35,7 +35,10 @@ minimumReleaseAgeExclude:
|
|
|
35
35
|
- eslint-config-ash-nazg@43.1.0 || 43.1.6
|
|
36
36
|
- eslint-plugin-escompat@3.12.0
|
|
37
37
|
- eslint-plugin-jsdoc@64.3.0 || 64.3.3
|
|
38
|
-
- indexeddbshim@17.4.1 || 17.4.2 || 17.4.3 || 17.5.0 || 17.6.0
|
|
38
|
+
- indexeddbshim@17.4.1 || 17.4.2 || 17.4.3 || 17.5.0 || 17.6.0 || 19.0.2
|
|
39
39
|
- open@11.0.2
|
|
40
40
|
- powershell-utils@0.2.1
|
|
41
41
|
- websql-configurable@4.0.0
|
|
42
|
+
- '@node-static/node-static@0.9.2'
|
|
43
|
+
- baseline-browser-mapping@2.11.22
|
|
44
|
+
- jamilih@0.70.0
|
|
@@ -300,7 +300,7 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
300
300
|
* @overload
|
|
301
301
|
* @param {Element|string} elName
|
|
302
302
|
* @param {(this: DOMJoiningTransformer) => void} cb
|
|
303
|
-
* @returns {DOMJoiningTransformer}
|
|
303
|
+
* @returns {DOMJoiningTransformer|Promise<DOMJoiningTransformer>}
|
|
304
304
|
*/
|
|
305
305
|
/**
|
|
306
306
|
* @overload
|
|
@@ -314,9 +314,15 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
314
314
|
* childNodes, or callback
|
|
315
315
|
* @param {(Node|string)[]|((this: DOMJoiningTransformer) => void)
|
|
316
316
|
* } [childNodes] - Child nodes or callback
|
|
317
|
-
* @param {(this: DOMJoiningTransformer) => void} [cb] -
|
|
317
|
+
* @param {(this: DOMJoiningTransformer) => void} [cb] -
|
|
318
|
+
* Callback; may be async (e.g. to `await` a `$indexedDB` fetch), in
|
|
319
|
+
* which case `element()` itself returns a `Promise` instead of `this`.
|
|
320
|
+
* (Typed as returning plain `void`, not `void|Promise<void>` — a union
|
|
321
|
+
* there would lose TypeScript's usual "a void-returning callback
|
|
322
|
+
* parameter accepts any actual return value" leniency, which existing
|
|
323
|
+
* callers rely on for e.g. `() => this.text(...)` concise arrows.)
|
|
318
324
|
* @param {string[]} [useAttributeSets] - Attribute set names to apply
|
|
319
|
-
* @returns {DOMJoiningTransformer}
|
|
325
|
+
* @returns {DOMJoiningTransformer|Promise<DOMJoiningTransformer>}
|
|
320
326
|
*/
|
|
321
327
|
element (elem, atts, childNodes, cb, useAttributeSets) {
|
|
322
328
|
// Handle argument overloading like other transformers
|
|
@@ -440,12 +446,24 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
440
446
|
}
|
|
441
447
|
}
|
|
442
448
|
|
|
449
|
+
const finishRoot = () => {
|
|
450
|
+
this._dom = oldDOM;
|
|
451
|
+
return this;
|
|
452
|
+
};
|
|
443
453
|
if (cb) {
|
|
444
|
-
cb
|
|
454
|
+
// `cb`'s declared return type is plain `void` (see the parameter's
|
|
455
|
+
// JSDoc), preserving TypeScript's void-return leniency for
|
|
456
|
+
// callers — cast here to duck-type the real value.
|
|
457
|
+
const cbResult = /** @type {any} */ (cb.call(this._context || this));
|
|
458
|
+
// `cb` may be an async function (e.g. one that awaits a
|
|
459
|
+
// `$indexedDB` fetch); resume only once it settles, so `_dom` is
|
|
460
|
+
// restored in the right order.
|
|
461
|
+
if (cbResult && typeof cbResult.then === 'function') {
|
|
462
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- Not async
|
|
463
|
+
return cbResult.then(() => finishRoot());
|
|
464
|
+
}
|
|
445
465
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
return this;
|
|
466
|
+
return finishRoot();
|
|
449
467
|
}
|
|
450
468
|
|
|
451
469
|
// Non-root elements
|
|
@@ -494,12 +512,20 @@ class DOMJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
494
512
|
}
|
|
495
513
|
}
|
|
496
514
|
|
|
515
|
+
const finish = () => {
|
|
516
|
+
this._dom = oldDOM;
|
|
517
|
+
return this;
|
|
518
|
+
};
|
|
497
519
|
if (cb) {
|
|
498
|
-
cb
|
|
520
|
+
// `cb`'s declared return type is plain `void` — cast here to
|
|
521
|
+
// duck-type the real value; see the note above the other branch.
|
|
522
|
+
const cbResult = /** @type {any} */ (cb.call(this._context || this));
|
|
523
|
+
if (cbResult && typeof cbResult.then === 'function') {
|
|
524
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- Not async
|
|
525
|
+
return cbResult.then(() => finish());
|
|
526
|
+
}
|
|
499
527
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
return this;
|
|
528
|
+
return finish();
|
|
503
529
|
}
|
|
504
530
|
|
|
505
531
|
/**
|
|
@@ -30,6 +30,12 @@ function _makeDatasetAttribute (n0) {
|
|
|
30
30
|
* : import('./DOMJoiningTransformer.js').default}
|
|
31
31
|
* @returns {void}
|
|
32
32
|
*/
|
|
33
|
+
// Kept as plain `void` (not `void|Promise<void>`) rather than a union:
|
|
34
|
+
// TypeScript's "a void-returning callback parameter accepts any actual
|
|
35
|
+
// return value" leniency — relied on throughout the test suite for
|
|
36
|
+
// concise-body arrows like `() => this.text(...)` — only applies to a
|
|
37
|
+
// literal `void` return type, not a union. An async callback (returning
|
|
38
|
+
// `Promise<void>`) is still assignable here for the same reason.
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* Attributes object for element() allowing standard string attributes
|
|
@@ -392,9 +398,11 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
392
398
|
* @param {ElementAttributes|any[]|SimpleCallback} [atts]
|
|
393
399
|
* Attrs, children, or cb.
|
|
394
400
|
* @param {any[]|SimpleCallback} [childNodes] Children or cb.
|
|
395
|
-
* @param {SimpleCallback} [cb] Builder callback.
|
|
401
|
+
* @param {SimpleCallback} [cb] Builder callback; may be async (e.g. to
|
|
402
|
+
* `await` a `$indexedDB` fetch), in which case `element()` itself
|
|
403
|
+
* returns a `Promise` instead of `this`.
|
|
396
404
|
* @param {string[]} [useAttributeSets] - Attribute set names to apply
|
|
397
|
-
* @returns {JSONJoiningTransformer}
|
|
405
|
+
* @returns {JSONJoiningTransformer|Promise<JSONJoiningTransformer>}
|
|
398
406
|
*/
|
|
399
407
|
element (elem, atts, childNodes, cb, useAttributeSets) {
|
|
400
408
|
this._requireSameChildren('json', 'element');
|
|
@@ -485,77 +493,96 @@ class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
|
485
493
|
}));
|
|
486
494
|
}
|
|
487
495
|
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
/** @type {any[]} */
|
|
501
|
-
const jmlEl = [elementName];
|
|
502
|
-
if (Object.keys(attsObj).length) {
|
|
503
|
-
jmlEl.push(attsObj, jmlChildren);
|
|
504
|
-
} else {
|
|
505
|
-
jmlEl.push(jmlChildren);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (isRoot) {
|
|
509
|
-
// todo: indent, cdataSectionElements
|
|
510
|
-
const {
|
|
511
|
-
omitXmlDeclaration, bareDoctype, doctypePublic, doctypeSystem, method
|
|
512
|
-
} = this._outputConfig ?? {};
|
|
513
|
-
|
|
514
|
-
const dtd = bareDoctype !== false
|
|
515
|
-
? [{$DOCTYPE: {
|
|
516
|
-
name: elementName,
|
|
517
|
-
publicId: doctypePublic ?? null, // Public ID (optional)
|
|
518
|
-
systemId: doctypeSystem ?? null // System ID (optional)
|
|
519
|
-
}}]
|
|
520
|
-
: [];
|
|
521
|
-
|
|
522
|
-
let xmlDeclaration;
|
|
523
|
-
/* c8 ignore start -- third OR condition short-circuits */
|
|
524
|
-
if (!omitXmlDeclaration && (
|
|
525
|
-
method === 'xml' || method === 'xhtml' || omitXmlDeclaration === false)
|
|
526
|
-
) {
|
|
527
|
-
const {version, encoding, standalone} = this._outputConfig ?? {};
|
|
528
|
-
|
|
529
|
-
xmlDeclaration = {
|
|
530
|
-
version,
|
|
531
|
-
encoding,
|
|
532
|
-
standalone
|
|
533
|
-
};
|
|
496
|
+
// Everything from here on depends on `attsObj`/`jmlChildren` having
|
|
497
|
+
// been fully populated by `cb` (when given) — deferred into a
|
|
498
|
+
// continuation so an async `cb` (e.g. one that awaits a `$indexedDB`
|
|
499
|
+
// fetch) can be awaited first, keeping output correctly ordered.
|
|
500
|
+
const finishElement = () => {
|
|
501
|
+
// Build Jamilih array
|
|
502
|
+
/** @type {any[]} */
|
|
503
|
+
const jmlEl = [elementName];
|
|
504
|
+
if (Object.keys(attsObj).length) {
|
|
505
|
+
jmlEl.push(attsObj, jmlChildren);
|
|
506
|
+
} else {
|
|
507
|
+
jmlEl.push(jmlChildren);
|
|
534
508
|
}
|
|
535
|
-
/* c8 ignore stop */
|
|
536
509
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
510
|
+
if (isRoot) {
|
|
511
|
+
// todo: indent, cdataSectionElements
|
|
512
|
+
const {
|
|
513
|
+
omitXmlDeclaration, bareDoctype, doctypePublic, doctypeSystem, method
|
|
514
|
+
} = this._outputConfig ?? {};
|
|
515
|
+
|
|
516
|
+
const dtd = bareDoctype !== false
|
|
517
|
+
? [{$DOCTYPE: {
|
|
518
|
+
name: elementName,
|
|
519
|
+
publicId: doctypePublic ?? null, // Public ID (optional)
|
|
520
|
+
systemId: doctypeSystem ?? null // System ID (optional)
|
|
521
|
+
}}]
|
|
522
|
+
: [];
|
|
523
|
+
|
|
524
|
+
let xmlDeclaration;
|
|
525
|
+
/* c8 ignore start -- third OR condition short-circuits */
|
|
526
|
+
if (!omitXmlDeclaration && (
|
|
527
|
+
method === 'xml' || method === 'xhtml' ||
|
|
528
|
+
omitXmlDeclaration === false)
|
|
529
|
+
) {
|
|
530
|
+
const {version, encoding, standalone} = this._outputConfig ?? {};
|
|
531
|
+
|
|
532
|
+
xmlDeclaration = {
|
|
533
|
+
version,
|
|
534
|
+
encoding,
|
|
535
|
+
standalone
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/* c8 ignore stop */
|
|
539
|
+
|
|
540
|
+
const doc = {$document: {
|
|
541
|
+
...(xmlDeclaration ? {xmlDeclaration} : {}),
|
|
542
|
+
childNodes: [
|
|
543
|
+
...(method === 'xml' || method === 'xhtml' ? dtd : []),
|
|
544
|
+
jmlEl
|
|
545
|
+
]
|
|
546
|
+
}};
|
|
547
|
+
|
|
548
|
+
// Removed this._doc; use this._docs only
|
|
549
|
+
if (this._cfg.exposeDocuments) {
|
|
550
|
+
this._docs.push(doc);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
544
553
|
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
|
|
554
|
+
// If inside a parent element, append as its child; otherwise
|
|
555
|
+
// append to root
|
|
556
|
+
if (this._elementStack.length) {
|
|
557
|
+
const top = /** @type {ElementInfo} */ (this._elementStack.at(-1));
|
|
558
|
+
top.jmlChildren.push(jmlEl);
|
|
559
|
+
} else {
|
|
560
|
+
this.append(jmlEl);
|
|
548
561
|
}
|
|
549
|
-
|
|
562
|
+
return this;
|
|
563
|
+
};
|
|
550
564
|
|
|
551
|
-
//
|
|
552
|
-
if (
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
565
|
+
// Callback-driven building (attribute/text/nested element mutate stack)
|
|
566
|
+
if (cb) {
|
|
567
|
+
// Push current state onto a stack
|
|
568
|
+
this._elementStack.push({attsObj, jmlChildren});
|
|
569
|
+
// `cb`'s declared return type is plain `void` (see `SimpleCallback`)
|
|
570
|
+
// — cast here to duck-type the real value.
|
|
571
|
+
const cbResult = /** @type {any} */ (cb.call(this._context || this));
|
|
572
|
+
const finishWithStack = () => {
|
|
573
|
+
const state = /** @type {ElementInfo} */ (this._elementStack.pop());
|
|
574
|
+
({attsObj} = state);
|
|
575
|
+
// Children may have been mutated by nested element()/text();
|
|
576
|
+
// already in jmlChildren
|
|
577
|
+
return finishElement();
|
|
578
|
+
};
|
|
579
|
+
if (cbResult && typeof cbResult.then === 'function') {
|
|
580
|
+
// eslint-disable-next-line promise/prefer-await-to-then -- Not async
|
|
581
|
+
return cbResult.then(() => finishWithStack());
|
|
582
|
+
}
|
|
583
|
+
return finishWithStack();
|
|
557
584
|
}
|
|
558
|
-
return
|
|
585
|
+
return finishElement();
|
|
559
586
|
}
|
|
560
587
|
|
|
561
588
|
/**
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import JSONPathTransformerContext from './JSONPathTransformerContext.js';
|
|
2
|
+
import {
|
|
3
|
+
compileJSONTemplate, isJSONTemplateNodeArray
|
|
4
|
+
} from './jsonTemplate.js';
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Applies named JSONPath-driven templates to JSON data.
|
|
@@ -106,18 +109,35 @@ class JSONPathTransformer {
|
|
|
106
109
|
throw new TypeError('config.templates is required');
|
|
107
110
|
}
|
|
108
111
|
this.templates = config.templates.map(function (template) {
|
|
112
|
+
/** @type {import('./index.js').JSONPathTemplateObject<T>} */
|
|
113
|
+
let normalized;
|
|
109
114
|
if (Array.isArray(template)) {
|
|
110
115
|
// Todo: We could allow a third argument (at beginning or
|
|
111
116
|
// end?) to represent template name
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
normalized = /**
|
|
118
|
+
* @type {import('./index.js').
|
|
119
|
+
JSONPathTemplateObject<T>} */ (
|
|
120
|
+
{path: template[0], template: template[1]}
|
|
121
|
+
);
|
|
122
|
+
} else if (template.match && !template.path) {
|
|
123
|
+
// Normalize 'match' to 'path' for XSLT compatibility
|
|
124
|
+
normalized = {...template, path: template.match};
|
|
125
|
+
} else {
|
|
126
|
+
normalized = template;
|
|
115
127
|
}
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
// A declarative (jamilih-shaped) node array, as opposed to a
|
|
129
|
+
// `TemplateFunction` — compile it once, up front, so every later
|
|
130
|
+
// dispatch site (root, applyTemplates, callTemplate) only ever sees
|
|
131
|
+
// a plain function.
|
|
132
|
+
if (isJSONTemplateNodeArray(normalized.template)) {
|
|
133
|
+
const format = normalized.format || config.defaultTemplateFormat ||
|
|
134
|
+
'json';
|
|
135
|
+
normalized = {
|
|
136
|
+
...normalized,
|
|
137
|
+
template: compileJSONTemplate(normalized.template, {format})
|
|
138
|
+
};
|
|
119
139
|
}
|
|
120
|
-
return
|
|
140
|
+
return normalized;
|
|
121
141
|
});
|
|
122
142
|
this.templates.forEach((template, i, templates) => {
|
|
123
143
|
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
@@ -164,16 +184,26 @@ class JSONPathTransformer {
|
|
|
164
184
|
}
|
|
165
185
|
// Set up parameter context for valueOf() access in root template
|
|
166
186
|
jte._params = {0: jte._contextObj};
|
|
187
|
+
// `template` is always a function here: a declarative (jamilih-shaped)
|
|
188
|
+
// Array is compiled to one up front, in the constructor.
|
|
189
|
+
const {template: rootTemplateFn} =
|
|
190
|
+
/**
|
|
191
|
+
* @type {import('./index.js').JSONPathTemplateObject<T> &
|
|
192
|
+
* {template: import('./index.js').
|
|
193
|
+
* TemplateFunction<T, "json", JSONPathTransformerContext<T>>}}
|
|
194
|
+
*/ (
|
|
195
|
+
templateObj
|
|
196
|
+
);
|
|
167
197
|
/**
|
|
168
198
|
* The template may return a value synchronously or a Promise (e.g. from
|
|
169
199
|
* `await this.indexedDB(...)`), which is awaited unless `config.sync`.
|
|
170
200
|
* @type {any}
|
|
171
201
|
*/
|
|
172
|
-
const ret =
|
|
173
|
-
templateObj
|
|
202
|
+
const ret = (
|
|
174
203
|
// `this` carries runtime `extensions`; a consumer's `ContextExtensions`
|
|
175
204
|
// augmentation would otherwise reject the bare context here.
|
|
176
|
-
|
|
205
|
+
rootTemplateFn
|
|
206
|
+
).call(/** @type {any} */ (jte), undefined, {mode});
|
|
177
207
|
|
|
178
208
|
if (ret !== null && typeof ret !== 'undefined' &&
|
|
179
209
|
typeof ret.then === 'function') {
|