@jarenjs/json 0.9.2
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/ARCHITECTURE.md +175 -0
- package/LICENSE +21 -0
- package/README.md +471 -0
- package/dist/types/basic.d.ts +32 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/jslt/dispatch.d.ts +11 -0
- package/dist/types/jslt/errors.d.ts +18 -0
- package/dist/types/jslt/index.d.ts +53 -0
- package/dist/types/jslt/stylesheet.d.ts +8 -0
- package/dist/types/jtlt/desugar.d.ts +19 -0
- package/dist/types/jtlt/errors.d.ts +18 -0
- package/dist/types/jtlt/index.d.ts +57 -0
- package/dist/types/jtlt/template.d.ts +8 -0
- package/dist/types/jtlt/writer.d.ts +6 -0
- package/dist/types/path.d.ts +235 -0
- package/dist/types/pointer.d.ts +114 -0
- package/dist/types/query/compile.d.ts +21 -0
- package/dist/types/query/errors.d.ts +18 -0
- package/dist/types/query/index.d.ts +70 -0
- package/dist/types/query/normalize.d.ts +68 -0
- package/dist/types/query/operators.d.ts +424 -0
- package/dist/types/query/runtime.d.ts +93 -0
- package/dist/types/segments.d.ts +62 -0
- package/dist/types/xquery/index.d.ts +19 -0
- package/dist/types/xquery/parse.d.ts +20 -0
- package/docs/JSLT-FORMAT.md +861 -0
- package/docs/JSLT-PRELUDE.md +159 -0
- package/docs/JTLT-FORMAT.md +659 -0
- package/docs/QUERY-FORMAT.md +1221 -0
- package/docs/XQUERY-FRONTEND.md +321 -0
- package/package.json +81 -0
- package/schemas/jaren-jslt.draft-07.schema.json +776 -0
- package/schemas/jaren-jslt.schema.json +776 -0
- package/schemas/jaren-query.draft-07.schema.json +613 -0
- package/schemas/jaren-query.schema.json +375 -0
- package/src/basic.js +300 -0
- package/src/index.js +4 -0
- package/src/jslt/dispatch.js +934 -0
- package/src/jslt/errors.js +34 -0
- package/src/jslt/index.js +121 -0
- package/src/jslt/stylesheet.js +234 -0
- package/src/jtlt/desugar.js +231 -0
- package/src/jtlt/errors.js +34 -0
- package/src/jtlt/index.js +155 -0
- package/src/jtlt/template.js +130 -0
- package/src/jtlt/writer.js +110 -0
- package/src/path.js +977 -0
- package/src/pointer.js +453 -0
- package/src/query/compile.js +817 -0
- package/src/query/errors.js +33 -0
- package/src/query/index.js +150 -0
- package/src/query/normalize.js +1047 -0
- package/src/query/operators.js +1253 -0
- package/src/query/runtime.js +233 -0
- package/src/segments.js +627 -0
- package/src/xquery/index.js +35 -0
- package/src/xquery/parse.js +1647 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
# The Jaren JTLT Format
|
|
2
|
+
|
|
3
|
+
**Version 0.1 — Specification**
|
|
4
|
+
|
|
5
|
+
Module: `@jarenjs/json/jtlt`. This document is the language contract for the
|
|
6
|
+
Jaren JTLT text-template layer, the way [JSLT-FORMAT](./JSLT-FORMAT.md) is
|
|
7
|
+
the contract for the dispatch engine it compiles to, and
|
|
8
|
+
[QUERY-FORMAT](./QUERY-FORMAT.md) the contract for the expression language
|
|
9
|
+
both share.
|
|
10
|
+
|
|
11
|
+
> **Naming note (non-normative).** "JTLT" here names the Jaren template
|
|
12
|
+
> layer for text — *JSON template language for text*, the
|
|
13
|
+
> XSLT-`method="text"` / T4 derivative of this stack. Final naming/branding
|
|
14
|
+
> is an open question and is deliberately not settled here, mirroring the
|
|
15
|
+
> query and JSLT naming notes.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Introduction
|
|
20
|
+
|
|
21
|
+
### 1.1 What this language is
|
|
22
|
+
|
|
23
|
+
A JTLT **template** is a JSON document holding an ordered list of template
|
|
24
|
+
rules — the same rule shape as a JSLT stylesheet — whose bodies are
|
|
25
|
+
**segment lists** (literal text, interpolated query expressions, `$apply`
|
|
26
|
+
splices) and whose transformation result is a **string**. Where JSLT
|
|
27
|
+
answers "reshape this JSON into that JSON", JTLT answers "render this JSON
|
|
28
|
+
as that text": Markdown, XML, source code, configuration files — anything
|
|
29
|
+
with the shape of a character stream.
|
|
30
|
+
|
|
31
|
+
JTLT is a **front-end, not a second engine** — the same relationship the
|
|
32
|
+
XQuery module has to the query engine. A consumer compiles the template
|
|
33
|
+
into an ordinary JSLT 0.1 stylesheet and serializes the dispatched result;
|
|
34
|
+
the reference implementation exposes that stylesheet as
|
|
35
|
+
`render.stylesheet` (§11). Matching, conflict resolution, modes, dispatch,
|
|
36
|
+
recursion limits, externals, and the type-test hook are therefore
|
|
37
|
+
**inherited from JSLT by construction**, not restated: this document
|
|
38
|
+
specifies only what JTLT adds —
|
|
39
|
+
|
|
40
|
+
- the **template document** (§2): the `$jtlt` envelope and its `output`
|
|
41
|
+
member;
|
|
42
|
+
- the **segment vocabulary** (§3): how rule bodies denote text;
|
|
43
|
+
- the **built-in template rules** (§4): what unmatched values render as;
|
|
44
|
+
- **serialization** (§5): the output methods and their escaping contract;
|
|
45
|
+
- the **error surface** (§9): `TL`-prefixed codes with `docPath` pointers
|
|
46
|
+
into the *template* document.
|
|
47
|
+
|
|
48
|
+
The layer lives at `packages/json/src/jtlt/` — a module boundary inside
|
|
49
|
+
`@jarenjs/json`, like `jslt/` and `xquery/`. It adds **zero operators** to
|
|
50
|
+
the query vocabulary and **zero members** to the JSLT vocabulary; the JSLT
|
|
51
|
+
engine compiles JTLT's output without knowing JTLT exists.
|
|
52
|
+
|
|
53
|
+
### 1.2 Conformance and normative language
|
|
54
|
+
|
|
55
|
+
The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL
|
|
56
|
+
NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and
|
|
57
|
+
**OPTIONAL** in this document are to be interpreted as described in
|
|
58
|
+
RFC 2119.
|
|
59
|
+
|
|
60
|
+
- A **producer** emits template documents and MUST emit documents valid per
|
|
61
|
+
this specification.
|
|
62
|
+
- A **consumer** (template compiler + renderer) MUST accept every valid
|
|
63
|
+
template, MUST reject invalid ones with the compile errors of §9, MUST
|
|
64
|
+
raise the runtime errors of §9 under the conditions specified there, and
|
|
65
|
+
MUST produce the exact output strings this document and its fixtures
|
|
66
|
+
define.
|
|
67
|
+
|
|
68
|
+
Everything JSLT-FORMAT.md specifies for stylesheets applies to the
|
|
69
|
+
compiled form of a template except where this document says otherwise; in
|
|
70
|
+
particular, everything QUERY-FORMAT.md specifies applies inside expression
|
|
71
|
+
segments verbatim.
|
|
72
|
+
|
|
73
|
+
### 1.3 Terminology
|
|
74
|
+
|
|
75
|
+
- **Template** — the top-level JSON document handed to the compiler (§2).
|
|
76
|
+
- **Rule** — one template rule object (§2.2).
|
|
77
|
+
- **Segment list** — a rule's body: a JSON array of segments (§3).
|
|
78
|
+
- **Segment** — one body element: literal text, an expression, a nested
|
|
79
|
+
segment list, or one of the three segment operators (§3.1).
|
|
80
|
+
- **Interpolation** — serializing an expression segment's result into the
|
|
81
|
+
output text (§3.2).
|
|
82
|
+
- **Output method** — the template's serialization mode, `"text"` or
|
|
83
|
+
`"xml"` (§5).
|
|
84
|
+
- **Built-in template rules** — the rendering of values no user rule
|
|
85
|
+
matches (§4).
|
|
86
|
+
|
|
87
|
+
### 1.4 What JTLT inherits from JSLT
|
|
88
|
+
|
|
89
|
+
The delegation table, stated once. Each row is normative by reference:
|
|
90
|
+
|
|
91
|
+
| concern | contract |
|
|
92
|
+
|---|---|
|
|
93
|
+
| rule `match` (path, schema, both) | JSLT-FORMAT §3 |
|
|
94
|
+
| conflict resolution (`priority`, document order) | JSLT-FORMAT §4, plus the reserved band of §2.2 |
|
|
95
|
+
| dispatch, recursion, `maxDepth` guard | JSLT-FORMAT §5.1, §5.4 |
|
|
96
|
+
| `$apply` value forms, semantics, location propagation, mode defaults | JSLT-FORMAT §6 |
|
|
97
|
+
| modes | JSLT-FORMAT §7 |
|
|
98
|
+
| parameters, reserved `root`/`path` externals | JSLT-FORMAT §8 |
|
|
99
|
+
| the `compileTypeTest` hook | JSLT-FORMAT §9 |
|
|
100
|
+
| expression evaluation inside segments | QUERY-FORMAT, all of it |
|
|
101
|
+
|
|
102
|
+
Two JSLT concepts do **not** carry over:
|
|
103
|
+
|
|
104
|
+
1. **`unmatched` dispositions.** JTLT's built-in template rules (§4) match
|
|
105
|
+
every value, so no value is ever "unmatched" in the JSLT sense; the
|
|
106
|
+
`share`/`fresh`/`error` vocabulary and the `modes` envelope member have
|
|
107
|
+
no meaning here and are not part of the template envelope (§2.1).
|
|
108
|
+
2. **Sharing semantics.** The result of a rendering is a fresh string;
|
|
109
|
+
JSLT's normative `===` sharing (JSLT-FORMAT §5.3) is about JSON
|
|
110
|
+
outputs and does not apply.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 2. The template document
|
|
115
|
+
|
|
116
|
+
### 2.1 Top level
|
|
117
|
+
|
|
118
|
+
A template is either:
|
|
119
|
+
|
|
120
|
+
1. a JSON **array of rules** (the shorthand form) — implies version
|
|
121
|
+
`"0.1"` and the `"text"` output method; or
|
|
122
|
+
2. the **envelope object**:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{ "$jtlt": "0.1",
|
|
126
|
+
"output": "xml",
|
|
127
|
+
"rules": [] }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`$jtlt` and `rules` are REQUIRED (`rules` MUST be an array of rules);
|
|
131
|
+
`output` is OPTIONAL. Unknown envelope members are compile error
|
|
132
|
+
TL0001. A `$jtlt` value other than the string `"0.1"` — including
|
|
133
|
+
non-string values — is compile error TL0006.
|
|
134
|
+
|
|
135
|
+
A template that is neither an array nor an object of the envelope shape is
|
|
136
|
+
TL0001.
|
|
137
|
+
|
|
138
|
+
- `"output"`: one of `"text"` (the default) or `"xml"` — the serialization
|
|
139
|
+
method of §5. Any other value is TL0001, and the error message SHOULD
|
|
140
|
+
name the supported methods. `"json"` and `"toml"` are deliberately not
|
|
141
|
+
methods — see §5.1.
|
|
142
|
+
|
|
143
|
+
There is no `unmatched` member and no `modes` member (§1.4); a producer
|
|
144
|
+
MUST NOT emit them and a consumer MUST reject them as unknown members.
|
|
145
|
+
|
|
146
|
+
### 2.2 Rules
|
|
147
|
+
|
|
148
|
+
A **rule** is an object with the members
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{ "match": "$.store.book[*]", "mode": "toc", "priority": 2, "body": ["- ", "$.title", "\n"] }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- `body` (REQUIRED) — a **segment list**: a JSON array of segments (§3).
|
|
155
|
+
A `body` that is not an array is TL0002 — this is the one place JTLT is
|
|
156
|
+
*narrower* than JSLT, where a body is any query expression.
|
|
157
|
+
- `match` (OPTIONAL) — what the rule fires on, per JSLT-FORMAT §3
|
|
158
|
+
verbatim: a JSONPath string, or an object with `path` and/or `schema`
|
|
159
|
+
members. A rule with no `match` matches every value. The template
|
|
160
|
+
compiler checks only that `match` is a string or an object (TL0002);
|
|
161
|
+
everything deeper is validated by the JSLT layer and surfaces as TL0005
|
|
162
|
+
(§9).
|
|
163
|
+
- `mode` (OPTIONAL) — a string naming the rule's mode (JSLT-FORMAT §7);
|
|
164
|
+
default is the unnamed mode `""`. A non-string `mode` is TL0002.
|
|
165
|
+
- `priority` (OPTIONAL) — a finite JSON number for explicit conflict
|
|
166
|
+
resolution (JSLT-FORMAT §4). A non-number or non-finite `priority` is
|
|
167
|
+
TL0002. Priorities **at or below `-1e307` are RESERVED** for the
|
|
168
|
+
built-in template rules (§4) and are compile error TL0003.
|
|
169
|
+
|
|
170
|
+
A rule that is not an object, or that lacks `body`, is TL0002. Unknown
|
|
171
|
+
rule members are TL0002 — the vocabulary is **closed**, the same culture
|
|
172
|
+
as the query and JSLT formats.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 3. Segments
|
|
177
|
+
|
|
178
|
+
### 3.1 The segment forms
|
|
179
|
+
|
|
180
|
+
A segment list is a JSON array; each element is exactly one of the
|
|
181
|
+
following. The list is **closed**: anything else is compile error TL0004,
|
|
182
|
+
pointing at the offending element.
|
|
183
|
+
|
|
184
|
+
| segment | meaning |
|
|
185
|
+
|---|---|
|
|
186
|
+
| string not starting with `$` | **literal text**, emitted raw (§5) |
|
|
187
|
+
| string starting with `$$` | **literal text** with one leading `$` removed: `"$$price"` emits `$price` — the query format's own escape, applied to text |
|
|
188
|
+
| any other string starting with `$` | an **expression segment**: a query expression per QUERY-FORMAT §4 (absolute path, variable-rooted path, or invalid — invalid forms are compile errors surfacing as TL0005), interpolated per §3.2 |
|
|
189
|
+
| array | a **nested segment list**, rendered in place; nesting is purely organizational and has no semantic effect |
|
|
190
|
+
| `{ "$apply": … }` (single-key) | an **apply splice** (§3.4) |
|
|
191
|
+
| `{ "$raw": expr }` (single-key) | **unescaped interpolation** (§3.3) |
|
|
192
|
+
| `{ "$json": expr }` (single-key) | **JSON embedding** (§3.3) |
|
|
193
|
+
| any other object with at least one `$`-prefixed key | an **expression segment**: an operator phrase per QUERY-FORMAT (FLWOR, `$if`, `$concat`, …), interpolated per §3.2 |
|
|
194
|
+
| object with no `$`-prefixed key | TL0004 — a map constructor cannot be serialized; this is always a mistake |
|
|
195
|
+
| number, boolean, `null` | TL0004 — write literal text as a string, so `42` and `"42"` cannot be silently conflated |
|
|
196
|
+
|
|
197
|
+
`$raw`, `$json`, and the segment-position treatment of `$apply` are
|
|
198
|
+
**segment-level forms**: they are recognized only as complete body-list
|
|
199
|
+
elements (at any nesting depth of segment lists). Inside an expression
|
|
200
|
+
segment, `$raw` and `$json` are unknown operators (a compile error via
|
|
201
|
+
TL0005), and `$apply` is the ordinary JSLT operator whose sequence result
|
|
202
|
+
becomes payload values — almost never what a template author wants; see
|
|
203
|
+
§3.4.
|
|
204
|
+
|
|
205
|
+
### 3.2 Interpolation
|
|
206
|
+
|
|
207
|
+
An expression segment is evaluated per QUERY-FORMAT with the dispatched
|
|
208
|
+
value as `$`, then serialized:
|
|
209
|
+
|
|
210
|
+
1. Each item of the result sequence is converted to its **text value**:
|
|
211
|
+
|
|
212
|
+
| item | text value |
|
|
213
|
+
|---|---|
|
|
214
|
+
| string | the string itself |
|
|
215
|
+
| number | the shortest round-trip decimal form (ECMAScript `Number::toString`) |
|
|
216
|
+
| `true` / `false` | `"true"` / `"false"` |
|
|
217
|
+
| `null` | the empty string |
|
|
218
|
+
| object or array | runtime error TL2001 |
|
|
219
|
+
|
|
220
|
+
2. The text values are joined with a **single space** (U+0020) — the XSLT
|
|
221
|
+
`xsl:value-of` separator default.
|
|
222
|
+
3. The empty sequence renders as the empty string.
|
|
223
|
+
4. Under the `"xml"` output method the joined text is escaped (§5.3);
|
|
224
|
+
under `"text"` it is emitted raw.
|
|
225
|
+
|
|
226
|
+
The `null` row is a deliberate deviation from the `$string` cast
|
|
227
|
+
(QUERY-FORMAT §8.10), which spells `null` as `"null"`: interpolation is
|
|
228
|
+
*text serialization* — an absent-ish value renders as nothing — while
|
|
229
|
+
`$string` is a *data cast*. An author who wants the spelling writes
|
|
230
|
+
`{ "$string": expr }` as the segment.
|
|
231
|
+
|
|
232
|
+
The TL2001 row is deliberate too: there is no default text value of a
|
|
233
|
+
container. Dispatch into it with `$apply`, or embed it with `$json`. The
|
|
234
|
+
error names the segment's `docPath`.
|
|
235
|
+
|
|
236
|
+
### 3.3 `$raw` and `$json`
|
|
237
|
+
|
|
238
|
+
- `{ "$raw": expr }` interpolates exactly per §3.2 steps 1–3 but is
|
|
239
|
+
**never escaped** — the XSLT `disable-output-escaping` analogue for
|
|
240
|
+
data-carried markup. Under the `"text"` method, `$raw` and a plain
|
|
241
|
+
expression segment are indistinguishable.
|
|
242
|
+
- `{ "$json": expr }` serializes each result item as **JSON text**
|
|
243
|
+
(ECMAScript `JSON.stringify`; containers are permitted and expected),
|
|
244
|
+
space-joins multiple items, and escapes the result per the output
|
|
245
|
+
method like any interpolation. The empty sequence renders as the empty
|
|
246
|
+
string.
|
|
247
|
+
|
|
248
|
+
### 3.4 `$apply` splices
|
|
249
|
+
|
|
250
|
+
As a segment, `{ "$apply": … }` takes every value form and semantics of
|
|
251
|
+
JSLT-FORMAT §6 — selector-only, `[selector, mode]`, location propagation,
|
|
252
|
+
the rule's own mode as the default target — and **splices the rendered
|
|
253
|
+
output** of the dispatched values into the surrounding text, in order.
|
|
254
|
+
Nothing separates consecutive dispatch outputs; rules own their own
|
|
255
|
+
whitespace.
|
|
256
|
+
|
|
257
|
+
Because JTLT rule bodies are segment lists — not object constructors —
|
|
258
|
+
the `[]` idiom that JSLT-FORMAT §6.3 warns about does not arise: a bare
|
|
259
|
+
`{ "$apply": … }` element and a nested `[{ "$apply": … }]` element render
|
|
260
|
+
identically (§3.1's nesting rule).
|
|
261
|
+
|
|
262
|
+
`$apply` remains an ordinary operator *inside* expression segments (it is
|
|
263
|
+
injected by the JSLT layer into every body compile), but there its result
|
|
264
|
+
items are interpolated as payload values — containers raise TL2001 —
|
|
265
|
+
rather than spliced as rendered text. Consumers MUST NOT alter that
|
|
266
|
+
inherited behavior; authors SHOULD keep `$apply` at segment level.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 4. The built-in template rules
|
|
271
|
+
|
|
272
|
+
Dispatching a value **no user rule matches** renders it with the built-in
|
|
273
|
+
template rules — the XSLT built-ins, restated for JSON:
|
|
274
|
+
|
|
275
|
+
- a **container** (object or array) applies templates to **every child in
|
|
276
|
+
document order**, in the current mode, and splices the results —
|
|
277
|
+
as if by `[{ "$apply": "$[*]" }]`;
|
|
278
|
+
- an **atom** (string, number, boolean, `null`) is **interpolated** per
|
|
279
|
+
§3.2, including method escaping — as if by `["$"]`.
|
|
280
|
+
|
|
281
|
+
Consequences, all normative:
|
|
282
|
+
|
|
283
|
+
1. The empty template renders any input as the concatenated text values
|
|
284
|
+
of its atoms, in document order (Appendix A.1).
|
|
285
|
+
2. `{ "$apply": "$.title" }` against an unmatched string is a *value-of
|
|
286
|
+
with rule-override capability*: it renders the string today, and a
|
|
287
|
+
later rule matching `$.title` takes over that rendering without the
|
|
288
|
+
call site changing (Appendix A.5).
|
|
289
|
+
3. Every value matches *some* rule, so JSLT's `unmatched` dispositions
|
|
290
|
+
never trigger (§1.4).
|
|
291
|
+
|
|
292
|
+
The built-in rules sit **below every user rule**: they lose to any user
|
|
293
|
+
rule of any priority. A consumer implementing them as ordinary appended
|
|
294
|
+
rules MUST place them at a priority at or below the reserved band of
|
|
295
|
+
§2.2, one per mode in use. A **matchless user rule** (JSLT default
|
|
296
|
+
priority `-1`) therefore replaces the built-in behavior for its mode —
|
|
297
|
+
the override mechanism, exactly as in XSLT.
|
|
298
|
+
|
|
299
|
+
Recursion through the built-in container rule and through `$apply` is
|
|
300
|
+
bounded by the inherited `maxDepth` guard (JSLT-FORMAT §5.4); exceeding
|
|
301
|
+
it surfaces as TL2003 wrapping JT2001.
|
|
302
|
+
|
|
303
|
+
> **Location note.** The built-in container rule's children are selected
|
|
304
|
+
> by a `$`-rooted path, so they carry locations and can match path rules
|
|
305
|
+
> below (JSLT-FORMAT §6.4). A value dispatched **location-less** — e.g.
|
|
306
|
+
> by `{ "$apply": "$" }`, whose bare-`$` selector is not a located path —
|
|
307
|
+
> can match only schema and matchless rules; the built-ins still render
|
|
308
|
+
> it.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## 5. Output methods and serialization
|
|
313
|
+
|
|
314
|
+
### 5.1 The `output` member
|
|
315
|
+
|
|
316
|
+
`"output"` selects how the rendered stream becomes the final string. 0.1
|
|
317
|
+
defines two methods; the member is designed for growth (a future method
|
|
318
|
+
is a new envelope value, not a new document shape).
|
|
319
|
+
|
|
320
|
+
**Non-normative rationale — why not `"json"` and `"toml"`.** JTLT's model
|
|
321
|
+
is a *text stream*: rules contribute character runs in document order.
|
|
322
|
+
JSON and TOML are *whole-document* serializations — they need the
|
|
323
|
+
complete value tree before the first byte is right, and their natural
|
|
324
|
+
producer is a JSLT transform followed by a serializer, not a template.
|
|
325
|
+
Keeping the method list stream-shaped keeps this contract honest.
|
|
326
|
+
|
|
327
|
+
### 5.2 The `"text"` method
|
|
328
|
+
|
|
329
|
+
Every emitted text run — literal, interpolation, `$raw`, `$json`, the
|
|
330
|
+
built-in atom rule — is written **verbatim**. There is no escaping, no
|
|
331
|
+
trimming, no newline normalization: whitespace in literal segments is the
|
|
332
|
+
author's, preserved exactly.
|
|
333
|
+
|
|
334
|
+
### 5.3 The `"xml"` method
|
|
335
|
+
|
|
336
|
+
Literal text segments and `$raw` interpolations are written verbatim —
|
|
337
|
+
literal template text *is* the markup, the XSLT literal-result-element /
|
|
338
|
+
T4 text-block contract. Every other emission — expression segments,
|
|
339
|
+
`$json` segments, and the built-in atom rule — is **escaped**: each
|
|
340
|
+
occurrence of the five characters below is replaced by its reference.
|
|
341
|
+
|
|
342
|
+
| character | replacement |
|
|
343
|
+
|---|---|
|
|
344
|
+
| `&` | `&` |
|
|
345
|
+
| `<` | `<` |
|
|
346
|
+
| `>` | `>` |
|
|
347
|
+
| `"` | `"` |
|
|
348
|
+
| `'` | `'` |
|
|
349
|
+
|
|
350
|
+
All five are always escaped, so one rule serves element content and
|
|
351
|
+
single- or double-quoted attribute values alike.
|
|
352
|
+
|
|
353
|
+
**Non-normative.** The method escapes *data*; it does not police
|
|
354
|
+
*documents*. Well-formedness — balanced tags, one root element, legal
|
|
355
|
+
name characters, no literal `&` in literal text — is the author's
|
|
356
|
+
responsibility, exactly as in T4. `"xml"` is equally suitable for HTML
|
|
357
|
+
output; the escape set is safe HTML.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## 6. The compiled stylesheet (non-normative)
|
|
362
|
+
|
|
363
|
+
Only the rendered string is normative. The reference implementation
|
|
364
|
+
reaches it by **desugaring**: literal segments become `$const` pairs,
|
|
365
|
+
expression segments become tagged constructors carrying their template
|
|
366
|
+
`docPath`, `$apply` passes through verbatim, and the built-in rules of §4
|
|
367
|
+
are appended as matchless rules at priority `-1e308` — one per mode
|
|
368
|
+
named anywhere in the template. The result is a valid JSLT 0.1 stylesheet
|
|
369
|
+
with **user rule indexes preserved**, exposed as `render.stylesheet`
|
|
370
|
+
(§11) so authors can inspect exactly what dispatches on their behalf.
|
|
371
|
+
|
|
372
|
+
Consumers MAY compile templates any other way — a fused single-walk
|
|
373
|
+
renderer is an explicitly anticipated future — provided every observable
|
|
374
|
+
of this document (output strings, error codes, template `docPath`s,
|
|
375
|
+
`render.stylesheet` validity) is preserved.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## 7. Externals and parameters
|
|
380
|
+
|
|
381
|
+
JSLT-FORMAT §8 verbatim: free variables in expression segments are the
|
|
382
|
+
template's parameters, bound at render time
|
|
383
|
+
(`render(data, { rate: 1.21 })`); `root` and `path` are reserved,
|
|
384
|
+
engine-bound names; the compiled renderer exposes `render.externals` with
|
|
385
|
+
the user parameters in first-appearance order. Appendix A.6 exercises all
|
|
386
|
+
three.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## 8. The type-test hook
|
|
391
|
+
|
|
392
|
+
JSLT-FORMAT §9 verbatim: `options.compileTypeTest` serves `schema` match
|
|
393
|
+
conditions and the `$valid`/`$assert`/`$as` operators inside expression
|
|
394
|
+
segments. A template using schema matches compiled without a hook fails
|
|
395
|
+
exactly as in JSLT (JT0006), surfacing as TL0005.
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## 9. Errors
|
|
400
|
+
|
|
401
|
+
### 9.1 Error objects
|
|
402
|
+
|
|
403
|
+
Consumers MUST raise compile-time errors as `JtltCompileError` and
|
|
404
|
+
runtime errors as `JtltRuntimeError`, with the same shape as the query
|
|
405
|
+
and JSLT errors:
|
|
406
|
+
|
|
407
|
+
- `code` — a stable identifier from the registry below;
|
|
408
|
+
- `message` — human-readable, non-normative;
|
|
409
|
+
- `docPath` — an RFC 6901 JSON Pointer into the **template document**
|
|
410
|
+
(e.g. `/rules/1/body/2`);
|
|
411
|
+
- `cause` — the wrapped underlying error, where the registry says so.
|
|
412
|
+
|
|
413
|
+
In the bare-array shorthand (§2.1) the document has no `rules` member;
|
|
414
|
+
`docPath` pointers then start at the rule index (`/1/body/2`).
|
|
415
|
+
|
|
416
|
+
**The remap requirement.** Errors raised by the underlying JSLT/query
|
|
417
|
+
layers point into the *compiled stylesheet*; a consumer MUST translate
|
|
418
|
+
`docPath` back into the template document wherever the pointer targets a
|
|
419
|
+
construct the template author wrote (a rule member, a segment, a position
|
|
420
|
+
inside an expression segment). Pointers into consumer-generated
|
|
421
|
+
constructs (the built-in rules of §4) carry `docPath` `""` — the whole
|
|
422
|
+
document. Message text MAY still quote compiled-stylesheet paths; the
|
|
423
|
+
`docPath` member is the contract.
|
|
424
|
+
|
|
425
|
+
### 9.2 Registry
|
|
426
|
+
|
|
427
|
+
| code | condition |
|
|
428
|
+
|---|---|
|
|
429
|
+
| TL0001 | template shape: not array/object, missing/invalid `rules`, unknown envelope member, unknown `output` method |
|
|
430
|
+
| TL0002 | rule shape: not an object, missing or non-array `body`, unknown member, `match`/`mode`/`priority` of the wrong type |
|
|
431
|
+
| TL0003 | `priority` in the reserved band (at or below `-1e307`) |
|
|
432
|
+
| TL0004 | not a segment: `null`/number/boolean element, or an object with no `$`-prefixed key |
|
|
433
|
+
| TL0005 | the compiled stylesheet was rejected — wraps `JsltCompileError` (which may itself wrap query or hook errors); `docPath` remapped per §9.1 |
|
|
434
|
+
| TL0006 | unknown `$jtlt` version |
|
|
435
|
+
| TL2001 | interpolating an object or array (§3.2); `docPath` names the segment |
|
|
436
|
+
| TL2002 | malformed segment stream — an engine-contract violation, never author error; a consumer bug if ever observed |
|
|
437
|
+
| TL2003 | rendering raised a `JsltRuntimeError` — depth guard, body runtime errors, unbound parameters; wraps it, `docPath` remapped per §9.1 |
|
|
438
|
+
|
|
439
|
+
TL0xxx are compile errors (`JtltCompileError`), TL2xxx runtime errors
|
|
440
|
+
(`JtltRuntimeError`) — the same numbering convention as JQ/JT.
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## 10. Correspondence with XSLT and T4 (non-normative)
|
|
445
|
+
|
|
446
|
+
| XSLT / T4 | JTLT 0.1 |
|
|
447
|
+
|---|---|
|
|
448
|
+
| `<xsl:output method="text"/>` / T4 template | `"output": "text"` (the default) |
|
|
449
|
+
| `<xsl:output method="xml"/>` | `"output": "xml"` (§5.3) |
|
|
450
|
+
| literal result text / T4 text block | literal string segment |
|
|
451
|
+
| `<xsl:value-of select="…"/>` / T4 `<#= … #>` | expression segment (§3.2) |
|
|
452
|
+
| `value-of`'s default `separator=" "` | the single-space sequence join (§3.2) |
|
|
453
|
+
| `<xsl:apply-templates select="…" mode="…"/>` | `{ "$apply": [selector, mode] }` splice (§3.4) |
|
|
454
|
+
| built-in template rules (text output) | §4 — containers recurse, atoms emit text |
|
|
455
|
+
| `disable-output-escaping="yes"` | `{ "$raw": expr }` |
|
|
456
|
+
| — (no analogue) | `{ "$json": expr }` |
|
|
457
|
+
| template rule / `match` / `mode` / `priority` | inherited from JSLT verbatim (§1.4) |
|
|
458
|
+
|
|
459
|
+
Deliberate deviations:
|
|
460
|
+
|
|
461
|
+
1. **Unmatched atoms render under the built-ins** exactly as XSLT's text
|
|
462
|
+
built-ins emit text nodes — including the classic surprise that an
|
|
463
|
+
over-broad `$apply` leaks stray text into the output. JTLT keeps the
|
|
464
|
+
behavior because it is what makes `$apply` a value-of (§4); the
|
|
465
|
+
remedy, as in XSLT, is a more precise selector or an overriding rule.
|
|
466
|
+
2. **`null` renders as nothing** (§3.2) — JSON's `null` is closer to an
|
|
467
|
+
absent text node than to the four-letter word.
|
|
468
|
+
3. **No indentation engine in 0.1.** T4's structured-whitespace helpers
|
|
469
|
+
and `xsl:output/@indent` have no analogue yet; literal whitespace is
|
|
470
|
+
preserved verbatim and is the whole story. An indentation story is
|
|
471
|
+
roadmap.
|
|
472
|
+
4. **One document, no imports, no named templates** — inherited from
|
|
473
|
+
JSLT 0.1's composition stance.
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## 11. API note (non-normative)
|
|
478
|
+
|
|
479
|
+
```
|
|
480
|
+
compileJtltStylesheet(doc, options) -> render
|
|
481
|
+
render(data, externals?) // string out
|
|
482
|
+
render.externals // user parameter names (§7)
|
|
483
|
+
render.output // the resolved output method
|
|
484
|
+
render.doc // deeply frozen copy of the template
|
|
485
|
+
render.stylesheet // the frozen compiled JSLT stylesheet (§6)
|
|
486
|
+
renderText(template, data, externals?, options?) // one-shot, WeakMap-cached
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Module: `@jarenjs/json/jtlt`. `options` carries `compileTypeTest` (§8) and
|
|
490
|
+
`maxDepth` (JSLT-FORMAT §5.4), both for `compileJtltStylesheet` and as the
|
|
491
|
+
optional fourth argument of `renderText`. The one-shot function is the
|
|
492
|
+
counterpart of `queryJson`/`transformJson`, caching compiled templates by
|
|
493
|
+
document identity and compile-option values in a WeakMap.
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Appendix A. Worked examples (normative fixtures)
|
|
498
|
+
|
|
499
|
+
Every example is complete and destined to run verbatim as engine tests.
|
|
500
|
+
Unless noted, the output method is the default `"text"` and the mode is
|
|
501
|
+
the unnamed mode. Output strings are shown with escaped newlines.
|
|
502
|
+
|
|
503
|
+
### A.1 The empty template renders the input's text
|
|
504
|
+
|
|
505
|
+
```json
|
|
506
|
+
[]
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Input:
|
|
510
|
+
|
|
511
|
+
```json
|
|
512
|
+
{ "greeting": "hello", "count": 2, "flag": true, "gap": null }
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Output: `"hello2true"` — the built-in rules walk the tree in document
|
|
516
|
+
order, atoms emit their text values, `null` emits nothing (§3.2, §4).
|
|
517
|
+
Contrast JSLT, whose empty stylesheet is the identity *JSON* transform.
|
|
518
|
+
|
|
519
|
+
### A.2 A Markdown list
|
|
520
|
+
|
|
521
|
+
```json
|
|
522
|
+
[ { "match": "$", "body": ["# Books\n", { "$apply": "$.store.book[*]" }] },
|
|
523
|
+
{ "match": "$.store.book[*]", "body": ["- ", "$.title", " (", "$.price", ")\n"] } ]
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
Input:
|
|
527
|
+
|
|
528
|
+
```json
|
|
529
|
+
{ "store": { "book": [ { "title": "A", "price": 8.95 },
|
|
530
|
+
{ "title": "B", "price": 12.99 } ] } }
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
Output: `"# Books\n- A (8.95)\n- B (12.99)\n"`. The root rule owns the
|
|
534
|
+
frame, the `$apply` splices one rendered line per book, each rule owns its
|
|
535
|
+
own newline (§3.4).
|
|
536
|
+
|
|
537
|
+
### A.3 XML — escaped interpolation, raw literals, `$raw`
|
|
538
|
+
|
|
539
|
+
```json
|
|
540
|
+
{ "$jtlt": "0.1",
|
|
541
|
+
"output": "xml",
|
|
542
|
+
"rules": [
|
|
543
|
+
{ "match": "$",
|
|
544
|
+
"body": ["<note title=\"", "$.title", "\">", { "$raw": "$.markup" }, "</note>"] }
|
|
545
|
+
] }
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Input:
|
|
549
|
+
|
|
550
|
+
```json
|
|
551
|
+
{ "title": "Q&A", "markup": "<b>hi</b>" }
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Output: `"<note title=\"Q&A\"><b>hi</b></note>"`. The interpolated
|
|
555
|
+
title is escaped — the same escape set serves the attribute value — while
|
|
556
|
+
the literal markup and the `$raw` splice pass through verbatim (§5.3).
|
|
557
|
+
|
|
558
|
+
### A.4 Two modes: table of contents + body text
|
|
559
|
+
|
|
560
|
+
```json
|
|
561
|
+
{ "$jtlt": "0.1",
|
|
562
|
+
"rules": [
|
|
563
|
+
{ "match": "$",
|
|
564
|
+
"body": ["TOC\n", { "$apply": ["$.sections[*]", "toc"] }, "\n",
|
|
565
|
+
{ "$apply": "$.sections[*]" }] },
|
|
566
|
+
{ "mode": "toc", "match": "$.sections[*]", "body": ["- ", "$.heading", "\n"] },
|
|
567
|
+
{ "match": "$.sections[*]",
|
|
568
|
+
"body": ["== ", "$.heading", " ==\n", "$.text", "\n"] }
|
|
569
|
+
] }
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Input:
|
|
573
|
+
|
|
574
|
+
```json
|
|
575
|
+
{ "sections": [
|
|
576
|
+
{ "heading": "Introduction", "text": "Start here." },
|
|
577
|
+
{ "heading": "Usage", "text": "Then this." } ] }
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Output:
|
|
581
|
+
|
|
582
|
+
```
|
|
583
|
+
"TOC\n- Introduction\n- Usage\n\n== Introduction ==\nStart here.\n== Usage ==\nThen this.\n"
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
The same sections render twice — once per mode, each mode with its own
|
|
587
|
+
rule chain, exactly JSLT-FORMAT §7 (and its Appendix A.4, in text).
|
|
588
|
+
|
|
589
|
+
### A.5 `$apply` is a value-of with rule override
|
|
590
|
+
|
|
591
|
+
```json
|
|
592
|
+
[ { "match": "$", "body": ["Title: ", { "$apply": "$.title" }, "\n"] } ]
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Input `{ "title": "Moby Dick" }` renders `"Title: Moby Dick\n"` — no rule
|
|
596
|
+
matches the string, so the built-in atom rule interpolates it (§4).
|
|
597
|
+
Appending a rule:
|
|
598
|
+
|
|
599
|
+
```json
|
|
600
|
+
[ { "match": "$", "body": ["Title: ", { "$apply": "$.title" }, "\n"] },
|
|
601
|
+
{ "match": "$.title", "body": ["«", "$", "»"] } ]
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
renders `"Title: «Moby Dick»\n"` — the override takes the rendering
|
|
605
|
+
without the call site changing.
|
|
606
|
+
|
|
607
|
+
### A.6 Parameters and the reserved externals
|
|
608
|
+
|
|
609
|
+
```json
|
|
610
|
+
[ { "match": "$..price",
|
|
611
|
+
"body": ["$path", " = ", { "$mul": ["$", "$rate"] }, " ", "$root.currency", "\n"] } ]
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Called as `render(input, { rate: 1.21 })`; `render.externals` is
|
|
615
|
+
`["rate"]` — `root` and `path` are engine-bound and excluded (§7).
|
|
616
|
+
|
|
617
|
+
Input:
|
|
618
|
+
|
|
619
|
+
```json
|
|
620
|
+
{ "currency": "EUR", "items": [ { "sku": "a1", "price": 10 } ] }
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
Output: `"$['items'][0]['price'] = 12.1 EUR\n"`. The other atoms
|
|
624
|
+
(`"EUR"`, `"a1"`) sit outside the matched location's rendering only
|
|
625
|
+
because the matched rule's output replaces the price *within the
|
|
626
|
+
built-in walk* — the walk still visits `currency` and `sku`, so the full
|
|
627
|
+
output begins with `EUR` and contains `a1`: precisely,
|
|
628
|
+
`"EURa1$['items'][0]['price'] = 12.1 EUR\n"`. This is deviation 1 of §10
|
|
629
|
+
in action; match the root to own the frame (A.2) when stray text is
|
|
630
|
+
unwelcome.
|
|
631
|
+
|
|
632
|
+
### A.7 Embedding JSON with `$json`
|
|
633
|
+
|
|
634
|
+
```json
|
|
635
|
+
[ { "match": "$", "body": ["const data = ", { "$json": "$" }, ";"] } ]
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
Input `{ "a": [1, 2] }` renders `"const data = {\"a\":[1,2]};"` — the one
|
|
639
|
+
sanctioned way to put a container into the stream (§3.3).
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
## Appendix B. LLM structured output (non-normative)
|
|
644
|
+
|
|
645
|
+
No schema artifact is published for templates in version 0.1 — this is
|
|
646
|
+
the honest gap between JTLT and its siblings, and closing it is roadmap.
|
|
647
|
+
The intended derivation follows the JSLT artifact's discipline
|
|
648
|
+
(JSLT-FORMAT Appendix B): reuse the committed query artifact's definition
|
|
649
|
+
map for expression segments, define the segment alternation of §3.1 over
|
|
650
|
+
it (strings, nested lists, `$raw`/`$json`/`$apply` phrases), reuse the
|
|
651
|
+
JSLT artifact's rule scaffolding with `body` narrowed to the segment-list
|
|
652
|
+
array, and derive a draft-07 twin mechanically. Until that artifact
|
|
653
|
+
exists, generated templates should be validated by compiling them:
|
|
654
|
+
`compileJtltStylesheet` is the authority, and every rejection carries a
|
|
655
|
+
`docPath` into the template for a repair loop.
|
|
656
|
+
|
|
657
|
+
Templates remain ordinary JSON throughout a toolchain — function-call
|
|
658
|
+
arguments, retrieved rule sets, reviewed diffs, audit-log entries, and
|
|
659
|
+
replayable renderers without a text parser.
|