{"version":3,"file":"serializer-DQzTTNOS.mjs","names":[],"sources":["../src/xml/serializer.ts"],"sourcesContent":["// DOM-style XML serializer. Inverse of src/xml/parser.ts.\n//\n// XmlNode trees use Clark-notation names (`{ns}local`); the serializer\n// rebuilds prefix mappings and emits a single UTF-8 byte payload.\n//\n// Algorithm:\n//   1. walk the tree once, collecting every namespace URI used by\n//      elements or attributes;\n//   2. assign each URI a prefix — DEFAULT_PREFIXES first, then\n//      auto-generated `ns{N}` — and decide which (if any) becomes the\n//      default (xmlns=\"…\") namespace by reusing the root element's NS;\n//   3. emit the XML declaration, root open tag with all xmlns / xmlns:*\n//      declarations, then a recursive children walk that escapes text\n//      and attribute values.\n//\n// Output style matches openpyxl/Excel: no whitespace between elements,\n// `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>` on its own\n// line, attribute values quoted with `\"`.\n\nimport { escapeXmlAttr, escapeXmlText } from '../utils/escape';\nimport { DEFAULT_PREFIXES, parseQName, XML_NS } from './namespaces';\nimport type { XmlNode } from './tree';\n\nexport interface SerializeOptions {\n  /** Emit `<?xml … ?>` declaration. Defaults to true. */\n  xmlDeclaration?: boolean;\n  /** `standalone` attribute on the declaration. Defaults to 'yes'. */\n  standalone?: 'yes' | 'no' | 'omit';\n}\n\n/**\n * Serialize an {@link XmlNode} tree into a UTF-8 byte payload. The\n * inverse of {@link parseXml}: `parseXml(serializeXml(n))` yields a\n * tree structurally equivalent to `n` modulo attribute insertion order\n * (which both directions preserve on Node 18+ / V8).\n */\nexport function serializeXml(root: XmlNode, opts: SerializeOptions = {}): Uint8Array {\n  const { xmlDeclaration = true, standalone = 'yes' } = opts;\n\n  const allocation = allocatePrefixes(root);\n  const out: string[] = [];\n\n  if (xmlDeclaration) {\n    out.push('<?xml version=\"1.0\" encoding=\"UTF-8\"');\n    if (standalone !== 'omit') out.push(` standalone=\"${standalone}\"`);\n    out.push('?>\\n');\n  }\n\n  emit(out, root, allocation, /* isRoot */ true);\n\n  return new TextEncoder().encode(out.join(''));\n}\n\n// ---- prefix allocation ------------------------------------------------------\n\ninterface Allocation {\n  /** ns URI → prefix ('' = default namespace) */\n  prefixOf: Map<string, string>;\n  /** prefix declarations to emit on the root element, in deterministic order */\n  declarations: Array<{ prefix: string; ns: string }>;\n}\n\nconst allocatePrefixes = (root: XmlNode): Allocation => {\n  const used = new Set<string>();\n  collectNamespaces(root, used, /* attrsToo */ true);\n  used.delete(''); // empty NS = unprefixed names; not emitted as xmlns\n\n  // Decide the default namespace. Prefer the root element's NS if its\n  // canonical prefix in DEFAULT_PREFIXES is empty (i.e. designed to live\n  // as a default), so the bulk of the root subtree stays unprefixed.\n  const rootNs = parseQName(root.name).ns;\n  let defaultNs = '';\n  if (rootNs && DEFAULT_PREFIXES[rootNs] === '') {\n    defaultNs = rootNs;\n  }\n\n  const prefixOf = new Map<string, string>();\n  if (defaultNs !== '') prefixOf.set(defaultNs, '');\n  // `xml` and `xmlns` are reserved by the XMLNS spec; xml ↔ XML_NS is\n  // predefined and must NOT be redeclared via xmlns:xml=\"…\".\n  prefixOf.set(XML_NS, 'xml');\n\n  // Pass 1: respect DEFAULT_PREFIXES for namespaces that have a\n  // canonical short prefix.\n  let auto = 0;\n  const usedPrefixes = new Set<string>(['', 'xml', 'xmlns']);\n  const ordered = Array.from(used).sort();\n  for (const ns of ordered) {\n    if (prefixOf.has(ns)) continue;\n    const preferred = DEFAULT_PREFIXES[ns];\n    if (preferred !== undefined && preferred !== '' && !usedPrefixes.has(preferred)) {\n      prefixOf.set(ns, preferred);\n      usedPrefixes.add(preferred);\n    }\n  }\n  // Pass 2: auto-allocate for the remainder.\n  for (const ns of ordered) {\n    if (prefixOf.has(ns)) continue;\n    let prefix = `ns${auto++}`;\n    while (usedPrefixes.has(prefix)) prefix = `ns${auto++}`;\n    prefixOf.set(ns, prefix);\n    usedPrefixes.add(prefix);\n  }\n\n  // Emission order: default first, then other prefixes alphabetically by ns\n  // URI for determinism. XML_NS is reserved and never declared.\n  const declarations: Array<{ prefix: string; ns: string }> = [];\n  if (defaultNs !== '') declarations.push({ prefix: '', ns: defaultNs });\n  for (const ns of ordered) {\n    if (ns === defaultNs) continue;\n    if (ns === XML_NS) continue;\n    const prefix = prefixOf.get(ns);\n    if (prefix === undefined) continue;\n    declarations.push({ prefix, ns });\n  }\n\n  return { prefixOf, declarations };\n};\n\nconst collectNamespaces = (node: XmlNode, into: Set<string>, attrsToo: boolean): void => {\n  into.add(parseQName(node.name).ns);\n  if (attrsToo) {\n    for (const attrName of Object.keys(node.attrs)) {\n      into.add(parseQName(attrName).ns);\n    }\n  }\n  for (const c of node.children) collectNamespaces(c, into, attrsToo);\n};\n\n// ---- emission ---------------------------------------------------------------\n\nconst buildElementPrefix = (name: string, prefixOf: Map<string, 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\nconst buildAttrPrefix = (name: string, prefixOf: Map<string, string>): string => {\n  const { ns, local } = parseQName(name);\n  if (ns === '') return local;\n  // Attributes never inherit the default namespace; if the namespace is\n  // mapped to the default prefix, they still need an explicit prefix.\n  const prefix = prefixOf.get(ns);\n  if (prefix === undefined || prefix === '') {\n    // Attribute lives in a namespace that we marked as default-only;\n    // fall through to no prefix (the namespace must already be declared\n    // by ancestor walk, but for default-namespace attrs that's invalid\n    // XML — we keep it bare anyway since OOXML never produces this).\n    return local;\n  }\n  return `${prefix}:${local}`;\n};\n\nconst emit = (out: string[], node: XmlNode, allocation: Allocation, isRoot: boolean): void => {\n  const tag = buildElementPrefix(node.name, allocation.prefixOf);\n  out.push('<', tag);\n\n  if (isRoot) {\n    for (const { prefix, ns } of allocation.declarations) {\n      out.push(prefix === '' ? ` xmlns=\"${escapeXmlAttr(ns)}\"` : ` xmlns:${prefix}=\"${escapeXmlAttr(ns)}\"`);\n    }\n  }\n\n  for (const [name, value] of Object.entries(node.attrs)) {\n    const attrName = buildAttrPrefix(name, allocation.prefixOf);\n    out.push(' ', attrName, '=\"', escapeXmlAttr(value), '\"');\n  }\n\n  const text = node.text;\n  const hasText = text !== undefined && text !== '';\n  const hasChildren = node.children.length > 0;\n  if (!hasText && !hasChildren) {\n    out.push('/>');\n    return;\n  }\n\n  out.push('>');\n  if (hasText) out.push(escapeXmlText(text));\n  for (const c of node.children) emit(out, c, allocation, /* isRoot */ false);\n  out.push('</', tag, '>');\n};\n"],"mappings":"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"}