jtlt 0.13.0 → 0.14.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 +10 -0
- package/README.md +99 -85
- package/demo/calltemplate-params-demo.js +13 -22
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.ObjectArrayDelegator.js +17 -17
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.js +60 -61
- package/demo/vendor/jhtml/src/jhtml-browser.js +1 -0
- package/demo/vendor/jhtml/src/jhtml-node.js +1 -0
- package/demo/vendor/jhtml/src/jhtml.js +18 -15
- package/dist/AbstractJoiningTransformer.d.ts +5 -5
- package/dist/AbstractJoiningTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformer.d.ts +13 -12
- package/dist/JSONPathTransformer.d.ts.map +1 -1
- package/dist/JSONPathTransformerContext.d.ts +50 -20
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/XPathTransformer.d.ts +9 -2
- package/dist/XPathTransformer.d.ts.map +1 -1
- package/dist/XPathTransformerContext.d.ts +46 -9
- package/dist/XPathTransformerContext.d.ts.map +1 -1
- package/dist/index.d.ts +104 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/indexedDB.d.ts +107 -0
- package/dist/indexedDB.d.ts.map +1 -0
- package/dist/maybeAsync.d.ts +15 -0
- package/dist/maybeAsync.d.ts.map +1 -0
- package/docs/API.expanded.md +70 -20
- package/docs/API.md +34 -4
- package/docs/TO-DO.md +2 -2
- package/eslint.config.js +11 -3
- package/package.json +7 -5
- package/pnpm-workspace.yaml +9 -0
- package/src/AbstractJoiningTransformer.js +3 -3
- package/src/JSONPathTransformer.js +45 -7
- package/src/JSONPathTransformerContext.js +157 -22
- package/src/XPathTransformer.js +33 -1
- package/src/XPathTransformerContext.js +136 -16
- package/src/index.js +85 -8
- package/src/indexedDB.js +530 -0
- package/src/maybeAsync.js +44 -0
package/CHANGES.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# jtlt CHANGES
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
- feat: indexedDB JSONPath and XPath support
|
|
6
|
+
- feat: async templates are awaited by default; new off-by-default `sync`
|
|
7
|
+
option throws instead (replaces `async`/`syncOnly`)
|
|
8
|
+
- fix: `getKey()` resolves its `match` against the document root, so it now
|
|
9
|
+
works inside `forEach()`/`applyTemplates()` callbacks
|
|
10
|
+
- fix(types): more precise typing (any -> unknown)
|
|
11
|
+
- docs: lead with the `jtlt()` function; correct examples
|
|
12
|
+
|
|
3
13
|
## 0.13.0
|
|
4
14
|
|
|
5
15
|
- fix(types): more precise typing
|
package/README.md
CHANGED
|
@@ -8,8 +8,6 @@ As with XSLT, allows for declarative, linear declaration of
|
|
|
8
8
|
(recursive) templates and can be transformed into different
|
|
9
9
|
formats (e.g., strings, JSON, or DOM objects).
|
|
10
10
|
|
|
11
|
-
***Beta state!!!***
|
|
12
|
-
|
|
13
11
|
See the [Demo](https://brettz9.github.io/jtlt/demo/).
|
|
14
12
|
|
|
15
13
|
## Credits
|
|
@@ -29,9 +27,56 @@ See the [test file](./test/browser/index.html).
|
|
|
29
27
|
|
|
30
28
|
## Basic usage
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
The quickest way to run a transform is the **`jtlt()`** function. Give it a
|
|
31
|
+
config object; it runs the transform and returns a `Promise` that resolves to
|
|
32
|
+
the result:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
import {jtlt} from 'jtlt';
|
|
36
|
+
|
|
37
|
+
const data = {title: 'Hello', items: ['a', 'b']};
|
|
38
|
+
|
|
39
|
+
const templates = [
|
|
40
|
+
{path: '$', template () {
|
|
41
|
+
this.applyTemplates('$.title');
|
|
42
|
+
this.applyTemplates('$.items[*]');
|
|
43
|
+
}},
|
|
44
|
+
{path: '$.title', template (v) {
|
|
45
|
+
this.element('h1', {}, [], () => this.text(v));
|
|
46
|
+
}},
|
|
47
|
+
{path: '$.items[*]', template (v) {
|
|
48
|
+
this.element('li', {}, [], () => this.text(v));
|
|
49
|
+
}}
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const out = await jtlt({data, templates, outputType: 'string'});
|
|
53
|
+
// -> <h1>Hello</h1><li>a</li><li>b</li>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Templates may be `async` (for example to `await this.indexedDB(...)`);
|
|
57
|
+
`jtlt()` awaits them automatically. Pass `sync: true` to forbid asynchronous
|
|
58
|
+
templates (a template that then returns a Promise throws).
|
|
59
|
+
|
|
60
|
+
The same call works in Node and the browser (in the browser you must also
|
|
61
|
+
load the dependencies — see the [test file](./test/browser/index.html)). For
|
|
62
|
+
XML/HTML sources, add `engineType: 'xpath'` — see
|
|
63
|
+
[Quick start (XML source with XPath)](#quick-start-xml-source-with-xpath).
|
|
64
|
+
|
|
65
|
+
### `jtlt()` vs `JTLT.create()`
|
|
66
|
+
|
|
67
|
+
`jtlt()` is a thin, Promise-returning wrapper around the lower-level
|
|
68
|
+
`JTLT` class. Prefer `jtlt()`. Reach for `JTLT.create()` /
|
|
69
|
+
`new JTLT()` only when you need the instance itself, `autostart: false`, or
|
|
70
|
+
to drive `.transform(mode)` yourself.
|
|
33
71
|
|
|
34
|
-
|
|
72
|
+
| | `jtlt(config)` | `JTLT.create(config)` |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| Returns | a `Promise` of the result | a `JTLT` instance |
|
|
75
|
+
| Result delivery | the resolved value | a required `success` callback (also returned by `.transform()`) |
|
|
76
|
+
| Async templates | awaited automatically | awaited automatically; `.transform()` returns a Promise |
|
|
77
|
+
|
|
78
|
+
Anywhere below that shows `JTLT.create({…}).transform(mode)` can instead be
|
|
79
|
+
written `await jtlt({…, mode})`.
|
|
35
80
|
|
|
36
81
|
## API
|
|
37
82
|
|
|
@@ -39,16 +84,20 @@ See the [docs](docs/API.md). A high‑level overview is below.
|
|
|
39
84
|
|
|
40
85
|
## API overview
|
|
41
86
|
|
|
42
|
-
|
|
87
|
+
Run a transform with the **`jtlt(config)`** function (Promise-returning,
|
|
88
|
+
recommended) or the lower-level **`JTLT`** class (`JTLT.create(config)` /
|
|
89
|
+
`new JTLT(config)`, which delivers the result through a required `success`
|
|
90
|
+
callback). Under the hood JTLT has two layers:
|
|
43
91
|
|
|
44
92
|
- Engine (template application):
|
|
45
93
|
- JSONPathTransformer: Applies templates to JSON by matching JSONPath selectors (and optional modes), resolving priority, and invoking the winning template. Falls back to built‑in default rules when no user template matches.
|
|
46
94
|
- JSONPathTransformerContext: The execution context passed to templates. It mirrors the joiner API (e.g., string(), object(), array()) so templates can emit results. It also provides helpers like applyTemplates(), callTemplate(), valueOf(), variable(), and forEach().
|
|
47
95
|
- XPathTransformer (experimental): Applies templates to
|
|
48
96
|
XML/HTML DOM by matching XPath selectors (and optional modes).
|
|
49
|
-
Supports three evaluation modes: version 1 (native
|
|
50
|
-
XPathEvaluator), version 2 (via xpath2.js), and version 3 (via
|
|
51
|
-
Falls back to built‑in default rules when no template
|
|
97
|
+
Supports three evaluation modes: version `1` (native
|
|
98
|
+
XPathEvaluator), version `2` (via xpath2.js), and version `3.1` (via
|
|
99
|
+
fontoxpath). Falls back to built‑in default rules when no template
|
|
100
|
+
matches.
|
|
52
101
|
- XPathTransformerContext (experimental): Execution context for
|
|
53
102
|
XPath. Offers get(), forEach(), valueOf(), variable(), key()
|
|
54
103
|
and the same joiner helpers as the JSONPath context.
|
|
@@ -69,16 +118,16 @@ JTLT has two layers:
|
|
|
69
118
|
|
|
70
119
|
### Common joiner methods
|
|
71
120
|
|
|
72
|
-
- append(value)
|
|
73
|
-
- get()
|
|
74
|
-
- object(obj?, cb?, usePropertySets?, propSets?)
|
|
75
|
-
- array(arr?, cb?)
|
|
76
|
-
- string(str, cb?)
|
|
77
|
-
- number(num), boolean(bool), null(), undefined() (JS mode only), nonfiniteNumber(NaN|Infinity), function(fn) (JS mode only): Emit primitives/functions.
|
|
78
|
-
- element(name, attrs?, children?, cb?)
|
|
79
|
-
- attribute(name, value, avoidEscape?)
|
|
80
|
-
- text(txt)
|
|
81
|
-
- plainText(str)
|
|
121
|
+
- `append(value)`: Central sink. Based on context, concatenates to string, pushes to array, or assigns to an object property.
|
|
122
|
+
- `get()`: Return the accumulated result.
|
|
123
|
+
- `object(obj?, cb?, usePropertySets?, propSets?)`: Enter object context; optionally seed from an object or build via cb.
|
|
124
|
+
- `array(arr?, cb?)`: Enter array context; optionally seed from an array or build via cb.
|
|
125
|
+
- `string(str, cb?)`: Emit a string value (no HTML escaping). In String joiner, optional cb lets you compose nested fragments before emitting.
|
|
126
|
+
- `number(num), boolean(bool), null(), undefined()` (JS mode only), `nonfiniteNumber(NaN|Infinity), function(fn)` (JS mode only): Emit primitives/functions.
|
|
127
|
+
- `element(name, attrs?, children?, cb?)`: Build elements (String and DOM joiners). In String joiner, uses Jamilih under the hood to serialize; in DOM joiner, creates Elements.
|
|
128
|
+
- `attribute(name, value, avoidEscape?)`: Add attributes to the most recently opened element (String joiner) or to the current Element (DOM joiner).
|
|
129
|
+
- `text(txt)`: Emit text content. In String joiner, escapes & and <, and closes an open start tag if needed.
|
|
130
|
+
- `plainText(str)`: Raw, no‑escape append that bypasses context routing in the String joiner (always writes to top‑level buffer). In DOM/JSON joiners, it maps to text()/string() respectively.
|
|
82
131
|
|
|
83
132
|
### string() vs text() vs plainText() (String joiner)
|
|
84
133
|
|
|
@@ -95,56 +144,32 @@ Provide joiningConfig when constructing JTLT:
|
|
|
95
144
|
- joiningConfig.xmlElements: Switch element() to XML serialization mode in the String joiner.
|
|
96
145
|
- joiningConfig.preEscapedAttributes: Skip escaping attribute values in the String joiner.
|
|
97
146
|
|
|
98
|
-
##
|
|
99
|
-
|
|
100
|
-
```js
|
|
101
|
-
import {jtlt} from 'jtlt';
|
|
102
|
-
|
|
103
|
-
const data = {title: 'Hello', items: ['a', 'b']};
|
|
104
|
-
|
|
105
|
-
const templates = [
|
|
106
|
-
{path: '$', template () {
|
|
107
|
-
this.applyTemplates();
|
|
108
|
-
}},
|
|
109
|
-
{path: '$.title', template (v) {
|
|
110
|
-
this.string('<h1>', () => this.text(v));
|
|
111
|
-
this.string('</h1>');
|
|
112
|
-
}},
|
|
113
|
-
{path: '$.items[*]', template (v) {
|
|
114
|
-
this.element('li', {}, [], () => this.text(v));
|
|
115
|
-
}}
|
|
116
|
-
];
|
|
117
|
-
|
|
118
|
-
const out = await jtlt({data, templates, outputType: 'string'});
|
|
119
|
-
|
|
120
|
-
console.log(out);
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Notes:
|
|
147
|
+
## Notes on the basic example
|
|
124
148
|
|
|
125
149
|
- Modes let you organize multiple passes or output targets.
|
|
126
|
-
- You can also call templates by name via this.callTemplate('name')
|
|
127
|
-
- For DOM output, use outputType: 'dom'
|
|
150
|
+
- You can also call templates by name via `this.callTemplate('name')`.
|
|
151
|
+
- For DOM output, use `outputType: 'dom'`. For JSON output, use `'json'`
|
|
152
|
+
(the default is `'string'`).
|
|
128
153
|
|
|
129
|
-
|
|
154
|
+
## Quick start (XML source with XPath)
|
|
130
155
|
|
|
131
156
|
You can run templates against XML/HTML using XPath instead of JSONPath.
|
|
132
157
|
|
|
133
158
|
- `data` should be a Document or Element (e.g., from `DOMParser` with
|
|
134
159
|
`text/xml`).
|
|
135
|
-
- `xpathVersion`: `1` uses native XPath (browser
|
|
136
|
-
`xpath2.js` for XPath 2.0‑style evaluation.
|
|
137
|
-
|
|
160
|
+
- `xpathVersion`: `1` uses native XPath (browser‑like). `2` uses
|
|
161
|
+
`xpath2.js` for XPath 2.0‑style evaluation. `3.1` uses `fontoxpath` for
|
|
162
|
+
XPath 3.1. Default is `1`.
|
|
138
163
|
- In version 2, some functions may be missing; prefer simple path
|
|
139
164
|
expressions. Use version 1 for standard XPath 1.0 function support.
|
|
140
165
|
|
|
141
|
-
Example (string output)
|
|
166
|
+
Example (string output) with `jtlt()` and the XPath engine:
|
|
142
167
|
|
|
143
168
|
```js
|
|
144
169
|
import {JSDOM} from 'jsdom';
|
|
145
170
|
import {jtlt} from 'jtlt';
|
|
146
171
|
|
|
147
|
-
const {window} = new JSDOM('<!doctype><html><body></body></html>');
|
|
172
|
+
const {window} = new JSDOM('<!doctype html><html><body></body></html>');
|
|
148
173
|
const parser = new window.DOMParser();
|
|
149
174
|
const doc = parser.parseFromString(
|
|
150
175
|
'<root><item>a</item><item>b</item></root>', 'text/xml'
|
|
@@ -160,8 +185,7 @@ const templates = [
|
|
|
160
185
|
{
|
|
161
186
|
path: '//item',
|
|
162
187
|
template (n) {
|
|
163
|
-
this.
|
|
164
|
-
this.string('</li>');
|
|
188
|
+
this.element('li', {}, [], () => this.text(n.textContent));
|
|
165
189
|
}
|
|
166
190
|
}
|
|
167
191
|
];
|
|
@@ -171,8 +195,7 @@ const out = await jtlt({
|
|
|
171
195
|
templates,
|
|
172
196
|
outputType: 'string',
|
|
173
197
|
engineType: 'xpath',
|
|
174
|
-
xpathVersion: 1
|
|
175
|
-
success: (res) => res
|
|
198
|
+
xpathVersion: 1 // or 2, or 3.1
|
|
176
199
|
});
|
|
177
200
|
// -> <li>a</li><li>b</li>
|
|
178
201
|
```
|
|
@@ -182,15 +205,14 @@ const out = await jtlt({
|
|
|
182
205
|
If you just want to run a single, non-recursive query (similar to an XQuery "for … where … return …"), you can skip defining templates and use `forQuery` to seed a root function that iterates a JSONPath and emits results.
|
|
183
206
|
|
|
184
207
|
- `forQuery` takes the same arguments you’d pass to `this.forEach(select, cb)`: an absolute JSONPath selector and a callback invoked for each match.
|
|
185
|
-
-
|
|
208
|
+
- The callback runs once per match with `this` bound to that match, so use plain JavaScript `if` for conditions (there is no dedicated `this.if`).
|
|
186
209
|
|
|
187
|
-
Example: collect item names whose price
|
|
210
|
+
Example: collect item names whose price is at least 10.
|
|
188
211
|
|
|
189
212
|
```js
|
|
190
|
-
import
|
|
213
|
+
import {jtlt} from 'jtlt';
|
|
191
214
|
|
|
192
215
|
const data = {
|
|
193
|
-
threshold: 10,
|
|
194
216
|
items: [
|
|
195
217
|
{name: 'A', price: 8},
|
|
196
218
|
{name: 'B', price: 12},
|
|
@@ -198,37 +220,29 @@ const data = {
|
|
|
198
220
|
]
|
|
199
221
|
};
|
|
200
222
|
|
|
201
|
-
const
|
|
223
|
+
const result = await jtlt({
|
|
202
224
|
data,
|
|
203
225
|
outputType: 'json', // Top-level result will be a JSON array
|
|
204
226
|
// forQuery mirrors: this.forEach(select, cb)
|
|
205
227
|
forQuery: [
|
|
206
228
|
'$.items[*]',
|
|
207
229
|
function (item) {
|
|
208
|
-
// Set a reusable variable from the root context
|
|
209
|
-
this.variable('threshold', '$.threshold');
|
|
210
|
-
const {threshold} = this.vars;
|
|
211
|
-
|
|
212
230
|
// Use normal JS conditionals (no this.if helper)
|
|
213
|
-
if (item.price >=
|
|
231
|
+
if (item.price >= 10) {
|
|
214
232
|
// In JSON output mode, appending a string pushes into
|
|
215
233
|
// the top-level array
|
|
216
234
|
this.string(item.name);
|
|
217
235
|
}
|
|
218
236
|
}
|
|
219
|
-
]
|
|
220
|
-
// success receives the final result; return it for convenience
|
|
221
|
-
success: (out) => out
|
|
237
|
+
]
|
|
222
238
|
});
|
|
223
|
-
|
|
224
|
-
const result = jtlt.transform();
|
|
225
239
|
// result => ['B', 'C']
|
|
226
240
|
```
|
|
227
241
|
|
|
228
242
|
Tips:
|
|
229
243
|
|
|
230
244
|
- For string output, set `outputType: 'string'` and emit with `this.text()`/`this.string()` in the callback.
|
|
231
|
-
- `
|
|
245
|
+
- `forQuery`'s callback context is the matched item, not the root — to use a value from the root (e.g. a `threshold`), use a root template instead: `this.variable('threshold', '$.threshold')` then `this.forEach('$.items[*]', cb)` (see the FLWOR example below).
|
|
232
246
|
- If you need multiple passes or richer logic, switch to named templates and modes.
|
|
233
247
|
|
|
234
248
|
## FLWOR-style (XQuery) example
|
|
@@ -238,7 +252,7 @@ You can express the essentials of a FLWOR expression (For, Let, Where, Order by,
|
|
|
238
252
|
Scenario: list book titles whose price is at/above a threshold, ordered by price descending and then title ascending.
|
|
239
253
|
|
|
240
254
|
```js
|
|
241
|
-
import
|
|
255
|
+
import {jtlt} from 'jtlt';
|
|
242
256
|
|
|
243
257
|
const data = {
|
|
244
258
|
threshold: 10,
|
|
@@ -274,8 +288,7 @@ const templates = [
|
|
|
274
288
|
}}
|
|
275
289
|
];
|
|
276
290
|
|
|
277
|
-
const out =
|
|
278
|
-
transform('html');
|
|
291
|
+
const out = await jtlt({data, templates, outputType: 'string', mode: 'html'});
|
|
279
292
|
|
|
280
293
|
// -> <ul><li>Brave New</li><li>Cobalt</li><li>Delta</li></ul>
|
|
281
294
|
console.log(out);
|
|
@@ -297,7 +310,7 @@ You can model a join across two arrays (e.g., orders ↔ customers) using two `f
|
|
|
297
310
|
Example: render an HTML list of orders annotated with customer names.
|
|
298
311
|
|
|
299
312
|
```js
|
|
300
|
-
import
|
|
313
|
+
import {jtlt} from 'jtlt';
|
|
301
314
|
|
|
302
315
|
const data = {
|
|
303
316
|
customers: [
|
|
@@ -333,10 +346,11 @@ const templates = [
|
|
|
333
346
|
}}
|
|
334
347
|
];
|
|
335
348
|
|
|
336
|
-
const out =
|
|
337
|
-
data, templates, outputType: 'string'
|
|
338
|
-
})
|
|
339
|
-
//
|
|
349
|
+
const out = await jtlt({
|
|
350
|
+
data, templates, outputType: 'string', mode: 'html'
|
|
351
|
+
});
|
|
352
|
+
// sorted by date ascending:
|
|
353
|
+
// -> <ul><li>Alice — Mouse</li><li>Bob — Keyboard</li></ul>
|
|
340
354
|
console.log(out);
|
|
341
355
|
```
|
|
342
356
|
|
|
@@ -352,7 +366,7 @@ Notes:
|
|
|
352
366
|
Define an index once, then perform O(1) lookups from another sequence when rendering. If no match is found, `getKey()` returns the current context (`this`) as a sentinel; check for that to skip safely.
|
|
353
367
|
|
|
354
368
|
```js
|
|
355
|
-
import
|
|
369
|
+
import {jtlt} from 'jtlt';
|
|
356
370
|
|
|
357
371
|
const data = {
|
|
358
372
|
customers: [
|
|
@@ -383,9 +397,9 @@ const templates = [
|
|
|
383
397
|
}}
|
|
384
398
|
];
|
|
385
399
|
|
|
386
|
-
const out =
|
|
387
|
-
data, templates, outputType: 'string'
|
|
388
|
-
})
|
|
400
|
+
const out = await jtlt({
|
|
401
|
+
data, templates, outputType: 'string', mode: 'html'
|
|
402
|
+
});
|
|
389
403
|
// -> <ul><li>Bob: Keyboard</li></ul>
|
|
390
404
|
console.log(out);
|
|
391
405
|
```
|
|
@@ -416,7 +430,7 @@ Differences / current limitations:
|
|
|
416
430
|
allow a particuluar subset of JavaScript.
|
|
417
431
|
- Stylesheet composition/precedence: no `xsl:import`/`xsl:include` equivalents; only basic priority and modes.
|
|
418
432
|
- Schema awareness: no type-aware processing (a major XSLT/XQuery feature).
|
|
419
|
-
-
|
|
433
|
+
- One output type per transform, though `document()` / `resultDocument()` can emit several documents of that type within a run.
|
|
420
434
|
|
|
421
435
|
## Differences between an exact equivalence with XSLT
|
|
422
436
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Demo: Using valueOf() to access parameters in callTemplate
|
|
3
3
|
*
|
|
4
|
-
* This demonstrates the
|
|
4
|
+
* This demonstrates the feature where parameters passed via callTemplate
|
|
5
5
|
* can be accessed within the template using valueOf({select: '$paramName'})
|
|
6
6
|
* instead of having to receive them as function parameters.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/* eslint-disable no-console -- Demo file */
|
|
10
10
|
|
|
11
|
-
import
|
|
11
|
+
import {jtlt} from '../src/index-node.js';
|
|
12
12
|
|
|
13
13
|
console.log('=== Demo 1: Named parameters ===');
|
|
14
|
-
|
|
14
|
+
console.log(await jtlt({
|
|
15
15
|
data: {
|
|
16
16
|
users: [
|
|
17
17
|
{name: 'Alice', role: 'Admin'},
|
|
@@ -50,15 +50,12 @@ JTLT.create({
|
|
|
50
50
|
this.string(')\n');
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log('\n');
|
|
57
|
-
}
|
|
58
|
-
});
|
|
53
|
+
]
|
|
54
|
+
}));
|
|
55
|
+
console.log('\n');
|
|
59
56
|
|
|
60
57
|
console.log('=== Demo 2: Indexed parameters (no names) ===');
|
|
61
|
-
|
|
58
|
+
console.log(await jtlt({
|
|
62
59
|
data: {value: 'Test'},
|
|
63
60
|
outputType: 'string',
|
|
64
61
|
templates: [
|
|
@@ -88,15 +85,12 @@ JTLT.create({
|
|
|
88
85
|
this.string('\n');
|
|
89
86
|
}
|
|
90
87
|
}
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.log('\n');
|
|
95
|
-
}
|
|
96
|
-
});
|
|
88
|
+
]
|
|
89
|
+
}));
|
|
90
|
+
console.log('\n');
|
|
97
91
|
|
|
98
92
|
console.log('=== Demo 3: Nested callTemplate ===');
|
|
99
|
-
|
|
93
|
+
console.log(await jtlt({
|
|
100
94
|
data: {company: 'ACME Corp'},
|
|
101
95
|
outputType: 'string',
|
|
102
96
|
templates: [
|
|
@@ -135,8 +129,5 @@ JTLT.create({
|
|
|
135
129
|
this.string('\n');
|
|
136
130
|
}
|
|
137
131
|
}
|
|
138
|
-
]
|
|
139
|
-
|
|
140
|
-
console.log(result);
|
|
141
|
-
}
|
|
142
|
-
});
|
|
132
|
+
]
|
|
133
|
+
}));
|
|
@@ -7,13 +7,12 @@ import SAJJ from './SAJJ.js';
|
|
|
7
7
|
/* eslint-enable jsdoc/reject-any-type -- Arbitrary */
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* @abstract
|
|
11
|
-
* @class
|
|
12
|
-
* @todo Might add an add() method which defines how to combine result values
|
|
13
|
-
* (so as to allow for other means besides string concatenation)
|
|
14
|
-
*/
|
|
10
|
+
* @abstract
|
|
11
|
+
* @class
|
|
12
|
+
* @todo Might add an add() method which defines how to combine result values
|
|
13
|
+
* (so as to allow for other means besides string concatenation)
|
|
14
|
+
*/
|
|
15
15
|
class ObjectArrayDelegator extends SAJJ {
|
|
16
|
-
/* eslint-disable jsdoc/require-returns-check -- Abstract */
|
|
17
16
|
/**
|
|
18
17
|
* @returns {AnyDelegated}
|
|
19
18
|
*/
|
|
@@ -76,7 +75,6 @@ class ObjectArrayDelegator extends SAJJ {
|
|
|
76
75
|
) {
|
|
77
76
|
throw new Error('Abstract');
|
|
78
77
|
}
|
|
79
|
-
/* eslint-enable jsdoc/require-returns-check -- Abstract */
|
|
80
78
|
|
|
81
79
|
// It is probably not necessary to override the defaults for the following
|
|
82
80
|
// two methods and perhaps not any of the others either
|
|
@@ -109,17 +107,19 @@ class ObjectArrayDelegator extends SAJJ {
|
|
|
109
107
|
}
|
|
110
108
|
} else {
|
|
111
109
|
for (const key in value) {
|
|
112
|
-
if (Object.hasOwn(value, key)) {
|
|
113
|
-
|
|
114
|
-
this.currentObject = value[key];
|
|
115
|
-
keyVals.push(
|
|
116
|
-
this.keyValueHandler(
|
|
117
|
-
value[key], key, value, parentKey,
|
|
118
|
-
parentObjectArrayBool, false, i
|
|
119
|
-
)
|
|
120
|
-
);
|
|
121
|
-
i++;
|
|
110
|
+
if (!Object.hasOwn(value, key)) {
|
|
111
|
+
continue;
|
|
122
112
|
}
|
|
113
|
+
|
|
114
|
+
this.currentKey = key;
|
|
115
|
+
this.currentObject = value[key];
|
|
116
|
+
keyVals.push(
|
|
117
|
+
this.keyValueHandler(
|
|
118
|
+
value[key], key, value, parentKey,
|
|
119
|
+
parentObjectArrayBool, false, i
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
i++;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
}
|
|
@@ -99,9 +99,9 @@
|
|
|
99
99
|
*/
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* @typedef {"undefined"|"null"|"boolean"|"symbol"
|
|
103
|
-
* "number"|"nonfiniteNumber"|"bigint"
|
|
104
|
-
* "string"|"function"|"array"|"object"|"ignore"} SAJJType
|
|
102
|
+
* @typedef {"undefined"|"null"|"boolean"|"symbol"
|
|
103
|
+
* |"number"|"nonfiniteNumber"|"bigint"
|
|
104
|
+
* |"string"|"function"|"array"|"object"|"ignore"} SAJJType
|
|
105
105
|
*/
|
|
106
106
|
|
|
107
107
|
// PRIVATE STATIC UTILITIES
|
|
@@ -119,7 +119,7 @@ function _copyObject (obj, deep) {
|
|
|
119
119
|
const copyObj = {};
|
|
120
120
|
// eslint-disable-next-line guard-for-in -- Deliberate iterating of prototype
|
|
121
121
|
for (const prop in obj) {
|
|
122
|
-
copyObj[prop] = deep && obj[prop] && typeof obj[prop] === 'object'
|
|
122
|
+
copyObj[prop] = deep && obj[prop] !== null && typeof obj[prop] === 'object'
|
|
123
123
|
? _copyObject(/** @type {NestedObject} */ (obj[prop]))
|
|
124
124
|
: obj[prop];
|
|
125
125
|
}
|
|
@@ -150,6 +150,17 @@ function _copyObject (obj, deep) {
|
|
|
150
150
|
class SAJJ {
|
|
151
151
|
ret = '';
|
|
152
152
|
|
|
153
|
+
/** @type {SAJJOptions|undefined} */
|
|
154
|
+
options;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {SAJJOptions} [options] See setDefaultOptions() function body for
|
|
158
|
+
* some possibilities
|
|
159
|
+
*/
|
|
160
|
+
constructor (options) {
|
|
161
|
+
this.setDefaultOptions(options);
|
|
162
|
+
}
|
|
163
|
+
|
|
153
164
|
/* eslint-disable jsdoc/require-returns-check -- Abstract */
|
|
154
165
|
/**
|
|
155
166
|
* Could override for logging; meant for allowing dropping of
|
|
@@ -398,18 +409,6 @@ class SAJJ {
|
|
|
398
409
|
}
|
|
399
410
|
/* eslint-enable jsdoc/require-returns-check -- Abstract */
|
|
400
411
|
|
|
401
|
-
/**
|
|
402
|
-
* @param {SAJJOptions} options See setDefaultOptions() function body for
|
|
403
|
-
* some possibilities
|
|
404
|
-
*/
|
|
405
|
-
constructor (options) {
|
|
406
|
-
/** @type {SAJJOptions} */
|
|
407
|
-
// eslint-disable-next-line no-unused-expressions -- TS
|
|
408
|
-
this.options;
|
|
409
|
-
|
|
410
|
-
this.setDefaultOptions(options);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
412
|
// OPTIONS
|
|
414
413
|
/**
|
|
415
414
|
* @param {SAJJOptions} [options]
|
|
@@ -447,11 +446,11 @@ class SAJJ {
|
|
|
447
446
|
}
|
|
448
447
|
|
|
449
448
|
/**
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
449
|
+
* Rather than use the strategy design pattern, we'll override our prototype
|
|
450
|
+
* selectively.
|
|
451
|
+
* @param {SAJJOptions} options
|
|
452
|
+
* @returns {void}
|
|
453
|
+
*/
|
|
455
454
|
alterDefaultHandlers (options) {
|
|
456
455
|
if (this.distinguishKeysValues) {
|
|
457
456
|
this.keyValueHandler = this.keyValueDistinguishedHandler;
|
|
@@ -464,17 +463,17 @@ class SAJJ {
|
|
|
464
463
|
// PUBLIC METHODS TO INITIATE PARSING
|
|
465
464
|
|
|
466
465
|
/**
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
466
|
+
* For strings, one may wish to use Clarinet (<https://github.com/dscape/clarinet>) to
|
|
467
|
+
* avoid extra overhead or parsing twice.
|
|
468
|
+
* @param {string} str The JSON string to be walked (after complete conversion
|
|
469
|
+
* to an object)
|
|
470
|
+
* @param {object|object[]} [parentObject] The parent object or array
|
|
471
|
+
* containing the string
|
|
472
|
+
* @param {string} [parentKey] The parent object or array's key
|
|
473
|
+
* @param {boolean} [parentObjectArrayBool] Whether the parent object is an
|
|
474
|
+
* array (not another object)
|
|
475
|
+
* @returns {AnyValue}
|
|
476
|
+
*/
|
|
478
477
|
walkJSONString (str, parentObject, parentKey, parentObjectArrayBool) {
|
|
479
478
|
return this.walkJSONObject(
|
|
480
479
|
JSON.parse(str), parentObject, parentKey, parentObjectArrayBool
|
|
@@ -482,26 +481,26 @@ class SAJJ {
|
|
|
482
481
|
}
|
|
483
482
|
|
|
484
483
|
/**
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
484
|
+
*
|
|
485
|
+
* @param {import('../jhtml.js').JSONObject} obj The JSON object to walk
|
|
486
|
+
* @param {object|object[]} [parentObject] The parent object or array
|
|
487
|
+
* containing the string
|
|
488
|
+
* @param {string} [parentKey] The parent object or array's key
|
|
489
|
+
* @param {boolean} [parentObjectArrayBool] Whether the parent object is an
|
|
490
|
+
* array (not another object)
|
|
491
|
+
* @property {string|AnyValue} ret The intermediate return value (if any) from
|
|
492
|
+
* beginHandler and delegateHandlersByType delegation
|
|
493
|
+
* @returns {string} The final return value including beginHandler and
|
|
494
|
+
* delegateHandlersByType delegation plus any endHandler additions;
|
|
495
|
+
* one may build one's own intermediate values, but "ret" should be
|
|
496
|
+
* set to return the value
|
|
497
|
+
*/
|
|
499
498
|
walkJSONObject (obj, parentObject, parentKey, parentObjectArrayBool) {
|
|
500
499
|
this.root = obj;
|
|
501
|
-
const parObj = parentObject || this.options
|
|
502
|
-
parKey = parentKey || this.options
|
|
500
|
+
const parObj = parentObject || this.options?.parentObject,
|
|
501
|
+
parKey = parentKey || this.options?.parentKey,
|
|
503
502
|
parObjArrBool = parentObjectArrayBool ||
|
|
504
|
-
this.options
|
|
503
|
+
this.options?.parentObjectArrayBool ||
|
|
505
504
|
(parObj && this.isArrayType(parObj));
|
|
506
505
|
this.ret = this.beginHandler(obj, parObj, parKey, parObjArrBool);
|
|
507
506
|
this.ret += this.delegateHandlersByType(obj, parObj, parKey, parObjArrBool);
|
|
@@ -661,23 +660,23 @@ class SAJJ {
|
|
|
661
660
|
}
|
|
662
661
|
|
|
663
662
|
/**
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
663
|
+
* Could override to always return false if one wished to merge
|
|
664
|
+
* arrayHandler/objectHandler or, if in JSMode, to merge detectObjectType
|
|
665
|
+
* and this isArrayType method. To merge arrayKeyValueHandler and
|
|
666
|
+
* objectKeyValueHandler, see keyValueHandler.
|
|
667
|
+
* @param {AnyValue} obj
|
|
668
|
+
* @returns {boolean}
|
|
669
|
+
*/
|
|
671
670
|
isArrayType (obj) {
|
|
672
671
|
return Object.prototype.toString.call(obj) === '[object Array]';
|
|
673
672
|
}
|
|
674
673
|
|
|
675
674
|
/**
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
675
|
+
* Allow overriding to detect `Date`, `RegExp`, or other types (which will in
|
|
676
|
+
* turn route to corresponding names).
|
|
677
|
+
* @param {AnyValue} obj
|
|
678
|
+
* @returns {"object"}
|
|
679
|
+
*/
|
|
681
680
|
detectObjectType (
|
|
682
681
|
// eslint-disable-next-line no-unused-vars -- Signature
|
|
683
682
|
obj
|