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
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# callTemplate Parameter Access via `valueOf()`
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Parameters can be accessed within the template using `valueOf({select: '$paramName'})`:
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
await jtlt({
|
|
9
|
+
templates: [{
|
|
10
|
+
name: 'myTemplate',
|
|
11
|
+
template () {
|
|
12
|
+
this.string('Value: ');
|
|
13
|
+
this.valueOf({select: '$paramName'});
|
|
14
|
+
}
|
|
15
|
+
}]
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Note: Named templates (those with a `name` property) that are only called via `callTemplate` do not need a `path` property.
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
### Named Parameters
|
|
24
|
+
|
|
25
|
+
You can pass named parameters using the `name` property in `withParam`:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
await jtlt({
|
|
29
|
+
templates: [
|
|
30
|
+
{
|
|
31
|
+
path: '$',
|
|
32
|
+
template () {
|
|
33
|
+
this.callTemplate({
|
|
34
|
+
name: 'formatUser',
|
|
35
|
+
withParam: [
|
|
36
|
+
{name: 'userName', value: 'Alice'},
|
|
37
|
+
{name: 'userRole', select: '$.role'}
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// ...
|
|
43
|
+
]
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Access them in the template:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
await jtlt({
|
|
51
|
+
templates: [
|
|
52
|
+
// ...
|
|
53
|
+
{
|
|
54
|
+
name: 'formatUser',
|
|
55
|
+
template () {
|
|
56
|
+
this.valueOf({select: '$userName'});
|
|
57
|
+
this.string(' - ');
|
|
58
|
+
this.valueOf({select: '$userRole'});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Indexed Parameters
|
|
66
|
+
|
|
67
|
+
When no `name` is provided, parameters are accessible by their index (0, 1, 2, etc.):
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
await jtlt({
|
|
71
|
+
templates: [
|
|
72
|
+
{
|
|
73
|
+
path: '$',
|
|
74
|
+
template () {
|
|
75
|
+
this.callTemplate({
|
|
76
|
+
name: 'format',
|
|
77
|
+
withParam: [
|
|
78
|
+
{value: 'First'},
|
|
79
|
+
{value: 'Second'}
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Access by index:
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
await jtlt({
|
|
92
|
+
templates: [
|
|
93
|
+
{
|
|
94
|
+
name: 'format',
|
|
95
|
+
template () {
|
|
96
|
+
this.valueOf({select: '$0'}); // First
|
|
97
|
+
this.valueOf({select: '$1'}); // Second
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Nested callTemplate Calls
|
|
105
|
+
|
|
106
|
+
Parameters are scoped to the current template call. When nesting `callTemplate` calls, each template has access to its own parameters:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
await jtlt({
|
|
110
|
+
templates: [
|
|
111
|
+
{
|
|
112
|
+
name: 'outer',
|
|
113
|
+
template () {
|
|
114
|
+
this.valueOf({select: '$outerParam'}); // Accesses outer parameter
|
|
115
|
+
this.callTemplate({
|
|
116
|
+
name: 'inner',
|
|
117
|
+
withParam: [{name: 'innerParam', value: 'inner-value'}]
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'inner',
|
|
123
|
+
template () {
|
|
124
|
+
this.valueOf({select: '$innerParam'}); // Accesses inner parameter
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Implementation Details
|
|
132
|
+
|
|
133
|
+
### How It Works
|
|
134
|
+
|
|
135
|
+
1. When `callTemplate()` is called, it stores the parameters in a temporary `_params` object on the context
|
|
136
|
+
2. Parameters are stored either by name (if provided) or by index
|
|
137
|
+
3. When `valueOf()` is called with a selector starting with `$`, it checks `_params` first
|
|
138
|
+
4. If the parameter is not found, it falls back to normal JSONPath/XPath evaluation
|
|
139
|
+
5. After the template completes, the previous parameter context is restored
|
|
140
|
+
|
|
141
|
+
### Parameter Priority
|
|
142
|
+
|
|
143
|
+
1. **Named parameters**: If `withParam[i].name` is provided, the parameter is stored with that name
|
|
144
|
+
2. **Indexed parameters**: If no name is provided, the parameter is stored with its index as a string
|
|
145
|
+
|
|
146
|
+
### Backward Compatibility
|
|
147
|
+
|
|
148
|
+
The old approach of receiving parameters as function arguments is no longer supported. Templates should be updated to use `valueOf()` for parameter access. This change was made to:
|
|
149
|
+
|
|
150
|
+
- Provide a more consistent API with XSLT-style parameter access
|
|
151
|
+
- Allow templates to use the fluent builder pattern throughout
|
|
152
|
+
- Enable better parameter naming and documentation
|
|
153
|
+
- Support both JSONPath and XPath contexts uniformly
|
|
154
|
+
|
|
155
|
+
## Examples
|
|
156
|
+
|
|
157
|
+
### Example 1: User Formatting
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
await jtlt({
|
|
161
|
+
data: {users: [{name: 'Alice', role: 'Admin'}]},
|
|
162
|
+
outputType: 'string',
|
|
163
|
+
templates: [
|
|
164
|
+
{
|
|
165
|
+
path: '$',
|
|
166
|
+
template () {
|
|
167
|
+
this.forEach('$.users[*]', function (user) {
|
|
168
|
+
this.callTemplate({
|
|
169
|
+
name: 'formatUser',
|
|
170
|
+
withParam: [
|
|
171
|
+
{name: 'userName', value: user.name},
|
|
172
|
+
{name: 'userRole', value: user.role}
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'formatUser',
|
|
180
|
+
template () {
|
|
181
|
+
this.string(' (');
|
|
182
|
+
this.valueOf({select: '$userRole'});
|
|
183
|
+
this.string(')\n');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
});
|
|
188
|
+
// Output: "Alice (Admin)\n"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Example 2: Nested callTemplate
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
await jtlt({
|
|
195
|
+
data: {company: 'ACME Corp', division: 'Engineering'},
|
|
196
|
+
outputType: 'string',
|
|
197
|
+
templates: [
|
|
198
|
+
{
|
|
199
|
+
path: '$',
|
|
200
|
+
template () {
|
|
201
|
+
this.callTemplate({
|
|
202
|
+
name: 'outer',
|
|
203
|
+
withParam: [
|
|
204
|
+
{name: 'companyName', select: '$.company'},
|
|
205
|
+
{name: 'divisionName', select: '$.division'}
|
|
206
|
+
]
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: 'outer',
|
|
212
|
+
template () {
|
|
213
|
+
this.string('Company: ');
|
|
214
|
+
this.valueOf({select: '$companyName'});
|
|
215
|
+
this.string('\n');
|
|
216
|
+
|
|
217
|
+
this.callTemplate({
|
|
218
|
+
name: 'inner',
|
|
219
|
+
withParam: [
|
|
220
|
+
{name: 'division', select: '$divisionName'}
|
|
221
|
+
]
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'inner',
|
|
227
|
+
template () {
|
|
228
|
+
this.string(' Division: ');
|
|
229
|
+
this.valueOf({select: '$division'});
|
|
230
|
+
this.string('\n');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
});
|
|
235
|
+
// Output: "Company: ACME Corp\n Division: Engineering\n"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Testing
|
|
239
|
+
|
|
240
|
+
Run the test suite to verify the implementation:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
pnpm test test/test.calltemplate-params.js
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Run the demo to see examples in action:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
node demo/calltemplate-params-demo.js
|
|
250
|
+
```
|
|
251
|
+
|
package/eslint.config.js
CHANGED
|
@@ -3,18 +3,22 @@ import ashNazg from 'eslint-config-ash-nazg';
|
|
|
3
3
|
export default [
|
|
4
4
|
{
|
|
5
5
|
ignores: [
|
|
6
|
-
'dist'
|
|
6
|
+
'dist',
|
|
7
|
+
'coverage'
|
|
7
8
|
]
|
|
8
9
|
},
|
|
9
10
|
...ashNazg(['sauron']),
|
|
10
11
|
{
|
|
11
12
|
rules: {
|
|
12
|
-
// Temporary only:
|
|
13
|
-
'sonarjs/public-static-readonly': 'off', // Until ash-nazg disables
|
|
14
|
-
'jsdoc/check-types': 0,
|
|
15
13
|
'jsdoc/reject-any-type': 0,
|
|
16
|
-
|
|
14
|
+
|
|
15
|
+
// Temporary only:
|
|
17
16
|
'no-unused-vars': 0,
|
|
17
|
+
|
|
18
|
+
// AI was frequently making egregious mistakes here, so make fixable
|
|
19
|
+
'jsdoc/check-alignment': 'error',
|
|
20
|
+
|
|
21
|
+
// We frequently use callbacks for nested interactions
|
|
18
22
|
'promise/prefer-await-to-callbacks': 0
|
|
19
23
|
}
|
|
20
24
|
},
|
package/package.json
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jtlt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Brett Zamir",
|
|
6
6
|
"contributors": [],
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
|
-
"main": "src/index.js",
|
|
9
|
+
"main": "src/index-node.js",
|
|
10
10
|
"exports": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
|
-
"
|
|
12
|
+
"browser": {
|
|
13
|
+
"import": "./src/index-browser.js"
|
|
14
|
+
},
|
|
15
|
+
"node": {
|
|
16
|
+
"import": "./src/index-node.js"
|
|
17
|
+
}
|
|
13
18
|
},
|
|
14
19
|
"engines": {
|
|
15
20
|
"node": ">=22.16.0"
|
|
@@ -20,25 +25,25 @@
|
|
|
20
25
|
"url": "https://github.com/brettz9/jtlt.git"
|
|
21
26
|
},
|
|
22
27
|
"dependencies": {
|
|
23
|
-
"jamilih": "0.
|
|
28
|
+
"jamilih": "0.62.1",
|
|
24
29
|
"jhtml": "0.7.1",
|
|
25
30
|
"jsdom": "27.1.0",
|
|
26
31
|
"jsonpath-plus": "10.3.0",
|
|
27
|
-
"lodash": "^4.17.21",
|
|
28
|
-
"object-assign": "4.x",
|
|
29
32
|
"simple-get-json": "10.0.0",
|
|
30
33
|
"xpath2.js": "1.0.0-alpha-6"
|
|
31
34
|
},
|
|
32
35
|
"devDependencies": {
|
|
33
36
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
37
|
+
"@node-static/node-static": "^0.8.1",
|
|
34
38
|
"@types/chai": "^5.2.3",
|
|
35
39
|
"@types/jsdom": "^27.0.0",
|
|
36
40
|
"@types/mocha": "^10.0.10",
|
|
37
41
|
"c8": "^10.1.3",
|
|
38
42
|
"chai": "^6.2.0",
|
|
39
43
|
"eslint": "^9.39.1",
|
|
40
|
-
"eslint-config-ash-nazg": "^39.
|
|
44
|
+
"eslint-config-ash-nazg": "^39.8.0",
|
|
41
45
|
"mocha": "^11.7.5",
|
|
46
|
+
"open": "^10.2.0",
|
|
42
47
|
"typescript": "^5.9.3"
|
|
43
48
|
},
|
|
44
49
|
"bugs": "https://github.com/brettz9/jtlt/issues",
|
|
@@ -50,6 +55,7 @@
|
|
|
50
55
|
"javascript"
|
|
51
56
|
],
|
|
52
57
|
"scripts": {
|
|
58
|
+
"start": "open http://localhost:8020/demo/ && static -p 8020",
|
|
53
59
|
"attw": "attw --pack",
|
|
54
60
|
"tsc": "tsc",
|
|
55
61
|
"build": "tsc -p tsconfig-prod.json",
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
// mid-transformation (e.g., building strings with
|
|
3
3
|
// string transformer but adding as text node in a DOM transformer)
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {{
|
|
7
|
+
* requireSameChildren?: boolean,
|
|
8
|
+
* JHTMLForJSON?: boolean,
|
|
9
|
+
* mode?: "JSON"|"JavaScript"
|
|
10
|
+
* }} BaseTransformerConfig
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {BaseTransformerConfig & {
|
|
15
|
+
* document: Document,
|
|
16
|
+
* exposeDocuments?: boolean
|
|
17
|
+
* }} DOMJoiningTransformerConfig
|
|
18
|
+
* When exposeDocuments is true, get() returns an array of XMLDocument
|
|
19
|
+
* objects (one per root element) instead of a DocumentFragment.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {object} JSONJoiningTransformerConfig
|
|
23
|
+
* @property {boolean} [requireSameChildren]
|
|
24
|
+
* @property {boolean} [unwrapSingleResult]
|
|
25
|
+
* @property {boolean} [exposeDocuments] - When true, get() returns an array
|
|
26
|
+
* of document wrapper objects (one per root element) instead of the raw array.
|
|
27
|
+
* @property {"JSON"|"JavaScript"} [mode]
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {BaseTransformerConfig & {
|
|
31
|
+
* xmlElements?: boolean,
|
|
32
|
+
* preEscapedAttributes?: boolean,
|
|
33
|
+
* exposeDocuments?: boolean
|
|
34
|
+
* }} StringJoiningTransformerConfig
|
|
35
|
+
* When exposeDocuments is true, get() returns an array of document
|
|
36
|
+
* strings (one per root element) instead of a single concatenated string.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* @template T
|
|
40
|
+
* @typedef {T extends "string" ? StringJoiningTransformerConfig :
|
|
41
|
+
* T extends "dom" ? DOMJoiningTransformerConfig :
|
|
42
|
+
* T extends "json" ? JSONJoiningTransformerConfig : never
|
|
43
|
+
* } JoiningTransformerConfig
|
|
44
|
+
*/
|
|
45
|
+
|
|
5
46
|
/**
|
|
6
47
|
* Base class for joining transformers.
|
|
7
48
|
*
|
|
@@ -16,18 +57,19 @@
|
|
|
16
57
|
* based on the current state.
|
|
17
58
|
* - get(): returns the accumulated result.
|
|
18
59
|
* - config(): temporarily tweak a config flag for the duration of a callback.
|
|
60
|
+
* @template T
|
|
19
61
|
*/
|
|
20
62
|
class AbstractJoiningTransformer {
|
|
21
63
|
/**
|
|
22
|
-
* @param {
|
|
64
|
+
* @param {JoiningTransformerConfig<T>} [cfg] - Configuration object
|
|
23
65
|
*/
|
|
24
66
|
constructor (cfg) {
|
|
25
67
|
// Todo: Might set some reasonable defaults across all classes
|
|
26
|
-
this.
|
|
68
|
+
this._cfg = cfg ?? /** @type {JoiningTransformerConfig<T>} */ ({});
|
|
27
69
|
}
|
|
28
70
|
|
|
29
71
|
/**
|
|
30
|
-
* @param {
|
|
72
|
+
* @param {JoiningTransformerConfig<T>} cfg - Configuration object
|
|
31
73
|
* @returns {void}
|
|
32
74
|
*/
|
|
33
75
|
setConfig (cfg) {
|
|
@@ -40,8 +82,8 @@ class AbstractJoiningTransformer {
|
|
|
40
82
|
* @returns {void}
|
|
41
83
|
*/
|
|
42
84
|
_requireSameChildren (type, embedType) {
|
|
43
|
-
|
|
44
|
-
|
|
85
|
+
const cfg = this._cfg;
|
|
86
|
+
if (cfg.requireSameChildren) {
|
|
45
87
|
throw new Error(
|
|
46
88
|
'Cannot embed ' + embedType + ' children for a ' + type +
|
|
47
89
|
' joining transformer.'
|
|
@@ -51,21 +93,18 @@ class AbstractJoiningTransformer {
|
|
|
51
93
|
|
|
52
94
|
/**
|
|
53
95
|
* @param {string} prop - Configuration property name
|
|
54
|
-
* @param {
|
|
55
|
-
* @param {
|
|
96
|
+
* @param {any} val - Configuration property value
|
|
97
|
+
* @param {(this: AbstractJoiningTransformer<T>) => void} [cb]
|
|
98
|
+
* Optional callback invoked with this instance
|
|
56
99
|
* @returns {void}
|
|
57
100
|
*/
|
|
58
101
|
config (prop, val, cb) {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/** @type {any} */ (this._cfg)[prop] = val;
|
|
63
|
-
}
|
|
102
|
+
const cfg = /** @type {Record<string, unknown>} */ (this._cfg);
|
|
103
|
+
const oldCfgProp = cfg[prop];
|
|
104
|
+
cfg[prop] = val;
|
|
64
105
|
if (cb) {
|
|
65
106
|
cb.call(this);
|
|
66
|
-
|
|
67
|
-
/** @type {any} */ (this._cfg)[prop] = oldCfgProp;
|
|
68
|
-
}
|
|
107
|
+
cfg[prop] = oldCfgProp;
|
|
69
108
|
}
|
|
70
109
|
}
|
|
71
110
|
}
|