@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,155 @@
|
|
|
1
|
+
//#region Jaren JTLT public API
|
|
2
|
+
// JTLT: template-driven text output over the Jaren JSLT dispatcher.
|
|
3
|
+
// A JTLT template is a JSLT-shaped rule document whose bodies are
|
|
4
|
+
// segment lists (literal text, interpolated queries, $apply splices)
|
|
5
|
+
// and whose result is a string. It compiles down to an ordinary JSLT
|
|
6
|
+
// 0.1 stylesheet - inspectable as `render.stylesheet` - so dispatch,
|
|
7
|
+
// modes, conflict resolution, and schema matching are inherited, not
|
|
8
|
+
// reimplemented.
|
|
9
|
+
|
|
10
|
+
import { deepFreezeCopy } from '../query/normalize.js';
|
|
11
|
+
import {
|
|
12
|
+
compileJsltStylesheet,
|
|
13
|
+
JsltCompileError,
|
|
14
|
+
JsltRuntimeError,
|
|
15
|
+
} from '../jslt/index.js';
|
|
16
|
+
import { JtltCompileError, JtltRuntimeError } from './errors.js';
|
|
17
|
+
import { normalizeJtltTemplate } from './template.js';
|
|
18
|
+
import { desugarTemplate, remapDocPath } from './desugar.js';
|
|
19
|
+
import { createWriter } from './writer.js';
|
|
20
|
+
|
|
21
|
+
export { JtltCompileError, JtltRuntimeError } from './errors.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Compile a Jaren JTLT 0.1 template into a reusable renderer.
|
|
25
|
+
*
|
|
26
|
+
* The returned function renders any input document to a string in the
|
|
27
|
+
* template's output method ('text' by default, 'xml' for markup with
|
|
28
|
+
* escaped interpolation). Metadata:
|
|
29
|
+
*
|
|
30
|
+
* - `render.externals` - user parameter names in first-appearance order
|
|
31
|
+
* (`root` and `path` are engine-bound and excluded)
|
|
32
|
+
* - `render.output` - the resolved output method
|
|
33
|
+
* - `render.doc` - an independent, deeply frozen template copy
|
|
34
|
+
* - `render.stylesheet` - the frozen JSLT stylesheet it compiled to
|
|
35
|
+
*
|
|
36
|
+
* @param {any} doc - a bare rule array or `{"$jtlt":"0.1","output":...,
|
|
37
|
+
* "rules":[]}` template envelope
|
|
38
|
+
* @param {object} [options] - compile options, passed to the JSLT layer
|
|
39
|
+
* @param {(schemaJson: any, docPath: string) => ((value: any) => boolean)}
|
|
40
|
+
* [options.compileTypeTest] - validator-agnostic hook for schema
|
|
41
|
+
* matches and schema operators inside segment expressions
|
|
42
|
+
* @param {number} [options.maxDepth=1024] - maximum dispatch nesting depth
|
|
43
|
+
* @returns {function} reusable `render(data, externals?)` function
|
|
44
|
+
* @throws {JtltCompileError} when compilation fails
|
|
45
|
+
* @example
|
|
46
|
+
* const render = compileJtltStylesheet([
|
|
47
|
+
* { match: '$.items[*]', body: ['- ', '$.name', '\n'] },
|
|
48
|
+
* ]);
|
|
49
|
+
* render({ items: [{ name: 'a' }, { name: 'b' }] });
|
|
50
|
+
* // '- a\n- b\n'
|
|
51
|
+
*/
|
|
52
|
+
export function compileJtltStylesheet(doc, options = {}) {
|
|
53
|
+
const frozenDoc = deepFreezeCopy(doc);
|
|
54
|
+
const model = normalizeJtltTemplate(frozenDoc);
|
|
55
|
+
const stylesheet = desugarTemplate(model);
|
|
56
|
+
let transform;
|
|
57
|
+
try {
|
|
58
|
+
transform = compileJsltStylesheet(stylesheet, options);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (!(error instanceof JsltCompileError))
|
|
62
|
+
throw error;
|
|
63
|
+
throw new JtltCompileError('TL0005', error.message,
|
|
64
|
+
remapDocPath(model, error.docPath), error);
|
|
65
|
+
}
|
|
66
|
+
const write = createWriter(model.output);
|
|
67
|
+
|
|
68
|
+
const render = (data, externals) => {
|
|
69
|
+
let value;
|
|
70
|
+
try {
|
|
71
|
+
value = transform(data, externals);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (!(error instanceof JsltRuntimeError))
|
|
75
|
+
throw error;
|
|
76
|
+
throw new JtltRuntimeError('TL2003', error.message,
|
|
77
|
+
remapDocPath(model, error.docPath), error);
|
|
78
|
+
}
|
|
79
|
+
return write(value);
|
|
80
|
+
};
|
|
81
|
+
render.externals = transform.externals;
|
|
82
|
+
render.output = model.output;
|
|
83
|
+
render.doc = frozenDoc;
|
|
84
|
+
render.stylesheet = transform.doc;
|
|
85
|
+
return render;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const TEMPLATE_CACHE = new WeakMap();
|
|
89
|
+
|
|
90
|
+
function cachedRender(template, options) {
|
|
91
|
+
let record = TEMPLATE_CACHE.get(template);
|
|
92
|
+
if (record === undefined) {
|
|
93
|
+
record = {
|
|
94
|
+
defaultRender: null,
|
|
95
|
+
variants: null,
|
|
96
|
+
};
|
|
97
|
+
TEMPLATE_CACHE.set(template, record);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const compileTypeTest = typeof options?.compileTypeTest === 'function'
|
|
101
|
+
? options.compileTypeTest
|
|
102
|
+
: null;
|
|
103
|
+
const maxDepth = options?.maxDepth === undefined ? 1024 : options.maxDepth;
|
|
104
|
+
if (compileTypeTest === null && maxDepth === 1024) {
|
|
105
|
+
if (record.defaultRender === null)
|
|
106
|
+
record.defaultRender = compileJtltStylesheet(template);
|
|
107
|
+
return record.defaultRender;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let variants = record.variants;
|
|
111
|
+
if (variants === null) {
|
|
112
|
+
variants = [];
|
|
113
|
+
record.variants = variants;
|
|
114
|
+
}
|
|
115
|
+
for (let i = 0; i < variants.length; i++) {
|
|
116
|
+
const variant = variants[i];
|
|
117
|
+
if (variant.compileTypeTest === compileTypeTest && variant.maxDepth === maxDepth)
|
|
118
|
+
return variant.render;
|
|
119
|
+
}
|
|
120
|
+
const render = compileJtltStylesheet(template, options);
|
|
121
|
+
variants.push({ compileTypeTest, maxDepth, render });
|
|
122
|
+
return render;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Render a JSON value with a JTLT template in one call. Object/array
|
|
127
|
+
* template documents are compiled once and cached by identity in a
|
|
128
|
+
* WeakMap.
|
|
129
|
+
* @param {any} template - JTLT template document
|
|
130
|
+
* @param {any} data - input JSON value
|
|
131
|
+
* @param {object} [externals] - user parameter bindings
|
|
132
|
+
* @param {object} [options] - compile options used on a cache miss
|
|
133
|
+
* @param {(schemaJson: any, docPath: string) => ((value: any) => boolean)}
|
|
134
|
+
* [options.compileTypeTest] - schema type-test compiler
|
|
135
|
+
* @param {number} [options.maxDepth=1024] - maximum dispatch nesting depth
|
|
136
|
+
* @returns {string} the rendered output text
|
|
137
|
+
* @throws {JtltCompileError} when compilation fails
|
|
138
|
+
* @throws {JtltRuntimeError} when rendering fails
|
|
139
|
+
* @example
|
|
140
|
+
* renderText([{ match: '$.name', body: ['Hello ', '$', '!'] }],
|
|
141
|
+
* { name: 'world' });
|
|
142
|
+
* // 'Hello world!'
|
|
143
|
+
*/
|
|
144
|
+
export function renderText(template, data, externals, options = undefined) {
|
|
145
|
+
let render;
|
|
146
|
+
if (typeof template === 'object' && template !== null) {
|
|
147
|
+
render = cachedRender(template, options);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
render = compileJtltStylesheet(template, options);
|
|
151
|
+
}
|
|
152
|
+
return render(data, externals);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//#region Jaren JTLT template normalizer
|
|
2
|
+
// Validates the template envelope and rule shape. Segment bodies are
|
|
3
|
+
// validated during desugaring (desugar.js); everything the JSLT layer
|
|
4
|
+
// checks itself (match details, mode strings inside $apply, query
|
|
5
|
+
// vocabulary) is deliberately left to it so the two vocabularies cannot
|
|
6
|
+
// drift apart.
|
|
7
|
+
|
|
8
|
+
import { JtltCompileError } from './errors.js';
|
|
9
|
+
|
|
10
|
+
const hasOwn = Object.hasOwn;
|
|
11
|
+
const ENVELOPE_KEYS = new Set(['$jtlt', 'output', 'rules']);
|
|
12
|
+
const RULE_KEYS = new Set(['match', 'mode', 'priority', 'body']);
|
|
13
|
+
const OUTPUT_METHODS = new Set(['text', 'xml']);
|
|
14
|
+
|
|
15
|
+
// Priorities at or below the floor are reserved for the injected
|
|
16
|
+
// built-in rules, which sit at -1e308 - beneath every user rule.
|
|
17
|
+
export const RESERVED_PRIORITY_FLOOR = -1e307;
|
|
18
|
+
|
|
19
|
+
function isObject(value) {
|
|
20
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function escapeToken(token) {
|
|
24
|
+
if (token.indexOf('~') < 0 && token.indexOf('/') < 0)
|
|
25
|
+
return token;
|
|
26
|
+
return token.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function fail(code, message, docPath) {
|
|
30
|
+
throw new JtltCompileError(code, message, docPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeRule(value, index, rulesPath) {
|
|
34
|
+
const rulePath = rulesPath + '/' + index;
|
|
35
|
+
if (!isObject(value))
|
|
36
|
+
fail('TL0002', 'a template rule must be an object', rulePath);
|
|
37
|
+
|
|
38
|
+
const keys = Object.keys(value);
|
|
39
|
+
for (let i = 0; i < keys.length; i++) {
|
|
40
|
+
if (!RULE_KEYS.has(keys[i]))
|
|
41
|
+
fail('TL0002', `unknown rule member '${keys[i]}'`,
|
|
42
|
+
rulePath + '/' + escapeToken(keys[i]));
|
|
43
|
+
}
|
|
44
|
+
if (!hasOwn(value, 'body'))
|
|
45
|
+
fail('TL0002', "a template rule requires 'body'", rulePath + '/body');
|
|
46
|
+
if (!Array.isArray(value.body))
|
|
47
|
+
fail('TL0002', "'body' must be a segment array", rulePath + '/body');
|
|
48
|
+
if (hasOwn(value, 'mode') && typeof value.mode !== 'string')
|
|
49
|
+
fail('TL0002', "'mode' must be a string", rulePath + '/mode');
|
|
50
|
+
const hasPriority = hasOwn(value, 'priority');
|
|
51
|
+
if (hasPriority) {
|
|
52
|
+
if (typeof value.priority !== 'number' || !Number.isFinite(value.priority))
|
|
53
|
+
fail('TL0002', "'priority' must be a finite JSON number", rulePath + '/priority');
|
|
54
|
+
if (value.priority <= RESERVED_PRIORITY_FLOOR)
|
|
55
|
+
fail('TL0003', `priorities at or below ${RESERVED_PRIORITY_FLOOR} are reserved for the built-in rules`,
|
|
56
|
+
rulePath + '/priority');
|
|
57
|
+
}
|
|
58
|
+
const hasMatch = hasOwn(value, 'match');
|
|
59
|
+
if (hasMatch && typeof value.match !== 'string' && !isObject(value.match))
|
|
60
|
+
fail('TL0002', "'match' must be a JSONPath string or an object", rulePath + '/match');
|
|
61
|
+
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
index,
|
|
64
|
+
docPath: rulePath,
|
|
65
|
+
bodyDocPath: rulePath + '/body',
|
|
66
|
+
body: value.body,
|
|
67
|
+
hasMatch,
|
|
68
|
+
match: hasMatch ? value.match : null,
|
|
69
|
+
mode: hasOwn(value, 'mode') ? value.mode : '',
|
|
70
|
+
hasPriority,
|
|
71
|
+
priority: hasPriority ? value.priority : 0,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Normalize a frozen JTLT template document into a frozen model.
|
|
77
|
+
* @param {any} doc - deeply frozen template document
|
|
78
|
+
* @returns {object} frozen template model
|
|
79
|
+
* @throws {JtltCompileError} on TL0001-TL0003 and TL0006 shape errors
|
|
80
|
+
*/
|
|
81
|
+
export function normalizeJtltTemplate(doc) {
|
|
82
|
+
let sourceRules;
|
|
83
|
+
let rulesPath;
|
|
84
|
+
let output = 'text';
|
|
85
|
+
|
|
86
|
+
if (Array.isArray(doc)) {
|
|
87
|
+
sourceRules = doc;
|
|
88
|
+
rulesPath = '';
|
|
89
|
+
}
|
|
90
|
+
else if (isObject(doc)) {
|
|
91
|
+
const keys = Object.keys(doc);
|
|
92
|
+
for (let i = 0; i < keys.length; i++) {
|
|
93
|
+
if (!ENVELOPE_KEYS.has(keys[i]))
|
|
94
|
+
fail('TL0001', `unknown template member '${keys[i]}'`,
|
|
95
|
+
'/' + escapeToken(keys[i]));
|
|
96
|
+
}
|
|
97
|
+
if (!hasOwn(doc, '$jtlt'))
|
|
98
|
+
fail('TL0001', "the template envelope requires '$jtlt'", '/$jtlt');
|
|
99
|
+
if (doc.$jtlt !== '0.1')
|
|
100
|
+
fail('TL0006', `unknown JTLT format version ${JSON.stringify(doc.$jtlt)}`, '/$jtlt');
|
|
101
|
+
if (hasOwn(doc, 'output') && !OUTPUT_METHODS.has(doc.output)) {
|
|
102
|
+
fail('TL0001',
|
|
103
|
+
`unknown output method ${JSON.stringify(doc.output)} (supported: "text", "xml")`,
|
|
104
|
+
'/output');
|
|
105
|
+
}
|
|
106
|
+
if (hasOwn(doc, 'output'))
|
|
107
|
+
output = doc.output;
|
|
108
|
+
if (!hasOwn(doc, 'rules'))
|
|
109
|
+
fail('TL0001', "the template envelope requires 'rules'", '/rules');
|
|
110
|
+
if (!Array.isArray(doc.rules))
|
|
111
|
+
fail('TL0001', "'rules' must be an array", '/rules');
|
|
112
|
+
sourceRules = doc.rules;
|
|
113
|
+
rulesPath = '/rules';
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
fail('TL0001', 'a template must be a rule array or a version envelope', '');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const rules = new Array(sourceRules.length);
|
|
120
|
+
for (let i = 0; i < sourceRules.length; i++)
|
|
121
|
+
rules[i] = normalizeRule(sourceRules[i], i, rulesPath);
|
|
122
|
+
|
|
123
|
+
return Object.freeze({
|
|
124
|
+
output,
|
|
125
|
+
rulesPrefix: rulesPath,
|
|
126
|
+
rules: Object.freeze(rules),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
//#region Jaren JTLT writer
|
|
2
|
+
// Serializes the tagged segment stream a desugared stylesheet produces
|
|
3
|
+
// (see desugar.js for the closed grammar) into the output text. The
|
|
4
|
+
// writer is chosen once per compiled template; escaping applies to
|
|
5
|
+
// interpolated data only - literal template text is the author's markup
|
|
6
|
+
// and passes through raw, exactly as in XSLT and T4.
|
|
7
|
+
|
|
8
|
+
import { JtltRuntimeError } from './errors.js';
|
|
9
|
+
|
|
10
|
+
const XML_TEST = /[&<>"']/;
|
|
11
|
+
const XML_PATTERN = /[&<>"']/g;
|
|
12
|
+
|
|
13
|
+
function xmlEntity(c) {
|
|
14
|
+
switch (c) {
|
|
15
|
+
case '&': return '&';
|
|
16
|
+
case '<': return '<';
|
|
17
|
+
case '>': return '>';
|
|
18
|
+
case '"': return '"';
|
|
19
|
+
default: return ''';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function escapeXml(s) {
|
|
24
|
+
return XML_TEST.test(s) ? s.replace(XML_PATTERN, xmlEntity) : s;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The text value of one interpolated item: null is empty (text
|
|
28
|
+
// serialization, not the $string cast), containers are a template error
|
|
29
|
+
// pointing at the segment that produced them.
|
|
30
|
+
function stringifyItem(v, docPath) {
|
|
31
|
+
switch (typeof v) {
|
|
32
|
+
case 'string':
|
|
33
|
+
return v;
|
|
34
|
+
case 'number':
|
|
35
|
+
return String(v);
|
|
36
|
+
case 'boolean':
|
|
37
|
+
return v ? 'true' : 'false';
|
|
38
|
+
default:
|
|
39
|
+
if (v === null)
|
|
40
|
+
return '';
|
|
41
|
+
throw new JtltRuntimeError('TL2001',
|
|
42
|
+
`cannot interpolate ${Array.isArray(v) ? 'an array' : 'an object'} into text output; dispatch into it with $apply, or embed it with $json`,
|
|
43
|
+
docPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Multi-item payloads (a sequence-valued expression) join with a single
|
|
48
|
+
// space, the XSLT value-of separator default.
|
|
49
|
+
function pushPayload(pair, parts, escape, asJson) {
|
|
50
|
+
const tag = pair[0];
|
|
51
|
+
const docPath = tag.length > 1 ? tag.slice(1) : '';
|
|
52
|
+
for (let i = 1; i < pair.length; i++) {
|
|
53
|
+
if (i > 1)
|
|
54
|
+
parts.push(' ');
|
|
55
|
+
const s = asJson ? JSON.stringify(pair[i]) : stringifyItem(pair[i], docPath);
|
|
56
|
+
parts.push(escape === null ? s : escape(s));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function walk(x, parts, escape) {
|
|
61
|
+
if (!Array.isArray(x)) {
|
|
62
|
+
// every dispatched node fires a rule and every rule body is a
|
|
63
|
+
// segment constructor, so a bare value here is an engine-contract
|
|
64
|
+
// break, never author error
|
|
65
|
+
throw new JtltRuntimeError('TL2002',
|
|
66
|
+
'malformed segment stream (engine contract violation)', '');
|
|
67
|
+
}
|
|
68
|
+
const head = x.length === 0 ? null : x[0];
|
|
69
|
+
if (typeof head === 'string') {
|
|
70
|
+
const kind = head.charCodeAt(0);
|
|
71
|
+
if (kind === 0x72) { // 'r'
|
|
72
|
+
parts.push(x[1]);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (kind === 0x65) { // 'e'
|
|
76
|
+
pushPayload(x, parts, escape, false);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (kind === 0x77) { // 'w'
|
|
80
|
+
pushPayload(x, parts, null, false);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (kind === 0x6A) { // 'j'
|
|
84
|
+
pushPayload(x, parts, escape, true);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
throw new JtltRuntimeError('TL2002',
|
|
88
|
+
`unknown segment tag ${JSON.stringify(head)}`, '');
|
|
89
|
+
}
|
|
90
|
+
for (let i = 0; i < x.length; i++)
|
|
91
|
+
walk(x[i], parts, escape);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create the serializer for one output method.
|
|
96
|
+
* @param {'text' | 'xml'} method - the template's output method
|
|
97
|
+
* @returns {(value: any) => string} segment-stream serializer
|
|
98
|
+
*/
|
|
99
|
+
export function createWriter(method) {
|
|
100
|
+
const escape = method === 'xml' ? escapeXml : null;
|
|
101
|
+
return (value) => {
|
|
102
|
+
if (value === undefined)
|
|
103
|
+
return '';
|
|
104
|
+
const parts = [];
|
|
105
|
+
walk(value, parts, escape);
|
|
106
|
+
return parts.join('');
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|