@endevops/effect-codec-xml 0.0.1 → 0.1.0-beta.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 (47) hide show
  1. package/README.md +63 -62
  2. package/dist/codec.d.ts +17 -9
  3. package/dist/codec.d.ts.map +1 -1
  4. package/dist/codec.js +25 -16
  5. package/dist/codec.js.map +1 -1
  6. package/dist/conventions.d.ts +4 -4
  7. package/dist/conventions.js +7 -7
  8. package/dist/conventions.js.map +1 -1
  9. package/dist/entities/entity-decoder.d.ts +33 -33
  10. package/dist/entities/entity-decoder.d.ts.map +1 -1
  11. package/dist/entities/entity-decoder.js +63 -64
  12. package/dist/entities/entity-decoder.js.map +1 -1
  13. package/dist/errors.d.ts +2 -2
  14. package/dist/errors.js +2 -2
  15. package/dist/errors.js.map +1 -1
  16. package/dist/namespaces.js +2 -2
  17. package/dist/namespaces.js.map +1 -1
  18. package/dist/naming.d.ts +6 -6
  19. package/dist/naming.d.ts.map +1 -1
  20. package/dist/naming.js +3 -3
  21. package/dist/naming.js.map +1 -1
  22. package/dist/parse.d.ts +5 -5
  23. package/dist/parse.js +14 -14
  24. package/dist/parse.js.map +1 -1
  25. package/dist/render.d.ts +1 -1
  26. package/dist/render.d.ts.map +1 -1
  27. package/dist/render.js +28 -28
  28. package/dist/render.js.map +1 -1
  29. package/dist/xml-error.d.ts +9 -9
  30. package/dist/xml-error.js +18 -18
  31. package/dist/xml-error.js.map +1 -1
  32. package/dist/xml-value.d.ts +7 -7
  33. package/dist/xml-value.d.ts.map +1 -1
  34. package/dist/xml-value.js +6 -7
  35. package/dist/xml-value.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/codec.ts +91 -71
  38. package/src/conventions.ts +7 -7
  39. package/src/entities/entity-decoder.ts +98 -99
  40. package/src/errors.ts +3 -3
  41. package/src/index.ts +3 -3
  42. package/src/namespaces.ts +16 -16
  43. package/src/naming.ts +34 -35
  44. package/src/parse.ts +26 -26
  45. package/src/render.ts +44 -44
  46. package/src/xml-error.ts +18 -18
  47. package/src/xml-value.ts +10 -11
package/dist/parse.js.map CHANGED
@@ -1 +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":";;;;;AAUA,MAAM,UAAU,cAAc,KAAK,CAAC,CAAC,KAAK,OAAO,OAAO;;;;;;;;;;;;;;;;;;AA0ExD,MAAa,YAAY,MAAc,UAA2B,CAAC,MACjE,OAAO,cAAc,OAAO,WAAW,oBAAoB,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,KAC1E,OAAO,KAAI,aAAY,SAAS,KAAK,GACrC,OAAO,SAAS,qBAAqB,EAAE,YAAY,EAAE,cAAc,KAAK,OAAO,EAAE,CAAC,CACpF;;;;;;;;AAkCF,MAAM,gBAAgB,SAA0B,SAAS,MAAM,SAAS,MAAM,SAAS,KAAK,SAAS;;;;AAKrG,MAAM,KAAK;;;;AAIX,MAAM,KAAK;;;;AAIX,MAAM,QAAQ;;;;AAId,MAAM,SAAS;;;;;;;;;;;;AA2Df,MAAM,iBAAiB,MAAc,YAA0C;CAC7E,MAAM,WAA4B;EAChC,oBAAoB,QAAQ,sBAAsB;EAClD,UAAU,QAAQ,YAAY;EAC9B,MAAM,QAAQ,QAAQ;EACtB,YAAY,QAAQ,cAAc;CACpC;CAEA,IAAI,KAAK;;;;;CAMT,MAAM,cAAc;EAAE,MAAM,SAAS;EAAM,YAAY,SAAS;CAAW;;;;;CAM3E,MAAM,4BAAY,IAAI,IAAoB;CAE1C,MAAM,WAAW,KAAa,MAAc,aAA6B;EACvE,MAAM,SAAS,UAAU,IAAI,GAAG;EAChC,IAAI,WAAW,KAAA,GAAW,OAAO;EAKjC,IAAI;EACJ,IAAI;GACF,OAAO,gBAAgB,KAAK,WAAW;EACzC,SAAS,SAAS;GAChB,MAAM,SAAS,UAAU,QAAQ,OAAO,IAAI,QAAQ,UAAU,OAAO,OAAO;GAC5E,MAAM,IAAI,cAAc;IAAE,SAAS,GAAG,KAAK,GAAG,KAAK,UAAU,GAAG,EAAE,4BAA4B;IAAU;IAAU,OAAO;GAAK,CAAC;EACjI;EAEA,UAAU,IAAI,KAAK,IAAI;EACvB,OAAO;CACT;;;;CAKA,MAAM,aAAa,QAAgB,OAAe,SAAyB;EACzE,MAAM,MAAM,KAAK,QAAQ,QAAQ,KAAK;EACtC,IAAI,QAAQ,IAAI,MAAM,IAAI,cAAc;GAAE,SAAS,gBAAgB;GAAQ,UAAU;GAAO,OAAO;EAAK,CAAC;EACzG,OAAO,MAAM,OAAO;CACtB;CAEA,MAAM,eAAe,UAA0B;EAC7C,IAAI,QAAQ;EACZ,KAAK,IAAI,IAAI,QAAQ,GAAG,IAAI,KAAK,QAAQ,KAAK;GAC5C,MAAM,OAAO,KAAK;GAClB,IAAI,SAAS,KAAK;QACb,IAAI,SAAS,KAAK;QAClB,IAAI,SAAS,OAAO,SAAS,GAAG,OAAO,IAAI;EAClD;EACA,MAAM,IAAI,cAAc;GAAE,SAAS;GAAoC,UAAU;GAAO,OAAO;EAAK,CAAC;CACvG;;;;;CAMA,MAAM,iBAAuB;EAC3B,SAAS;GACP,OAAO,KAAK,KAAK,UAAU,aAAa,KAAK,WAAW,EAAE,CAAC,GACzD;GAGF,IAAI,MAAM,KAAK,QACb;GAGF,IAAI,KAAK,WAAW,EAAE,MAAM,IAC1B;GAGF,IAAI,KAAK,WAAW,QAAQ,EAAE,GAC5B,KAAK,UAAU,OAAO,KAAK,GAAG,SAAS;QAClC,IAAI,KAAK,WAAW,MAAM,EAAE,GACjC,KAAK,UAAU,MAAM,KAAK,GAAG,wBAAwB;QAChD,IAAI,KAAK,WAAW,aAAa,EAAE,GACxC,KAAK,YAAY,EAAE;QAEnB;EAEJ;CACF;;;;CAKA,MAAM,YAAY,SAAyB;EACzC,MAAM,QAAQ;EACd,OAAO,KAAK,KAAK,QAAQ;GACvB,MAAM,OAAO,KAAK,WAAW,EAAE;GAE/B,IAAI,aAAa,IAAI,KAAK,SAAS,SAAS,SAAS,UAAU,SAAS,IACtE;GAEF;EACF;EACA,IAAI,OAAO,OACT,MAAM,IAAI,cAAc;GAAE,SAAS,cAAc;GAAQ,UAAU;GAAO,OAAO;EAAK,CAAC;EAEzF,OAAO,KAAK,MAAM,OAAO,EAAE;CAC7B;CAEA,MAAM,mBAAyB;EAC7B,OAAO,KAAK,KAAK,UAAU,aAAa,KAAK,WAAW,EAAE,CAAC,GAAG;CAChE;CAEA,MAAM,sBAAsB,MAAc,cAA8B;EACtE,MAAM,QAAQ,KAAK;EAInB,IAAI,UAAU,QAAO,UAAU,KAC7B,MAAM,IAAI,cAAc;GAAE,SAAS,cAAc,KAAK;GAAwB,UAAU;GAAW,OAAO;EAAK,CAAC;EAGlH;EAEA,MAAM,MAAM,KAAK,QAAQ,SAAS,IAAI,EAAE;EAExC,IAAI,QAAQ,IACV,MAAM,IAAI,cAAc;GAAE,SAAS,qCAAqC,KAAK;GAAI,UAAU;GAAI,OAAO;EAAK,CAAC;EAG9G,MAAM,MAAM,KAAK,MAAM,IAAI,GAAG;EAC9B,KAAK,MAAM;EAEX,OAAO,eAAe,GAAG;CAC3B;CAEA,MAAM,qBAA+B;EAInC,MAAM,SAAmC,CAAC;EAC1C,IAAI,gBAAgB;EACpB,SAAS;GACP,WAAW;GACX,IAAI,MAAM,KAAK,QAAQ,MAAM,IAAI,cAAc;IAAE,SAAS;IAA0B,UAAU;IAAI,OAAO;GAAK,CAAC;GAC/G,IAAI,KAAK,WAAW,EAAE,MAAM,IAAI;IAC9B;IACA,OAAO;KAAE;KAAQ,aAAa;KAAO;IAAc;GACrD;GACA,IAAI,KAAK,WAAW,EAAE,MAAM,SAAS,KAAK,KAAK,OAAO,KAAK;IACzD,MAAM;IACN,OAAO;KAAE;KAAQ,aAAa;KAAM;IAAc;GACpD;GACA,MAAM,YAAY;GAClB,MAAM,OAAO,QAAQ,SAAS,gBAAgB,GAAG,aAAa,SAAS;GACvE,WAAW;GACX,IAAI,KAAK,WAAW,EAAE,MAAM,QAAQ,MAAM,IAAI,cAAc;IAAE,SAAS,cAAc,KAAK;IAAe,UAAU;IAAI,OAAO;GAAK,CAAC;GACpI;GACA,WAAW;GACX,OAAA,MAA0B,QAAQ,mBAAmB,MAAM,SAAS;GACpE,gBAAgB;EAClB;CACF;CAEA,MAAM,eAAe,UAA2B;EAC9C,IAAI,QAAQ,SAAS,UACnB,MAAM,IAAI,cAAc;GAAE,SAAS,sCAAsC,SAAS,SAAS;GAAI,UAAU;GAAI,OAAO;EAAK,CAAC;EAC5H,IAAI,KAAK,WAAW,EAAE,MAAM,IAAI,MAAM,IAAI,cAAc;GAAE,SAAS;GAAuB,UAAU;GAAI,OAAO;EAAK,CAAC;EACrH;EAEA,MAAM,OAAO,QAAQ,SAAS,cAAc,GAAG,WAAW,EAAE;EAC5D,MAAM,EAAE,QAAQ,aAAa,kBAAkB,aAAa;EAE5D,IAAI,aAAa,OAAO;GAAE;GAAM,OAAO,cAAc,QAAQ,eAAe,IAAI,KAAK;EAAE;EAMvF,MAAM,UAAU,YAAY,MAAM,QAAQ,KAAK;EAE/C,OAAO;GAAE;GAAM,OAAO,cAAc,QAAQ,eAAe,QAAQ,MAAM,QAAQ,WAAW;EAAE;CAChG;;;;;;;;;;;;CAaA,MAAM,eAAe,MAAc,QAAkC,UAA2B;EAC9F,IAAI,YAAY;EAChB,IAAI,cAAc;EAElB,SACE,QAAQ,gBAAgB,IAAI,GAA5B;GACE,KAAK;IACH,aAAa,YAAY;IACzB;GACF,KAAK;IACH,eAAe,IAAI;IACnB,OAAO;KAAE,MAAM;KAAW;IAAY;GACxC,KAAK;IACH,KAAK,UAAU,OAAO,KAAK,GAAG,SAAS;IACvC;GACF,KAAK;IACH,aAAa,UAAU;IACvB;GACF,KAAK;IACH,KAAK,UAAU,MAAM,KAAK,GAAG,wBAAwB;IACrD;GACF,KAAK;IACH,cAAc;IACd,SAAS,QAAQ,YAAY,QAAQ,CAAC,CAAC;EAG3C;CAEJ;;;;;;;;;;;CAYA,MAAM,mBAAmB,SAA4B;EACnD,IAAI,MAAM,KAAK,QACb,MAAM,IAAI,cAAc;GAAE,SAAS,qBAAqB,KAAK;GAAI,UAAU;GAAI,OAAO;EAAK,CAAC;EAE9F,IAAI,KAAK,WAAW,EAAE,MAAM,IAC1B,OAAO;EAET,IAAI,KAAK,WAAW,MAAM,EAAE,GAC1B,OAAO;EAET,IAAI,KAAK,WAAW,QAAQ,EAAE,GAC5B,OAAO;EAET,IAAI,KAAK,WAAW,aAAa,EAAE,GACjC,OAAO;EAET,IAAI,KAAK,WAAW,MAAM,EAAE,GAC1B,OAAO;EAET,IAAI,KAAK,WAAW,MAAM,EAAE,GAC1B,MAAM,IAAI,cAAc;GAAE,SAAS;GAAkD,UAAU;GAAI,OAAO;EAAK,CAAC;EAElH,OAAO;CACT;;;;;;CAOA,MAAM,kBAAkB,SAAuB;EAC7C,MAAM,aAAa;EACnB,MAAM;EACN,MAAM,UAAU,SAAS,cAAc;EACvC,IAAI,YAAY,MACd,MAAM,IAAI,cAAc;GAAE,SAAS,iBAAiB,QAAQ,oBAAoB,KAAK;GAAI,UAAU;GAAY,OAAO;EAAK,CAAC;EAE9H,WAAW;EACX,IAAI,KAAK,WAAW,EAAE,MAAM,IAC1B,MAAM,IAAI,cAAc;GAAE,SAAS,2BAA2B,QAAQ;GAAI,UAAU;GAAI,OAAO;EAAK,CAAC;EAEvG;CACF;;;;;;CAOA,MAAM,oBAA4B;EAChC,MAAM,OAAO,KAAK,QAAQ,KAAK,EAAE;EACjC,MAAM,MAAM,SAAS,KAAK,KAAK,SAAS;EACxC,MAAM,MAAM,eAAe,KAAK,MAAM,IAAI,GAAG,CAAC;EAC9C,KAAK;EACL,OAAO;CACT;;;;;;;CAQA,MAAM,kBAA0B;EAC9B,MAAM,MAAM,KAAK,QAAQ,OAAO,KAAK,CAAC;EACtC,IAAI,QAAQ,IAAI,MAAM,IAAI,cAAc;GAAE,SAAS;GAA8B,UAAU;GAAI,OAAO;EAAK,CAAC;EAC5G,MAAM,OAAO,KAAK,MAAM,KAAK,GAAG,GAAG;EACnC,KAAK,MAAM;EACX,OAAO;CACT;;;;;;;;CASA,MAAM,YAAY,QAAkC,UAAyB;EAC3E,MAAM,WAAW,OAAO,MAAM;EAC9B,IAAI,aAAa,KAAA,GAAW,OAAO,MAAM,QAAQ,MAAM;OAClD,IAAI,MAAM,QAAQ,QAAQ,GAAG,SAA8B,KAAK,MAAM,KAAK;OAC3E,OAAO,MAAM,QAAQ,CAAC,UAAU,MAAM,KAAK;CAClD;;;;CAKA,MAAM,iBAAiB,QAAkC,eAAwB,MAAc,gBAAmC;EAOhI,MAAM,UAAU,SAAS,qBAAqB,OAAO,KAAK,KAAK;EAE/D,IAAI,CAAC,iBAAiB,CAAC,aAGrB,OAAO;EAKT,IAAI,YAAY,IAAI,OAAO,YAAY;EACvC,OAAO;CACT;CAEA,SAAS;CACT,IAAI,MAAM,KAAK,UAAU,KAAK,WAAW,EAAE,MAAM,IAC/C,MAAM,IAAI,cAAc;EAAE,SAAS;EAAgC,UAAU;EAAI,OAAO;CAAK,CAAC;CAGhG,MAAM,OAAO,YAAY,CAAC;CAE1B,SAAS;CACT,IAAI,KAAK,KAAK,QACZ,MAAM,IAAI,cAAc;EAAE,SAAS;EAA6C,UAAU;EAAI,OAAO;CAAK,CAAC;CAG7G,OAAO;EAAE,MAAM,KAAK;EAAM,OAAO,KAAK;CAAM;AAC9C;;;;;;;;;;AAWA,MAAM,uBAAuB,MAAc,YAAwE;CACjH,IAAI;EACF,OAAO,OAAO,QAAQ,cAAc,MAAM,OAAO,CAAC;CACpD,SAAS,OAAO;EACd,IAAI,iBAAiB,eACnB,OAAO,OAAO,KAAK,KAAK;EAE1B,OAAO,OAAO,KAAK,IAAI,cAAc;GAAE,SAAS,UAAU,QAAQ,KAAK,IAAI,MAAM,UAAU,OAAO,KAAK;GAAG,UAAU;GAAI,OAAO;EAAK,CAAC,CAAC;CACxI;AACF;;;;;;;;;;;;AAaA,MAAM,kBAAkB,QAAwB;CAC9C,IAAI,IAAI,QAAQ,GAAG,MAAM,IAAI,OAAO;CAKpC,OAAO,OAAO,QAAQ,OAAO,cAAc,QAAQ,OAAO,GAAG,SAAS,GAAG,CAAC;AAC5E"}
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. Trimming 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, and those combinators key off it, so 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 one fiber step per construct dominated the `parse 500 rows` benchmark. 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 lets the parent file\n * it correctly, because the value alone cannot say: 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 lets the content loop stay a dispatch:\n * each construct is recognised in one place, against the ones that cannot be confused with it, rather than by a chain of `startsWith` guesses where\n * 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 `>` 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 rules out the shorter prefixes first: `</` before `<?` before any other `<!`, and `<![CDATA[` before the `<!` that would otherwise\n * 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 as it goes 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. Trimming here 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 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 keeps 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 to\n * 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 a\n * parse still runs one. It is only reached when the raw text holds an `&`, since 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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"}
package/dist/render.d.ts CHANGED
@@ -84,7 +84,7 @@ export declare const escapeAttribute: (value: string) => string;
84
84
  * A record becomes an element:
85
85
  *
86
86
  * - `@`-prefixed keys become attributes, the reserved `#text` key becomes character data, and every other key becomes a child element.
87
- * - An array repeats its name — a document whose root value is an array wraps it in the root element and names each member `itemName`.
87
+ * - An array repeats its name. A document whose root value is an array wraps it in the root element and names each member `itemName`.
88
88
  * - A string is character data. The walk is synchronous, and what can go wrong is reported by throwing an {@link XmlRenderError}; {@link renderXml}
89
89
  * folds that into the effect's typed error channel. A caller not already in an `Effect` runs it with `Effect.runSync`, which throws the failure it
90
90
  * produced.
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","names":[],"sources":["../src/render.ts"],"mappings":";;;;;;;;;iBA+EiB;;;;;;;WAON;;;;;;WAOA;;;;;;WAOA;;;;WAKA;;;;;;WAOA;;;;;;;WAQA;;;;;;WAOA,OAAO;;;;;;WAOP,aAAa;;;;;;WAOb;;;;;;;;;qBAiHE,aAAU;;;;;;;;qBASV,kBAAe;;;;;;;;;;;;;;;;qBAmDf,YAAS,OAAW,UAAQ,UAAW,qBAAwB,OAAO,eAAe"}
1
+ {"version":3,"file":"render.d.ts","names":[],"sources":["../src/render.ts"],"mappings":";;;;;;;;;iBAgFiB;;;;;;;WAON;;;;;;WAOA;;;;;;WAOA;;;;WAKA;;;;;;WAOA;;;;;;;WAQA;;;;;;WAOA,OAAO;;;;;;WAOP,aAAa;;;;;;WAOb;;;;;;;;;qBAgHE,aAAU;;;;;;;;qBASV,kBAAe;;;;;;;;;;;;;;;;qBAmDf,YAAS,OAAW,UAAQ,UAAW,qBAAwB,OAAO,eAAe"}
package/dist/render.js CHANGED
@@ -42,20 +42,21 @@ const buildTable = (extra) => {
42
42
  const TEXT_TABLE = buildTable({});
43
43
  const ATTRIBUTE_TABLE = buildTable(ATTRIBUTE_WHITESPACE);
44
44
  /**
45
- * @description The characters each table escapes, as a pattern rather than as a set of replacement passes. Finding the first one with a pattern is what makes
46
- * clean text cheap: V8 compiles a single character class into a scan that is several times faster than a JavaScript loop reading the same string a
47
- * code unit at a time, and clean text is most text. `render 20k of clean text` in `bench/codec.bench.ts` is the row that says so — a hand-written
48
- * loop over the same twenty thousand characters is roughly two and a half times slower. Neither pattern is global, so `exec` ignores `lastIndex` and
49
- * always starts at the beginning. One module-level instance of each is therefore safe to reuse, and nothing has to be reset between calls.
45
+ * @description The characters each table escapes, as a pattern rather than as a set of replacement passes. A single pattern finds the first character that needs
46
+ * replacing, which keeps clean text cheap: V8 compiles a single character class into a scan that is several times faster than a JavaScript loop
47
+ * reading the same string a code unit at a time, and clean text is most text. The `render 20k of clean text` benchmark in `bench/codec.bench.ts`
48
+ * measures that difference: a hand-written loop over the same twenty thousand characters is roughly two and a half times slower. Neither pattern is
49
+ * global, so `exec` ignores `lastIndex` and always starts at the beginning. One module-level instance of each is therefore safe to reuse, and nothing
50
+ * has to be reset between calls.
50
51
  */
51
52
  const TEXT_UNSAFE = /[<>&"']/;
52
53
  const ATTRIBUTE_UNSAFE = /[<>&"'\n\r\t]/;
53
54
  /**
54
55
  * @description Builds the name resolver for one render. Every element and every attribute name goes through here, and a document repeats names: a thousand
55
56
  * `<item>` elements, or the same `id` on every row. A validator that runs a regex per occurrence pays that cost a thousand times for one answer, so
56
- * the first result is remembered and the rest are lookups. It also keeps the mode and version in one place, which is what stops a caller from
57
- * resolving a name with different settings than the render it is part of. A cache miss calls {@link resolveNameSync}, which throws an
58
- * {@link XmlParseError} in `'error'` mode; {@link renderXml} catches it and reports it as an {@link XmlRenderError}.
57
+ * the first result is remembered and the rest are lookups. Keeping the mode and version in one place also stops a caller from resolving a name with
58
+ * different settings than the render it is part of. A cache miss calls {@link resolveNameSync}, which throws an {@link XmlParseError} in `'error'`
59
+ * mode; {@link renderXml} catches it and reports it as an {@link XmlRenderError}.
59
60
  *
60
61
  * @param options - Resolved render options.
61
62
  *
@@ -77,8 +78,7 @@ const makeNamer = (options) => {
77
78
  /**
78
79
  * @description A boolean option's value, with an absent one read as the default. The three boolean options are spelled through here rather than through a `??` of
79
80
  * their own, so the table below reads as a list of what each option _is_ instead of a list of nine separate decisions about what an omitted option
80
- * means — and so a reader looking for "which options are on by default" finds three words rather than three mixes of `?? true` and `?? false` to
81
- * read.
81
+ * means, and a reader looking for "which options are on by default" finds three words rather than three mixes of `?? true` and `?? false` to read.
82
82
  *
83
83
  * @param value - The option as the caller wrote it, or `undefined` when the caller left it out.
84
84
  * @param fallback - The value to use when the caller left it out.
@@ -148,10 +148,10 @@ const escapeText = (value) => escape(value, TEXT_UNSAFE, TEXT_TABLE);
148
148
  const escapeAttribute = (value) => escape(value, ATTRIBUTE_UNSAFE, ATTRIBUTE_TABLE);
149
149
  /**
150
150
  * @description Replaces every character the table has an entry for, in one pass over the string. The pattern finds the first character that needs replacing, and a
151
- * string with none is handed straight back — which is the common case, and the one the pattern is there to make fast. From there the rest of the
152
- * string is copied in runs between the replacements rather than a character at a time, so the cost is one pattern scan, one copy, and one
153
- * concatenation per replacement, rather than a whole pass per character class. Only ASCII is looked up. XML carries every other character natively,
154
- * and a code unit above 127 has no entity an XML parser is required to know.
151
+ * string with none is handed straight back. That is the common case, and the pattern exists to keep it fast. From there the rest of the string is
152
+ * copied in runs between the replacements rather than a character at a time, so the cost is one pattern scan, one copy, and one concatenation per
153
+ * replacement, rather than a whole pass per character class. Only ASCII is looked up. XML carries every other character natively, and a code unit
154
+ * above 127 has no entity an XML parser is required to know.
155
155
  *
156
156
  * @param value - The text to escape.
157
157
  * @param pattern - Matches the first character that needs replacing.
@@ -181,7 +181,7 @@ const escape = (value, pattern, table) => {
181
181
  * A record becomes an element:
182
182
  *
183
183
  * - `@`-prefixed keys become attributes, the reserved `#text` key becomes character data, and every other key becomes a child element.
184
- * - An array repeats its name — a document whose root value is an array wraps it in the root element and names each member `itemName`.
184
+ * - An array repeats its name. A document whose root value is an array wraps it in the root element and names each member `itemName`.
185
185
  * - A string is character data. The walk is synchronous, and what can go wrong is reported by throwing an {@link XmlRenderError}; {@link renderXml}
186
186
  * folds that into the effect's typed error channel. A caller not already in an `Effect` runs it with `Effect.runSync`, which throws the failure it
187
187
  * produced.
@@ -245,9 +245,9 @@ const render = (value, options) => {
245
245
  return out.join("");
246
246
  };
247
247
  /**
248
- * @description Renders one named element and its subtree. The value an {@link XmlValue} holds decides which of the four shapes below it takes — a repeated run of
249
- * children, character data, an absent field, or a record — and each of those is written by a function of its own, so this one is the dispatch rather
250
- * than the document.
248
+ * @description Renders one named element and its subtree. The value an {@link XmlValue} holds decides which of the four shapes below it takes: a repeated run of
249
+ * children, character data, an absent field, or a record. Each of those is written by a function of its own, so this one is the dispatch rather than
250
+ * the document.
251
251
  *
252
252
  * @param out - The chunk buffer to append to.
253
253
  * @param name - The element name, not yet resolved.
@@ -272,7 +272,7 @@ const renderElement = (out, name, value, depth, options) => {
272
272
  };
273
273
  /**
274
274
  * @description Refuses to walk deeper than the render allows. A value can nest without end, and every one of those levels costs a stack frame here, so the cap is
275
- * checked on the way down rather than trusted to the caller.
275
+ * checked as the walk descends rather than trusted to the caller.
276
276
  *
277
277
  * @param depth - The depth about to be written.
278
278
  * @param options - Resolved render options.
@@ -304,8 +304,8 @@ const renderRepeated = (out, name, members, depth, options) => {
304
304
  };
305
305
  /**
306
306
  * @description Renders an element whose value is character data, or nothing. An empty string is character data that happens to be empty, and an element holding
307
- * none of it is the same element as one holding nothing at all — as is an `undefined` element, which is an absent one. The renderer is handed values
308
- * that never went through the schema — a caller building a document by hand — so the absent case is reachable, and an empty element is the honest
307
+ * none of it is the same element as one holding nothing at all, as is an `undefined` element, which is an absent one. The renderer is handed values
308
+ * that never went through the schema (a caller building a document by hand), so the absent case is reachable, and an empty element is the correct
309
309
  * rendering of both.
310
310
  *
311
311
  * @param out - The chunk buffer to append to.
@@ -353,9 +353,9 @@ const renderRecord = (out, tag, record, depth, options) => {
353
353
  * @description One pass over a record's keys, collecting all three roles at once: the attributes are rendered as they are found, the child names are set aside for
354
354
  * the pass that writes them, and the text key is left to {@link textOf}. A pass for the attributes, a pass for the children and an index for the text
355
355
  * instead walks the keys three times and allocates the key array twice, which on a document of a few thousand elements is thousands of allocations
356
- * for nothing. Sorting is off by default, and the default path is the one that matters, so the attributes are built as they are found and there is
356
+ * for nothing. Sorting is off by default, and the default path is the important one, so the attributes are built as they are found and there is
357
357
  * nothing to sort. When it is on, the attribute keys are collected instead and rendered afterwards in sorted order, which costs an array per element
358
- * and buys output that does not depend on the order the fields happened to be declared in.
358
+ * and gives output independent of the order the fields happened to be declared in.
359
359
  *
360
360
  * @param record - The element's value.
361
361
  * @param options - Resolved render options.
@@ -447,9 +447,9 @@ const openLine = (out, depth, options) => {
447
447
  };
448
448
  /**
449
449
  * @description Writes an element with no content, in whichever of the two forms the options ask for. Every path that produces an element with nothing in it goes
450
- * through here, so the self-closing decision is made in exactly one place. That matters because "nothing in it" arrives four different ways — an
451
- * empty string, an absent value, an empty array, and a record whose fields are all absent — and four separate decisions are four chances for one of
452
- * them to write the long form by accident.
450
+ * through here, so the self-closing decision is made in exactly one place. That is important because "nothing in it" arrives four different ways: an
451
+ * empty string, an absent value, an empty array, and a record whose fields are all absent. Four separate decisions are four chances for one of them
452
+ * to write the long form by accident.
453
453
  *
454
454
  * @param out - The chunk buffer to append to.
455
455
  * @param tag - The element's name, already resolved.
@@ -487,8 +487,8 @@ const attributeText = (value) => {
487
487
  return renderScalar(value);
488
488
  };
489
489
  /**
490
- * @description Renders a leaf that is not a string as the character data an XML document can hold. A schema-derived value never reaches here —
491
- * `Schema.toCodecStringTree` has already turned every scalar into a string — so this is for values a caller built by hand. A value with no sensible
490
+ * @description Renders a leaf that is not a string as the character data an XML document can hold. A schema-derived value never reaches here, because
491
+ * `Schema.toCodecStringTree` has already turned every scalar into a string, so this is for values a caller built by hand. A value with no sensible
492
492
  * text form is rendered as nothing rather than as `[object Object]`, which would silently write a document that parses back to something else.
493
493
  *
494
494
  * @param value - The leaf to render.
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","names":[],"sources":["../src/render.ts"],"sourcesContent":["// Rendering: an `XmlValue` to XML text.\n//\n// This is the hot path for an application that serializes often, so it is built\n// around three things: one pass over each record's keys rather than one per\n// role a key can play, one pass over each character rather than one per\n// character class, and one array of chunks joined once rather than a growing\n// string.\n//\n// The walk itself is plain synchronous functions rather than a chain of\n// `yield*`es. Publicly `renderXml` is still an `Effect` — it suspends the walk so\n// it runs lazily, and folds the one failure the walk can report into the typed\n// error channel — but inside a document there is no effect boundary per element\n// or per attribute. A 500-row report is thousands of elements, and a fiber step\n// for each of them was most of what the `render 500 rows` row measured. The\n// typed failure survives: the walk throws an {@link XmlRenderError} and\n// `renderXml` catches it into `Effect.fail`.\n//\n// Escaping is the part that scales with the size of the document rather than\n// with its structure, and it is written out here rather than delegated, for a\n// measured reason. The entity encoder that used to live beside this package\n// escaped by applying five sequential global replacements, one per character,\n// so a document with a single `&` in twenty thousand characters was scanned\n// five times over to change one byte -- which is what the `render 20k` rows in\n// `bench/codec.bench.ts` measure. The table below covers the same five\n// characters that encoder escaped, and the explicit expectations in\n// `test/render.spec.ts` pin the fast path.\n\nimport { Effect, Predicate, Result } from 'effect';\n\nimport type { NameMode } from './conventions.ts';\nimport type { XmlVersion } from './naming.ts';\nimport type { XmlRecord, XmlValue } from './xml-value.ts';\n\nimport { attributeName, DEFAULT_ITEM_NAME, DEFAULT_ROOT_NAME, isAttributeKey, isTextKey, resolveNameSync, TEXT_KEY } from './conventions.ts';\nimport { XmlRenderError } from './errors.ts';\n\n/**\n * @description The five characters XML predefines an entity for, and the names to write for them. Written out rather than referenced from the entity decoder\n * because the table is indexed by character code below.\n */\nconst XML_PREDEFINED = { 34: '&quot;', 38: '&amp;', 39: '&apos;', 60: '&lt;', 62: '&gt;' } as const;\n\n/**\n * @description The character references for the whitespace XML normalizes inside an attribute value. A parser replaces a literal newline, carriage return or tab\n * in an attribute with a space, so a value that has to survive a round trip has to spell them as references. They are in the attribute table and not\n * the text one: in character data they are content, and only an attribute value is normalized.\n */\nconst ATTRIBUTE_WHITESPACE = { 9: '&#9;', 10: '&#10;', 13: '&#13;' } as const;\n\n/**\n * @description The replacement for each ASCII character that needs one, and `undefined` for the ones that do not. Indexed by character code and 128 long, so the\n * check is one comparison and one array read with no string search in it.\n *\n * @param extra - Characters to escape in addition to the five predefines.\n *\n * @returns The lookup table.\n */\nconst buildTable = (extra: Record<number, string>): ReadonlyArray<string | undefined> => {\n const table = Array.from<string | undefined>({ length: 128 }).fill(undefined);\n for (const [code, entity] of Object.entries({ ...XML_PREDEFINED, ...extra })) table[Number(code)] = entity;\n return table;\n};\n\nconst TEXT_TABLE = buildTable({});\nconst ATTRIBUTE_TABLE = buildTable(ATTRIBUTE_WHITESPACE);\n\n/**\n * @description The characters each table escapes, as a pattern rather than as a set of replacement passes. Finding the first one with a pattern is what makes\n * clean text cheap: V8 compiles a single character class into a scan that is several times faster than a JavaScript loop reading the same string a\n * code unit at a time, and clean text is most text. `render 20k of clean text` in `bench/codec.bench.ts` is the row that says so — a hand-written\n * loop over the same twenty thousand characters is roughly two and a half times slower. Neither pattern is global, so `exec` ignores `lastIndex` and\n * always starts at the beginning. One module-level instance of each is therefore safe to reuse, and nothing has to be reset between calls.\n */\nconst TEXT_UNSAFE = /[<>&\"']/;\nconst ATTRIBUTE_UNSAFE = /[<>&\"'\\n\\r\\t]/;\n\n/**\n * @description Options for {@link renderXml}.\n */\nexport interface XmlRenderOptions {\n /**\n * @description Name of the root element.\n *\n * @default 'root'\\\n * A codec passes the name it took from the schema's `identifier` annotation when the caller did not set one.\n */\n readonly rootName?: string | undefined;\n\n /**\n * @description Element name used for the members of a document whose root value is an array.\n *\n * @default 'item'\n */\n readonly itemName?: string | undefined;\n\n /**\n * @description Indent nested elements on their own lines.\n *\n * @default false\n */\n readonly format?: boolean | undefined;\n\n /**\n * @description The string one indent level is made of. Defaults to two spaces.\n */\n readonly indent?: string | undefined;\n\n /**\n * @description Write an element with no attributes, text or children as `<a/>` rather than `<a></a>`.\n *\n * @default true\n */\n readonly suppressEmptyNode?: boolean | undefined;\n\n /**\n * @description Sort an element's keys so the same value always renders to the same bytes.\n *\n * @default false\\\n * Which keeps declaration order.\\ Worth turning on for snapshot tests, where key order is otherwise the only thing that can make two equal values differ.\n */\n readonly sortKeys?: boolean | undefined;\n\n /**\n * @description What to do with a field name that is not a legal XML name.\n *\n * @default 'repair'.\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 * @description How deep to nest before giving up. Guards against a value that nests without end taking the stack with it.\n *\n * @default 256\n */\n readonly maxDepth?: number | undefined;\n}\n\n/**\n * @description Options every render call needs, with the defaults already applied.\n */\ninterface ResolvedOptions {\n readonly rootName: string;\n readonly itemName: string;\n readonly format: boolean;\n readonly indent: string;\n readonly suppressEmptyNode: boolean;\n readonly sortKeys: boolean;\n readonly name: NameMode;\n readonly xmlVersion: XmlVersion;\n readonly maxDepth: number;\n\n /**\n * @description Resolves a field name to a legal XML name, in the mode this render was configured with. Synchronous and memoized; see {@link makeNamer}.\n */\n readonly namer: (name: string) => string;\n\n /**\n * @description The indent for a given depth, when pretty-printing. Built on first use at each depth and kept, so an indented document builds one string per\n * level rather than one per line.\n */\n readonly lineAt: (depth: number) => string;\n}\n\n/**\n * @description Builds the name resolver for one render. Every element and every attribute name goes through here, and a document repeats names: a thousand\n * `<item>` elements, or the same `id` on every row. A validator that runs a regex per occurrence pays that cost a thousand times for one answer, so\n * the first result is remembered and the rest are lookups. It also keeps the mode and version in one place, which is what stops a caller from\n * resolving a name with different settings than the render it is part of. A cache miss calls {@link resolveNameSync}, which throws an\n * {@link XmlParseError} in `'error'` mode; {@link renderXml} catches it and reports it as an {@link XmlRenderError}.\n *\n * @param options - Resolved render options.\n *\n * @returns A function from field name to the legal XML name.\n */\nconst makeNamer = (options: Omit<ResolvedOptions, 'namer' | 'lineAt'>): ((name: string) => string) => {\n const cache = new Map<string, string>();\n return (name: string): string => {\n const hit = cache.get(name);\n if (hit !== undefined) return hit;\n const resolved = resolveNameSync(name, { mode: options.name, xmlVersion: options.xmlVersion });\n cache.set(name, resolved);\n return resolved;\n };\n};\n\n/**\n * @description A boolean option's value, with an absent one read as the default. The three boolean options are spelled through here rather than through a `??` of\n * their own, so the table below reads as a list of what each option _is_ instead of a list of nine separate decisions about what an omitted option\n * means — and so a reader looking for \"which options are on by default\" finds three words rather than three mixes of `?? true` and `?? false` to\n * read.\n *\n * @param value - The option as the caller wrote it, or `undefined` when the caller left it out.\n * @param fallback - The value to use when the caller left it out.\n *\n * @returns The option's value.\n */\nconst flag = (value: boolean | undefined, fallback: boolean): boolean => value ?? fallback;\n\n/**\n * @description The indent for a given depth, built the first time a render reaches that depth and kept. A document of a few thousand elements on several lines\n * each would otherwise call `repeat` once per line and allocate the same handful of strings thousands of times over.\n *\n * @param indent - The string one level of indentation is made of.\n *\n * @returns A function from depth to the indent for that depth.\n */\nconst makeLineAt = (indent: string): ((depth: number) => string) => {\n const lines: Array<string> = [''];\n return depth => {\n const line = lines[depth];\n if (line !== undefined) return line;\n const built = indent.repeat(depth);\n lines[depth] = built;\n return built;\n };\n};\n\n/**\n * @description Applies the defaults to one call's options, and builds the two things a render needs that are not options: the memoized name resolver and the\n * memoized indent lines.\n *\n * @param options - The options as the caller wrote them.\n *\n * @returns Every option a render reads, defaulted, with the resolver and the indent lines attached.\n */\nconst resolveOptions = (options: XmlRenderOptions): ResolvedOptions => {\n const resolved = {\n rootName: options.rootName ?? DEFAULT_ROOT_NAME,\n itemName: options.itemName ?? DEFAULT_ITEM_NAME,\n format: flag(options.format, false),\n indent: options.indent ?? ' ',\n suppressEmptyNode: flag(options.suppressEmptyNode, true),\n sortKeys: flag(options.sortKeys, false),\n name: options.name ?? ('repair' as NameMode),\n xmlVersion: options.xmlVersion ?? ('1.0' as XmlVersion),\n maxDepth: options.maxDepth ?? 256,\n };\n return { ...resolved, namer: makeNamer(resolved), lineAt: makeLineAt(resolved.indent) };\n};\n\n/**\n * @description Escapes a value for use as character data.\n *\n * @param value - The text to escape.\n *\n * @returns The text with the XML-unsafe characters replaced by predefined entities, or the very same string when there is nothing to escape.\n */\nexport const escapeText = (value: string): string => escape(value, TEXT_UNSAFE, TEXT_TABLE);\n\n/**\n * @description Escapes a value for use inside a double-quoted attribute.\n *\n * @param value - The text to escape.\n *\n * @returns The escaped text, with the whitespace that XML would otherwise normalize spelled as character references.\n */\nexport const escapeAttribute = (value: string): string => escape(value, ATTRIBUTE_UNSAFE, ATTRIBUTE_TABLE);\n\n/**\n * @description Replaces every character the table has an entry for, in one pass over the string. The pattern finds the first character that needs replacing, and a\n * string with none is handed straight back — which is the common case, and the one the pattern is there to make fast. From there the rest of the\n * string is copied in runs between the replacements rather than a character at a time, so the cost is one pattern scan, one copy, and one\n * concatenation per replacement, rather than a whole pass per character class. Only ASCII is looked up. XML carries every other character natively,\n * and a code unit above 127 has no entity an XML parser is required to know.\n *\n * @param value - The text to escape.\n * @param pattern - Matches the first character that needs replacing.\n * @param table - The replacement for each ASCII character that needs one.\n *\n * @returns The escaped text, or `value` itself when there is nothing to escape.\n */\nconst escape = (value: string, pattern: RegExp, table: ReadonlyArray<string | undefined>): string => {\n const found = pattern.exec(value);\n if (found === null) return value; // nothing to escape: hand back the same string\n\n const length = value.length;\n const start = found.index;\n let out = value.slice(0, start);\n let copied = start;\n\n for (let index = start; index < length; index++) {\n const code = value.charCodeAt(index);\n const entity = code < 128 ? table[code] : undefined;\n if (entity !== undefined) {\n out += value.slice(copied, index) + entity;\n copied = index + 1;\n }\n }\n\n return copied === length ? out : out + value.slice(copied);\n};\n\n/**\n * @description Renders an {@link XmlValue} as an XML document.\\\n * A record becomes an element:\n *\n * - `@`-prefixed keys become attributes, the reserved `#text` key becomes character data, and every other key becomes a child element.\n * - An array repeats its name — a document whose root value is an array wraps it in the root element and names each member `itemName`.\n * - A string is character data. The walk is synchronous, and what can go wrong is reported by throwing an {@link XmlRenderError}; {@link renderXml}\n * folds that into the effect's typed error channel. A caller not already in an `Effect` runs it with `Effect.runSync`, which throws the failure it\n * produced.\n *\n * @param value - The value to render.\n * @param options - Root name, formatting, empty-element and name-resolution settings.\n *\n * @returns An effect producing the XML document as a string.\n */\nexport const renderXml = (value: XmlValue, options: XmlRenderOptions = {}): Effect.Effect<string, XmlRenderError> =>\n Effect.suspend(() => Effect.fromResult(renderResult(value, options)));\n\n/**\n * @description Runs the synchronous walk and folds the one failure it reports into a {@link Result}, which {@link renderXml} turns back into an `Effect`. Kept\n * separate so the walk itself can throw without the public API ever throwing.\n *\n * @param value - The value to render.\n * @param options - The options as the caller wrote them.\n *\n * @returns The document, or the failure to report.\n */\nconst renderResult = (value: XmlValue, options: XmlRenderOptions): Result.Result<string, XmlRenderError> => {\n try {\n return Result.succeed(render(value, options));\n } catch (cause) {\n return Result.fail(toRenderError(cause));\n }\n};\n\n/**\n * @description Reports a failure the synchronous walk threw in the render's own error type. The walk only throws an {@link XmlRenderError} of its own or an\n * {@link XmlParseError} from the name resolver; the latter carries the message the spec asserts on, so it is carried across rather than replaced.\n *\n * @param cause - Whatever was thrown.\n *\n * @returns The failure to report.\n */\nconst toRenderError = (cause: unknown): XmlRenderError => {\n if (cause instanceof XmlRenderError) return cause;\n if (Predicate.isError(cause)) return new XmlRenderError({ message: cause.message });\n return new XmlRenderError({ message: String(cause) });\n};\n\n/**\n * @description The synchronous walk behind {@link renderXml}.\n *\n * @param value - The value to render.\n * @param options - The options as the caller wrote them.\n *\n * @returns The XML document as a string.\n *\n * @throws {XmlRenderError} When the value nests past `maxDepth`, or the name resolver refuses a field name.\n */\nconst render = (value: XmlValue, options: XmlRenderOptions): string => {\n const resolved = resolveOptions(options);\n const out: Array<string> = [];\n\n // A document has exactly one root element, so a root value that is an array\n // is wrapped rather than emitted as several roots. Inside a named element an\n // array repeats that element's own name, so this wrapping is the only place\n // `itemName` is ever used.\n if (Array.isArray(value)) {\n const tag = resolved.namer(resolved.rootName);\n out.push('<', tag, '>');\n for (const member of value) renderElement(out, resolved.itemName, member, 1, resolved);\n if (resolved.format) out.push('\\n');\n out.push('</', tag, '>');\n } else {\n renderElement(out, resolved.rootName, value, 0, resolved);\n }\n\n if (resolved.format) out.push('\\n');\n return out.join('');\n};\n\n/**\n * @description Renders one named element and its subtree. The value an {@link XmlValue} holds decides which of the four shapes below it takes — a repeated run of\n * children, character data, an absent field, or a record — and each of those is written by a function of its own, so this one is the dispatch rather\n * than the document.\n *\n * @param out - The chunk buffer to append to.\n * @param name - The element name, not yet resolved.\n * @param value - The element's value.\n * @param depth - Current nesting depth, for indentation and the depth cap.\n * @param options - Resolved render options.\n */\nconst renderElement = (out: Array<string>, name: string, value: XmlValue, depth: number, options: ResolvedOptions): void => {\n assertWithinDepth(depth, options);\n\n if (Array.isArray(value)) {\n renderRepeated(out, name, value, depth, options);\n return;\n }\n\n // An element opens its own line rather than having its caller do it, which is\n // what keeps a repeated run of children on separate lines. The root is the\n // one element that has nothing in front of it.\n if (options.format && depth > 0) openLine(out, depth, options);\n\n const tag = options.namer(name);\n\n if (Predicate.isString(value) || Predicate.isUndefined(value)) {\n renderLeaf(out, tag, value, options);\n return;\n }\n\n // The array case returned above; `Predicate.isObject` narrows what is left to\n // a record, since `Array.isArray` alone leaves a `ReadonlyArray` in the union.\n if (!Predicate.isObject(value)) return;\n renderRecord(out, tag, value as XmlRecord, depth, options);\n};\n\n/**\n * @description Refuses to walk deeper than the render allows. A value can nest without end, and every one of those levels costs a stack frame here, so the cap is\n * checked on the way down rather than trusted to the caller.\n *\n * @param depth - The depth about to be written.\n * @param options - Resolved render options.\n *\n * @throws {XmlRenderError} When the depth is past the cap.\n */\nconst assertWithinDepth = (depth: number, options: ResolvedOptions): void => {\n if (depth > options.maxDepth) {\n throw new XmlRenderError({\n message: `XML nesting exceeded maxDepth (${options.maxDepth}). Raise the limit if the document is legitimately this deep.`,\n });\n }\n};\n\n/**\n * @description Renders a repeated run of children under one name: `tags: ['a', 'b']` renders `<tags>a</tags><tags>b</tags>`, not one element wrapping both. The\n * name is already the element's own, so a name only has to be supplied where no name is available. Checked before the line break and the name are\n * taken, because the array itself is not an element: opening a line for it as well as for each of its members would leave a blank line where the\n * array was.\n *\n * @param out - The chunk buffer to append to.\n * @param name - The element name, not yet resolved.\n * @param members - The children to write, one after another.\n * @param depth - The depth the run sits at.\n * @param options - Resolved render options.\n */\nconst renderRepeated = (out: Array<string>, name: string, members: ReadonlyArray<XmlValue>, depth: number, options: ResolvedOptions): void => {\n // An empty array still gets an element. Writing nothing would make a field\n // that was present and empty indistinguishable from one that was never\n // there, and a document that came from a schema is easier to trust when the\n // element it describes is actually in the output.\n if (members.length === 0) {\n if (options.format && depth > 0) openLine(out, depth, options);\n writeEmpty(out, options.namer(name), options);\n return;\n }\n for (const member of members) renderElement(out, name, member, depth, options);\n};\n\n/**\n * @description Renders an element whose value is character data, or nothing. An empty string is character data that happens to be empty, and an element holding\n * none of it is the same element as one holding nothing at all — as is an `undefined` element, which is an absent one. The renderer is handed values\n * that never went through the schema — a caller building a document by hand — so the absent case is reachable, and an empty element is the honest\n * rendering of both.\n *\n * @param out - The chunk buffer to append to.\n * @param tag - The element's name, already resolved.\n * @param value - The element's character data, or `undefined` for an absent element.\n * @param options - Resolved render options.\n */\nconst renderLeaf = (out: Array<string>, tag: string, value: string | undefined, options: ResolvedOptions): void => {\n if (Predicate.isUndefined(value) || value === '') {\n writeEmpty(out, tag, options);\n return;\n }\n out.push('<', tag, '>', escapeText(value), '</', tag, '>');\n};\n\n/**\n * @description Renders an element holding a record: the attributes gathered from its `@` keys, the character data from its `#text` key, and its remaining keys as\n * child elements.\n *\n * @param out - The chunk buffer to append to.\n * @param tag - The element's name, already resolved.\n * @param record - The element's value.\n * @param depth - The depth the element sits at.\n * @param options - Resolved render options.\n */\nconst renderRecord = (out: Array<string>, tag: string, record: XmlRecord, depth: number, options: ResolvedOptions): void => {\n const fields = collectFields(record, options);\n const text = textOf(record);\n const children = fields.children;\n\n // Self-closing is decided by whether the element has any *content*, not by\n // whether it has attributes: `<a id=\"1\"/>` is the same element as\n // `<a id=\"1\">` with nothing in it, and the short form is what every XML\n // writer produces.\n if (children === undefined && text === '') {\n writeEmpty(out, tag, options, fields.attributes);\n return;\n }\n\n out.push('<', tag, fields.attributes, '>');\n\n // Character data sits inline when it is all an element has, and on its own\n // line when the element also has children, so an indented document does not\n // end up with its first line of text glued to its opening tag.\n if (text !== '') {\n if (children !== undefined && options.format) openLine(out, depth + 1, options);\n out.push(escapeText(text));\n }\n\n if (children !== undefined) {\n writeChildren(out, record, children, depth, options);\n if (options.format) openLine(out, depth, options);\n }\n\n out.push('</', tag, '>');\n};\n\n/**\n * @description An element's keys resolved into the roles they play.\n */\ninterface Fields {\n /**\n * @description The element's rendered attributes, each with its leading space, or `''` when it has none.\n */\n readonly attributes: string;\n\n /**\n * @description The names of the child elements, in the order they will be written, or `undefined` when the element has none. `undefined` rather than an empty\n * array because \"has children\" is one of the two things the self-closing decision turns on, and the other is the text.\n */\n readonly children: Array<string> | undefined;\n}\n\n/**\n * @description One pass over a record's keys, collecting all three roles at once: the attributes are rendered as they are found, the child names are set aside for\n * the pass that writes them, and the text key is left to {@link textOf}. A pass for the attributes, a pass for the children and an index for the text\n * instead walks the keys three times and allocates the key array twice, which on a document of a few thousand elements is thousands of allocations\n * for nothing. Sorting is off by default, and the default path is the one that matters, so the attributes are built as they are found and there is\n * nothing to sort. When it is on, the attribute keys are collected instead and rendered afterwards in sorted order, which costs an array per element\n * and buys output that does not depend on the order the fields happened to be declared in.\n *\n * @param record - The element's value.\n * @param options - Resolved render options.\n *\n * @returns The element's rendered attributes and its child names.\n */\nconst collectFields = (record: XmlRecord, options: ResolvedOptions): Fields => {\n const keys = Object.keys(record);\n let attributes = '';\n let children: Array<string> | undefined;\n const sortAttributes = options.sortKeys ? ([] as Array<string>) : undefined;\n\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i] as string;\n const child = record[key];\n\n // An absent field is not written at all, which is what keeps an unset\n // optional attribute out of the document rather than in it as `a=\"\"`, and\n // an absent child out of it rather than in it as `<a/>`. The text key is\n // read by `textOf` either way, so skipping it here costs nothing.\n if (child === undefined) continue;\n\n if (isAttributeKey(key)) {\n // Only keys with a value are collected, so every name in `sortAttributes`\n // has one to read back out of.\n if (sortAttributes === undefined) attributes += renderAttribute(key, child, options);\n else sortAttributes.push(key);\n continue;\n }\n\n if (!isTextKey(key)) (children ??= []).push(key);\n }\n\n if (sortAttributes !== undefined) {\n sortAttributes.sort();\n attributes += sortedAttributes(sortAttributes, record, options);\n children?.sort();\n }\n\n return { attributes, children };\n};\n\n/**\n * @description The attributes named by `keys`, rendered in the order given. Only reached when the render was asked to sort keys, where the names are collected\n * during the key pass and written here so their order does not follow the order the fields were declared in.\n *\n * @param keys - The attribute keys to write, in the order to write them.\n * @param record - The element's value, to read the attribute values out of.\n * @param options - Resolved render options.\n *\n * @returns The rendered attributes, each with its leading space.\n */\nconst sortedAttributes = (keys: ReadonlyArray<string>, record: XmlRecord, options: ResolvedOptions): string => {\n let attributes = '';\n for (const key of keys) attributes += renderAttribute(key, record[key], options);\n return attributes;\n};\n\n/**\n * @description One attribute, written whole. The leading space is part of it so the caller can concatenate attributes and the opening tag without a separator of\n * its own.\n *\n * @param key - The attribute's key, with or without its `@` prefix.\n * @param value - The attribute's value.\n * @param options - Resolved render options.\n *\n * @returns The attribute, ready to write inside the opening tag.\n */\nconst renderAttribute = (key: string, value: XmlValue, options: ResolvedOptions): string => {\n const name = options.namer(attributeName(key));\n return ' ' + name + '=\"' + escapeAttribute(attributeText(value)) + '\"';\n};\n\n/**\n * @description Writes an element's children, by name and in the order their keys were found. The names are what the key pass kept; the values are read back out of\n * the record here, because keeping both would mean a second array per element.\n *\n * @param out - The chunk buffer to append to.\n * @param record - The element's value.\n * @param children - The child names, in the order to write them.\n * @param depth - The depth the parent sits at; its children are one deeper.\n * @param options - Resolved render options.\n */\nconst writeChildren = (out: Array<string>, record: XmlRecord, children: ReadonlyArray<string>, depth: number, options: ResolvedOptions): void => {\n for (let i = 0; i < children.length; i++) {\n const key = children[i] as string;\n const child = record[key];\n if (child === undefined) continue;\n renderElement(out, key, child, depth + 1, options);\n }\n};\n\n/**\n * @description Starts a new line at the given depth, when pretty-printing.\n *\n * @param out - The chunk buffer to append to.\n * @param depth - The depth the line sits at.\n * @param options - Resolved render options.\n */\nconst openLine = (out: Array<string>, depth: number, options: ResolvedOptions): void => {\n out.push('\\n');\n out.push(options.lineAt(depth));\n};\n\n/**\n * @description Writes an element with no content, in whichever of the two forms the options ask for. Every path that produces an element with nothing in it goes\n * through here, so the self-closing decision is made in exactly one place. That matters because \"nothing in it\" arrives four different ways — an\n * empty string, an absent value, an empty array, and a record whose fields are all absent — and four separate decisions are four chances for one of\n * them to write the long form by accident.\n *\n * @param out - The chunk buffer to append to.\n * @param tag - The element's name, already resolved.\n * @param options - Resolved render options.\n * @param attributes - The element's rendered attributes, if it has any. Defaults to none.\n */\nconst writeEmpty = (out: Array<string>, tag: string, options: ResolvedOptions, attributes = ''): void => {\n if (options.suppressEmptyNode) out.push('<', tag, attributes, '/>');\n else out.push('<', tag, attributes, '></', tag, '>');\n};\n\n/**\n * @description An element's character data, with the reserved text key read off its value.\n *\n * @param record - The element's value.\n *\n * @returns The text to write between the tags, or `''` when the element has none.\n */\nconst textOf = (record: XmlRecord): string => {\n const text = record[TEXT_KEY];\n if (Predicate.isUndefined(text)) return '';\n return Predicate.isString(text) ? text : renderScalar(text);\n};\n\n/**\n * @description An attribute value as the character data it is written as. A bare string is the only sensible shape, since an attribute holds nothing else. A\n * non-string is stringified rather than rejected: the schema is what enforces the field's type, and rejecting here would duplicate that check with a\n * different error and a message that names neither the field nor the document.\n *\n * @param value - The attribute's value.\n *\n * @returns The text to escape and write between the quotes.\n */\nconst attributeText = (value: XmlValue): string => {\n if (Predicate.isString(value)) return value;\n if (Predicate.isUndefined(value)) return '';\n return renderScalar(value);\n};\n\n/**\n * @description Renders a leaf that is not a string as the character data an XML document can hold. A schema-derived value never reaches here —\n * `Schema.toCodecStringTree` has already turned every scalar into a string — so this is for values a caller built by hand. A value with no sensible\n * text form is rendered as nothing rather than as `[object Object]`, which would silently write a document that parses back to something else.\n *\n * @param value - The leaf to render.\n *\n * @returns The leaf's textual form.\n */\nconst renderScalar = (value: Exclude<XmlValue, string | undefined>): string => {\n if (Predicate.isNull(value)) return 'null';\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return ''; // circular or otherwise not representable\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"render.js","names":[],"sources":["../src/render.ts"],"sourcesContent":["// Rendering: an `XmlValue` to XML text.\n//\n// This is the hot path for an application that serializes often, so it is built\n// around three things: one pass over each record's keys rather than one per\n// role a key can play, one pass over each character rather than one per\n// character class, and one array of chunks joined once rather than a growing\n// string.\n//\n// The walk itself is plain synchronous functions rather than a chain of\n// `yield*`es. Publicly `renderXml` is still an `Effect`: it suspends the walk so\n// it runs lazily, and folds the one failure the walk can report into the typed\n// error channel, but inside a document there is no effect boundary per element\n// or per attribute. A 500-row report is thousands of elements, and one fiber\n// step per element dominated the `render 500 rows` benchmark. The typed failure\n// survives: the walk throws an {@link XmlRenderError} and `renderXml` catches\n// it into `Effect.fail`.\n//\n// Escaping is the part that scales with the size of the document rather than\n// with its structure, and it is written out here rather than delegated, for a\n// measured reason. The entity encoder that used to live beside this package\n// escaped by applying five sequential global replacements, one per character,\n// so a document with a single `&` in twenty thousand characters was scanned\n// five times over to change one byte. The `render 20k` rows in\n// `bench/codec.bench.ts` measure that five-pass cost. The table below covers\n// the same five characters that encoder escaped, and the explicit expectations\n// in `test/render.spec.ts` pin the fast path.\n\nimport { Effect, Predicate, Result } from 'effect';\n\nimport type { NameMode } from './conventions.ts';\nimport type { XmlVersion } from './naming.ts';\nimport type { XmlRecord, XmlValue } from './xml-value.ts';\n\nimport { attributeName, DEFAULT_ITEM_NAME, DEFAULT_ROOT_NAME, isAttributeKey, isTextKey, resolveNameSync, TEXT_KEY } from './conventions.ts';\nimport { XmlRenderError } from './errors.ts';\n\n/**\n * @description The five characters XML predefines an entity for, and the names to write for them. Written out rather than referenced from the entity decoder\n * because the table is indexed by character code below.\n */\nconst XML_PREDEFINED = { 34: '&quot;', 38: '&amp;', 39: '&apos;', 60: '&lt;', 62: '&gt;' } as const;\n\n/**\n * @description The character references for the whitespace XML normalizes inside an attribute value. A parser replaces a literal newline, carriage return or tab\n * in an attribute with a space, so a value that has to survive a round trip has to spell them as references. They are in the attribute table and not\n * the text one: in character data they are content, and only an attribute value is normalized.\n */\nconst ATTRIBUTE_WHITESPACE = { 9: '&#9;', 10: '&#10;', 13: '&#13;' } as const;\n\n/**\n * @description The replacement for each ASCII character that needs one, and `undefined` for the ones that do not. Indexed by character code and 128 long, so the\n * check is one comparison and one array read with no string search in it.\n *\n * @param extra - Characters to escape in addition to the five predefines.\n *\n * @returns The lookup table.\n */\nconst buildTable = (extra: Record<number, string>): ReadonlyArray<string | undefined> => {\n const table = Array.from<string | undefined>({ length: 128 }).fill(undefined);\n for (const [code, entity] of Object.entries({ ...XML_PREDEFINED, ...extra })) table[Number(code)] = entity;\n return table;\n};\n\nconst TEXT_TABLE = buildTable({});\nconst ATTRIBUTE_TABLE = buildTable(ATTRIBUTE_WHITESPACE);\n\n/**\n * @description The characters each table escapes, as a pattern rather than as a set of replacement passes. A single pattern finds the first character that needs\n * replacing, which keeps clean text cheap: V8 compiles a single character class into a scan that is several times faster than a JavaScript loop\n * reading the same string a code unit at a time, and clean text is most text. The `render 20k of clean text` benchmark in `bench/codec.bench.ts`\n * measures that difference: a hand-written loop over the same twenty thousand characters is roughly two and a half times slower. Neither pattern is\n * global, so `exec` ignores `lastIndex` and always starts at the beginning. One module-level instance of each is therefore safe to reuse, and nothing\n * has to be reset between calls.\n */\nconst TEXT_UNSAFE = /[<>&\"']/;\nconst ATTRIBUTE_UNSAFE = /[<>&\"'\\n\\r\\t]/;\n\n/**\n * @description Options for {@link renderXml}.\n */\nexport interface XmlRenderOptions {\n /**\n * @description Name of the root element.\n *\n * @default 'root'\\\n * A codec passes the name it took from the schema's `identifier` annotation when the caller did not set one.\n */\n readonly rootName?: string | undefined;\n\n /**\n * @description Element name used for the members of a document whose root value is an array.\n *\n * @default 'item'\n */\n readonly itemName?: string | undefined;\n\n /**\n * @description Indent nested elements on their own lines.\n *\n * @default false\n */\n readonly format?: boolean | undefined;\n\n /**\n * @description The string one indent level is made of. Defaults to two spaces.\n */\n readonly indent?: string | undefined;\n\n /**\n * @description Write an element with no attributes, text or children as `<a/>` rather than `<a></a>`.\n *\n * @default true\n */\n readonly suppressEmptyNode?: boolean | undefined;\n\n /**\n * @description Sort an element's keys so the same value always renders to the same bytes.\n *\n * @default false\\\n * Which keeps declaration order.\\ Worth turning on for snapshot tests, where key order is otherwise the only thing that can make two equal values differ.\n */\n readonly sortKeys?: boolean | undefined;\n\n /**\n * @description What to do with a field name that is not a legal XML name.\n *\n * @default 'repair'.\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 * @description How deep to nest before giving up. Guards against a value that nests without end taking the stack with it.\n *\n * @default 256\n */\n readonly maxDepth?: number | undefined;\n}\n\n/**\n * @description Options every render call needs, with the defaults already applied.\n */\ninterface ResolvedOptions {\n readonly rootName: string;\n readonly itemName: string;\n readonly format: boolean;\n readonly indent: string;\n readonly suppressEmptyNode: boolean;\n readonly sortKeys: boolean;\n readonly name: NameMode;\n readonly xmlVersion: XmlVersion;\n readonly maxDepth: number;\n\n /**\n * @description Resolves a field name to a legal XML name, in the mode this render was configured with. Synchronous and memoized; see {@link makeNamer}.\n */\n readonly namer: (name: string) => string;\n\n /**\n * @description The indent for a given depth, when pretty-printing. Built on first use at each depth and kept, so an indented document builds one string per\n * level rather than one per line.\n */\n readonly lineAt: (depth: number) => string;\n}\n\n/**\n * @description Builds the name resolver for one render. Every element and every attribute name goes through here, and a document repeats names: a thousand\n * `<item>` elements, or the same `id` on every row. A validator that runs a regex per occurrence pays that cost a thousand times for one answer, so\n * the first result is remembered and the rest are lookups. Keeping the mode and version in one place also stops a caller from resolving a name with\n * different settings than the render it is part of. A cache miss calls {@link resolveNameSync}, which throws an {@link XmlParseError} in `'error'`\n * mode; {@link renderXml} catches it and reports it as an {@link XmlRenderError}.\n *\n * @param options - Resolved render options.\n *\n * @returns A function from field name to the legal XML name.\n */\nconst makeNamer = (options: Omit<ResolvedOptions, 'namer' | 'lineAt'>): ((name: string) => string) => {\n const cache = new Map<string, string>();\n return (name: string): string => {\n const hit = cache.get(name);\n if (hit !== undefined) return hit;\n const resolved = resolveNameSync(name, { mode: options.name, xmlVersion: options.xmlVersion });\n cache.set(name, resolved);\n return resolved;\n };\n};\n\n/**\n * @description A boolean option's value, with an absent one read as the default. The three boolean options are spelled through here rather than through a `??` of\n * their own, so the table below reads as a list of what each option _is_ instead of a list of nine separate decisions about what an omitted option\n * means, and a reader looking for \"which options are on by default\" finds three words rather than three mixes of `?? true` and `?? false` to read.\n *\n * @param value - The option as the caller wrote it, or `undefined` when the caller left it out.\n * @param fallback - The value to use when the caller left it out.\n *\n * @returns The option's value.\n */\nconst flag = (value: boolean | undefined, fallback: boolean): boolean => value ?? fallback;\n\n/**\n * @description The indent for a given depth, built the first time a render reaches that depth and kept. A document of a few thousand elements on several lines\n * each would otherwise call `repeat` once per line and allocate the same handful of strings thousands of times over.\n *\n * @param indent - The string one level of indentation is made of.\n *\n * @returns A function from depth to the indent for that depth.\n */\nconst makeLineAt = (indent: string): ((depth: number) => string) => {\n const lines: Array<string> = [''];\n return depth => {\n const line = lines[depth];\n if (line !== undefined) return line;\n const built = indent.repeat(depth);\n lines[depth] = built;\n return built;\n };\n};\n\n/**\n * @description Applies the defaults to one call's options, and builds the two things a render needs that are not options: the memoized name resolver and the\n * memoized indent lines.\n *\n * @param options - The options as the caller wrote them.\n *\n * @returns Every option a render reads, defaulted, with the resolver and the indent lines attached.\n */\nconst resolveOptions = (options: XmlRenderOptions): ResolvedOptions => {\n const resolved = {\n rootName: options.rootName ?? DEFAULT_ROOT_NAME,\n itemName: options.itemName ?? DEFAULT_ITEM_NAME,\n format: flag(options.format, false),\n indent: options.indent ?? ' ',\n suppressEmptyNode: flag(options.suppressEmptyNode, true),\n sortKeys: flag(options.sortKeys, false),\n name: options.name ?? ('repair' as NameMode),\n xmlVersion: options.xmlVersion ?? ('1.0' as XmlVersion),\n maxDepth: options.maxDepth ?? 256,\n };\n return { ...resolved, namer: makeNamer(resolved), lineAt: makeLineAt(resolved.indent) };\n};\n\n/**\n * @description Escapes a value for use as character data.\n *\n * @param value - The text to escape.\n *\n * @returns The text with the XML-unsafe characters replaced by predefined entities, or the very same string when there is nothing to escape.\n */\nexport const escapeText = (value: string): string => escape(value, TEXT_UNSAFE, TEXT_TABLE);\n\n/**\n * @description Escapes a value for use inside a double-quoted attribute.\n *\n * @param value - The text to escape.\n *\n * @returns The escaped text, with the whitespace that XML would otherwise normalize spelled as character references.\n */\nexport const escapeAttribute = (value: string): string => escape(value, ATTRIBUTE_UNSAFE, ATTRIBUTE_TABLE);\n\n/**\n * @description Replaces every character the table has an entry for, in one pass over the string. The pattern finds the first character that needs replacing, and a\n * string with none is handed straight back. That is the common case, and the pattern exists to keep it fast. From there the rest of the string is\n * copied in runs between the replacements rather than a character at a time, so the cost is one pattern scan, one copy, and one concatenation per\n * replacement, rather than a whole pass per character class. Only ASCII is looked up. XML carries every other character natively, and a code unit\n * above 127 has no entity an XML parser is required to know.\n *\n * @param value - The text to escape.\n * @param pattern - Matches the first character that needs replacing.\n * @param table - The replacement for each ASCII character that needs one.\n *\n * @returns The escaped text, or `value` itself when there is nothing to escape.\n */\nconst escape = (value: string, pattern: RegExp, table: ReadonlyArray<string | undefined>): string => {\n const found = pattern.exec(value);\n if (found === null) return value; // nothing to escape: hand back the same string\n\n const length = value.length;\n const start = found.index;\n let out = value.slice(0, start);\n let copied = start;\n\n for (let index = start; index < length; index++) {\n const code = value.charCodeAt(index);\n const entity = code < 128 ? table[code] : undefined;\n if (entity !== undefined) {\n out += value.slice(copied, index) + entity;\n copied = index + 1;\n }\n }\n\n return copied === length ? out : out + value.slice(copied);\n};\n\n/**\n * @description Renders an {@link XmlValue} as an XML document.\\\n * A record becomes an element:\n *\n * - `@`-prefixed keys become attributes, the reserved `#text` key becomes character data, and every other key becomes a child element.\n * - An array repeats its name. A document whose root value is an array wraps it in the root element and names each member `itemName`.\n * - A string is character data. The walk is synchronous, and what can go wrong is reported by throwing an {@link XmlRenderError}; {@link renderXml}\n * folds that into the effect's typed error channel. A caller not already in an `Effect` runs it with `Effect.runSync`, which throws the failure it\n * produced.\n *\n * @param value - The value to render.\n * @param options - Root name, formatting, empty-element and name-resolution settings.\n *\n * @returns An effect producing the XML document as a string.\n */\nexport const renderXml = (value: XmlValue, options: XmlRenderOptions = {}): Effect.Effect<string, XmlRenderError> =>\n Effect.suspend(() => Effect.fromResult(renderResult(value, options)));\n\n/**\n * @description Runs the synchronous walk and folds the one failure it reports into a {@link Result}, which {@link renderXml} turns back into an `Effect`. Kept\n * separate so the walk itself can throw without the public API ever throwing.\n *\n * @param value - The value to render.\n * @param options - The options as the caller wrote them.\n *\n * @returns The document, or the failure to report.\n */\nconst renderResult = (value: XmlValue, options: XmlRenderOptions): Result.Result<string, XmlRenderError> => {\n try {\n return Result.succeed(render(value, options));\n } catch (cause) {\n return Result.fail(toRenderError(cause));\n }\n};\n\n/**\n * @description Reports a failure the synchronous walk threw in the render's own error type. The walk only throws an {@link XmlRenderError} of its own or an\n * {@link XmlParseError} from the name resolver; the latter carries the message the spec asserts on, so it is carried across rather than replaced.\n *\n * @param cause - Whatever was thrown.\n *\n * @returns The failure to report.\n */\nconst toRenderError = (cause: unknown): XmlRenderError => {\n if (cause instanceof XmlRenderError) return cause;\n if (Predicate.isError(cause)) return new XmlRenderError({ message: cause.message });\n return new XmlRenderError({ message: String(cause) });\n};\n\n/**\n * @description The synchronous walk behind {@link renderXml}.\n *\n * @param value - The value to render.\n * @param options - The options as the caller wrote them.\n *\n * @returns The XML document as a string.\n *\n * @throws {XmlRenderError} When the value nests past `maxDepth`, or the name resolver refuses a field name.\n */\nconst render = (value: XmlValue, options: XmlRenderOptions): string => {\n const resolved = resolveOptions(options);\n const out: Array<string> = [];\n\n // A document has exactly one root element, so a root value that is an array\n // is wrapped rather than emitted as several roots. Inside a named element an\n // array repeats that element's own name, so this wrapping is the only place\n // `itemName` is ever used.\n if (Array.isArray(value)) {\n const tag = resolved.namer(resolved.rootName);\n out.push('<', tag, '>');\n for (const member of value) renderElement(out, resolved.itemName, member, 1, resolved);\n if (resolved.format) out.push('\\n');\n out.push('</', tag, '>');\n } else {\n renderElement(out, resolved.rootName, value, 0, resolved);\n }\n\n if (resolved.format) out.push('\\n');\n return out.join('');\n};\n\n/**\n * @description Renders one named element and its subtree. The value an {@link XmlValue} holds decides which of the four shapes below it takes: a repeated run of\n * children, character data, an absent field, or a record. Each of those is written by a function of its own, so this one is the dispatch rather than\n * the document.\n *\n * @param out - The chunk buffer to append to.\n * @param name - The element name, not yet resolved.\n * @param value - The element's value.\n * @param depth - Current nesting depth, for indentation and the depth cap.\n * @param options - Resolved render options.\n */\nconst renderElement = (out: Array<string>, name: string, value: XmlValue, depth: number, options: ResolvedOptions): void => {\n assertWithinDepth(depth, options);\n\n if (Array.isArray(value)) {\n renderRepeated(out, name, value, depth, options);\n return;\n }\n\n // An element opens its own line rather than having its caller do it, so a\n // repeated run of children stays on separate lines. The root is the\n // one element that has nothing in front of it.\n if (options.format && depth > 0) openLine(out, depth, options);\n\n const tag = options.namer(name);\n\n if (Predicate.isString(value) || Predicate.isUndefined(value)) {\n renderLeaf(out, tag, value, options);\n return;\n }\n\n // The array case returned above; `Predicate.isObject` narrows what is left to\n // a record, since `Array.isArray` alone leaves a `ReadonlyArray` in the union.\n if (!Predicate.isObject(value)) return;\n renderRecord(out, tag, value as XmlRecord, depth, options);\n};\n\n/**\n * @description Refuses to walk deeper than the render allows. A value can nest without end, and every one of those levels costs a stack frame here, so the cap is\n * checked as the walk descends rather than trusted to the caller.\n *\n * @param depth - The depth about to be written.\n * @param options - Resolved render options.\n *\n * @throws {XmlRenderError} When the depth is past the cap.\n */\nconst assertWithinDepth = (depth: number, options: ResolvedOptions): void => {\n if (depth > options.maxDepth) {\n throw new XmlRenderError({\n message: `XML nesting exceeded maxDepth (${options.maxDepth}). Raise the limit if the document is legitimately this deep.`,\n });\n }\n};\n\n/**\n * @description Renders a repeated run of children under one name: `tags: ['a', 'b']` renders `<tags>a</tags><tags>b</tags>`, not one element wrapping both. The\n * name is already the element's own, so a name only has to be supplied where no name is available. Checked before the line break and the name are\n * taken, because the array itself is not an element: opening a line for it as well as for each of its members would leave a blank line where the\n * array was.\n *\n * @param out - The chunk buffer to append to.\n * @param name - The element name, not yet resolved.\n * @param members - The children to write, one after another.\n * @param depth - The depth the run sits at.\n * @param options - Resolved render options.\n */\nconst renderRepeated = (out: Array<string>, name: string, members: ReadonlyArray<XmlValue>, depth: number, options: ResolvedOptions): void => {\n // An empty array still gets an element. Writing nothing would make a field\n // that was present and empty indistinguishable from one that was never\n // there, and a document that came from a schema is easier to trust when the\n // element it describes is actually in the output.\n if (members.length === 0) {\n if (options.format && depth > 0) openLine(out, depth, options);\n writeEmpty(out, options.namer(name), options);\n return;\n }\n for (const member of members) renderElement(out, name, member, depth, options);\n};\n\n/**\n * @description Renders an element whose value is character data, or nothing. An empty string is character data that happens to be empty, and an element holding\n * none of it is the same element as one holding nothing at all, as is an `undefined` element, which is an absent one. The renderer is handed values\n * that never went through the schema (a caller building a document by hand), so the absent case is reachable, and an empty element is the correct\n * rendering of both.\n *\n * @param out - The chunk buffer to append to.\n * @param tag - The element's name, already resolved.\n * @param value - The element's character data, or `undefined` for an absent element.\n * @param options - Resolved render options.\n */\nconst renderLeaf = (out: Array<string>, tag: string, value: string | undefined, options: ResolvedOptions): void => {\n if (Predicate.isUndefined(value) || value === '') {\n writeEmpty(out, tag, options);\n return;\n }\n out.push('<', tag, '>', escapeText(value), '</', tag, '>');\n};\n\n/**\n * @description Renders an element holding a record: the attributes gathered from its `@` keys, the character data from its `#text` key, and its remaining keys as\n * child elements.\n *\n * @param out - The chunk buffer to append to.\n * @param tag - The element's name, already resolved.\n * @param record - The element's value.\n * @param depth - The depth the element sits at.\n * @param options - Resolved render options.\n */\nconst renderRecord = (out: Array<string>, tag: string, record: XmlRecord, depth: number, options: ResolvedOptions): void => {\n const fields = collectFields(record, options);\n const text = textOf(record);\n const children = fields.children;\n\n // Self-closing is decided by whether the element has any *content*, not by\n // whether it has attributes: `<a id=\"1\"/>` is the same element as\n // `<a id=\"1\">` with nothing in it, and the short form is what every XML\n // writer produces.\n if (children === undefined && text === '') {\n writeEmpty(out, tag, options, fields.attributes);\n return;\n }\n\n out.push('<', tag, fields.attributes, '>');\n\n // Character data sits inline when it is all an element has, and on its own\n // line when the element also has children, so an indented document does not\n // end up with its first line of text glued to its opening tag.\n if (text !== '') {\n if (children !== undefined && options.format) openLine(out, depth + 1, options);\n out.push(escapeText(text));\n }\n\n if (children !== undefined) {\n writeChildren(out, record, children, depth, options);\n if (options.format) openLine(out, depth, options);\n }\n\n out.push('</', tag, '>');\n};\n\n/**\n * @description An element's keys resolved into the roles they play.\n */\ninterface Fields {\n /**\n * @description The element's rendered attributes, each with its leading space, or `''` when it has none.\n */\n readonly attributes: string;\n\n /**\n * @description The names of the child elements, in the order they will be written, or `undefined` when the element has none. `undefined` rather than an empty\n * array because \"has children\" is one of the two things the self-closing decision turns on, and the other is the text.\n */\n readonly children: Array<string> | undefined;\n}\n\n/**\n * @description One pass over a record's keys, collecting all three roles at once: the attributes are rendered as they are found, the child names are set aside for\n * the pass that writes them, and the text key is left to {@link textOf}. A pass for the attributes, a pass for the children and an index for the text\n * instead walks the keys three times and allocates the key array twice, which on a document of a few thousand elements is thousands of allocations\n * for nothing. Sorting is off by default, and the default path is the important one, so the attributes are built as they are found and there is\n * nothing to sort. When it is on, the attribute keys are collected instead and rendered afterwards in sorted order, which costs an array per element\n * and gives output independent of the order the fields happened to be declared in.\n *\n * @param record - The element's value.\n * @param options - Resolved render options.\n *\n * @returns The element's rendered attributes and its child names.\n */\nconst collectFields = (record: XmlRecord, options: ResolvedOptions): Fields => {\n const keys = Object.keys(record);\n let attributes = '';\n let children: Array<string> | undefined;\n const sortAttributes = options.sortKeys ? ([] as Array<string>) : undefined;\n\n for (let i = 0; i < keys.length; i++) {\n const key = keys[i] as string;\n const child = record[key];\n\n // An absent field is not written at all, so an unset optional attribute\n // stays out of the document rather than appearing as `a=\"\"`, and an absent\n // child stays out rather than appearing as `<a/>`. The text key is read by\n // `textOf` either way, so skipping it here costs nothing.\n if (child === undefined) continue;\n\n if (isAttributeKey(key)) {\n // Only keys with a value are collected, so every name in `sortAttributes`\n // has one to read back out of.\n if (sortAttributes === undefined) attributes += renderAttribute(key, child, options);\n else sortAttributes.push(key);\n continue;\n }\n\n if (!isTextKey(key)) (children ??= []).push(key);\n }\n\n if (sortAttributes !== undefined) {\n sortAttributes.sort();\n attributes += sortedAttributes(sortAttributes, record, options);\n children?.sort();\n }\n\n return { attributes, children };\n};\n\n/**\n * @description The attributes named by `keys`, rendered in the order given. Only reached when the render was asked to sort keys, where the names are collected\n * during the key pass and written here so their order does not follow the order the fields were declared in.\n *\n * @param keys - The attribute keys to write, in the order to write them.\n * @param record - The element's value, to read the attribute values out of.\n * @param options - Resolved render options.\n *\n * @returns The rendered attributes, each with its leading space.\n */\nconst sortedAttributes = (keys: ReadonlyArray<string>, record: XmlRecord, options: ResolvedOptions): string => {\n let attributes = '';\n for (const key of keys) attributes += renderAttribute(key, record[key], options);\n return attributes;\n};\n\n/**\n * @description One attribute, written whole. The leading space is part of it so the caller can concatenate attributes and the opening tag without a separator of\n * its own.\n *\n * @param key - The attribute's key, with or without its `@` prefix.\n * @param value - The attribute's value.\n * @param options - Resolved render options.\n *\n * @returns The attribute, ready to write inside the opening tag.\n */\nconst renderAttribute = (key: string, value: XmlValue, options: ResolvedOptions): string => {\n const name = options.namer(attributeName(key));\n return ' ' + name + '=\"' + escapeAttribute(attributeText(value)) + '\"';\n};\n\n/**\n * @description Writes an element's children, by name and in the order their keys were found. The names are what the key pass kept; the values are read back out of\n * the record here, because keeping both would mean a second array per element.\n *\n * @param out - The chunk buffer to append to.\n * @param record - The element's value.\n * @param children - The child names, in the order to write them.\n * @param depth - The depth the parent sits at; its children are one deeper.\n * @param options - Resolved render options.\n */\nconst writeChildren = (out: Array<string>, record: XmlRecord, children: ReadonlyArray<string>, depth: number, options: ResolvedOptions): void => {\n for (let i = 0; i < children.length; i++) {\n const key = children[i] as string;\n const child = record[key];\n if (child === undefined) continue;\n renderElement(out, key, child, depth + 1, options);\n }\n};\n\n/**\n * @description Starts a new line at the given depth, when pretty-printing.\n *\n * @param out - The chunk buffer to append to.\n * @param depth - The depth the line sits at.\n * @param options - Resolved render options.\n */\nconst openLine = (out: Array<string>, depth: number, options: ResolvedOptions): void => {\n out.push('\\n');\n out.push(options.lineAt(depth));\n};\n\n/**\n * @description Writes an element with no content, in whichever of the two forms the options ask for. Every path that produces an element with nothing in it goes\n * through here, so the self-closing decision is made in exactly one place. That is important because \"nothing in it\" arrives four different ways: an\n * empty string, an absent value, an empty array, and a record whose fields are all absent. Four separate decisions are four chances for one of them\n * to write the long form by accident.\n *\n * @param out - The chunk buffer to append to.\n * @param tag - The element's name, already resolved.\n * @param options - Resolved render options.\n * @param attributes - The element's rendered attributes, if it has any. Defaults to none.\n */\nconst writeEmpty = (out: Array<string>, tag: string, options: ResolvedOptions, attributes = ''): void => {\n if (options.suppressEmptyNode) out.push('<', tag, attributes, '/>');\n else out.push('<', tag, attributes, '></', tag, '>');\n};\n\n/**\n * @description An element's character data, with the reserved text key read off its value.\n *\n * @param record - The element's value.\n *\n * @returns The text to write between the tags, or `''` when the element has none.\n */\nconst textOf = (record: XmlRecord): string => {\n const text = record[TEXT_KEY];\n if (Predicate.isUndefined(text)) return '';\n return Predicate.isString(text) ? text : renderScalar(text);\n};\n\n/**\n * @description An attribute value as the character data it is written as. A bare string is the only sensible shape, since an attribute holds nothing else. A\n * non-string is stringified rather than rejected: the schema is what enforces the field's type, and rejecting here would duplicate that check with a\n * different error and a message that names neither the field nor the document.\n *\n * @param value - The attribute's value.\n *\n * @returns The text to escape and write between the quotes.\n */\nconst attributeText = (value: XmlValue): string => {\n if (Predicate.isString(value)) return value;\n if (Predicate.isUndefined(value)) return '';\n return renderScalar(value);\n};\n\n/**\n * @description Renders a leaf that is not a string as the character data an XML document can hold. A schema-derived value never reaches here, because\n * `Schema.toCodecStringTree` has already turned every scalar into a string, so this is for values a caller built by hand. A value with no sensible\n * text form is rendered as nothing rather than as `[object Object]`, which would silently write a document that parses back to something else.\n *\n * @param value - The leaf to render.\n *\n * @returns The leaf's textual form.\n */\nconst renderScalar = (value: Exclude<XmlValue, string | undefined>): string => {\n if (Predicate.isNull(value)) return 'null';\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return ''; // circular or otherwise not representable\n }\n};\n"],"mappings":"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"}
@@ -2,13 +2,13 @@ import { Schema } from "effect";
2
2
  //#region src/xml-error.d.ts
3
3
  /**
4
4
  * @description The specific cause of an {@link XmlError}, as a tagged union. The `_tag` on each member is the discriminant `Effect.catchReason` matches on, and
5
- * the payload is what a handler needs in order to decide or to report. Every member is a case the decoder or the name validators actually raise —
6
- * there is no catch-all member, so an exhaustive `match` stays exhaustive as causes are added.
5
+ * the payload is what a handler needs in order to decide or to report. Every member is a case the decoder or the name validators actually raise.
6
+ * There is no catch-all member, so an exhaustive `match` stays exhaustive as causes are added.
7
7
  */
8
8
  export declare const XmlErrorReason: Schema.TaggedUnion<{
9
9
  readonly EntityRejected: Schema.TaggedStruct<"EntityRejected", {
10
10
  /**
11
- * @description Which registration was in progress. Both are runtime-injected, which is why they share a tier for limit accounting.
11
+ * @description Which registration was in progress. The runtime injects both, so they share a tier for limit accounting.
12
12
  */
13
13
  readonly context: Schema.Literals<readonly ["external", "input"]>;
14
14
  /**
@@ -28,7 +28,7 @@ export declare const XmlErrorReason: Schema.TaggedUnion<{
28
28
  }>;
29
29
  readonly ExpansionLimitExceeded: Schema.TaggedStruct<"ExpansionLimitExceeded", {
30
30
  /**
31
- * @description The count that tripped the limit. Deliberately not reset on failure, so this is the real over-limit total rather than the ceiling.
31
+ * @description The count that tripped the limit. The counter is not reset on failure, so this is the real over-limit total rather than the ceiling.
32
32
  */
33
33
  readonly actual: Schema.Number;
34
34
  /**
@@ -82,7 +82,7 @@ declare const XmlError_base: Schema.Class<XmlError, Schema.TaggedStruct<"XmlErro
82
82
  readonly reason: Schema.TaggedUnion<{
83
83
  readonly EntityRejected: Schema.TaggedStruct<"EntityRejected", {
84
84
  /**
85
- * @description Which registration was in progress. Both are runtime-injected, which is why they share a tier for limit accounting.
85
+ * @description Which registration was in progress. The runtime injects both, so they share a tier for limit accounting.
86
86
  */
87
87
  readonly context: Schema.Literals<readonly ["external", "input"]>;
88
88
  /**
@@ -102,7 +102,7 @@ declare const XmlError_base: Schema.Class<XmlError, Schema.TaggedStruct<"XmlErro
102
102
  }>;
103
103
  readonly ExpansionLimitExceeded: Schema.TaggedStruct<"ExpansionLimitExceeded", {
104
104
  /**
105
- * @description The count that tripped the limit. Deliberately not reset on failure, so this is the real over-limit total rather than the ceiling.
105
+ * @description The count that tripped the limit. The counter is not reset on failure, so this is the real over-limit total rather than the ceiling.
106
106
  */
107
107
  readonly actual: Schema.Number;
108
108
  /**
@@ -147,19 +147,19 @@ declare const XmlError_base: Schema.Class<XmlError, Schema.TaggedStruct<"XmlErro
147
147
  }>;
148
148
  /**
149
149
  * @description Human-readable description. The `[EntityReplacer]` and `[EntityDecoder]` prefixes are reproduced verbatim from the original throw sites, because
150
- * they are documented as load-bearing for anything matching on them.
150
+ * anything matching on them depends on them.
151
151
  */
152
152
  readonly message: Schema.String;
153
153
  }>, import("effect/Cause").YieldableError>;
154
154
  /**
155
- * @description Every failure this package can report, in the `E` channel of the effects that can fail. Carries both a `reason` — the typed, matchable cause — and
155
+ * @description Every failure this package can report, in the `E` channel of the effects that can fail. Carries both a `reason`, the typed and matchable cause, and
156
156
  * a `message`, which is the human-readable form the package has always produced. Both are part of the schema, so an error survives a round-trip
157
157
  * through a serialised boundary without losing either.
158
158
  *
159
159
  * @example
160
160
  * ```typescript
161
161
  * import { Effect } from 'effect';
162
- * import { EntityDecoder } from '@endevops/effect-xml-codec';
162
+ * import { EntityDecoder } from '@endevops/effect-codec-xml';
163
163
  *
164
164
  * const program = Effect.gen(function*() {
165
165
  * const decoder = yield* EntityDecoder.make({});