{"version":3,"file":"parser-nk0Lqmcb.mjs","names":[],"sources":["../src/xml/parser.ts"],"sourcesContent":["// DOM-style XML parser. fast-xml-parser does the lexing, then we walk its\n// preserveOrder tree to:\n//   1. resolve `prefix:local` element + attribute names to Clark notation\n//      (`{ns}local`) using a namespace-declaration stack;\n//   2. fold text segments into XmlNode.text for text-only elements;\n//   3. drop XML declarations and processing instructions.\n//\n// DOCTYPE / external entity declarations are rejected outright via a byte-level\n// prescan before the parser ever sees the input — fast-xml-parser does not\n// expand external entities, but we still want the offending document to fail\n// loudly.\n\nimport { XMLParser } from 'fast-xml-parser';\nimport { OpenXmlSchemaError } from '../utils/exceptions';\nimport { qname } from './namespaces';\nimport { el, type XmlNode } from './tree';\n\n// ---- DOCTYPE / DTD prescan --------------------------------------------------\n\nconst decoder = new TextDecoder('utf-8', { fatal: false });\n\nconst decodeForPrescan = (input: Uint8Array | string): string => {\n  if (typeof input === 'string') return input;\n  return decoder.decode(input);\n};\n\nconst checkForDoctype = (text: string): void => {\n  // Strip XML declaration so any subsequent `<!DOCTYPE` is the real thing. The\n  // declaration is always the first non-BOM token in well-formed XML.\n  const stripped = text.replace(/^﻿/, '');\n  if (/<!DOCTYPE\\b/.test(stripped)) {\n    throw new OpenXmlSchemaError('DTD declarations are not permitted in OOXML payloads');\n  }\n  if (/<!ENTITY\\b/.test(stripped)) {\n    throw new OpenXmlSchemaError('Entity declarations are not permitted in OOXML payloads');\n  }\n};\n\n// ---- fast-xml-parser configuration ------------------------------------------\n\nconst parser = new XMLParser({\n  preserveOrder: true,\n  ignoreAttributes: false,\n  attributeNamePrefix: '',\n  attributesGroupName: ':@',\n  trimValues: false,\n  parseTagValue: false,\n  parseAttributeValue: false,\n  // OOXML needs the standard XML entities (&amp; / &lt; / &gt; / &quot; /\n  // &apos;) expanded; HTML / numeric entities outside that set are not used in\n  // SpreadsheetML payloads.\n  processEntities: true,\n  htmlEntities: false,\n});\n\n// ---- preserveOrder shape ----------------------------------------------------\n\ntype FxpAttrs = Record<string, string>;\ntype FxpEntry = { ':@'?: FxpAttrs } & { [tagOrText: string]: FxpEntry[] | string | FxpAttrs | undefined };\ntype FxpTree = FxpEntry[];\n\nconst ATTR_KEY = ':@';\nconst TEXT_KEY = '#text';\n\n// ---- public API -------------------------------------------------------------\n\n/**\n * Parse a UTF-8 XML payload into an {@link XmlNode} tree. Element and attribute\n * names are returned in Clark notation. Throws {@link OpenXmlSchemaError} on\n * DTD/entity declarations or on multi-root documents.\n */\nexport function parseXml(input: Uint8Array | string): XmlNode {\n  const text = decodeForPrescan(input);\n  checkForDoctype(text);\n\n  let raw: FxpTree;\n  try {\n    raw = parser.parse(text) as FxpTree;\n  } catch (cause) {\n    throw new OpenXmlSchemaError('parseXml: failed to parse XML payload', { cause });\n  }\n\n  // Skip XML declaration, processing instructions and any leading whitespace\n  // text nodes.\n  const roots: FxpEntry[] = [];\n  for (const entry of raw) {\n    const tag = elementTag(entry);\n    if (tag === undefined) continue; // text-only entry\n    if (isProcessingInstruction(tag)) continue; // <?xml …?>, other PIs\n    roots.push(entry);\n  }\n  if (roots.length === 0) {\n    throw new OpenXmlSchemaError('parseXml: document has no root element');\n  }\n  if (roots.length > 1) {\n    throw new OpenXmlSchemaError(`parseXml: document has ${roots.length} root elements; expected exactly one`);\n  }\n\n  const initial: NamespaceStack = { default: '', byPrefix: {} };\n  const [root] = roots;\n  if (root === undefined) {\n    throw new OpenXmlSchemaError('parseXml: no root element');\n  }\n  return convertElement(root, initial);\n}\n\n// ---- conversion -------------------------------------------------------------\n\ninterface NamespaceStack {\n  /** Default namespace URI (xmlns=\"…\"); empty string means no default. */\n  readonly default: string;\n  /** Map of prefix → namespace URI declared in this scope or any ancestor. */\n  readonly byPrefix: Readonly<Record<string, string>>;\n}\n\nconst elementTag = (entry: FxpEntry): string | undefined => {\n  for (const k of Object.keys(entry)) {\n    if (k === ATTR_KEY) continue;\n    return k;\n  }\n  return undefined;\n};\n\nconst isProcessingInstruction = (tag: string): boolean => tag.startsWith('?');\n\nconst splitPrefixed = (qname0: string): { prefix: string; local: string } => {\n  const idx = qname0.indexOf(':');\n  if (idx < 0) return { prefix: '', local: qname0 };\n  return { prefix: qname0.slice(0, idx), local: qname0.slice(idx + 1) };\n};\n\nconst extendStack = (parent: NamespaceStack, attrs: FxpAttrs | undefined): NamespaceStack => {\n  if (attrs === undefined) return parent;\n  let nextDefault = parent.default;\n  let nextByPrefix: Record<string, string> | undefined;\n  for (const [k, v] of Object.entries(attrs)) {\n    if (k === 'xmlns') {\n      nextDefault = v;\n      continue;\n    }\n    if (k.startsWith('xmlns:')) {\n      const prefix = k.slice('xmlns:'.length);\n      nextByPrefix ??= { ...parent.byPrefix };\n      nextByPrefix[prefix] = v;\n    }\n  }\n  if (nextDefault === parent.default && nextByPrefix === undefined) return parent;\n  return {\n    default: nextDefault,\n    byPrefix: nextByPrefix ?? parent.byPrefix,\n  };\n};\n\nconst resolveElementName = (raw: string, stack: NamespaceStack): string => {\n  const { prefix, local } = splitPrefixed(raw);\n  if (prefix === '') return qname(stack.default, local);\n  const ns = stack.byPrefix[prefix];\n  if (ns === undefined) {\n    throw new OpenXmlSchemaError(`parseXml: undeclared namespace prefix \"${prefix}\" on element <${raw}>`);\n  }\n  return qname(ns, local);\n};\n\nconst resolveAttrName = (raw: string, stack: NamespaceStack): string => {\n  const { prefix, local } = splitPrefixed(raw);\n  // Unprefixed attributes do NOT inherit the default namespace (XMLNS spec).\n  if (prefix === '') return local;\n  if (prefix === 'xml') return qname('http://www.w3.org/XML/1998/namespace', local);\n  const ns = stack.byPrefix[prefix];\n  if (ns === undefined) {\n    throw new OpenXmlSchemaError(`parseXml: undeclared namespace prefix \"${prefix}\" on attribute \"${raw}\"`);\n  }\n  return qname(ns, local);\n};\n\nconst filterAttrs = (rawAttrs: FxpAttrs | undefined, stack: NamespaceStack): { resolved: Record<string, string> } => {\n  const resolved: Record<string, string> = {};\n  if (rawAttrs === undefined) return { resolved };\n  for (const [k, v] of Object.entries(rawAttrs)) {\n    // xmlns / xmlns:* declarations: dropped from the XmlNode attribute table.\n    // The serializer rebuilds them from the Clark-notation namespaces it walks,\n    // so round-tripping does not require preserving the declarations.\n    if (k === 'xmlns' || k.startsWith('xmlns:')) continue;\n    resolved[resolveAttrName(k, stack)] = v;\n  }\n  return { resolved };\n};\n\nconst isWhitespaceOnly = (s: string): boolean => /^\\s*$/.test(s);\n\nconst convertElement = (entry: FxpEntry, parentStack: NamespaceStack): XmlNode => {\n  const rawTag = elementTag(entry);\n  if (rawTag === undefined) {\n    throw new OpenXmlSchemaError('parseXml: encountered an entry with no element tag');\n  }\n  if (isProcessingInstruction(rawTag)) {\n    throw new OpenXmlSchemaError(`parseXml: processing instructions are not supported (saw \"<${rawTag}>\")`);\n  }\n\n  const rawAttrs = entry[ATTR_KEY] as FxpAttrs | undefined;\n  const stack = extendStack(parentStack, rawAttrs);\n\n  const { resolved } = filterAttrs(rawAttrs, stack);\n  const node = el(resolveElementName(rawTag, stack), resolved);\n\n  const childEntries = entry[rawTag] as FxpEntry[] | undefined;\n  if (childEntries === undefined || childEntries.length === 0) return node;\n\n  const textParts: string[] = [];\n  for (const child of childEntries) {\n    if (Object.hasOwn(child, TEXT_KEY)) {\n      const t = child[TEXT_KEY];\n      if (typeof t === 'string') textParts.push(t);\n      continue;\n    }\n    if (textParts.length > 0 && node.children.length === 0) {\n      // text accumulated *before* any child element — keep collecting.\n    } else if (textParts.length > 0 && node.children.length > 0) {\n      const acc = textParts.join('');\n      if (!isWhitespaceOnly(acc)) {\n        throw new OpenXmlSchemaError(`parseXml: mixed content not supported (text between elements under <${rawTag}>)`);\n      }\n      // whitespace-only inter-element text: drop.\n      textParts.length = 0;\n    }\n    node.children.push(convertElement(child, stack));\n  }\n  // Trailing text after the last child element.\n  if (textParts.length > 0) {\n    const acc = textParts.join('');\n    if (node.children.length === 0) {\n      // text-only element (the common case): keep as the element's text.\n      node.text = acc;\n    } else if (!isWhitespaceOnly(acc)) {\n      throw new OpenXmlSchemaError(`parseXml: mixed content not supported (trailing text under <${rawTag}>)`);\n    }\n  }\n  return node;\n};\n"],"mappings":"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"}