jtlt 0.3.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.
Files changed (40) hide show
  1. package/CHANGES.md +9 -0
  2. package/README.md +46 -78
  3. package/demo/codemirror.esm.js +28242 -0
  4. package/demo/codemirror.js +94 -0
  5. package/demo/index.css +7 -0
  6. package/demo/index.html +11 -14
  7. package/demo/index.js +206 -26
  8. package/demo/vendor/jamilih/dist/jml.mjs +2341 -0
  9. package/demo/vendor/jhtml/src/SAJJ/SAJJ.ObjectArrayDelegator.js +356 -0
  10. package/demo/vendor/jhtml/src/SAJJ/SAJJ.Stringifier.js +186 -0
  11. package/demo/vendor/jhtml/src/SAJJ/SAJJ.js +746 -0
  12. package/demo/vendor/jhtml/src/SAJJ/testing/SAJJ.html +33 -0
  13. package/demo/vendor/jhtml/src/SAJJ/testing/SAJJ.testing.js +25 -0
  14. package/demo/vendor/jhtml/src/jhtml-browser.js +5 -0
  15. package/demo/vendor/jhtml/src/jhtml-node.cts +3 -0
  16. package/demo/vendor/jhtml/src/jhtml-node.js +8 -0
  17. package/demo/vendor/jhtml/src/jhtml-node.mts +1 -0
  18. package/demo/vendor/jhtml/src/jhtml.cts +3 -0
  19. package/demo/vendor/jhtml/src/jhtml.js +602 -0
  20. package/demo/vendor/jhtml/src/jhtml.mts +1 -0
  21. package/demo/vendor/jsonpath-plus/dist/index-browser-esm.js +2158 -0
  22. package/demo/vendor/simple-get-json/dist/index-es.js +151 -0
  23. package/dist/JSONPathTransformerContext.d.ts +88 -4
  24. package/dist/JSONPathTransformerContext.d.ts.map +1 -1
  25. package/dist/XPathTransformer.d.ts +2 -2
  26. package/dist/XPathTransformerContext.d.ts +92 -8
  27. package/dist/XPathTransformerContext.d.ts.map +1 -1
  28. package/dist/index.d.ts +4 -4
  29. package/dist/index.d.ts.map +1 -1
  30. package/docs/API.expanded.md +5 -3
  31. package/docs/API.md +1 -1
  32. package/docs/TO-DO.md +55 -31
  33. package/eslint.config.js +3 -1
  34. package/package.json +24 -4
  35. package/rollup.config.js +13 -0
  36. package/src/JSONPathTransformerContext.js +422 -4
  37. package/src/XPathTransformer.js +1 -1
  38. package/src/XPathTransformerContext.js +462 -11
  39. package/src/index.js +9 -6
  40. package/tsconfig.json +5 -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,5 @@
1
+ import {setWindow} from './jhtml.js';
2
+
3
+ setWindow(/** @type {Window & typeof globalThis} */ (globalThis));
4
+
5
+ export * from './jhtml.js';
@@ -0,0 +1,3 @@
1
+ import * as jhtml from './jhtml-node.js';
2
+
3
+ export = jhtml;
@@ -0,0 +1,8 @@
1
+ import {JSDOM} from 'jsdom';
2
+ import {setWindow} from './jhtml.js';
3
+
4
+ const {window} = new JSDOM();
5
+
6
+ setWindow(window);
7
+
8
+ export * from './jhtml.js';
@@ -0,0 +1 @@
1
+ export * from './jhtml-node.js';
@@ -0,0 +1,3 @@
1
+ import * as jhtml from './jhtml.js';
2
+
3
+ export = jhtml;
@@ -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('&', '&amp;').replaceAll('<', '&lt;');
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';