{"version":3,"file":"xml.mjs","names":[],"sources":["../src/xml/stream-writer.ts"],"sourcesContent":["// Streaming XML emitter. Used by writers that need to compose larger documents\n// incrementally — chiefly the worksheet writer where the `<sheetData>` block is\n// emitted row-by-row to keep heap use bounded.\n//\n// Phase 1 §5: buffered output only (a chunk array materialised on `result()`).\n// Streaming via `WritableStream<Uint8Array>` is added in the phase-4 streaming\n// worksheet writer; the structural API stays the same so that change is\n// mechanical.\n//\n// The worksheet hot path emits cells through templated strings, NOT through\n// this writer's start / end / writeNode methods. Use `writeRaw` to splice those\n// in.\n\nimport { escapeXmlAttr, escapeXmlText } from '../utils/escape';\nimport { OpenXmlIoError } from '../utils/exceptions';\nimport { utf8ByteLength } from '../utils/utf8';\nimport { DEFAULT_PREFIXES, parseQName, XML_NS } from './namespaces';\nimport type { XmlNode } from './tree';\n\nexport interface XmlStreamWriterOptions {\n  /** Map of namespace URI → prefix. Merged on top of DEFAULT_PREFIXES. */\n  prefixMap?: Readonly<Record<string, string>>;\n  /** Emit `<?xml … ?>` declaration first. Defaults to true. */\n  xmlDeclaration?: boolean;\n  /** `standalone` attribute on the declaration. Defaults to 'yes'. */\n  standalone?: 'yes' | 'no' | 'omit';\n  /**\n   * Auto-flush threshold in bytes. Once the in-flight string buffer crosses\n   * this size it gets encoded into a chunk and parked. Larger values trade\n   * memory for fewer TextEncoder calls; smaller values lower peak memory at the\n   * cost of CPU.\n   */\n  flushBytes?: number;\n}\n\nexport interface XmlStreamWriter {\n  /**\n   * Open an element. Names are in Clark notation (`{ns}local`); the writer\n   * prefixes them via the configured prefix map.\n   */\n  start(name: string, attrs?: Record<string, string>): void;\n  /** Emit a text node inside the currently open element. */\n  text(s: string): void;\n  /** Emit a complete subtree. Closes any pending start tag first. */\n  writeNode(n: XmlNode): void;\n  /** Emit pre-rendered XML bytes verbatim — escape-hatch for hot paths. */\n  writeRaw(s: string): void;\n  /** Close the currently open element (matched against the start stack). */\n  end(): void;\n  /** Force any buffered bytes into the chunk store immediately. */\n  flush(): void;\n  /**\n   * Materialise everything written so far. Throws if any element is still open.\n   * Idempotent.\n   */\n  result(): Uint8Array;\n}\n\nconst DEFAULT_FLUSH_BYTES = 64 * 1024;\nconst encoder = new TextEncoder();\n\nconst buildPrefixMap = (user: Readonly<Record<string, string>> | undefined): Map<string, string> => {\n  const out = new Map<string, string>();\n  for (const [ns, prefix] of Object.entries(DEFAULT_PREFIXES)) out.set(ns, prefix);\n  // The xml prefix is reserved by the XMLNS spec for XML_NS; never override.\n  out.set(XML_NS, 'xml');\n  if (user !== undefined) {\n    for (const [ns, prefix] of Object.entries(user)) out.set(ns, prefix);\n  }\n  return out;\n};\n\nexport function createXmlStreamWriter(opts: XmlStreamWriterOptions = {}): XmlStreamWriter {\n  const { xmlDeclaration = true, standalone = 'yes', flushBytes = DEFAULT_FLUSH_BYTES } = opts;\n  const prefixOf = buildPrefixMap(opts.prefixMap);\n\n  const chunks: Uint8Array[] = [];\n  let buf = '';\n  // Running UTF-8 byte count of `buf`, maintained incrementally via `append()`.\n  // Without this the flush threshold compares UTF-16 code units against a\n  // byte budget — non-ASCII payloads then balloon past the configured limit.\n  let bufBytes = 0;\n  let openStartTag = false;\n  let finalised = false;\n  const stack: string[] = [];\n\n  const append = (s: string): void => {\n    buf += s;\n    bufBytes += utf8ByteLength(s);\n  };\n\n  const elementName = (name: string): string => {\n    const { ns, local } = parseQName(name);\n    if (ns === '') return local;\n    const prefix = prefixOf.get(ns);\n    if (prefix === undefined || prefix === '') return local;\n    return `${prefix}:${local}`;\n  };\n  const attributeName = (name: string): string => {\n    const { ns, local } = parseQName(name);\n    if (ns === '') return local;\n    const prefix = prefixOf.get(ns);\n    if (prefix === undefined || prefix === '') return local;\n    return `${prefix}:${local}`;\n  };\n\n  const flushImpl = (): void => {\n    if (buf.length === 0) return;\n    chunks.push(encoder.encode(buf));\n    buf = '';\n    bufBytes = 0;\n  };\n\n  const maybeFlush = (): void => {\n    if (bufBytes >= flushBytes) flushImpl();\n  };\n\n  const closeStartTagIfOpen = (): void => {\n    if (!openStartTag) return;\n    append('>');\n    openStartTag = false;\n  };\n\n  if (xmlDeclaration) {\n    append('<?xml version=\"1.0\" encoding=\"UTF-8\"');\n    if (standalone !== 'omit') append(` standalone=\"${standalone}\"`);\n    append('?>\\n');\n  }\n\n  // ---- writeNode internals\n  // ---------------------------------------------------\n\n  const emitNodeInline = (n: XmlNode): void => {\n    const tag = elementName(n.name);\n    append(`<${tag}`);\n    for (const [name, value] of Object.entries(n.attrs)) {\n      append(` ${attributeName(name)}=\"${escapeXmlAttr(value)}\"`);\n    }\n    const text = n.text;\n    const hasText = text !== undefined && text !== '';\n    const hasChildren = n.children.length > 0;\n    if (!hasText && !hasChildren) {\n      append('/>');\n      return;\n    }\n    append('>');\n    if (hasText) append(escapeXmlText(text));\n    for (const c of n.children) emitNodeInline(c);\n    append(`</${tag}>`);\n  };\n\n  // ---- public surface\n  // --------------------------------------------------------\n\n  return {\n    start(name, attrs) {\n      if (finalised) throw new OpenXmlIoError('XmlStreamWriter: start() after result()');\n      closeStartTagIfOpen();\n      const tag = elementName(name);\n      append(`<${tag}`);\n      if (attrs !== undefined) {\n        for (const [k, v] of Object.entries(attrs)) {\n          append(` ${attributeName(k)}=\"${escapeXmlAttr(v)}\"`);\n        }\n      }\n      stack.push(tag);\n      openStartTag = true;\n      maybeFlush();\n    },\n    text(s) {\n      if (finalised) throw new OpenXmlIoError('XmlStreamWriter: text() after result()');\n      closeStartTagIfOpen();\n      append(escapeXmlText(s));\n      maybeFlush();\n    },\n    writeNode(n) {\n      if (finalised) throw new OpenXmlIoError('XmlStreamWriter: writeNode() after result()');\n      closeStartTagIfOpen();\n      emitNodeInline(n);\n      maybeFlush();\n    },\n    writeRaw(s) {\n      if (finalised) throw new OpenXmlIoError('XmlStreamWriter: writeRaw() after result()');\n      closeStartTagIfOpen();\n      append(s);\n      maybeFlush();\n    },\n    end() {\n      if (finalised) throw new OpenXmlIoError('XmlStreamWriter: end() after result()');\n      const tag = stack.pop();\n      if (tag === undefined) throw new OpenXmlIoError('XmlStreamWriter: end() with no open element');\n      if (openStartTag) {\n        append('/>');\n        openStartTag = false;\n      } else {\n        append(`</${tag}>`);\n      }\n      maybeFlush();\n    },\n    flush() {\n      flushImpl();\n    },\n    result(): Uint8Array {\n      if (stack.length > 0) {\n        throw new OpenXmlIoError(`XmlStreamWriter: ${stack.length} unclosed element(s) at result()`);\n      }\n      flushImpl();\n      finalised = true;\n      let total = 0;\n      for (const c of chunks) total += c.byteLength;\n      const out = new Uint8Array(total);\n      let off = 0;\n      for (const c of chunks) {\n        out.set(c, off);\n        off += c.byteLength;\n      }\n      return out;\n    },\n  };\n}\n"],"mappings":"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"}