jtlt 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.editorconfig +16 -0
  2. package/CHANGES.md +5 -0
  3. package/LICENSE-MIT.txt +21 -0
  4. package/README.md +534 -0
  5. package/dist/AbstractJoiningTransformer.d.ts +42 -0
  6. package/dist/AbstractJoiningTransformer.d.ts.map +1 -0
  7. package/dist/DOMJoiningTransformer.d.ts +113 -0
  8. package/dist/DOMJoiningTransformer.d.ts.map +1 -0
  9. package/dist/JSONJoiningTransformer.d.ts +160 -0
  10. package/dist/JSONJoiningTransformer.d.ts.map +1 -0
  11. package/dist/JSONPathTransformer.d.ts +95 -0
  12. package/dist/JSONPathTransformer.d.ts.map +1 -0
  13. package/dist/JSONPathTransformerContext.d.ts +263 -0
  14. package/dist/JSONPathTransformerContext.d.ts.map +1 -0
  15. package/dist/StringJoiningTransformer.d.ts +168 -0
  16. package/dist/StringJoiningTransformer.d.ts.map +1 -0
  17. package/dist/XPathTransformer.d.ts +51 -0
  18. package/dist/XPathTransformer.d.ts.map +1 -0
  19. package/dist/XPathTransformerContext.d.ts +260 -0
  20. package/dist/XPathTransformerContext.d.ts.map +1 -0
  21. package/dist/XSLTStyleJSONPathResolver.d.ts +16 -0
  22. package/dist/XSLTStyleJSONPathResolver.d.ts.map +1 -0
  23. package/dist/index.d.ts +168 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/docs/API.expanded.md +263 -0
  26. package/docs/API.md +69 -0
  27. package/eslint.config.js +30 -0
  28. package/package.json +53 -0
  29. package/pnpm-workspace.yaml +3 -0
  30. package/src/AbstractJoiningTransformer.js +73 -0
  31. package/src/DOMJoiningTransformer.js +237 -0
  32. package/src/JSONJoiningTransformer.js +472 -0
  33. package/src/JSONPathTransformer.js +159 -0
  34. package/src/JSONPathTransformerContext.js +807 -0
  35. package/src/StringJoiningTransformer.js +589 -0
  36. package/src/XPathTransformer.js +94 -0
  37. package/src/XPathTransformerContext.js +496 -0
  38. package/src/XSLTStyleJSONPathResolver.js +39 -0
  39. package/src/index.js +299 -0
  40. package/src/types/xpath2-js.d.ts +2 -0
  41. package/tsconfig-prod.json +19 -0
  42. package/tsconfig.json +14 -0
  43. package/typings/xpath2-js.d.ts +2 -0
@@ -0,0 +1,263 @@
1
+ # API (Expanded)
2
+
3
+ This document describes the public API surface of JTLT (JavaScript Template Language Transformations), including the JSONPath and experimental XPath engines, execution contexts, and joiners.
4
+
5
+ > Early alpha: APIs may evolve; experimental sections (XPath) can change without major version bumps.
6
+
7
+ ## Core façade: `JTLT`
8
+
9
+ High-level entry point for running a transform over JSON data using JSONPath.
10
+
11
+ ```js
12
+ import JTLT from 'jtlt';
13
+
14
+ const out = new JTLT({
15
+ data: {title: 'Hello'},
16
+ outputType: 'string',
17
+ templates: [
18
+ {
19
+ path: '$.title',
20
+ template (v) {
21
+ this.string('<h1>', () => this.text(v));
22
+ this.string('</h1>');
23
+ }
24
+ }
25
+ ],
26
+ success: (res) => res
27
+ }).transform('');
28
+ ```
29
+
30
+ ### `new JTLT(config)` options
31
+
32
+ | Option | Type | Description |
33
+ | ------ | ---- | ----------- |
34
+ | `data` | any | Root JSON/JS object. Required unless `ajaxData` provided. |
35
+ | `ajaxData` | string | URL to fetch JSON (async start). |
36
+ | `templates` | Array<TemplateObject> | Template declarations; see below. |
37
+ | `template` | Function \| TemplateObject | Single root template convenience. |
38
+ | `query` | Function | Root template convenience (wrapped as `path: '$'`). |
39
+ | `forQuery` | [select, cb] | One-off query (like FLWOR `for`). Auto-wrapped as root template. |
40
+ | `success` | Function(result) | Required callback; also receives transform return. |
41
+ | `mode` | string | Starting mode for template matching. |
42
+ | `outputType` | 'string' \| 'dom' \| 'json' | Chooses joiner. Default 'string'. |
43
+ | `joiningTransformer` | Joiner instance | Custom joiner; skip auto creation. |
44
+ | `joiningConfig` | object | Passed to joiner (e.g., `{xmlElements:true}`). |
45
+ | `unwrapSingleResult` | boolean | For JSON joiner: unwrap single-item root array. |
46
+ | `errorOnEqualPriority` | boolean | Throw when multiple templates share priority on a node. |
47
+ | `specificityPriorityResolver` | fn(path)=>number | Custom resolver for relative priorities. Defaults to XSLT-like JSONPath resolver. |
48
+ | `engine` | fn(config)=>any | Override transform engine (defaults to JSONPathTransformer). |
49
+ | `autostart` | boolean | If `false`, don’t auto-call `transform()` in constructor. |
50
+ | `preventEval` | boolean | Disable parenthetical eval portions of JSONPath (security). |
51
+
52
+ ## Template objects
53
+
54
+ ```ts
55
+ interface TemplateObject {
56
+ name?: string; // Optional identifier (for callTemplate)
57
+ path: string; // JSONPath or XPath expression
58
+ mode?: string; // Optional mode segregation
59
+ priority?: number; // Numeric priority (higher wins); fallback uses specificity resolver
60
+ template(nodeValue, cfg): any; // Executed with `this` bound to context
61
+ }
62
+ ```
63
+
64
+ Edge cases:
65
+ - For JSONPath: `path` examples: `$.items[*]`, `$['prop']`, `$..deep`.
66
+ - For XPath: `//item`, `/root/item`, `//*[@id='x']`.
67
+ - Root template: path `$` (JSONPath) or `/` (XPath).
68
+
69
+ ## Engines
70
+
71
+ ### `JSONPathTransformer`
72
+
73
+ Applies JSONPath-based templates over JSON data.
74
+
75
+ ```js
76
+ import {JSONPathTransformer} from 'jtlt';
77
+ const engine = new JSONPathTransformer({data, templates});
78
+ const out = engine.transform('html');
79
+ ```
80
+
81
+ Responsibilities:
82
+ - Selects matching templates for current node + mode.
83
+ - Sorts by priority (numeric or specificity resolver).
84
+ - Invokes winning template; falls back to default rules when no match.
85
+
86
+ ### `XPathTransformer` (experimental)
87
+
88
+ Same pattern for XML/HTML DOM data using XPath selectors.
89
+
90
+ Config additions:
91
+ - `xpathVersion`: `1` (native `XPathEvaluator`), `2` (`xpath2.js`). Default `1`.
92
+
93
+ ```js
94
+ import {XPathTransformer, StringJoiningTransformer} from 'jtlt';
95
+ // Assume `doc` is an XML Document
96
+ const joiner = new StringJoiningTransformer('', {document: doc});
97
+ const templates = [
98
+ {
99
+ name: 'root',
100
+ path: '/',
101
+ template () {
102
+ this.applyTemplates('//item');
103
+ }
104
+ },
105
+ {
106
+ name: 'item',
107
+ path: '//item',
108
+ template (node) {
109
+ this.element('li', {}, [], () => this.text(node.textContent));
110
+ }
111
+ }
112
+ ];
113
+ const engine = new XPathTransformer({
114
+ data: doc,
115
+ templates,
116
+ joiningTransformer: joiner,
117
+ xpathVersion: 1
118
+ });
119
+ const out = engine.transform('');
120
+ ```
121
+
122
+ Limitations:
123
+ - Version 2 (`xpath2.js`) may lack some XPath 2.0 functions; stick to basic location paths and simple predicates.
124
+ - Namespace resolution not yet exposed (future `namespaceResolver` option).
125
+
126
+ ## Contexts
127
+
128
+ ### `JSONPathTransformerContext`
129
+
130
+ Methods (subset):
131
+ - `applyTemplates(select, mode?, sort?)`
132
+ - `forEach(select, cb, sort?)`
133
+ - `valueOf(select?)`
134
+ - `variable(name, select)` – stores value/array from JSONPath.
135
+ - `callTemplate(name, withParam?)`
136
+ - `key(name, match, use)` / `getKey(name, value)`
137
+ - Joiner passthrough: `string()`, `text()`, `element()`, `object()`, `array()`, `number()`, `boolean()`, etc.
138
+
139
+ ### `XPathTransformerContext`
140
+
141
+ Parallels JSONPath context with XPath evaluation:
142
+ - `get(select, asNodes?)` – returns node array when `asNodes=true` (v1 uses snapshot type; v2 coerces scalar to array).
143
+ - `forEach(select, cb)` – iterates matches.
144
+ - `applyTemplates(select, mode?)` – default initialization to `.` then `*` for subsequent calls.
145
+ - `variable(name, select)` – always stores node arrays for XPath.
146
+ - `key(name, match, use)` – index by attribute value; `getKey` returns matching Element or context sentinel (`this`).
147
+ - Default template rules: root traverses `.`, element traverses `*`, text nodes emit `nodeValue`, scalars emit `valueOf('.')`.
148
+
149
+ ## Sorting API
150
+
151
+ Both `applyTemplates` and `forEach` accept a `sort` parameter.
152
+
153
+ Types:
154
+ 1. String JSONPath (or XPath) – ascending text compare of selected values.
155
+ 2. Function comparator `(a, b) => number`.
156
+ 3. Object spec:
157
+ ```js
158
+ const sortSpec = {
159
+ select: '$.price',
160
+ // type can be 'number' or 'text'
161
+ type: 'number',
162
+ // order can be 'ascending' or 'descending'
163
+ order: 'ascending',
164
+ // optional Intl collation/language options
165
+ locale,
166
+ localeOptions
167
+ };
168
+ ```
169
+ 4. Array of string/object specs for multi-key sorting.
170
+
171
+ Numeric sorting treats non-numeric or `NaN` values with stable ordering (equal falls back to next spec or preserves order).
172
+
173
+ ## Keys (Indexing)
174
+
175
+ `key(name, match, use)` builds an index of nodes/items by an attribute/property value. Later `getKey(name, value)` performs O(1) lookup. If not found, JSONPath returns context object; XPath returns context (`this`). Check identity to skip.
176
+
177
+ Example (join):
178
+ ```js
179
+ ctx.key('customerById', '$.customers[*]', 'id');
180
+ const c = ctx.getKey('customerById', order.customerId);
181
+ if (c !== ctx) {
182
+ // emit joined row
183
+ }
184
+ ```
185
+
186
+ ## Variables
187
+
188
+ `variable(name, select)` caches selection results:
189
+ - JSONPath: plain value or array depending on path.
190
+ - XPath: node array (even single result).
191
+
192
+ Use variables to avoid repeated path evaluation inside loops.
193
+
194
+ ## Property sets
195
+
196
+ `propertySet(name, obj, useNames?)` registers a named property object; `useNames` merges listed sets into base.
197
+ `_usePropertySets(obj, name)` (internal) merges a set into `obj`.
198
+
199
+ Use within joiner object building:
200
+ ```js
201
+ ctx.propertySet('base', {role: 'user'});
202
+ ctx.propertySet('admin', {priv: 'all'}, ['base']);
203
+ // Later inside object-building
204
+ ctx.object({}, () => {
205
+ ctx.propSets = ['admin'];
206
+ });
207
+ ```
208
+
209
+ ## Default template rules
210
+
211
+ When no user template matches:
212
+ - JSONPath: objects iterate property names / values; arrays iterate members; functions emit return value; primitives emitted directly; property-names mode concatenates keys.
213
+ - XPath: element nodes traverse children; text nodes emit text; scalars emit value via `valueOf('.')`.
214
+
215
+ ## Joiners
216
+
217
+ ### `StringJoiningTransformer`
218
+ - Builds a string; supports HTML/XML element creation (`element`, `attribute`, `text`). Maintains object/array state for structured emission when in JavaScript/JSON modes.
219
+
220
+ ### `DOMJoiningTransformer`
221
+ - Builds a `DocumentFragment` or `Element` tree; `element`/`attribute` create real nodes; `text` appends text nodes.
222
+
223
+ ### `JSONJoiningTransformer`
224
+ - Builds real JS values; `object`/`array` manage scope; primitives appended directly. `attribute` no-op; `text` no-op; `plainText` maps to string append.
225
+
226
+ Common joiner methods summary:
227
+ - `append(value)`
228
+ - `get()`
229
+ - `object(seed?, cb?, usePropertySets?, propSets?)`
230
+ - `array(seed?, cb?)`
231
+ - `element(name, attrs?, children?, cb?)`
232
+ - `attribute(name, value, avoidEscape?)`
233
+ - `text(str)` vs `string(str, cb?)` vs `plainText(str)`
234
+ - `number()`, `boolean()`, `null()`, `undefined()` (JS mode), `nonfiniteNumber()`, `function(fn)`
235
+
236
+ ## Error handling
237
+
238
+ - Equal priority templates: either last wins (default) or error if `errorOnEqualPriority=true`.
239
+ - Missing `success` callback on `JTLT` transform: throws TypeError.
240
+ - Missing data and ajaxData: throws.
241
+ - XPath v1 without native evaluator: throws "Native XPath unavailable".
242
+ - `propValue` outside object state, `propOnly` misuse, attribute after tag closed: throw.
243
+
244
+ ## Performance notes
245
+
246
+ - Prefer variables and keys for repeated lookups.
247
+ - Sorting with multiple specs incurs multiple value extractions; cache via variables when repeated.
248
+ - JSONPath recursive descent (`..`) more expensive; target precise paths when possible.
249
+
250
+ ## Examples
251
+
252
+ See `README.md` for FLWOR-style and join patterns.
253
+
254
+ ## Roadmap (selected)
255
+
256
+ - Namespace support for XPath.
257
+ - Copy helpers (deep/shallow) parity with XSLT.
258
+ - Streaming / async query execution.
259
+ - Schema-aware template targeting.
260
+
261
+ ## Versioning
262
+
263
+ Experimental features (XPath) are subject to change until stabilized; check changelog before relying in production.
package/docs/API.md ADDED
@@ -0,0 +1,69 @@
1
+ # API (Concise Overview)
2
+
3
+ For the full reference (engines, contexts, joiners, sorting, keys, variables,
4
+ property sets, defaults, roadmap), see `docs/API.expanded.md`.
5
+
6
+ ## Facade: JTLT
7
+
8
+ ```js
9
+ import JTLT from 'jtlt';
10
+ new JTLT({
11
+ data: {title: 'Hi'},
12
+ templates: [
13
+ {path: '$.title', template (v) {
14
+ this.string('<h1>', () => this.text(v));
15
+ this.string('</h1>');
16
+ }}
17
+ ],
18
+ outputType: 'string',
19
+ success: (out) => out
20
+ }).transform('');
21
+ ```
22
+
23
+ ## Engines
24
+
25
+ - JSONPathTransformer: on JSON with JSONPath selectors; resolves
26
+ priority and falls back to defaults when no template matches.
27
+ - XPathTransformer (experimental): on XML/HTML DOM. Choose
28
+ `engineType: 'xpath'` on JTLT and set `xpathVersion: 1 | 2`.
29
+
30
+ ## Context basics
31
+
32
+ `applyTemplates(select?, mode?)`, `forEach(select, cb)`, `valueOf(path?)`,
33
+ `variable(name, select)`, `key(name, match, use)`, `getKey(name, value)`.
34
+ JSONPath variables store values; XPath variables store arrays of nodes.
35
+
36
+ ## Joiners
37
+
38
+ StringJoiningTransformer, DOMJoiningTransformer, JSONJoiningTransformer with
39
+ helpers: `string`, `text`, `element`, `attribute`, `object`, `array`,
40
+ `number`, `boolean`, `plainText`, `append`, `get`.
41
+
42
+ ## Sorting
43
+
44
+ Path string (ascending text), comparator function, object spec
45
+ `{select, type, order}`, or array of specs.
46
+
47
+ ## Keys
48
+
49
+ `key(name, match, use)` builds an index; `getKey(name, value)` returns the
50
+ match or the context sentinel if missing.
51
+
52
+ ## Variables & valueOf
53
+
54
+ `variable(name, select)` caches a selection. `valueOf(path)` returns the first
55
+ match or current node when no path is given.
56
+
57
+ ## Property sets
58
+
59
+ Register with `propertySet(name, obj, extendNames?)`. Inside `object()` set
60
+ `propSets = ['name']` to activate.
61
+
62
+ ## Defaults
63
+
64
+ - JSONPath: objects traverse properties; arrays iterate items; primitives
65
+ emit; functions call and emit return.
66
+ - XPath: elements traverse children; text emits value; scalars use
67
+ `valueOf('.')`.
68
+
69
+ See `API.expanded.md` for details and examples.
@@ -0,0 +1,30 @@
1
+ import ashNazg from 'eslint-config-ash-nazg';
2
+
3
+ export default [
4
+ {
5
+ ignores: [
6
+ 'dist'
7
+ ]
8
+ },
9
+ ...ashNazg(['sauron']),
10
+ {
11
+ rules: {
12
+ // Temporary only:
13
+ 'sonarjs/public-static-readonly': 'off', // Until ash-nazg disables
14
+ 'jsdoc/check-types': 0,
15
+ 'jsdoc/reject-any-type': 0,
16
+ 'jsdoc/reject-function-type': 0,
17
+ 'no-unused-vars': 0,
18
+ 'promise/prefer-await-to-callbacks': 0
19
+ }
20
+ },
21
+ {
22
+ files: ['**/*.md/*.js'],
23
+ rules: {
24
+ 'import/unambiguous': 'off',
25
+ 'import/no-unresolved': 'off',
26
+ 'no-console': 'off',
27
+ 'no-undef': 'off'
28
+ }
29
+ }
30
+ ];
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "jtlt",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "author": "Brett Zamir",
6
+ "contributors": [],
7
+ "license": "MIT",
8
+ "main": "src/index.js",
9
+ "engines": {
10
+ "node": ">=22.16.0"
11
+ },
12
+ "homepage": "https://github.com/s3u/JSONPath/",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/brettz9/jtlt.git"
16
+ },
17
+ "dependencies": {
18
+ "jamilih": "0.61.4",
19
+ "jhtml": "0.7.1",
20
+ "jsdom": "27.1.0",
21
+ "jsonpath-plus": "10.3.0",
22
+ "lodash": "^4.17.21",
23
+ "object-assign": "4.x",
24
+ "simple-get-json": "10.0.0",
25
+ "xpath2.js": "1.0.0-alpha-6"
26
+ },
27
+ "devDependencies": {
28
+ "@types/chai": "^5.2.3",
29
+ "@types/jsdom": "^27.0.0",
30
+ "@types/mocha": "^10.0.10",
31
+ "c8": "^10.1.3",
32
+ "chai": "^6.2.0",
33
+ "eslint": "^9.39.1",
34
+ "eslint-config-ash-nazg": "^39.7.1",
35
+ "mocha": "^11.7.5",
36
+ "typescript": "^5.9.3"
37
+ },
38
+ "bugs": "https://github.com/brettz9/jtlt/issues",
39
+ "description": "Uses an approach similar to [XSLT](https://www.w3.org/Style/XSL/) for declarative, linear declaration of templates, but with JSON or JavaScript object data sources. As with XSLT, can be transformed into different formats (e.g., HTML strings, JSON, DOM objects, etc.).",
40
+ "keywords": [
41
+ "xsl",
42
+ "xslt",
43
+ "json",
44
+ "javascript"
45
+ ],
46
+ "scripts": {
47
+ "tsc": "tsc",
48
+ "build": "tsc -p tsconfig-prod.json",
49
+ "lint": "eslint .",
50
+ "mocha": "mocha",
51
+ "test": "c8 npm run mocha"
52
+ }
53
+ }
@@ -0,0 +1,3 @@
1
+ onlyBuiltDependencies:
2
+ - ejs
3
+ - unrs-resolver
@@ -0,0 +1,73 @@
1
+ // Todo: Allow swapping of joining transformer types in
2
+ // mid-transformation (e.g., building strings with
3
+ // string transformer but adding as text node in a DOM transformer)
4
+
5
+ /**
6
+ * Base class for joining transformers.
7
+ *
8
+ * A "joining transformer" is the sink that receives template outputs and
9
+ * accumulates them into a particular representation (string, DOM, JSON).
10
+ * Subclasses implement a consistent set of methods (string, number, object,
11
+ * array, element, text, etc.) but may interpret them differently according
12
+ * to their target representation.
13
+ *
14
+ * Common patterns supported by all joiners:
15
+ * - append(): central method that either concatenates, pushes, or assigns
16
+ * based on the current state.
17
+ * - get(): returns the accumulated result.
18
+ * - config(): temporarily tweak a config flag for the duration of a callback.
19
+ */
20
+ class AbstractJoiningTransformer {
21
+ /**
22
+ * @param {object} [cfg] - Configuration object
23
+ */
24
+ constructor (cfg) {
25
+ // Todo: Might set some reasonable defaults across all classes
26
+ this.setConfig(cfg);
27
+ }
28
+
29
+ /**
30
+ * @param {any} [cfg] - Configuration object
31
+ * @returns {void}
32
+ */
33
+ setConfig (cfg) {
34
+ this._cfg = cfg;
35
+ }
36
+
37
+ /**
38
+ * @param {string} type - Type name
39
+ * @param {string} embedType - Embed type name
40
+ * @returns {void}
41
+ */
42
+ _requireSameChildren (type, embedType) {
43
+ if (this._cfg && /** @type {any} */ (this._cfg)[type] &&
44
+ /** @type {any} */ (this._cfg)[type].requireSameChildren) {
45
+ throw new Error(
46
+ 'Cannot embed ' + embedType + ' children for a ' + type +
47
+ ' joining transformer.'
48
+ );
49
+ }
50
+ }
51
+
52
+ /**
53
+ * @param {string} prop - Configuration property name
54
+ * @param {*} val - Configuration property value
55
+ * @param {Function} [cb] - Callback function
56
+ * @returns {void}
57
+ */
58
+ config (prop, val, cb) {
59
+ const oldCfgProp = this._cfg &&
60
+ /** @type {any} */ (this._cfg)[prop];
61
+ if (this._cfg) {
62
+ /** @type {any} */ (this._cfg)[prop] = val;
63
+ }
64
+ if (cb) {
65
+ cb.call(this);
66
+ if (this._cfg) {
67
+ /** @type {any} */ (this._cfg)[prop] = oldCfgProp;
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ export default AbstractJoiningTransformer;