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.
- package/.editorconfig +16 -0
- package/CHANGES.md +5 -0
- package/LICENSE-MIT.txt +21 -0
- package/README.md +534 -0
- package/dist/AbstractJoiningTransformer.d.ts +42 -0
- package/dist/AbstractJoiningTransformer.d.ts.map +1 -0
- package/dist/DOMJoiningTransformer.d.ts +113 -0
- package/dist/DOMJoiningTransformer.d.ts.map +1 -0
- package/dist/JSONJoiningTransformer.d.ts +160 -0
- package/dist/JSONJoiningTransformer.d.ts.map +1 -0
- package/dist/JSONPathTransformer.d.ts +95 -0
- package/dist/JSONPathTransformer.d.ts.map +1 -0
- package/dist/JSONPathTransformerContext.d.ts +263 -0
- package/dist/JSONPathTransformerContext.d.ts.map +1 -0
- package/dist/StringJoiningTransformer.d.ts +168 -0
- package/dist/StringJoiningTransformer.d.ts.map +1 -0
- package/dist/XPathTransformer.d.ts +51 -0
- package/dist/XPathTransformer.d.ts.map +1 -0
- package/dist/XPathTransformerContext.d.ts +260 -0
- package/dist/XPathTransformerContext.d.ts.map +1 -0
- package/dist/XSLTStyleJSONPathResolver.d.ts +16 -0
- package/dist/XSLTStyleJSONPathResolver.d.ts.map +1 -0
- package/dist/index.d.ts +168 -0
- package/dist/index.d.ts.map +1 -0
- package/docs/API.expanded.md +263 -0
- package/docs/API.md +69 -0
- package/eslint.config.js +30 -0
- package/package.json +53 -0
- package/pnpm-workspace.yaml +3 -0
- package/src/AbstractJoiningTransformer.js +73 -0
- package/src/DOMJoiningTransformer.js +237 -0
- package/src/JSONJoiningTransformer.js +472 -0
- package/src/JSONPathTransformer.js +159 -0
- package/src/JSONPathTransformerContext.js +807 -0
- package/src/StringJoiningTransformer.js +589 -0
- package/src/XPathTransformer.js +94 -0
- package/src/XPathTransformerContext.js +496 -0
- package/src/XSLTStyleJSONPathResolver.js +39 -0
- package/src/index.js +299 -0
- package/src/types/xpath2-js.d.ts +2 -0
- package/tsconfig-prod.json +19 -0
- package/tsconfig.json +14 -0
- package/typings/xpath2-js.d.ts +2 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import * as JHTML from 'jhtml';
|
|
2
|
+
import AbstractJoiningTransformer from './AbstractJoiningTransformer.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Joining transformer that accumulates into a DOM tree.
|
|
6
|
+
*
|
|
7
|
+
* This transformer appends strings and nodes to a DocumentFragment/Element.
|
|
8
|
+
* It expects templates to build DOM nodes explicitly (e.g., via element(),
|
|
9
|
+
* attribute(), and text()), though string/number/boolean will append text
|
|
10
|
+
* nodes for convenience.
|
|
11
|
+
*/
|
|
12
|
+
class DOMJoiningTransformer extends AbstractJoiningTransformer {
|
|
13
|
+
/**
|
|
14
|
+
* @param {DocumentFragment|Element} o - Initial DOM node
|
|
15
|
+
* @param {object} cfg - Configuration object
|
|
16
|
+
* @param {object} [cfg.document] - Document object
|
|
17
|
+
*/
|
|
18
|
+
constructor (o, cfg) {
|
|
19
|
+
super(cfg); // Include this in any subclass of AbstractJoiningTransformer
|
|
20
|
+
this._dom = o ||
|
|
21
|
+
/** @type {any} */ (cfg).document.createDocumentFragment();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {Node} item
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
rawAppend (item) {
|
|
29
|
+
this._dom.append(item);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string|Node} item - Item to append
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
append (item) {
|
|
37
|
+
this._dom.append(item);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @returns {DocumentFragment|Element}
|
|
42
|
+
*/
|
|
43
|
+
get () {
|
|
44
|
+
return this._dom;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} prop - Property name
|
|
49
|
+
* @param {*} val - Property value
|
|
50
|
+
* @returns {void}
|
|
51
|
+
*/
|
|
52
|
+
// eslint-disable-next-line class-methods-use-this -- Incomplete?
|
|
53
|
+
propValue (prop, val) {
|
|
54
|
+
//
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {object} obj - Object to serialize
|
|
59
|
+
* @param {Function} [cb] - Callback function.
|
|
60
|
+
* @param {any[]} [usePropertySets] - Property sets to use
|
|
61
|
+
* @param {object} [propSets] - Additional property sets
|
|
62
|
+
* @returns {DOMJoiningTransformer}
|
|
63
|
+
*/
|
|
64
|
+
object (obj, cb, usePropertySets, propSets) {
|
|
65
|
+
this._requireSameChildren('dom', 'object');
|
|
66
|
+
if (this._cfg && /** @type {any} */ (this._cfg).JHTMLForJSON) {
|
|
67
|
+
this.append(JHTML.toJHTMLDOM(/** @type {any} */ (obj)));
|
|
68
|
+
} else {
|
|
69
|
+
// Todo: set current position and deal with children
|
|
70
|
+
this.append('');
|
|
71
|
+
}
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {any[]|Element} arr
|
|
77
|
+
* @param {Function} [cb] - Callback function
|
|
78
|
+
* @returns {DOMJoiningTransformer}
|
|
79
|
+
*/
|
|
80
|
+
array (arr, cb) {
|
|
81
|
+
this._requireSameChildren('dom', 'array');
|
|
82
|
+
if (this._cfg && /** @type {any} */ (this._cfg).JHTMLForJSON) {
|
|
83
|
+
this.append(JHTML.toJHTMLDOM(/** @type {any} */ (arr)));
|
|
84
|
+
} else {
|
|
85
|
+
// Todo: set current position and deal with children
|
|
86
|
+
this.append('');
|
|
87
|
+
}
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} str - String value
|
|
93
|
+
* @param {Function} cb - Callback function (unused)
|
|
94
|
+
* @returns {DOMJoiningTransformer}
|
|
95
|
+
*/
|
|
96
|
+
string (str, cb) {
|
|
97
|
+
// Todo: Conditionally add as JHTML (and in subsequent methods as well)
|
|
98
|
+
this.append(str);
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {number} num - Number value
|
|
104
|
+
* @returns {DOMJoiningTransformer}
|
|
105
|
+
*/
|
|
106
|
+
number (num) {
|
|
107
|
+
this.append(num.toString());
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {boolean} bool
|
|
113
|
+
* @returns {DOMJoiningTransformer}
|
|
114
|
+
*/
|
|
115
|
+
boolean (bool) {
|
|
116
|
+
this.append(bool ? 'true' : 'false');
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @returns {DOMJoiningTransformer}
|
|
122
|
+
*/
|
|
123
|
+
null () {
|
|
124
|
+
this.append('null');
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @returns {DOMJoiningTransformer}
|
|
130
|
+
*/
|
|
131
|
+
undefined () {
|
|
132
|
+
if (this._cfg && /** @type {any} */ (this._cfg).mode !== 'JavaScript') {
|
|
133
|
+
throw new Error(
|
|
134
|
+
'undefined is not allowed unless added in JavaScript mode'
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
this.append('undefined');
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {number} num - Non-finite number (NaN, Infinity, -Infinity)
|
|
143
|
+
* @returns {DOMJoiningTransformer}
|
|
144
|
+
*/
|
|
145
|
+
nonfiniteNumber (num) {
|
|
146
|
+
if (this._cfg && /** @type {any} */ (this._cfg).mode !== 'JavaScript') {
|
|
147
|
+
throw new Error(
|
|
148
|
+
'Non-finite numbers are not allowed unless added in JavaScript mode'
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
this.append(num.toString());
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {Function} func - Function to stringify
|
|
157
|
+
* @returns {DOMJoiningTransformer}
|
|
158
|
+
*/
|
|
159
|
+
function (func) {
|
|
160
|
+
if (this._cfg && /** @type {any} */ (this._cfg).mode !== 'JavaScript') {
|
|
161
|
+
throw new Error(
|
|
162
|
+
'function is not allowed unless added in JavaScript mode'
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
this.append(func.toString());
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @param {string} elName - Element name
|
|
171
|
+
* @param {object} [atts] - Attributes object
|
|
172
|
+
* @param {Function} [cb] - Callback function
|
|
173
|
+
* @returns {DOMJoiningTransformer}
|
|
174
|
+
*/
|
|
175
|
+
element (elName, atts, cb) {
|
|
176
|
+
// Todo: allow third argument to be array following Jamilih (also let
|
|
177
|
+
// "atts" follow Jamilih)
|
|
178
|
+
// Todo: allow for cfg to produce Jamilih DOM output or hXML
|
|
179
|
+
// Todo: allow separate XML DOM one with XML String and hXML conversions
|
|
180
|
+
// (HTML to XHTML is inevitably safe?)
|
|
181
|
+
|
|
182
|
+
const el = this._cfg &&
|
|
183
|
+
/** @type {any} */ (this._cfg).document.createElement(elName);
|
|
184
|
+
for (const att in atts) {
|
|
185
|
+
if (Object.hasOwn(atts, att)) {
|
|
186
|
+
/** @type {Record<string, any>} */
|
|
187
|
+
const attsObj = /** @type {any} */ (atts);
|
|
188
|
+
el.setAttribute(att, attsObj[att]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
this.append(el);
|
|
192
|
+
|
|
193
|
+
const oldDOM = this._dom;
|
|
194
|
+
|
|
195
|
+
this._dom = el;
|
|
196
|
+
if (cb) {
|
|
197
|
+
cb.call(this);
|
|
198
|
+
}
|
|
199
|
+
this._dom = oldDOM;
|
|
200
|
+
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @param {string} name
|
|
206
|
+
* @param {string} val
|
|
207
|
+
* @returns {DOMJoiningTransformer}
|
|
208
|
+
*/
|
|
209
|
+
attribute (name, val) {
|
|
210
|
+
if (!this._dom || typeof this._dom !== 'object' ||
|
|
211
|
+
this._dom.nodeType !== 1) {
|
|
212
|
+
throw new Error('You may only set an attribute on an element');
|
|
213
|
+
}
|
|
214
|
+
(/** @type {Element} */ (this._dom)).setAttribute(name, val);
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @param {string} txt - Text content
|
|
220
|
+
* @returns {DOMJoiningTransformer}
|
|
221
|
+
*/
|
|
222
|
+
text (txt) {
|
|
223
|
+
this.append(txt);
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @param {string} str
|
|
229
|
+
* @returns {DOMJoiningTransformer}
|
|
230
|
+
*/
|
|
231
|
+
plainText (str) {
|
|
232
|
+
this.text(str);
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export default DOMJoiningTransformer;
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import AbstractJoiningTransformer from './AbstractJoiningTransformer.js';
|
|
2
|
+
|
|
3
|
+
// Regex and helper for converting dataset camelCase to dash-lower
|
|
4
|
+
const camelCase = /[a-z][A-Z]/gv;
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} n0
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
function _makeDatasetAttribute (n0) {
|
|
10
|
+
return n0.charAt(0) + '-' + n0.charAt(1).toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* JSON-based joining transformer for building JSON/JavaScript objects.
|
|
15
|
+
*
|
|
16
|
+
* This joiner accumulates into an in-memory JSON value (object or array).
|
|
17
|
+
* append() will push to arrays or shallow-merge into objects; string/number/
|
|
18
|
+
* boolean/null add primitives accordingly. It does not perform HTML escaping
|
|
19
|
+
* or string serialization; it builds real JS values.
|
|
20
|
+
*/
|
|
21
|
+
class JSONJoiningTransformer extends AbstractJoiningTransformer {
|
|
22
|
+
/**
|
|
23
|
+
* @param {any[]|object} [o] - Initial object or array
|
|
24
|
+
* @param {object} [cfg] - Configuration object
|
|
25
|
+
*/
|
|
26
|
+
constructor (o, cfg) {
|
|
27
|
+
super(cfg);
|
|
28
|
+
/** @type {any[]|object} */
|
|
29
|
+
this._obj = o || [];
|
|
30
|
+
/** @type {boolean | undefined} */
|
|
31
|
+
this._objPropState = undefined;
|
|
32
|
+
/** @type {boolean | undefined} */
|
|
33
|
+
this._arrItemState = undefined;
|
|
34
|
+
/** @type {{attsObj: Record<string, any>, jmlChildren: any[]}[]} */
|
|
35
|
+
this._elementStack = [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Directly appends an item to the internal array without checks.
|
|
40
|
+
* @param {*} item - Item to append
|
|
41
|
+
* @returns {void}
|
|
42
|
+
*/
|
|
43
|
+
rawAppend (item) {
|
|
44
|
+
/** @type {any[]} */ (this._obj).push(item);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Appends an item to the current object or array.
|
|
49
|
+
* @param {*} item - Item to append
|
|
50
|
+
* @returns {JSONJoiningTransformer}
|
|
51
|
+
*/
|
|
52
|
+
append (item) {
|
|
53
|
+
// Todo: allow for first time
|
|
54
|
+
if (!this._obj || typeof this._obj !== 'object') {
|
|
55
|
+
throw new Error('You cannot append to a scalar or empty value.');
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(this._obj)) {
|
|
58
|
+
this._obj.push(item);
|
|
59
|
+
} else {
|
|
60
|
+
Object.assign(this._obj, item);
|
|
61
|
+
}
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Gets the current object or array. If unwrapSingleResult config option is
|
|
67
|
+
* enabled and the root array contains exactly one element, returns that
|
|
68
|
+
* element directly (unwrapped).
|
|
69
|
+
* @returns {any[]|object|any}
|
|
70
|
+
*/
|
|
71
|
+
get () {
|
|
72
|
+
// Unwrap single-element arrays at the root level if configured
|
|
73
|
+
if (this._cfg && /** @type {any} */ (this._cfg).unwrapSingleResult &&
|
|
74
|
+
Array.isArray(this._obj) && this._obj.length === 1) {
|
|
75
|
+
return this._obj[0];
|
|
76
|
+
}
|
|
77
|
+
return this._obj;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Sets a property value on the current object.
|
|
82
|
+
* @param {string} prop - Property name
|
|
83
|
+
* @param {*} val - Property value
|
|
84
|
+
* @returns {void}
|
|
85
|
+
*/
|
|
86
|
+
propValue (prop, val) {
|
|
87
|
+
if (!this._objPropState) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
'propValue() can only be called after an object state has been set up.'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
(/** @type {Record<string, any>} */ (this._obj))[prop] = val;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* c8 ignore next 13 -- JSDoc block incorrectly counted as coverable by c8 */
|
|
96
|
+
/**
|
|
97
|
+
* @param {object|Function} [objOrCb] - Seed object to start with, or
|
|
98
|
+
* callback if no seed provided
|
|
99
|
+
* @param {Function|any[]} [cbOrUsePropertySets] - Callback to be executed
|
|
100
|
+
* on this transformer but with a context nested within the newly created
|
|
101
|
+
* object, or array of property set names if first arg was an object
|
|
102
|
+
* @param {any[]|object} [usePropertySetsOrPropSets] - Array of string
|
|
103
|
+
* property set names to copy onto the new object, or propSets if second
|
|
104
|
+
* arg was a callback
|
|
105
|
+
* @param {object} [propSets] - An object of key-value pairs to copy onto
|
|
106
|
+
* the new object
|
|
107
|
+
* @returns {JSONJoiningTransformer}
|
|
108
|
+
*/
|
|
109
|
+
object (objOrCb, cbOrUsePropertySets, usePropertySetsOrPropSets, propSets) {
|
|
110
|
+
// eslint-disable-next-line unicorn/no-this-assignment -- Temporary
|
|
111
|
+
const that = this;
|
|
112
|
+
// Todo: Conditionally add as JHTML-based jml (and in subsequent methods
|
|
113
|
+
// as well)
|
|
114
|
+
const tempObj = this._obj;
|
|
115
|
+
|
|
116
|
+
// Determine if first arg is a seed object or callback
|
|
117
|
+
let obj;
|
|
118
|
+
let cb;
|
|
119
|
+
let usePropertySets;
|
|
120
|
+
let propSetsToUse;
|
|
121
|
+
|
|
122
|
+
if (typeof objOrCb === 'function') {
|
|
123
|
+
// No seed object: object(cb, usePropertySets, propSets)
|
|
124
|
+
obj = {};
|
|
125
|
+
cb = objOrCb;
|
|
126
|
+
usePropertySets = /** @type {any[]} */ (cbOrUsePropertySets);
|
|
127
|
+
propSetsToUse = /** @type {object} */ (usePropertySetsOrPropSets);
|
|
128
|
+
} else {
|
|
129
|
+
// Seed object provided: object(obj, cb, usePropertySets, propSets)
|
|
130
|
+
// Clone seed object to avoid mutating the original
|
|
131
|
+
obj = objOrCb ? {...objOrCb} : {};
|
|
132
|
+
cb = /** @type {Function} */ (cbOrUsePropertySets);
|
|
133
|
+
usePropertySets = /** @type {any[]} */ (usePropertySetsOrPropSets);
|
|
134
|
+
propSetsToUse = propSets;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (usePropertySets !== undefined) {
|
|
138
|
+
const merged = usePropertySets.reduce((o, psName) => {
|
|
139
|
+
return that._usePropertySets(o, psName); // Todo: Put in right scope
|
|
140
|
+
}, {});
|
|
141
|
+
Object.assign(obj, merged);
|
|
142
|
+
}
|
|
143
|
+
if (propSetsToUse !== undefined) {
|
|
144
|
+
Object.assign(obj, propSetsToUse);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** @type {any} */
|
|
148
|
+
const oldObjPropState = this._objPropState;
|
|
149
|
+
this._objPropState = true;
|
|
150
|
+
this._obj = obj; // Set current object so propValue() works
|
|
151
|
+
// We pass the object, but user should usually use other methods
|
|
152
|
+
if (cb) {
|
|
153
|
+
cb.call(this, obj);
|
|
154
|
+
}
|
|
155
|
+
// Append after callback so object has all properties, but before
|
|
156
|
+
// restoring tempObj
|
|
157
|
+
this._obj = tempObj;
|
|
158
|
+
this.append(obj);
|
|
159
|
+
this._objPropState = oldObjPropState;
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Creates a new array and executes a callback in its context.
|
|
165
|
+
* @param {any[]|Function} [arrOrCb] - Seed array to start with, or callback
|
|
166
|
+
* if no seed provided
|
|
167
|
+
* @param {Function} [cb] - Callback function (if first arg was a seed array)
|
|
168
|
+
* @returns {JSONJoiningTransformer}
|
|
169
|
+
*/
|
|
170
|
+
array (arrOrCb, cb) {
|
|
171
|
+
const tempObj = this._obj;
|
|
172
|
+
|
|
173
|
+
// Determine if first arg is a seed array or callback
|
|
174
|
+
/** @type {any[]} */
|
|
175
|
+
let arr;
|
|
176
|
+
let callback;
|
|
177
|
+
|
|
178
|
+
if (typeof arrOrCb === 'function') {
|
|
179
|
+
// No seed array: array(cb)
|
|
180
|
+
arr = [];
|
|
181
|
+
callback = arrOrCb;
|
|
182
|
+
} else {
|
|
183
|
+
// Seed array provided: array(arr, cb)
|
|
184
|
+
// Clone seed array to avoid mutating the original
|
|
185
|
+
arr = arrOrCb ? [...arrOrCb] : [];
|
|
186
|
+
callback = cb;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this._obj = arr; // Set current array so append() works
|
|
190
|
+
// We pass the array, but user should usually use other methods
|
|
191
|
+
if (callback) {
|
|
192
|
+
callback.call(this, arr);
|
|
193
|
+
}
|
|
194
|
+
// Append after callback so array has all items, but before
|
|
195
|
+
// restoring tempObj
|
|
196
|
+
this._obj = tempObj;
|
|
197
|
+
this.append(arr); // Todo: set current position and deal with children
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Appends a string value.
|
|
203
|
+
* @param {string} str - String value
|
|
204
|
+
* @param {Function} [cb] - Callback function (unused)
|
|
205
|
+
* @returns {JSONJoiningTransformer}
|
|
206
|
+
*/
|
|
207
|
+
string (str, cb) {
|
|
208
|
+
this._requireSameChildren('json', 'string');
|
|
209
|
+
this.append(str);
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Appends a number value.
|
|
215
|
+
* @param {number} num - Number value
|
|
216
|
+
* @returns {JSONJoiningTransformer}
|
|
217
|
+
*/
|
|
218
|
+
number (num) {
|
|
219
|
+
this.append(num);
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Appends a boolean value.
|
|
225
|
+
* @param {boolean} bool - Boolean value
|
|
226
|
+
* @returns {JSONJoiningTransformer}
|
|
227
|
+
*/
|
|
228
|
+
boolean (bool) {
|
|
229
|
+
this.append(bool);
|
|
230
|
+
return this;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Appends a null value.
|
|
235
|
+
* @returns {JSONJoiningTransformer}
|
|
236
|
+
*/
|
|
237
|
+
null () {
|
|
238
|
+
this.append(null);
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Appends an undefined value (JavaScript mode only).
|
|
244
|
+
* @returns {JSONJoiningTransformer}
|
|
245
|
+
*/
|
|
246
|
+
undefined () {
|
|
247
|
+
if (this._cfg && /** @type {any} */ (this._cfg).mode !== 'JavaScript') {
|
|
248
|
+
throw new Error(
|
|
249
|
+
'undefined is not allowed unless added in JavaScript mode'
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
this.append(undefined);
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Appends a non-finite number (JavaScript mode only).
|
|
258
|
+
* @param {number} num - Non-finite number (NaN, Infinity, -Infinity)
|
|
259
|
+
* @returns {JSONJoiningTransformer}
|
|
260
|
+
*/
|
|
261
|
+
nonfiniteNumber (num) {
|
|
262
|
+
if (this._cfg && /** @type {any} */ (this._cfg).mode !== 'JavaScript') {
|
|
263
|
+
throw new Error(
|
|
264
|
+
'Non-finite numbers are not allowed unless added in JavaScript mode'
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
this.append(num);
|
|
268
|
+
return this;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Appends a function value (JavaScript mode only).
|
|
273
|
+
* @param {Function} func - Function to append
|
|
274
|
+
* @returns {JSONJoiningTransformer}
|
|
275
|
+
*/
|
|
276
|
+
function (func) {
|
|
277
|
+
if (this._cfg && /** @type {any} */ (this._cfg).mode !== 'JavaScript') {
|
|
278
|
+
throw new Error(
|
|
279
|
+
'function is not allowed unless added in JavaScript mode'
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
this.append(func);
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Build a Jamilih-style element JSON array and append to current container.
|
|
288
|
+
* Result form: ['tag', {attr: 'val'}, child1, child2, ...]
|
|
289
|
+
* Helpers: dataset -> data-*; $a -> ordered attributes.
|
|
290
|
+
* Supported signatures mirror StringJoiningTransformer.element.
|
|
291
|
+
* @param {string|Element|object} elName - Element name or Element-like
|
|
292
|
+
* @param {object|any[]|Function} [atts] - Attributes object or children or cb
|
|
293
|
+
* @param {any[]|Function} [childNodes] - Child nodes array or callback
|
|
294
|
+
* @param {Function} [cb] - Callback for building children/attributes
|
|
295
|
+
* @returns {JSONJoiningTransformer}
|
|
296
|
+
*/
|
|
297
|
+
element (elName, atts, childNodes, cb) {
|
|
298
|
+
this._requireSameChildren('json', 'element');
|
|
299
|
+
// Normalize arguments similarly to StringJoiningTransformer.element
|
|
300
|
+
if (Array.isArray(atts)) {
|
|
301
|
+
cb = /** @type {Function} */ (childNodes);
|
|
302
|
+
childNodes = atts;
|
|
303
|
+
atts = {};
|
|
304
|
+
} else if (typeof atts === 'function') {
|
|
305
|
+
cb = /** @type {Function} */ (atts);
|
|
306
|
+
childNodes = [];
|
|
307
|
+
atts = {};
|
|
308
|
+
}
|
|
309
|
+
if (typeof childNodes === 'function') {
|
|
310
|
+
cb = /** @type {Function} */ (childNodes);
|
|
311
|
+
childNodes = [];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Element-like object (DOM Element) -> extract attributes
|
|
315
|
+
if (typeof elName === 'object' && elName && 'attributes' in elName) {
|
|
316
|
+
/** @type {Record<string, any>} */
|
|
317
|
+
const objAtts = {};
|
|
318
|
+
// @ts-ignore - treat elName as Element-like
|
|
319
|
+
[...elName.attributes].forEach((att) => {
|
|
320
|
+
objAtts[att.name] = att.value;
|
|
321
|
+
});
|
|
322
|
+
atts = Object.assign(objAtts, atts);
|
|
323
|
+
// @ts-ignore
|
|
324
|
+
elName = /** @type {any} */ (elName).nodeName;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** @type {Record<string, any>} */
|
|
328
|
+
let attsObj = /** @type {any} */ (atts) || {};
|
|
329
|
+
/** @type {any[]} */
|
|
330
|
+
const jmlChildren = [];
|
|
331
|
+
|
|
332
|
+
// Preprocess special attribute helpers present directly on attsObj
|
|
333
|
+
if (attsObj.dataset && typeof attsObj.dataset === 'object' &&
|
|
334
|
+
!Array.isArray(attsObj.dataset)
|
|
335
|
+
) {
|
|
336
|
+
const ds = attsObj.dataset;
|
|
337
|
+
for (const k in ds) {
|
|
338
|
+
if (Object.hasOwn(ds, k)) {
|
|
339
|
+
const dashed = k.replaceAll(camelCase, _makeDatasetAttribute);
|
|
340
|
+
attsObj['data-' + dashed] = ds[k];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
delete attsObj.dataset;
|
|
344
|
+
}
|
|
345
|
+
if (Array.isArray(attsObj.$a)) {
|
|
346
|
+
attsObj.$a.forEach((pair) => {
|
|
347
|
+
if (Array.isArray(pair) && pair.length > 1) {
|
|
348
|
+
attsObj[pair[0]] = pair[1];
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
delete attsObj.$a;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// If children provided as array, copy (may be primitives or nested JML)
|
|
355
|
+
if (Array.isArray(childNodes) && childNodes.length) {
|
|
356
|
+
jmlChildren.push(...childNodes);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Callback-driven building (attribute/text/nested element mutate stack)
|
|
360
|
+
if (cb) {
|
|
361
|
+
// Push current state onto a stack
|
|
362
|
+
this._elementStack.push({attsObj, jmlChildren});
|
|
363
|
+
cb.call(this);
|
|
364
|
+
const state = /** @type {any} */ (this._elementStack.pop());
|
|
365
|
+
({attsObj} = state);
|
|
366
|
+
// Children may have been mutated by nested element()/text();
|
|
367
|
+
// already in jmlChildren
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Build Jamilih array
|
|
371
|
+
/** @type {any[]} */
|
|
372
|
+
const jmlEl = [elName];
|
|
373
|
+
if (Object.keys(attsObj).length) {
|
|
374
|
+
jmlEl.push(attsObj);
|
|
375
|
+
}
|
|
376
|
+
jmlEl.push(...jmlChildren);
|
|
377
|
+
|
|
378
|
+
// If inside a parent element, append as its child; otherwise append to root
|
|
379
|
+
if (this._elementStack.length) {
|
|
380
|
+
const top = /** @type {any} */ (this._elementStack.at(-1));
|
|
381
|
+
top.jmlChildren.push(jmlEl);
|
|
382
|
+
} else {
|
|
383
|
+
this.append(jmlEl);
|
|
384
|
+
}
|
|
385
|
+
return this;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Adds/updates an attribute for the most recently open element built via
|
|
390
|
+
* a callback-driven element(). When not in an element callback context,
|
|
391
|
+
* throws. Supports the same dataset/$a helpers as string joiner.
|
|
392
|
+
* @param {string} name - Attribute name (or helper: dataset, $a)
|
|
393
|
+
* @param {string|object|any[]} val - Attribute value or helper object
|
|
394
|
+
* @returns {JSONJoiningTransformer}
|
|
395
|
+
*/
|
|
396
|
+
attribute (name, val) {
|
|
397
|
+
if (!this._elementStack.length) {
|
|
398
|
+
// No-op outside an element() callback (JSON joiner semantics)
|
|
399
|
+
return this;
|
|
400
|
+
}
|
|
401
|
+
const top = /** @type {any} */ (this._elementStack.at(-1));
|
|
402
|
+
const {attsObj} = top;
|
|
403
|
+
if (name === 'dataset' && val && typeof val === 'object' &&
|
|
404
|
+
!Array.isArray(val)
|
|
405
|
+
) {
|
|
406
|
+
for (const k in val) {
|
|
407
|
+
if (Object.hasOwn(val, k)) {
|
|
408
|
+
const dashed = k.replaceAll(camelCase, _makeDatasetAttribute);
|
|
409
|
+
attsObj['data-' + dashed] = (/** @type {any} */ (val))[k];
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return this;
|
|
413
|
+
}
|
|
414
|
+
if (name === '$a' && Array.isArray(val)) {
|
|
415
|
+
val.forEach((pair) => {
|
|
416
|
+
if (Array.isArray(pair) && pair.length > 1) {
|
|
417
|
+
attsObj[pair[0]] = pair[1];
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
return this;
|
|
421
|
+
}
|
|
422
|
+
attsObj[name] = val;
|
|
423
|
+
return this;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Adds a text node (string) as a child within the current element() callback
|
|
428
|
+
* context. Outside of an element callback, simply appends the text to the
|
|
429
|
+
* current array/object like string().
|
|
430
|
+
* @param {string} txt - Text content
|
|
431
|
+
* @returns {JSONJoiningTransformer}
|
|
432
|
+
*/
|
|
433
|
+
text (txt) {
|
|
434
|
+
if (this._elementStack.length) {
|
|
435
|
+
const top = /** @type {any} */ (this._elementStack.at(-1));
|
|
436
|
+
const {jmlChildren} = top;
|
|
437
|
+
jmlChildren.push(txt);
|
|
438
|
+
return this;
|
|
439
|
+
}
|
|
440
|
+
// No-op outside element context in JSON joiner
|
|
441
|
+
return this;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Appends plain text as a string.
|
|
446
|
+
* @param {string} str - Plain text string
|
|
447
|
+
* @returns {JSONJoiningTransformer}
|
|
448
|
+
*/
|
|
449
|
+
plainText (str) {
|
|
450
|
+
this.string(str);
|
|
451
|
+
return this;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Helper method to use property sets (to be implemented).
|
|
456
|
+
* @param {object} obj - Object to apply property set to
|
|
457
|
+
* @param {string} psName - Property set name
|
|
458
|
+
* @returns {object}
|
|
459
|
+
*/
|
|
460
|
+
_usePropertySets (obj, psName) {
|
|
461
|
+
// Merge the named property set (if present) into the provided object
|
|
462
|
+
if (this && /** @type {any} */ (this).propertySets &&
|
|
463
|
+
/** @type {any} */ (this).propertySets[psName]
|
|
464
|
+
) {
|
|
465
|
+
return Object.assign(obj, /** @type {any} */ (this).propertySets[psName]);
|
|
466
|
+
}
|
|
467
|
+
return obj;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export default JSONJoiningTransformer;
|
|
472
|
+
|