jtlt 0.17.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/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ coverage
3
+ .idea
package/.npmignore ADDED
@@ -0,0 +1,3 @@
1
+ test
2
+ coverage
3
+ .idea
package/CHANGES.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # jtlt CHANGES
2
2
 
3
+ ## 0.19.0
4
+
5
+ - feat: declarative jamilih node format
6
+
7
+ ## 0.18.0
8
+
9
+ - feat: `this.if()` / `choose()` / `assert()` accept a simple, non-`eval`
10
+ binary comparison as their test, e.g. `this.if('$name === "x"')` or
11
+ `this.if('$count < 50')` — a bare `$name` parameter (or, for the JSONPath
12
+ engine, a plain dotted/indexed `$...` path) on the left, one of
13
+ `===` / `!==` / `==` / `!=` / `<` / `<=` / `>` / `>=`, and a string,
14
+ number, boolean, `null`, or `undefined` literal on the right; anything
15
+ more complex is left to the JSONPath/XPath engine
16
+
3
17
  ## 0.17.0
4
18
 
5
19
  - feat: `this.if('$name', ...)` (and `choose()`/`assert()`) test a parameter
package/README.md CHANGED
@@ -419,6 +419,7 @@ Advantages (strong parallels with XSLT):
419
419
  - Built‑in default rules: when no template matches, defaults traverse and render objects, arrays, scalars, property names, and functions, similar to XSLT’s built‑in templates.
420
420
  - applyTemplates/forEach and sorting: `applyTemplates(select, mode, sort)` and `forEach(select, cb, sort)` mirror `xsl:apply-templates`/`xsl:for-each` and `xsl:sort`.
421
421
  - Named templates and parameters: `callTemplate(name, withParam)` reflects `xsl:call-template` + `xsl:with-param`. `this.param(name, default)` mirrors `xsl:param` — the declared default is used unless the caller supplied a value (via `this.withParam(name, value)` before the call, or `callTemplate`'s `withParam` array) or a runtime value was passed as `config.params` (like an XSLT processor's stylesheet parameters). `this.withParam(name, value)` stages a parameter for the next `callTemplate()`/`applyTemplates()`, which consumes and clears the staged set. `config.params` values are also readable as `$name` from any template. In each of `param()`/`withParam()`, the value argument is a selector expression string (or `{select}`), or a literal `{value}`.
422
+ - Conditionals: `this.if(test, cb)`, `this.choose(test, whenCb, otherwiseCb)`, and `this.assert(test, message)` mirror `xsl:if`, `xsl:choose`, and `xsl:assert`. `test` is a JSONPath/XPath expression (a non-empty result set is truthy), a bare `$name` parameter reference, or a simple non-`eval` comparison such as `'$name === "x"'` or `'$count < 50'` — a `$name` parameter (or, for the JSONPath engine, a plain dotted/indexed `$...` path) on the left, one of `===` / `!==` / `==` / `!=` / `<` / `<=` / `>` / `>=`, and a string, number, boolean, `null`, or `undefined` literal on the right.
422
423
  - Keys and lookups: `key(name, match, use)` + `getKey(name, value)` provide `xsl:key`-style indexing for joins and fast lookups.
423
424
  - Multiple output forms: string, DOM, and JSON builders ("joiners") allow emitting different result trees like XSLT’s result tree model.
424
425
 
@@ -442,6 +443,81 @@ from XSLT):
442
443
 
443
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.
444
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.
445
521
 
446
522
  ## To-dos
447
523