jtlt 0.18.0 → 0.19.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # jtlt CHANGES
2
2
 
3
+ ## 0.19.0
4
+
5
+ - feat: declarative jamilih node format
6
+
3
7
  ## 0.18.0
4
8
 
5
9
  - feat: `this.if()` / `choose()` / `assert()` accept a simple, non-`eval`
package/README.md CHANGED
@@ -443,6 +443,81 @@ from XSLT):
443
443
 
444
444
  1. The method `this.stylesheet()` (or `this.transform()`) is used similarly to XSLT for configuration, but it does not call for including the templates within it as nested content.
445
445
  2. JTLT adds `path` as an alias for `match` on templates.
446
+ 3. **No single-root-node restriction.** XSLT's content model has, at root,
447
+ a single node per template (not counting processing instructions) —
448
+ `xsl:output` is a separate top-level stylesheet declaration, and
449
+ `xsl:template`'s own content is one nested tree. JTLT has no equivalent
450
+ of that single `xsl:stylesheet`/`xsl:transform` wrapper to enforce it:
451
+ - A **function template** is a plain JavaScript function body, so it can
452
+ make any number of top-level calls in sequence — there is nothing
453
+ that limits it to producing (or delegating to) one node:
454
+ ```js
455
+ const templateObj = {path: '$', template () {
456
+ // Two independent things at the root, not one nested tree:
457
+ this.output({method: 'html'}); // configure output
458
+ this.applyTemplates('$.items[*]'); // then produce content
459
+ }};
460
+ ```
461
+ - A **declarative (jamilih-shaped) template** — the `Array`-of-nodes
462
+ form compiled by `compileJSONTemplate()` — is likewise an array of
463
+ *sibling* top-level nodes, not one node:
464
+ ```js
465
+ const templateObj = {
466
+ template: [
467
+ ['h1', ['Custom heading']], // a literal element, then
468
+ [{$applyTemplates: '$.items[*]'}] // an operation node
469
+ ]
470
+ // -> <h1>Custom heading</h1><li>...</li>...
471
+ };
472
+ ```
473
+ jamilih's own `#` fragment node offers a similar grouping when only
474
+ one node is structurally expected, but the top level here never
475
+ requires it — an array of nodes is already accepted directly.
476
+
477
+ This is also why a template holding just *one* operation node, with
478
+ no other siblings, still needs two levels of array, not one:
479
+
480
+ ```js
481
+ // Rejected: a bare `{$applyTemplates}` object is not itself a node —
482
+ // every node (elements included) is array-shaped, `[head, ...args]`.
483
+ const rejected = {template: [{$applyTemplates: '$.items[*]'}]};
484
+
485
+ // Accepted: the outer array is the sibling-node list (point 3, above);
486
+ // the inner array is this one operation node's own `[head, ...args]`
487
+ // shape — `$applyTemplates` just happens to need no further `args`.
488
+ const accepted = {template: [[{$applyTemplates: '$.items[*]'}]]};
489
+ ```
490
+
491
+ The inner array isn't there *for* `$applyTemplates` specifically — it's
492
+ the same shape every operation node uses, whether or not that
493
+ operation happens to need trailing arguments: `$if` and `$forEach`
494
+ both use later array items for their bodies (`[{$if}, thenNodes,
495
+ elseNodes?]`, `[{$forEach}, childNodes]`), so the node model treats
496
+ `[head, ...args]` as the one uniform shape rather than special-casing
497
+ the argument-less operations to a bare head object.
498
+
499
+ `$if`'s then/else and `$forEach`'s children are, in turn, each a
500
+ `thenNodes`/`elseNodes`/`childNodes` **array of sibling nodes** — the
501
+ exact same shape as `template` itself (point 3, above) — not a single
502
+ unwrapped node, even when the body happens to hold just one:
503
+
504
+ ```js
505
+ // Not this — a single node, unwrapped:
506
+ const unwrapped = [[{$if: '$flag'}, ['p', ['yes']]]];
507
+
508
+ // This — an array of (one) sibling node(s):
509
+ const wrapped = [[{$if: '$flag'}, [['p', ['yes']]]]];
510
+ ```
511
+
512
+ Requiring the wrapper isn't just consistency for its own sake — an
513
+ *unwrapped* single node would be genuinely ambiguous, silently so:
514
+ an element node is itself an array, `[name, attrs?, children?]`, so
515
+ `['p', ['yes']]` is indistinguishable, at the array level, from a
516
+ two-node *list*: a bare string `'p'` (a text node) followed by an
517
+ element node `['yes']` (an empty `<yes>` tag). That is exactly what
518
+ `unwrapped` above produces — `p<yes></yes>`, not `<p>yes</p>` — with
519
+ no error, since both readings are structurally valid nodes on their
520
+ own; only the wrapped array-of-nodes form is unambiguous.
446
521
 
447
522
  ## To-dos
448
523