jtlt 0.2.0 → 0.4.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 +27 -0
- package/README.md +61 -164
- package/demo/calltemplate-params-demo.js +138 -0
- package/demo/codemirror.esm.js +28242 -0
- package/demo/codemirror.js +94 -0
- package/demo/index.css +7 -0
- package/demo/index.html +28 -0
- package/demo/index.js +210 -0
- package/demo/vendor/jamilih/dist/jml.mjs +2341 -0
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.ObjectArrayDelegator.js +356 -0
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.Stringifier.js +186 -0
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.js +746 -0
- package/demo/vendor/jhtml/src/SAJJ/testing/SAJJ.html +33 -0
- package/demo/vendor/jhtml/src/SAJJ/testing/SAJJ.testing.js +25 -0
- package/demo/vendor/jhtml/src/jhtml-browser.js +5 -0
- package/demo/vendor/jhtml/src/jhtml-node.cts +3 -0
- package/demo/vendor/jhtml/src/jhtml-node.js +8 -0
- package/demo/vendor/jhtml/src/jhtml-node.mts +1 -0
- package/demo/vendor/jhtml/src/jhtml.cts +3 -0
- package/demo/vendor/jhtml/src/jhtml.js +602 -0
- package/demo/vendor/jhtml/src/jhtml.mts +1 -0
- package/demo/vendor/jsonpath-plus/dist/index-browser-esm.js +2158 -0
- package/demo/vendor/simple-get-json/dist/index-es.js +151 -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 +334 -124
- 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 +281 -105
- 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 +172 -5
- package/docs/API.md +92 -2
- package/docs/TO-DO.md +168 -0
- package/docs/calltemplate-params.md +251 -0
- package/eslint.config.js +11 -5
- package/package.json +35 -9
- package/pnpm-workspace.yaml +1 -0
- package/rollup.config.js +13 -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 +729 -107
- package/src/StringJoiningTransformer.js +311 -57
- package/src/XPathTransformer.js +27 -12
- package/src/XPathTransformerContext.js +928 -99
- package/src/index-browser.js +5 -0
- package/src/index-node.js +7 -0
- package/src/index.js +502 -98
- package/tsconfig.json +5 -2
- package/typings/xpath2-js.d.ts +40 -1
- package/src/types/xpath2-js.d.ts +0 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>SAJJ test</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<script type="module">
|
|
9
|
+
import Stringifier from '../SAJJ.Stringifier.js';
|
|
10
|
+
import {write, validateAndWrite} from './SAJJ.testing.js';
|
|
11
|
+
const sf = new Stringifier(),
|
|
12
|
+
sfJS = new Stringifier({mode: 'JavaScript'});
|
|
13
|
+
|
|
14
|
+
validateAndWrite(sf.walkJSONObject(null));
|
|
15
|
+
validateAndWrite(sf.walkJSONObject(true));
|
|
16
|
+
validateAndWrite(sf.walkJSONObject(4e5));
|
|
17
|
+
validateAndWrite(sf.walkJSONObject('abc'));
|
|
18
|
+
validateAndWrite(sf.walkJSONObject(['childItem', 'childWith"Dbl"Quotes']));
|
|
19
|
+
|
|
20
|
+
validateAndWrite(
|
|
21
|
+
sf.walkJSONObject({a: null, b: true, c: 3, d: ['childItem', 'childWith"Dbl"Quotes'], e: {nested: true}, f: Infinity})
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
write('');
|
|
25
|
+
write('Now try validating JavaScript as JSON (should get an error), but we can print it out:');
|
|
26
|
+
|
|
27
|
+
validateAndWrite(
|
|
28
|
+
sfJS.walkJSONObject({a: null, b: true, c: 3, d: ['childItem', 'childWith"Dbl"Quotes'], e: {nested: true}, f: Infinity, g: undefined, h () {}})
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
</script>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} txt
|
|
3
|
+
*/
|
|
4
|
+
export function write (txt) {
|
|
5
|
+
document.body.append(txt, document.createElement('br'));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} txt
|
|
10
|
+
*/
|
|
11
|
+
export function validate (txt) {
|
|
12
|
+
try {
|
|
13
|
+
JSON.parse(txt);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
write('error validating JSON:' + e);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} txt
|
|
21
|
+
*/
|
|
22
|
+
export function validateAndWrite (txt) {
|
|
23
|
+
validate(txt);
|
|
24
|
+
write(txt);
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './jhtml-node.js';
|
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JHTML is a format which can represent arbitrary JSON structures in a
|
|
3
|
+
* faithful, human-readable, and portable manner.
|
|
4
|
+
* It is also round-trippable except in the case when converting *from*
|
|
5
|
+
* object-containing JSON to JHTML when the
|
|
6
|
+
* ECMAScript/JSON interpreter does not iterate the properties in
|
|
7
|
+
* definition order (as it is not required to do).
|
|
8
|
+
* @namespace Contains methods for conversions between JSON and
|
|
9
|
+
* JHTML (as strings or objects)
|
|
10
|
+
* @todo Add polyfills, e.g., https://github.com/termi/Microdata-JS/
|
|
11
|
+
* @todo Add option for stringification (JSON or JHTML) to provide
|
|
12
|
+
* indentation, etc.
|
|
13
|
+
* @todo Make SAJJ as separate repo and require
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import ObjectArrayDelegator from './SAJJ/SAJJ.ObjectArrayDelegator.js';
|
|
17
|
+
import Stringifier from './SAJJ/SAJJ.Stringifier.js';
|
|
18
|
+
|
|
19
|
+
/* eslint-disable jsdoc/reject-function-type -- Generic */
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Function} GenericFunction
|
|
22
|
+
*/
|
|
23
|
+
/* eslint-enable jsdoc/reject-function-type -- Generic */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {Node} node
|
|
27
|
+
* @param {Element} item
|
|
28
|
+
* @throws {Error}
|
|
29
|
+
* @returns {boolean}
|
|
30
|
+
*/
|
|
31
|
+
function ignoreHarmlessNonelementNodes (node, item) {
|
|
32
|
+
if (
|
|
33
|
+
(node.nodeType === 3 || node.nodeType === 4) && // Text or CDATA node
|
|
34
|
+
(/\S/v).test(/** @type {string} */ (node.nodeValue))
|
|
35
|
+
) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
'Non-whitespace text or CDATA nodes are not allowed directly within <' +
|
|
38
|
+
item.nodeName.toLowerCase() + '>'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
// Todo: also ignore nodes like comments or processing instructions?
|
|
42
|
+
// (A mistake of JSON?); we might even convert comments into JavaScript
|
|
43
|
+
// comments if this is used in a non-JSON-restricted JavaScript environment
|
|
44
|
+
// Not an element (ignore comments, whitespace text nodes, etc.)
|
|
45
|
+
return node.nodeType !== 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {undefined|null|boolean|number|bigint|symbol|string|GenericFunction|
|
|
50
|
+
* Date|RegExp} NonObject
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {{
|
|
55
|
+
* [key: string]: NonObject | JSONObject | JSONObject[]
|
|
56
|
+
* }} JSONObjectPlain
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @typedef {(NonObject|JSONObject)[]} JSONObjectArray
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {NonObject|JSONObjectPlain|JSONObjectArray} JSONObject
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {Element} item
|
|
69
|
+
* @param {boolean} allowJS
|
|
70
|
+
* @param {boolean} [throwOnSpan]
|
|
71
|
+
* @throws {Error}
|
|
72
|
+
* @returns {JSONObject}
|
|
73
|
+
*/
|
|
74
|
+
function item2JSONObject (item, allowJS, throwOnSpan) {
|
|
75
|
+
/** @type {JSONObject} */
|
|
76
|
+
let ret;
|
|
77
|
+
|
|
78
|
+
/** @type {"dt"|"dd"} */
|
|
79
|
+
let state;
|
|
80
|
+
const {textContent} = item,
|
|
81
|
+
topLevelJSONElement = item.nodeName.toLowerCase();
|
|
82
|
+
switch (topLevelJSONElement) {
|
|
83
|
+
case 'span':
|
|
84
|
+
if (throwOnSpan) {
|
|
85
|
+
throw new Error('A <span> element is not allowed in this context');
|
|
86
|
+
}
|
|
87
|
+
return textContent;
|
|
88
|
+
// null, boolean, number (or undefined, function, non-finite
|
|
89
|
+
// number, Date or RegExp object)
|
|
90
|
+
case 'i':
|
|
91
|
+
switch (textContent) {
|
|
92
|
+
case 'null':
|
|
93
|
+
return null;
|
|
94
|
+
case 'true':
|
|
95
|
+
return true;
|
|
96
|
+
case 'false':
|
|
97
|
+
return false;
|
|
98
|
+
// Non-JSON
|
|
99
|
+
case 'undefined':
|
|
100
|
+
ret = undefined;
|
|
101
|
+
break;
|
|
102
|
+
case 'Infinity':
|
|
103
|
+
ret = Infinity;
|
|
104
|
+
break;
|
|
105
|
+
case '-Infinity':
|
|
106
|
+
ret = -Infinity;
|
|
107
|
+
break;
|
|
108
|
+
case 'NaN':
|
|
109
|
+
ret = Number.NaN;
|
|
110
|
+
break;
|
|
111
|
+
default: {
|
|
112
|
+
// number
|
|
113
|
+
if ((/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:e(?:[+\-])?\d+)?$/vi).test(textContent)) {
|
|
114
|
+
return Number.parseFloat(textContent);
|
|
115
|
+
}
|
|
116
|
+
// function
|
|
117
|
+
const funcMatch = textContent.match(/^function \w*\s*\(([\w, ]*)\) \{([\s\S]*)\}$/v);
|
|
118
|
+
// Todo: Add config to prevent this
|
|
119
|
+
if (funcMatch) {
|
|
120
|
+
// eslint-disable-next-line no-new-func -- Deliberate
|
|
121
|
+
ret = new Function(...funcMatch[1].split(/, /v).
|
|
122
|
+
// eslint-disable-next-line unicorn/prefer-spread -- Convenient
|
|
123
|
+
concat(funcMatch[2]));
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
// Date
|
|
127
|
+
if ((/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) /v).test(textContent)) {
|
|
128
|
+
ret = new Date(Date.parse(textContent));
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
// RegExp
|
|
132
|
+
const regexMatch = textContent.match(/^\/([\s\S]*)\/([gimydsuv]+)?$/v);
|
|
133
|
+
if (regexMatch) {
|
|
134
|
+
const flags = (regexMatch[2] ?? '');
|
|
135
|
+
ret = new RegExp(regexMatch[1], flags);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// BigInt
|
|
140
|
+
|
|
141
|
+
const bigintMatch = textContent.match(/^\d+n$/v);
|
|
142
|
+
if (bigintMatch) {
|
|
143
|
+
ret = BigInt(bigintMatch[0].slice(0, -1));
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const symbolMatch = textContent.match(/^Symbol\(('\w+')\)$/v);
|
|
148
|
+
if (symbolMatch) {
|
|
149
|
+
ret = Symbol(symbolMatch[1]);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throw new Error('Unrecognized type');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!allowJS) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
'The value type (' + String(ret) + ') cannot be used in JSON mode'
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return ret;
|
|
162
|
+
case 'dl': { // object
|
|
163
|
+
// JSON allows empty objects (and HTML allows empty <dl>'s) so we do also
|
|
164
|
+
state = 'dt';
|
|
165
|
+
ret = {};
|
|
166
|
+
/** @type {string} */
|
|
167
|
+
let key;
|
|
168
|
+
[...item.childNodes].forEach(function (node) {
|
|
169
|
+
if (ignoreHarmlessNonelementNodes(node, item)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const nodeName = node.nodeName.toLowerCase();
|
|
173
|
+
if (state !== nodeName) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Unexpected element ${nodeName} encountered where ${state} expected`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (nodeName === 'dt') {
|
|
179
|
+
if (/** @type {Element} */ (node).children.length) {
|
|
180
|
+
throw new Error('<dt> should not have any children');
|
|
181
|
+
}
|
|
182
|
+
state = 'dd';
|
|
183
|
+
key = /** @type {string} */ (node.textContent);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Can now only be a <dd>
|
|
187
|
+
state = 'dt';
|
|
188
|
+
if (/** @type {Element} */ (node).children.length > 1) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
'<dd> should not have more than one element ' +
|
|
191
|
+
'child (<ol>, <dl>, or <i>)'
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (!(/** @type {Element} */ (node)).children.length) { // String
|
|
195
|
+
/** @type {JSONObjectPlain} */
|
|
196
|
+
(ret)[key] = node.textContent;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** @type {JSONObjectPlain} */
|
|
201
|
+
(ret)[key] = item2JSONObject(/** @type {Element} */ (
|
|
202
|
+
node
|
|
203
|
+
).children[0], allowJS, true);
|
|
204
|
+
});
|
|
205
|
+
if (state !== 'dt') {
|
|
206
|
+
throw new Error(
|
|
207
|
+
'Ended a definition list without a final <dd> to ' +
|
|
208
|
+
'match the previous <dt>.'
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return ret;
|
|
212
|
+
} case 'ol': // array
|
|
213
|
+
if (item.getAttribute('start') !== '0') {
|
|
214
|
+
throw new Error(
|
|
215
|
+
'For the sake of readability, <ol> must include a ' +
|
|
216
|
+
'start="0" attribute within JHTML.'
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
ret = [];
|
|
221
|
+
// JSON allows empty arrays (and HTML allows empty <ol>'s) so we do also
|
|
222
|
+
[...item.childNodes].forEach(function (node) {
|
|
223
|
+
if (ignoreHarmlessNonelementNodes(node, item)) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const nodeName = node.nodeName.toLowerCase();
|
|
227
|
+
if (nodeName !== 'li') {
|
|
228
|
+
throw new Error('Unexpected child of <ol> element: ' + nodeName);
|
|
229
|
+
}
|
|
230
|
+
if (/** @type {Element} */ (node).children.length > 1) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
'<li> should not have more than a single element ' +
|
|
233
|
+
'child (<ol>, <dl>, or <i>)'
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
if (!(/** @type {Element} */ (node)).children.length) { // String
|
|
237
|
+
/** @type {JSONObjectArray} */
|
|
238
|
+
(ret).push(node.textContent);
|
|
239
|
+
} else {
|
|
240
|
+
/** @type {JSONObjectArray} */
|
|
241
|
+
(ret).push(item2JSONObject(
|
|
242
|
+
/** @type {Element} */ (node).children[0], allowJS, true
|
|
243
|
+
));
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
return ret;
|
|
247
|
+
default:
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
throw new Error('Unexpected element');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @param {string} str
|
|
255
|
+
* @returns {string}
|
|
256
|
+
*/
|
|
257
|
+
function escapeHTMLText (str) {
|
|
258
|
+
return str.replaceAll('&', '&').replaceAll('<', '<');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// eslint-disable-next-line sonarjs/no-clear-text-protocols -- NS
|
|
262
|
+
const jhtmlNs = 'http://brett-zamir.me/ns/microdata/json-as-html/2';
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
*
|
|
266
|
+
*/
|
|
267
|
+
class JHTMLStringifier extends ObjectArrayDelegator {
|
|
268
|
+
// JSON terminal handler methods
|
|
269
|
+
|
|
270
|
+
// These four methods can be overridden without affecting the logic of the
|
|
271
|
+
// objectHandler and arrayHandler to utilize reporting of the object
|
|
272
|
+
// as a whole
|
|
273
|
+
/**
|
|
274
|
+
* @param {object} value
|
|
275
|
+
* @param {object|undefined} parentObject
|
|
276
|
+
* @returns {string}
|
|
277
|
+
*/
|
|
278
|
+
beginObjectHandler (
|
|
279
|
+
value, parentObject /* , parentKey, parentObjectArrayBool */
|
|
280
|
+
) {
|
|
281
|
+
return '<dl' + (
|
|
282
|
+
parentObject ? '' : ' itemscope="" itemtype="' + jhtmlNs + '"'
|
|
283
|
+
) + '>';
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* @returns {string}
|
|
287
|
+
*/
|
|
288
|
+
endObjectHandler (
|
|
289
|
+
/* value, parentObject, parentKey, parentObjectArrayBool */
|
|
290
|
+
) {
|
|
291
|
+
return '</dl>';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @param {JSONObjectArray} value
|
|
296
|
+
* @param {object|undefined} parentObject
|
|
297
|
+
* @returns {string}
|
|
298
|
+
*/
|
|
299
|
+
beginArrayHandler (
|
|
300
|
+
value, parentObject /* , parentKey, parentObjectArrayBool */
|
|
301
|
+
) {
|
|
302
|
+
return '<ol start="0"' + (
|
|
303
|
+
parentObject ? '' : ' itemscope="" itemtype="' + jhtmlNs + '"'
|
|
304
|
+
) + '>';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* @returns {string}
|
|
309
|
+
*/
|
|
310
|
+
endArrayHandler (
|
|
311
|
+
/* value, parentObject, parentKey, parentObjectArrayBool */
|
|
312
|
+
) {
|
|
313
|
+
return '</ol>';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// JSON terminal key handler methods
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* @param {string} key
|
|
320
|
+
* @returns {string}
|
|
321
|
+
*/
|
|
322
|
+
objectKeyHandler (
|
|
323
|
+
key /* , parentObject, parentKey, parentObjectArrayBool, iterCt */
|
|
324
|
+
) {
|
|
325
|
+
return '<dt>' + escapeHTMLText(key) + '</dt>';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @returns {string}
|
|
330
|
+
*/
|
|
331
|
+
arrayKeyHandler (/* key, parentObject, parentKey, parentObjectArrayBool */) {
|
|
332
|
+
return '';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// JSON terminal joiner handler methods
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @returns {string}
|
|
339
|
+
*/
|
|
340
|
+
objectKeyValueJoinerHandler () {
|
|
341
|
+
return '';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @returns {string}
|
|
346
|
+
*/
|
|
347
|
+
arrayKeyValueJoinerHandler () {
|
|
348
|
+
return '';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// JSON terminal primitive handler methods
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* @param {null} obj
|
|
355
|
+
* @param {object|undefined} parentObject
|
|
356
|
+
* @returns {string}
|
|
357
|
+
*/
|
|
358
|
+
nullHandler (obj, parentObject /* , parentKey, parentObjectArrayBool */) {
|
|
359
|
+
if (!parentObject) {
|
|
360
|
+
return 'null';
|
|
361
|
+
}
|
|
362
|
+
return '<i>null</i>';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* @param {boolean} value
|
|
367
|
+
* @param {object|undefined} parentObject
|
|
368
|
+
* @returns {string}
|
|
369
|
+
*/
|
|
370
|
+
booleanHandler (
|
|
371
|
+
value, parentObject /* , parentKey, parentObjectArrayBool */
|
|
372
|
+
) {
|
|
373
|
+
if (!parentObject) {
|
|
374
|
+
return String(value);
|
|
375
|
+
}
|
|
376
|
+
return '<i>' + String(value) + '</i>';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* @param {number} value
|
|
381
|
+
* @param {object|undefined} parentObject
|
|
382
|
+
* @returns {string}
|
|
383
|
+
*/
|
|
384
|
+
numberHandler (value, parentObject /* , parentKey, parentObjectArrayBool */) {
|
|
385
|
+
if (!parentObject) {
|
|
386
|
+
return String(value);
|
|
387
|
+
}
|
|
388
|
+
return '<i>' + String(value) + '</i>';
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* @param {string} value
|
|
393
|
+
* @param {object|undefined} parentObject
|
|
394
|
+
* @returns {string}
|
|
395
|
+
*/
|
|
396
|
+
stringHandler (value, parentObject /* , parentKey, parentObjectArrayBool */) {
|
|
397
|
+
if (!parentObject) {
|
|
398
|
+
return escapeHTMLText(value);
|
|
399
|
+
}
|
|
400
|
+
return escapeHTMLText(value);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* eslint-disable jsdoc/reject-function-type -- Generic */
|
|
404
|
+
/**
|
|
405
|
+
* @typedef {Function} GenericFunction
|
|
406
|
+
*/
|
|
407
|
+
/* eslint-enable jsdoc/reject-function-type -- Generic */
|
|
408
|
+
|
|
409
|
+
// JavaScript-only (non-JSON) (terminal) handler methods (not used or
|
|
410
|
+
// required for JSON mode)
|
|
411
|
+
/**
|
|
412
|
+
* @param {GenericFunction} value
|
|
413
|
+
* @param {object|undefined} parentObject
|
|
414
|
+
* @returns {string}
|
|
415
|
+
*/
|
|
416
|
+
functionHandler (
|
|
417
|
+
value, parentObject /* , parentKey, parentObjectArrayBool */
|
|
418
|
+
) {
|
|
419
|
+
// May not be supported everywhere
|
|
420
|
+
const str = escapeHTMLText(value.toString());
|
|
421
|
+
if (!parentObject) {
|
|
422
|
+
return str;
|
|
423
|
+
}
|
|
424
|
+
return '<i>' + str + '</i>';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* @param {undefined} value
|
|
429
|
+
* @param {object|undefined} parentObject
|
|
430
|
+
* @returns {string}
|
|
431
|
+
*/
|
|
432
|
+
undefinedHandler (
|
|
433
|
+
value, parentObject /* , parentKey, parentObjectArrayBool */
|
|
434
|
+
) {
|
|
435
|
+
if (!parentObject) {
|
|
436
|
+
return 'undefined';
|
|
437
|
+
}
|
|
438
|
+
return '<i>undefined</i>';
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* @param {number} value
|
|
442
|
+
* @param {object|undefined} parentObject
|
|
443
|
+
* @returns {string}
|
|
444
|
+
*/
|
|
445
|
+
nonfiniteNumberHandler (
|
|
446
|
+
value, parentObject /* , parentKey, parentObjectArrayBool */
|
|
447
|
+
) {
|
|
448
|
+
if (!parentObject) {
|
|
449
|
+
return String(value);
|
|
450
|
+
}
|
|
451
|
+
return '<i>' + String(value) + '</i>';
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* @param {JSONObject} value
|
|
456
|
+
* @param {string} key
|
|
457
|
+
* @param {object|undefined} parentObject
|
|
458
|
+
* @param {string|undefined} parentKey
|
|
459
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
460
|
+
* @returns {string}
|
|
461
|
+
*/
|
|
462
|
+
objectValueHandler (
|
|
463
|
+
value, key, parentObject, parentKey, parentObjectArrayBool /* , iterCt */
|
|
464
|
+
) {
|
|
465
|
+
return '<dd>' + this.delegateHandlersByType(
|
|
466
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
467
|
+
) + '</dd>';
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @param {JSONObjectArray} value
|
|
472
|
+
* @param {string} key
|
|
473
|
+
* @param {object|undefined} parentObject
|
|
474
|
+
* @param {string|undefined} parentKey
|
|
475
|
+
* @param {boolean|undefined} parentObjectArrayBool
|
|
476
|
+
* @returns {string}
|
|
477
|
+
*/
|
|
478
|
+
arrayValueHandler (
|
|
479
|
+
value, key, parentObject, parentKey, parentObjectArrayBool
|
|
480
|
+
) {
|
|
481
|
+
return '<li>' + this.delegateHandlersByType(
|
|
482
|
+
value, parentObject, parentKey, parentObjectArrayBool
|
|
483
|
+
) + '</li>';
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* @param {JSONObject} obj
|
|
488
|
+
* @returns {string}
|
|
489
|
+
*/
|
|
490
|
+
beginHandler (obj /* , parObj, parKey, parObjArrBool */) {
|
|
491
|
+
const objType = typeof obj;
|
|
492
|
+
return obj && objType === 'object'
|
|
493
|
+
? ''
|
|
494
|
+
: '<' + (
|
|
495
|
+
[
|
|
496
|
+
'boolean', 'object', 'number', 'function', 'undefined'
|
|
497
|
+
].includes(objType)
|
|
498
|
+
? 'i'
|
|
499
|
+
: 'span'
|
|
500
|
+
) + ' itemscope="" itemtype="' + jhtmlNs + '">';
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* @param {JSONObject} obj
|
|
505
|
+
* @returns {string}
|
|
506
|
+
*/
|
|
507
|
+
endHandler (obj /* , parObj, parKey, parObjArrBool */) {
|
|
508
|
+
const objType = typeof obj;
|
|
509
|
+
return obj && objType === 'object'
|
|
510
|
+
? ''
|
|
511
|
+
: '</' + (
|
|
512
|
+
[
|
|
513
|
+
'boolean', 'object', 'number', 'function', 'undefined'
|
|
514
|
+
].includes(objType)
|
|
515
|
+
? 'i'
|
|
516
|
+
: 'span'
|
|
517
|
+
) + '>';
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** @type {import('jsdom').DOMWindow | Window & typeof globalThis} */
|
|
522
|
+
let _win;
|
|
523
|
+
/**
|
|
524
|
+
* Set the window object for DOM operations.
|
|
525
|
+
* @param {import('jsdom').DOMWindow |
|
|
526
|
+
* Window & typeof globalThis} win - The window object.
|
|
527
|
+
* @returns {void}
|
|
528
|
+
*/
|
|
529
|
+
export const setWindow = (win) => {
|
|
530
|
+
_win = win;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* @param {Element|Element[]} [items]
|
|
535
|
+
* @param {{
|
|
536
|
+
* mode?: "JSON"|"JavaScript"
|
|
537
|
+
* }} [options]
|
|
538
|
+
* @returns {JSONObject}
|
|
539
|
+
*/
|
|
540
|
+
export const toJSONObject = function (items, options) {
|
|
541
|
+
options = options || {};
|
|
542
|
+
const isElement = items && !Array.isArray(items) && items.nodeType === 1;
|
|
543
|
+
const jsonHtml = isElement
|
|
544
|
+
? [items]
|
|
545
|
+
: (/** @type {Element[]} */ (items) ||
|
|
546
|
+
_win.document.querySelectorAll(`[itemtype=${_win.CSS.escape(jhtmlNs)}]`));
|
|
547
|
+
const ret = [...jsonHtml].map((item) => {
|
|
548
|
+
return item2JSONObject(item, options.mode === 'JavaScript');
|
|
549
|
+
});
|
|
550
|
+
return isElement ? ret[0] : ret;
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* We don't validate that other attributes are not present, but they
|
|
555
|
+
* should not be.
|
|
556
|
+
* @todo Could make more efficient option
|
|
557
|
+
* @param {Element|Element[]} [items]
|
|
558
|
+
* @param {{
|
|
559
|
+
* mode?: "JSON"|"JavaScript"
|
|
560
|
+
* }} [options]
|
|
561
|
+
* @returns {string|string[]}
|
|
562
|
+
*/
|
|
563
|
+
export const toJSONString = function (items, options) {
|
|
564
|
+
options = options || {};
|
|
565
|
+
const isElement = items && !Array.isArray(items) && items.nodeType === 1;
|
|
566
|
+
const jsonHtml = isElement
|
|
567
|
+
? [items]
|
|
568
|
+
: (/** @type {Element[]} */ (items) ||
|
|
569
|
+
_win.document.querySelectorAll(`[itemtype=${_win.CSS.escape(jhtmlNs)}]`));
|
|
570
|
+
const ret = [...jsonHtml].map((item) => {
|
|
571
|
+
const jsonObj = item2JSONObject(item, options.mode === 'JavaScript');
|
|
572
|
+
const stringifier = new Stringifier(options);
|
|
573
|
+
return stringifier.walkJSONObject(jsonObj);
|
|
574
|
+
});
|
|
575
|
+
return isElement ? ret[0] : ret;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* @param {JSONObject} jsonObj
|
|
580
|
+
* @param {import('./SAJJ/SAJJ.js').SAJJOptions} [options]
|
|
581
|
+
* @returns {string}
|
|
582
|
+
*/
|
|
583
|
+
export const toJHTMLString = function (jsonObj, options) {
|
|
584
|
+
options = options || {};
|
|
585
|
+
options.distinguishKeysValues = true;
|
|
586
|
+
const jhtmlStringifier = new JHTMLStringifier(options);
|
|
587
|
+
return jhtmlStringifier.walkJSONObject(jsonObj);
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* @param {JSONObject} jsonObj
|
|
592
|
+
* @param {import('./SAJJ/SAJJ.js').SAJJOptions} [options]
|
|
593
|
+
* @returns {Element}
|
|
594
|
+
*/
|
|
595
|
+
export const toJHTMLDOM = function (jsonObj, options) {
|
|
596
|
+
const jhtmlStr = toJHTMLString(jsonObj, options);
|
|
597
|
+
return /** @type {Element} */ (new _win.DOMParser().parseFromString(
|
|
598
|
+
jhtmlStr, 'text/html'
|
|
599
|
+
).body.firstElementChild);
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
export {Stringifier};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './jhtml.js';
|