@endevops/effect-codec-xml 0.0.1

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 (61) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSE-is-entities +21 -0
  3. package/LICENSE-is-xml-naming +21 -0
  4. package/README.md +415 -0
  5. package/dist/codec.d.ts +48 -0
  6. package/dist/codec.d.ts.map +1 -0
  7. package/dist/codec.js +63 -0
  8. package/dist/codec.js.map +1 -0
  9. package/dist/conventions.d.ts +88 -0
  10. package/dist/conventions.d.ts.map +1 -0
  11. package/dist/conventions.js +113 -0
  12. package/dist/conventions.js.map +1 -0
  13. package/dist/entities/entity-decoder.d.ts +333 -0
  14. package/dist/entities/entity-decoder.d.ts.map +1 -0
  15. package/dist/entities/entity-decoder.js +841 -0
  16. package/dist/entities/entity-decoder.js.map +1 -0
  17. package/dist/entities/entity-tables.js +16 -0
  18. package/dist/entities/entity-tables.js.map +1 -0
  19. package/dist/errors.d.ts +49 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +48 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/index.d.ts +11 -0
  24. package/dist/index.js +11 -0
  25. package/dist/namespaces.d.ts +101 -0
  26. package/dist/namespaces.d.ts.map +1 -0
  27. package/dist/namespaces.js +663 -0
  28. package/dist/namespaces.js.map +1 -0
  29. package/dist/naming.d.ts +149 -0
  30. package/dist/naming.d.ts.map +1 -0
  31. package/dist/naming.js +296 -0
  32. package/dist/naming.js.map +1 -0
  33. package/dist/parse.d.ts +75 -0
  34. package/dist/parse.d.ts.map +1 -0
  35. package/dist/parse.js +437 -0
  36. package/dist/parse.js.map +1 -0
  37. package/dist/render.d.ts +99 -0
  38. package/dist/render.d.ts.map +1 -0
  39. package/dist/render.js +509 -0
  40. package/dist/render.js.map +1 -0
  41. package/dist/xml-error.d.ts +172 -0
  42. package/dist/xml-error.d.ts.map +1 -0
  43. package/dist/xml-error.js +157 -0
  44. package/dist/xml-error.js.map +1 -0
  45. package/dist/xml-value.d.ts +42 -0
  46. package/dist/xml-value.d.ts.map +1 -0
  47. package/dist/xml-value.js +79 -0
  48. package/dist/xml-value.js.map +1 -0
  49. package/package.json +69 -0
  50. package/src/codec.ts +136 -0
  51. package/src/conventions.ts +145 -0
  52. package/src/entities/entity-decoder.ts +1248 -0
  53. package/src/entities/entity-tables.ts +18 -0
  54. package/src/errors.ts +55 -0
  55. package/src/index.ts +79 -0
  56. package/src/namespaces.ts +968 -0
  57. package/src/naming.ts +519 -0
  58. package/src/parse.ts +597 -0
  59. package/src/render.ts +708 -0
  60. package/src/xml-error.ts +168 -0
  61. package/src/xml-value.ts +108 -0
@@ -0,0 +1,75 @@
1
+ import { XmlVersion } from "./naming.js";
2
+ import { XmlParseError } from "./errors.js";
3
+ import { NameMode } from "./conventions.js";
4
+ import { XmlValue } from "./xml-value.js";
5
+ import { Effect } from "effect";
6
+ //#region src/parse.d.ts
7
+ /**
8
+ * @description A parsed document: the root element's name, and its content as an {@link XmlValue}.
9
+ */
10
+ export interface XmlDocument {
11
+ /**
12
+ * @description The root element's name as it appeared in the source, after name resolution.
13
+ */
14
+ readonly name: string;
15
+ /**
16
+ * @description The root element's content. The root's own name is not part of it, the same way a schema's encoded form does not carry a name for the value it
17
+ * describes.
18
+ */
19
+ readonly value: XmlValue;
20
+ }
21
+ /**
22
+ * @description Options for {@link parseXml} and {@link parseXmlDocument}.
23
+ */
24
+ export interface XmlParseOptions {
25
+ /**
26
+ * @description Keep the whitespace at the edges of every text run.
27
+ *
28
+ * @default false\
29
+ * which trims it — and trimming is what makes a pretty-printed document
30
+ * read as the same value as an unindented one, because the indentation around a child element and around a closing tag lands at the edges of its
31
+ * parent's text. Whitespace _inside_ a run is content and is never touched either way, so `'one two'` and a paragraph with a newline in the middle
32
+ * of it survive. Set it to `true` to keep leading and trailing spaces in text exactly as written, at the cost of a document that was laid out on
33
+ * several lines no longer reading the same as one that was not.
34
+ */
35
+ readonly preserveWhitespace?: boolean | undefined;
36
+ /**
37
+ * @description How deep to nest before giving up. Guards against a document crafted to exhaust the stack.
38
+ *
39
+ * @default 256
40
+ */
41
+ readonly maxDepth?: number | undefined;
42
+ /**
43
+ * @description What to do with an element or attribute name that is not a legal XML name.
44
+ *
45
+ * @default 'repair'\
46
+ * the same default {@link renderXml} uses, so a name that renders and a name that parses come out the same.
47
+ */
48
+ readonly name?: NameMode | undefined;
49
+ /**
50
+ * @description XML version to validate names against.
51
+ *
52
+ * @default '1.0'
53
+ */
54
+ readonly xmlVersion?: XmlVersion | undefined;
55
+ }
56
+ /**
57
+ * @description Parses an XML document into its root element's content.\
58
+ * The walk itself is synchronous, but it reports a malformed document by failing with an {@link XmlParseError} rather than by throwing, so the failure lands in the effect's error channel where `catchTag`, `retry` and a fallback can all
59
+ * see it. A failed parse is an expected outcome of reading untrusted text — it is what those combinators key off — and only a defect would hide it.
60
+ * The span is the boundary a performance trace hangs off: it carries the document's length, which is the size that drives the parser's cost, so a
61
+ * slow parse in a profile can be attributed to the input that produced it. A caller that wants the value outside an `Effect` uses
62
+ * {@link parseXmlDocument}, which runs the same walk synchronously and throws instead. The walk is plain recursive descent rather than a chain of
63
+ * `yield*`es. Publicly `parseXml` is still an `Effect` — it suspends the walk so it runs lazily under the span, and folds the failure the walk throws
64
+ * into the typed error channel — but inside a document there is no effect boundary per tag, attribute or text run. A 500-row report is thousands of
65
+ * those, and a fiber step for each of them was most of what the `parse 500 rows` row measured. The typed failure survives: the walk throws an
66
+ * {@link XmlParseError} and `parseXml` catches it into `Effect.fail`.
67
+ *
68
+ * @param text - The document to read.
69
+ * @param options - Whitespace, depth and name-handling settings.
70
+ *
71
+ * @returns An effect producing the root element's content as an {@link XmlValue}.
72
+ */
73
+ export declare const parseXml: (text: string, options?: XmlParseOptions) => Effect.Effect<XmlValue, XmlParseError>;
74
+ //#endregion
75
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","names":[],"sources":["../src/parse.ts"],"mappings":";;;;;;;;;iBAeiB;;;;WAIN;;;;;WAMA,OAAO;;;;;iBAMD;;;;;;;;;;;WAWN;;;;;;WAOA;;;;;;;WAQA,OAAO;;;;;;WAOP,aAAa;;;;;;;;;;;;;;;;;;;qBAoBX,WAAQ,cAAgB,UAAW,oBAAuB,OAAO,OAAO,UAAU"}
package/dist/parse.js ADDED
@@ -0,0 +1,437 @@
1
+ import { XmlParseError } from "./errors.js";
2
+ import { TEXT_KEY, resolveNameSync } from "./conventions.js";
3
+ import { EntityDecoder } from "./entities/entity-decoder.js";
4
+ import { Effect, Predicate, Result } from "effect";
5
+ //#region src/parse.ts
6
+ const decoder = EntityDecoder.make().pipe(Effect.runSync);
7
+ /**
8
+ * @description Parses an XML document into its root element's content.\
9
+ * The walk itself is synchronous, but it reports a malformed document by failing with an {@link XmlParseError} rather than by throwing, so the failure lands in the effect's error channel where `catchTag`, `retry` and a fallback can all
10
+ * see it. A failed parse is an expected outcome of reading untrusted text — it is what those combinators key off — and only a defect would hide it.
11
+ * The span is the boundary a performance trace hangs off: it carries the document's length, which is the size that drives the parser's cost, so a
12
+ * slow parse in a profile can be attributed to the input that produced it. A caller that wants the value outside an `Effect` uses
13
+ * {@link parseXmlDocument}, which runs the same walk synchronously and throws instead. The walk is plain recursive descent rather than a chain of
14
+ * `yield*`es. Publicly `parseXml` is still an `Effect` — it suspends the walk so it runs lazily under the span, and folds the failure the walk throws
15
+ * into the typed error channel — but inside a document there is no effect boundary per tag, attribute or text run. A 500-row report is thousands of
16
+ * those, and a fiber step for each of them was most of what the `parse 500 rows` row measured. The typed failure survives: the walk throws an
17
+ * {@link XmlParseError} and `parseXml` catches it into `Effect.fail`.
18
+ *
19
+ * @param text - The document to read.
20
+ * @param options - Whitespace, depth and name-handling settings.
21
+ *
22
+ * @returns An effect producing the root element's content as an {@link XmlValue}.
23
+ */
24
+ const parseXml = (text, options = {}) => Effect.suspend(() => Effect.fromResult(parseDocumentResult(text, options))).pipe(Effect.map((document) => document.value), Effect.withSpan("XmlCodec.parseXml", { attributes: { "xml.length": text.length } }));
25
+ /**
26
+ * @description Whether a character is XML whitespace. XML defines exactly four, and they are the only ones a parser may treat as insignificant.
27
+ *
28
+ * @param code - A UTF-16 code unit.
29
+ *
30
+ * @returns Whether the character is XML whitespace.
31
+ */
32
+ const isWhitespace = (code) => code === 32 || code === 10 || code === 9 || code === 13;
33
+ /**
34
+ * @description `<`
35
+ */
36
+ const LT = 60;
37
+ /**
38
+ * @description `>`
39
+ */
40
+ const GT = 62;
41
+ /**
42
+ * @description `/`
43
+ */
44
+ const SLASH = 47;
45
+ /**
46
+ * @description `=`
47
+ */
48
+ const EQUALS = 61;
49
+ /**
50
+ * @description Parses a whole document: a prolog, exactly one root element, and nothing but whitespace after it. The walk is synchronous and reports a malformed
51
+ * document by throwing an {@link XmlParseError}; {@link parseXml} folds that into the effect's typed error channel.
52
+ *
53
+ * @param text - The document to read.
54
+ * @param options - Whitespace, depth and name-handling settings.
55
+ *
56
+ * @returns The root element's name and content.
57
+ *
58
+ * @throws {XmlParseError} When the document is not well-formed.
59
+ */
60
+ const parseDocument = (text, options) => {
61
+ const resolved = {
62
+ preserveWhitespace: options.preserveWhitespace ?? false,
63
+ maxDepth: options.maxDepth ?? 256,
64
+ name: options.name ?? "repair",
65
+ xmlVersion: options.xmlVersion ?? "1.0"
66
+ };
67
+ let at = 0;
68
+ /**
69
+ * @description The options every name is resolved with, built once. They cannot change during a parse, and building them per name would allocate one object per
70
+ * element and per attribute in the document.
71
+ */
72
+ const nameOptions = {
73
+ mode: resolved.name,
74
+ xmlVersion: resolved.xmlVersion
75
+ };
76
+ /**
77
+ * @description Names already resolved by this parse. A document repeats names — every one of five hundred rows has a `sku` — and a validator that ran per
78
+ * occurrence would pay for the same answer five hundred times.
79
+ */
80
+ const nameCache = /* @__PURE__ */ new Map();
81
+ const resolve = (raw, what, position) => {
82
+ const cached = nameCache.get(raw);
83
+ if (cached !== void 0) return cached;
84
+ let name;
85
+ try {
86
+ name = resolveNameSync(raw, nameOptions);
87
+ } catch (failure) {
88
+ const reason = Predicate.isError(failure) ? failure.message : String(failure);
89
+ throw new XmlParseError({
90
+ message: `${what} ${JSON.stringify(raw)} is not a legal XML name: ${reason}`,
91
+ position,
92
+ input: text
93
+ });
94
+ }
95
+ nameCache.set(raw, name);
96
+ return name;
97
+ };
98
+ /**
99
+ * @description Reads to the end of a `<!-- -->`, `<? ?>` or `<!DOCTYPE >` construct, and reports the one past its last character.
100
+ */
101
+ const skipUntil = (marker, start, what) => {
102
+ const end = text.indexOf(marker, start);
103
+ if (end === -1) throw new XmlParseError({
104
+ message: `Unterminated ${what}`,
105
+ position: start,
106
+ input: text
107
+ });
108
+ return end + marker.length;
109
+ };
110
+ const skipDoctype = (start) => {
111
+ let depth = 0;
112
+ for (let i = start + 9; i < text.length; i++) {
113
+ const char = text[i];
114
+ if (char === "[") depth++;
115
+ else if (char === "]") depth--;
116
+ else if (char === ">" && depth <= 0) return i + 1;
117
+ }
118
+ throw new XmlParseError({
119
+ message: "Unterminated DOCTYPE declaration",
120
+ position: start,
121
+ input: text
122
+ });
123
+ };
124
+ /**
125
+ * @description Consumes whitespace, comments, processing instructions and a DOCTYPE, leaving the cursor on the first character that is none of them — or at the
126
+ * end of the document.
127
+ */
128
+ const skipMisc = () => {
129
+ for (;;) {
130
+ while (at < text.length && isWhitespace(text.charCodeAt(at))) at++;
131
+ if (at >= text.length) return;
132
+ if (text.charCodeAt(at) !== LT) return;
133
+ if (text.startsWith("<!--", at)) at = skipUntil("-->", at + 4, "comment");
134
+ else if (text.startsWith("<?", at)) at = skipUntil("?>", at + 2, "processing instruction");
135
+ else if (text.startsWith("<!DOCTYPE", at)) at = skipDoctype(at);
136
+ else return;
137
+ }
138
+ };
139
+ /**
140
+ * @description Reads a name up to the character that ends it, advancing the cursor past it.
141
+ */
142
+ const readName = (what) => {
143
+ const start = at;
144
+ while (at < text.length) {
145
+ const char = text.charCodeAt(at);
146
+ if (isWhitespace(char) || char === SLASH || char === EQUALS || char === GT) break;
147
+ at++;
148
+ }
149
+ if (at === start) throw new XmlParseError({
150
+ message: `Expected a ${what}`,
151
+ position: start,
152
+ input: text
153
+ });
154
+ return text.slice(start, at);
155
+ };
156
+ const skipSpaces = () => {
157
+ while (at < text.length && isWhitespace(text.charCodeAt(at))) at++;
158
+ };
159
+ const readAttributeValue = (name, nameStart) => {
160
+ const quote = text[at];
161
+ if (quote !== "\"" && quote !== "'") throw new XmlParseError({
162
+ message: `Attribute "${name}" has no quoted value`,
163
+ position: nameStart,
164
+ input: text
165
+ });
166
+ at++;
167
+ const end = text.indexOf(quote ?? "", at);
168
+ if (end === -1) throw new XmlParseError({
169
+ message: `Unterminated value for attribute "${name}"`,
170
+ position: at,
171
+ input: text
172
+ });
173
+ const raw = text.slice(at, end);
174
+ at = end + 1;
175
+ return decodeEntities(raw);
176
+ };
177
+ const readStartTag = () => {
178
+ const record = {};
179
+ let hasAttributes = false;
180
+ for (;;) {
181
+ skipSpaces();
182
+ if (at >= text.length) throw new XmlParseError({
183
+ message: "Unterminated start tag",
184
+ position: at,
185
+ input: text
186
+ });
187
+ if (text.charCodeAt(at) === GT) {
188
+ at++;
189
+ return {
190
+ record,
191
+ selfClosing: false,
192
+ hasAttributes
193
+ };
194
+ }
195
+ if (text.charCodeAt(at) === SLASH && text[at + 1] === ">") {
196
+ at += 2;
197
+ return {
198
+ record,
199
+ selfClosing: true,
200
+ hasAttributes
201
+ };
202
+ }
203
+ const nameStart = at;
204
+ const name = resolve(readName("attribute name"), "Attribute", nameStart);
205
+ skipSpaces();
206
+ if (text.charCodeAt(at) !== EQUALS) throw new XmlParseError({
207
+ message: `Attribute "${name}" has no "="`,
208
+ position: at,
209
+ input: text
210
+ });
211
+ at++;
212
+ skipSpaces();
213
+ record["@" + name] = readAttributeValue(name, nameStart);
214
+ hasAttributes = true;
215
+ }
216
+ };
217
+ const readElement = (depth) => {
218
+ if (depth > resolved.maxDepth) throw new XmlParseError({
219
+ message: `Element nesting exceeded maxDepth (${resolved.maxDepth})`,
220
+ position: at,
221
+ input: text
222
+ });
223
+ if (text.charCodeAt(at) !== LT) throw new XmlParseError({
224
+ message: "Expected an element",
225
+ position: at,
226
+ input: text
227
+ });
228
+ at++;
229
+ const name = resolve(readName("element name"), "Element", at);
230
+ const { record, selfClosing, hasAttributes } = readStartTag();
231
+ if (selfClosing) return {
232
+ name,
233
+ value: finishElement(record, hasAttributes, "", false)
234
+ };
235
+ const content = readContent(name, record, depth);
236
+ return {
237
+ name,
238
+ value: finishElement(record, hasAttributes, content.text, content.hasChildren)
239
+ };
240
+ };
241
+ /**
242
+ * @description Reads an element's body up to and including its closing tag, folding what it finds into the record the start tag produced. Returns when the
243
+ * closing tag has been consumed; failing on it is {@link readClosingTag}'s job, so that a mismatched or unclosed tag is reported the same way
244
+ * wherever it was found.
245
+ *
246
+ * @param name - The name the start tag gave the element, which its closing tag has to match.
247
+ * @param record - The record to fold the children into.
248
+ * @param depth - The depth the element sits at; its children are one deeper.
249
+ *
250
+ * @returns The body as character data, and whether it held any child element.
251
+ */
252
+ const readContent = (name, record, depth) => {
253
+ let childText = "";
254
+ let hasChildren = false;
255
+ for (;;) switch (classifyContent(name)) {
256
+ case "text":
257
+ childText += readTextRun();
258
+ break;
259
+ case "close":
260
+ readClosingTag(name);
261
+ return {
262
+ text: childText,
263
+ hasChildren
264
+ };
265
+ case "comment":
266
+ at = skipUntil("-->", at + 4, "comment");
267
+ break;
268
+ case "cdata":
269
+ childText += readCdata();
270
+ break;
271
+ case "instruction":
272
+ at = skipUntil("?>", at + 2, "processing instruction");
273
+ break;
274
+ case "child":
275
+ hasChildren = true;
276
+ addChild(record, readElement(depth + 1));
277
+ }
278
+ };
279
+ /**
280
+ * @description What the cursor is sitting on inside an element's body. The two things the loop cannot read are refused here rather than in it: running out of
281
+ * document and a declaration, which is markup the parser does not accept inside an element. Recognising the constructs that _are_ read is the rest,
282
+ * and the order is the one that rules out the shorter prefixes first — `</` before `<?` before any other `<!`, and `<![CDATA[` before the `<!` that
283
+ * would otherwise match it.
284
+ *
285
+ * @param name - The name the enclosing element's start tag gave it, for the unterminated-body message.
286
+ *
287
+ * @returns What the cursor is on.
288
+ */
289
+ const classifyContent = (name) => {
290
+ if (at >= text.length) throw new XmlParseError({
291
+ message: `Unclosed element <${name}>`,
292
+ position: at,
293
+ input: text
294
+ });
295
+ if (text.charCodeAt(at) !== LT) return "text";
296
+ if (text.startsWith("</", at)) return "close";
297
+ if (text.startsWith("<!--", at)) return "comment";
298
+ if (text.startsWith("<![CDATA[", at)) return "cdata";
299
+ if (text.startsWith("<?", at)) return "instruction";
300
+ if (text.startsWith("<!", at)) throw new XmlParseError({
301
+ message: "A declaration is not allowed inside an element",
302
+ position: at,
303
+ input: text
304
+ });
305
+ return "child";
306
+ };
307
+ /**
308
+ * @description Consumes a `</name>`, checking on the way that it is the tag that closes this element and that it is well-formed.
309
+ *
310
+ * @param name - The name the start tag gave the element, which the closing tag has to match.
311
+ */
312
+ const readClosingTag = (name) => {
313
+ const closeStart = at;
314
+ at += 2;
315
+ const closing = readName("element name");
316
+ if (closing !== name) throw new XmlParseError({
317
+ message: `Closing tag </${closing}> does not match <${name}>`,
318
+ position: closeStart,
319
+ input: text
320
+ });
321
+ skipSpaces();
322
+ if (text.charCodeAt(at) !== GT) throw new XmlParseError({
323
+ message: `Malformed closing tag </${closing}>`,
324
+ position: at,
325
+ input: text
326
+ });
327
+ at++;
328
+ };
329
+ /**
330
+ * @description Reads the run of character data up to the next `<`, or to the end of the document.
331
+ *
332
+ * @returns The run, with its character references expanded.
333
+ */
334
+ const readTextRun = () => {
335
+ const next = text.indexOf("<", at);
336
+ const end = next === -1 ? text.length : next;
337
+ const run = decodeEntities(text.slice(at, end));
338
+ at = end;
339
+ return run;
340
+ };
341
+ /**
342
+ * @description Reads a `<![CDATA[…]]>` section. CDATA is character data, and character data is what it holds, so it joins the element's text as it stands — the
343
+ * entities in it are literal text and must not be expanded.
344
+ *
345
+ * @returns The section's contents.
346
+ */
347
+ const readCdata = () => {
348
+ const end = text.indexOf("]]>", at + 9);
349
+ if (end === -1) throw new XmlParseError({
350
+ message: "Unterminated CDATA section",
351
+ position: at,
352
+ input: text
353
+ });
354
+ const data = text.slice(at + 9, end);
355
+ at = end + 3;
356
+ return data;
357
+ };
358
+ /**
359
+ * @description Adds a child to its parent's record. Two children under one name make an array, and the first one does not: a schema can tell a repeated field
360
+ * from a single one by the shape, and an array of one is not what a single value encodes to.
361
+ *
362
+ * @param record - The parent's record, added to in place.
363
+ * @param child - The child element as it was read.
364
+ */
365
+ const addChild = (record, child) => {
366
+ const existing = record[child.name];
367
+ if (existing === void 0) record[child.name] = child.value;
368
+ else if (Array.isArray(existing)) existing.push(child.value);
369
+ else record[child.name] = [existing, child.value];
370
+ };
371
+ /**
372
+ * @description Decides what an element with the given attributes, text and children reduces to.
373
+ */
374
+ const finishElement = (record, hasAttributes, text, hasChildren) => {
375
+ const content = resolved.preserveWhitespace ? text : text.trim();
376
+ if (!hasAttributes && !hasChildren) return content;
377
+ if (content !== "") record[TEXT_KEY] = content;
378
+ return record;
379
+ };
380
+ skipMisc();
381
+ if (at >= text.length || text.charCodeAt(at) !== LT) throw new XmlParseError({
382
+ message: "Document has no root element",
383
+ position: at,
384
+ input: text
385
+ });
386
+ const root = readElement(0);
387
+ skipMisc();
388
+ if (at < text.length) throw new XmlParseError({
389
+ message: "Unexpected content after the root element",
390
+ position: at,
391
+ input: text
392
+ });
393
+ return {
394
+ name: root.name,
395
+ value: root.value
396
+ };
397
+ };
398
+ /**
399
+ * @description Runs the synchronous walk and folds the one failure it reports into a {@link Result}, which {@link parseXml} turns back into an `Effect`. Kept
400
+ * separate so the walk itself can throw without the public API ever throwing.
401
+ *
402
+ * @param text - The document to read.
403
+ * @param options - The options as the caller wrote them.
404
+ *
405
+ * @returns The document, or the failure to report.
406
+ */
407
+ const parseDocumentResult = (text, options) => {
408
+ try {
409
+ return Result.succeed(parseDocument(text, options));
410
+ } catch (cause) {
411
+ if (cause instanceof XmlParseError) return Result.fail(cause);
412
+ return Result.fail(new XmlParseError({
413
+ message: Predicate.isError(cause) ? cause.message : String(cause),
414
+ position: -1,
415
+ input: text
416
+ }));
417
+ }
418
+ };
419
+ /**
420
+ * @description Decodes character references, falling back to the raw text when the reference is not one the decoder recognises. The fallback is what makes a bare
421
+ * `&` survivable: the decoder treats it as a malformed reference and fails, and a document containing one is far more likely to be worth reading than
422
+ * to be rejected. The `&` is escaped on the way out, so the value still round-trips. The decoder answers with an `Effect`, and this is the one place
423
+ * a parse still runs one. It is only reached when the raw text holds an `&` — the common case returns before it — and the effect is synchronous, so
424
+ * the run is cheap next to the decoder's own work.
425
+ *
426
+ * @param raw - Text read straight from the source, with references unexpanded.
427
+ *
428
+ * @returns The decoded text, which cannot fail.
429
+ */
430
+ const decodeEntities = (raw) => {
431
+ if (raw.indexOf("&") === -1) return raw;
432
+ return Effect.runSync(Effect.orElseSucceed(decoder.decode(raw), () => raw));
433
+ };
434
+ //#endregion
435
+ export { parseXml };
436
+
437
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","names":[],"sources":["../src/parse.ts"],"sourcesContent":["import { Effect, Predicate, Result } from 'effect';\n\nimport type { NameMode } from './conventions.ts';\nimport type { XmlVersion } from './naming.ts';\nimport type { XmlValue } from './xml-value.ts';\n\nimport { ATTRIBUTE_PREFIX, resolveNameSync, TEXT_KEY } from './conventions.ts';\nimport { EntityDecoder } from './entities/entity-decoder.ts';\nimport { XmlParseError } from './errors.ts';\n\nconst decoder = EntityDecoder.make().pipe(Effect.runSync);\n\n/**\n * @description A parsed document: the root element's name, and its content as an {@link XmlValue}.\n */\nexport interface XmlDocument {\n /**\n * @description The root element's name as it appeared in the source, after name resolution.\n */\n readonly name: string;\n\n /**\n * @description The root element's content. The root's own name is not part of it, the same way a schema's encoded form does not carry a name for the value it\n * describes.\n */\n readonly value: XmlValue;\n}\n\n/**\n * @description Options for {@link parseXml} and {@link parseXmlDocument}.\n */\nexport interface XmlParseOptions {\n /**\n * @description Keep the whitespace at the edges of every text run.\n *\n * @default false\\\n * which trims it — and trimming is what makes a pretty-printed document\n * read as the same value as an unindented one, because the indentation around a child element and around a closing tag lands at the edges of its\n * parent's text. Whitespace _inside_ a run is content and is never touched either way, so `'one two'` and a paragraph with a newline in the middle\n * of it survive. Set it to `true` to keep leading and trailing spaces in text exactly as written, at the cost of a document that was laid out on\n * several lines no longer reading the same as one that was not.\n */\n readonly preserveWhitespace?: boolean | undefined;\n\n /**\n * @description How deep to nest before giving up. Guards against a document crafted to exhaust the stack.\n *\n * @default 256\n */\n readonly maxDepth?: number | undefined;\n\n /**\n * @description What to do with an element or attribute name that is not a legal XML name.\n *\n * @default 'repair'\\\n * the same default {@link renderXml} uses, so a name that renders and a name that parses come out the same.\n */\n readonly name?: NameMode | undefined;\n\n /**\n * @description XML version to validate names against.\n *\n * @default '1.0'\n */\n readonly xmlVersion?: XmlVersion | undefined;\n}\n\n/**\n * @description Parses an XML document into its root element's content.\\\n * The walk itself is synchronous, but it reports a malformed document by failing with an {@link XmlParseError} rather than by throwing, so the failure lands in the effect's error channel where `catchTag`, `retry` and a fallback can all\n * see it. A failed parse is an expected outcome of reading untrusted text — it is what those combinators key off — and only a defect would hide it.\n * The span is the boundary a performance trace hangs off: it carries the document's length, which is the size that drives the parser's cost, so a\n * slow parse in a profile can be attributed to the input that produced it. A caller that wants the value outside an `Effect` uses\n * {@link parseXmlDocument}, which runs the same walk synchronously and throws instead. The walk is plain recursive descent rather than a chain of\n * `yield*`es. Publicly `parseXml` is still an `Effect` — it suspends the walk so it runs lazily under the span, and folds the failure the walk throws\n * into the typed error channel — but inside a document there is no effect boundary per tag, attribute or text run. A 500-row report is thousands of\n * those, and a fiber step for each of them was most of what the `parse 500 rows` row measured. The typed failure survives: the walk throws an\n * {@link XmlParseError} and `parseXml` catches it into `Effect.fail`.\n *\n * @param text - The document to read.\n * @param options - Whitespace, depth and name-handling settings.\n *\n * @returns An effect producing the root element's content as an {@link XmlValue}.\n */\nexport const parseXml = (text: string, options: XmlParseOptions = {}): Effect.Effect<XmlValue, XmlParseError> =>\n Effect.suspend(() => Effect.fromResult(parseDocumentResult(text, options))).pipe(\n Effect.map(document => document.value),\n Effect.withSpan('XmlCodec.parseXml', { attributes: { 'xml.length': text.length } })\n );\n\n/**\n * @description Parses an XML document, keeping the root element's name. This is the synchronous form of {@link parseXml}: it runs the same walk and throws the\n * {@link XmlParseError} the effect would have failed with, for a caller that is not already in an `Effect`.\n *\n * @deprecated\n *\n * @param text - The document to read.\n * @param options - Whitespace, depth and name-handling settings.\n *\n * @returns The root element's name and content.\n *\n * @throws {XmlParseError} When the document is not well-formed.\n */\nexport const parseXmlDocument = (text: string, options: XmlParseOptions = {}): XmlDocument => parseDocument(text, options);\n\n/**\n * @description Options every parse call needs, with the defaults already applied.\n */\ninterface ResolvedOptions {\n readonly preserveWhitespace: boolean;\n readonly maxDepth: number;\n readonly name: NameMode;\n readonly xmlVersion: XmlVersion;\n}\n\n/**\n * @description Whether a character is XML whitespace. XML defines exactly four, and they are the only ones a parser may treat as insignificant.\n *\n * @param code - A UTF-16 code unit.\n *\n * @returns Whether the character is XML whitespace.\n */\nconst isWhitespace = (code: number): boolean => code === 32 || code === 10 || code === 9 || code === 13;\n\n/**\n * @description `<`\n */\nconst LT = 60;\n/**\n * @description `>`\n */\nconst GT = 62;\n/**\n * @description `/`\n */\nconst SLASH = 47;\n/**\n * @description `=`\n */\nconst EQUALS = 61;\n\n/**\n * @description One element as the parser saw it: the name it was written under, and the value it holds. Carrying the name alongside the value is what lets the\n * parent file it correctly — the value alone cannot say, because a text-only element reduces to a bare string.\n */\ninterface Element {\n readonly name: string;\n readonly value: XmlValue;\n}\n\n/**\n * @description What a start tag yielded: the record its attributes went into, which becomes the element's value, and whether the tag closed itself.\n */\ninterface StartTag {\n readonly record: Record<string, XmlValue>;\n readonly selfClosing: boolean;\n\n /**\n * @description Whether the tag carried any attribute. Counted as they are read rather than asked of the record afterwards, which would mean a key array per\n * element.\n */\n readonly hasAttributes: boolean;\n}\n\n/**\n * @description An element's body as the parser read it: the character data it accumulated, and whether any child element went into the record.\n */\ninterface Content {\n /**\n * @description Every text run in the body, concatenated in the order they appeared.\n */\n readonly text: string;\n\n /**\n * @description Whether the body held at least one child element. Counted rather than asked of the record afterwards, because a child whose fields are all absent\n * leaves no trace of itself in the record and must still keep its element from being written self-closing.\n */\n readonly hasChildren: boolean;\n}\n\n/**\n * @description What sits at the cursor inside an element's body. Naming what is there before deciding what to do with it is what lets the content loop stay a\n * dispatch: each construct is recognised in one place, against the ones that cannot be confused with it, rather than by a chain of `startsWith`\n * guesses where each had to remember what the last had already ruled out.\n */\ntype Construct = 'text' | 'close' | 'comment' | 'cdata' | 'instruction' | 'child';\n\n/**\n * @description Parses a whole document: a prolog, exactly one root element, and nothing but whitespace after it. The walk is synchronous and reports a malformed\n * document by throwing an {@link XmlParseError}; {@link parseXml} folds that into the effect's typed error channel.\n *\n * @param text - The document to read.\n * @param options - Whitespace, depth and name-handling settings.\n *\n * @returns The root element's name and content.\n *\n * @throws {XmlParseError} When the document is not well-formed.\n */\nconst parseDocument = (text: string, options: XmlParseOptions): XmlDocument => {\n const resolved: ResolvedOptions = {\n preserveWhitespace: options.preserveWhitespace ?? false,\n maxDepth: options.maxDepth ?? 256,\n name: options.name ?? 'repair',\n xmlVersion: options.xmlVersion ?? '1.0',\n };\n\n let at = 0;\n\n /**\n * @description The options every name is resolved with, built once. They cannot change during a parse, and building them per name would allocate one object per\n * element and per attribute in the document.\n */\n const nameOptions = { mode: resolved.name, xmlVersion: resolved.xmlVersion };\n\n /**\n * @description Names already resolved by this parse. A document repeats names — every one of five hundred rows has a `sku` — and a validator that ran per\n * occurrence would pay for the same answer five hundred times.\n */\n const nameCache = new Map<string, string>();\n\n const resolve = (raw: string, what: string, position: number): string => {\n const cached = nameCache.get(raw);\n if (cached !== undefined) return cached;\n\n // `resolveNameSync` reports an illegal name by throwing an `XmlParseError`; the failure is reworded\n // here so it names the position in the document and whether the name belonged to an element or\n // an attribute, which a generic name resolver cannot know.\n let name: string;\n try {\n name = resolveNameSync(raw, nameOptions);\n } catch (failure) {\n const reason = Predicate.isError(failure) ? failure.message : String(failure);\n throw new XmlParseError({ message: `${what} ${JSON.stringify(raw)} is not a legal XML name: ${reason}`, position, input: text });\n }\n\n nameCache.set(raw, name);\n return name;\n };\n\n /**\n * @description Reads to the end of a `<!-- -->`, `<? ?>` or `<!DOCTYPE >` construct, and reports the one past its last character.\n */\n const skipUntil = (marker: string, start: number, what: string): number => {\n const end = text.indexOf(marker, start);\n if (end === -1) throw new XmlParseError({ message: `Unterminated ${what}`, position: start, input: text });\n return end + marker.length;\n };\n\n const skipDoctype = (start: number): number => {\n let depth = 0;\n for (let i = start + 9; i < text.length; i++) {\n const char = text[i];\n if (char === '[') depth++;\n else if (char === ']') depth--;\n else if (char === '>' && depth <= 0) return i + 1;\n }\n throw new XmlParseError({ message: 'Unterminated DOCTYPE declaration', position: start, input: text });\n };\n\n /**\n * @description Consumes whitespace, comments, processing instructions and a DOCTYPE, leaving the cursor on the first character that is none of them — or at the\n * end of the document.\n */\n const skipMisc = (): void => {\n for (;;) {\n while (at < text.length && isWhitespace(text.charCodeAt(at))) {\n at++;\n }\n\n if (at >= text.length) {\n return; // whitespace ran to the end of the document: consumed, and that is the end\n }\n\n if (text.charCodeAt(at) !== LT) {\n return; // real content: leave the cursor on it for the caller\n }\n\n if (text.startsWith('<!--', at)) {\n at = skipUntil('-->', at + 4, 'comment');\n } else if (text.startsWith('<?', at)) {\n at = skipUntil('?>', at + 2, 'processing instruction');\n } else if (text.startsWith('<!DOCTYPE', at)) {\n at = skipDoctype(at);\n } else {\n return; // the start of the root element, or of a closing tag\n }\n }\n };\n\n /**\n * @description Reads a name up to the character that ends it, advancing the cursor past it.\n */\n const readName = (what: string): string => {\n const start = at;\n while (at < text.length) {\n const char = text.charCodeAt(at);\n // Whitespace, `/`, `=` and `>` all end a name. Stopping on `/` and `>` is what lets `<a/>` and `<a>` share one loop.\n if (isWhitespace(char) || char === SLASH || char === EQUALS || char === GT) {\n break;\n }\n at++;\n }\n if (at === start) {\n throw new XmlParseError({ message: `Expected a ${what}`, position: start, input: text });\n }\n return text.slice(start, at);\n };\n\n const skipSpaces = (): void => {\n while (at < text.length && isWhitespace(text.charCodeAt(at))) at++;\n };\n\n const readAttributeValue = (name: string, nameStart: number): string => {\n const quote = text[at];\n // `indexOf` below is only reached once `quote` is known to be a real quote,\n // which the guard establishes; the `?? ''` is unreachable and exists only to\n // keep the type of the index lookup a `string`.\n if (quote !== '\"' && quote !== \"'\") {\n throw new XmlParseError({ message: `Attribute \"${name}\" has no quoted value`, position: nameStart, input: text });\n }\n\n at++;\n\n const end = text.indexOf(quote ?? '', at);\n // A raw quote cannot appear inside a quoted value — it would have to be written `&quot;` — so the next quote of the same kind always closes it.\n if (end === -1) {\n throw new XmlParseError({ message: `Unterminated value for attribute \"${name}\"`, position: at, input: text });\n }\n\n const raw = text.slice(at, end);\n at = end + 1;\n\n return decodeEntities(raw);\n };\n\n const readStartTag = (): StartTag => {\n // Built as the record the element will end up holding rather than as a\n // separate set of attributes, so that folding the text and the children into\n // it later costs no copy. One object per element instead of two.\n const record: Record<string, XmlValue> = {};\n let hasAttributes = false;\n for (;;) {\n skipSpaces();\n if (at >= text.length) throw new XmlParseError({ message: 'Unterminated start tag', position: at, input: text });\n if (text.charCodeAt(at) === GT) {\n at++;\n return { record, selfClosing: false, hasAttributes };\n }\n if (text.charCodeAt(at) === SLASH && text[at + 1] === '>') {\n at += 2;\n return { record, selfClosing: true, hasAttributes };\n }\n const nameStart = at;\n const name = resolve(readName('attribute name'), 'Attribute', nameStart);\n skipSpaces();\n if (text.charCodeAt(at) !== EQUALS) throw new XmlParseError({ message: `Attribute \"${name}\" has no \"=\"`, position: at, input: text });\n at++;\n skipSpaces();\n record[ATTRIBUTE_PREFIX + name] = readAttributeValue(name, nameStart);\n hasAttributes = true;\n }\n };\n\n const readElement = (depth: number): Element => {\n if (depth > resolved.maxDepth)\n throw new XmlParseError({ message: `Element nesting exceeded maxDepth (${resolved.maxDepth})`, position: at, input: text });\n if (text.charCodeAt(at) !== LT) throw new XmlParseError({ message: 'Expected an element', position: at, input: text });\n at++;\n\n const name = resolve(readName('element name'), 'Element', at);\n const { record, selfClosing, hasAttributes } = readStartTag();\n\n if (selfClosing) return { name, value: finishElement(record, hasAttributes, '', false) };\n\n // The parser folds character data and child elements into the record the\n // start tag produced, as it goes rather than in passes, because the order\n // they appear in is the only order available: attributes always come first on\n // the tag, but text and children interleave freely.\n const content = readContent(name, record, depth);\n\n return { name, value: finishElement(record, hasAttributes, content.text, content.hasChildren) };\n };\n\n /**\n * @description Reads an element's body up to and including its closing tag, folding what it finds into the record the start tag produced. Returns when the\n * closing tag has been consumed; failing on it is {@link readClosingTag}'s job, so that a mismatched or unclosed tag is reported the same way\n * wherever it was found.\n *\n * @param name - The name the start tag gave the element, which its closing tag has to match.\n * @param record - The record to fold the children into.\n * @param depth - The depth the element sits at; its children are one deeper.\n *\n * @returns The body as character data, and whether it held any child element.\n */\n const readContent = (name: string, record: Record<string, XmlValue>, depth: number): Content => {\n let childText = '';\n let hasChildren = false;\n\n for (;;) {\n switch (classifyContent(name)) {\n case 'text':\n childText += readTextRun();\n break;\n case 'close':\n readClosingTag(name);\n return { text: childText, hasChildren };\n case 'comment':\n at = skipUntil('-->', at + 4, 'comment');\n break;\n case 'cdata':\n childText += readCdata();\n break;\n case 'instruction':\n at = skipUntil('?>', at + 2, 'processing instruction');\n break;\n case 'child': {\n hasChildren = true;\n addChild(record, readElement(depth + 1));\n break;\n }\n }\n }\n };\n\n /**\n * @description What the cursor is sitting on inside an element's body. The two things the loop cannot read are refused here rather than in it: running out of\n * document and a declaration, which is markup the parser does not accept inside an element. Recognising the constructs that _are_ read is the rest,\n * and the order is the one that rules out the shorter prefixes first — `</` before `<?` before any other `<!`, and `<![CDATA[` before the `<!` that\n * would otherwise match it.\n *\n * @param name - The name the enclosing element's start tag gave it, for the unterminated-body message.\n *\n * @returns What the cursor is on.\n */\n const classifyContent = (name: string): Construct => {\n if (at >= text.length) {\n throw new XmlParseError({ message: `Unclosed element <${name}>`, position: at, input: text });\n }\n if (text.charCodeAt(at) !== LT) {\n return 'text';\n }\n if (text.startsWith('</', at)) {\n return 'close';\n }\n if (text.startsWith('<!--', at)) {\n return 'comment';\n }\n if (text.startsWith('<![CDATA[', at)) {\n return 'cdata';\n }\n if (text.startsWith('<?', at)) {\n return 'instruction';\n }\n if (text.startsWith('<!', at)) {\n throw new XmlParseError({ message: 'A declaration is not allowed inside an element', position: at, input: text });\n }\n return 'child';\n };\n\n /**\n * @description Consumes a `</name>`, checking on the way that it is the tag that closes this element and that it is well-formed.\n *\n * @param name - The name the start tag gave the element, which the closing tag has to match.\n */\n const readClosingTag = (name: string): void => {\n const closeStart = at;\n at += 2;\n const closing = readName('element name');\n if (closing !== name) {\n throw new XmlParseError({ message: `Closing tag </${closing}> does not match <${name}>`, position: closeStart, input: text });\n }\n skipSpaces();\n if (text.charCodeAt(at) !== GT) {\n throw new XmlParseError({ message: `Malformed closing tag </${closing}>`, position: at, input: text });\n }\n at++;\n };\n\n /**\n * @description Reads the run of character data up to the next `<`, or to the end of the document.\n *\n * @returns The run, with its character references expanded.\n */\n const readTextRun = (): string => {\n const next = text.indexOf('<', at);\n const end = next === -1 ? text.length : next;\n const run = decodeEntities(text.slice(at, end));\n at = end;\n return run;\n };\n\n /**\n * @description Reads a `<![CDATA[…]]>` section. CDATA is character data, and character data is what it holds, so it joins the element's text as it stands — the\n * entities in it are literal text and must not be expanded.\n *\n * @returns The section's contents.\n */\n const readCdata = (): string => {\n const end = text.indexOf(']]>', at + 9);\n if (end === -1) throw new XmlParseError({ message: 'Unterminated CDATA section', position: at, input: text });\n const data = text.slice(at + 9, end);\n at = end + 3;\n return data;\n };\n\n /**\n * @description Adds a child to its parent's record. Two children under one name make an array, and the first one does not: a schema can tell a repeated field\n * from a single one by the shape, and an array of one is not what a single value encodes to.\n *\n * @param record - The parent's record, added to in place.\n * @param child - The child element as it was read.\n */\n const addChild = (record: Record<string, XmlValue>, child: Element): void => {\n const existing = record[child.name];\n if (existing === undefined) record[child.name] = child.value;\n else if (Array.isArray(existing)) (existing as Array<XmlValue>).push(child.value);\n else record[child.name] = [existing, child.value];\n };\n\n /**\n * @description Decides what an element with the given attributes, text and children reduces to.\n */\n const finishElement = (record: Record<string, XmlValue>, hasAttributes: boolean, text: string, hasChildren: boolean): XmlValue => {\n // Whitespace at the edges of a text run is dropped unless the caller asked to\n // keep it. This is what makes a pretty-printed document round trip: the\n // indentation a renderer puts around a child element and around a closing tag\n // lands at the edges of its parent's text, and trimming removes exactly that\n // and nothing else. Whitespace *inside* the run — between two words, or a\n // newline in the middle of a paragraph — is content and stays.\n const content = resolved.preserveWhitespace ? text : text.trim();\n\n if (!hasAttributes && !hasChildren) {\n // A leaf is character data on its own. Returning the string rather than a `{ '#text': … }` record is what lets\n // `Schema.Struct({ name: Schema.String })` round-trip.\n return content;\n }\n\n // Folded in place: the record is the one the start tag built and that the children were added to, so there is\n // nothing left to copy.\n if (content !== '') record[TEXT_KEY] = content;\n return record;\n };\n\n skipMisc();\n if (at >= text.length || text.charCodeAt(at) !== LT) {\n throw new XmlParseError({ message: 'Document has no root element', position: at, input: text });\n }\n\n const root = readElement(0);\n\n skipMisc();\n if (at < text.length) {\n throw new XmlParseError({ message: 'Unexpected content after the root element', position: at, input: text });\n }\n\n return { name: root.name, value: root.value };\n};\n\n/**\n * @description Runs the synchronous walk and folds the one failure it reports into a {@link Result}, which {@link parseXml} turns back into an `Effect`. Kept\n * separate so the walk itself can throw without the public API ever throwing.\n *\n * @param text - The document to read.\n * @param options - The options as the caller wrote them.\n *\n * @returns The document, or the failure to report.\n */\nconst parseDocumentResult = (text: string, options: XmlParseOptions): Result.Result<XmlDocument, XmlParseError> => {\n try {\n return Result.succeed(parseDocument(text, options));\n } catch (cause) {\n if (cause instanceof XmlParseError) {\n return Result.fail(cause);\n }\n return Result.fail(new XmlParseError({ message: Predicate.isError(cause) ? cause.message : String(cause), position: -1, input: text }));\n }\n};\n\n/**\n * @description Decodes character references, falling back to the raw text when the reference is not one the decoder recognises. The fallback is what makes a bare\n * `&` survivable: the decoder treats it as a malformed reference and fails, and a document containing one is far more likely to be worth reading than\n * to be rejected. The `&` is escaped on the way out, so the value still round-trips. The decoder answers with an `Effect`, and this is the one place\n * a parse still runs one. It is only reached when the raw text holds an `&` — the common case returns before it — and the effect is synchronous, so\n * the run is cheap next to the decoder's own work.\n *\n * @param raw - Text read straight from the source, with references unexpanded.\n *\n * @returns The decoded text, which cannot fail.\n */\nconst decodeEntities = (raw: string): string => {\n if (raw.indexOf('&') === -1) return raw; // nothing to expand: the common case, and no work\n // `orElseSucceed` rather than `try`/`catch`: the decoder reports a malformed\n // reference by failing in its error channel, and a document containing a bare\n // `&` is far more likely to be worth reading than to be rejected. The `&` is\n // escaped on the way out, so the value still round-trips.\n return Effect.runSync(Effect.orElseSucceed(decoder.decode(raw), () => raw));\n};\n"],"mappings":"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"}