{"record":{"id":"0aa5bf0c337a1ac2","repo":"can1357/oh-my-pi","slug":"invalid-xml-expected-one-root-element","errorCode":null,"errorMessage":"Invalid XML: expected one root element","messagePattern":"Invalid XML: expected one root element","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/utils/src/docx/xml.ts","lineNumber":130,"sourceCode":"\t\t\tconst whitespace = tag.search(/\\s/);\n\t\t\tconst name = whitespace === -1 ? tag : tag.slice(0, whitespace);\n\t\t\tconst attributes = new Map<string, string>();\n\t\t\tATTRIBUTE_PATTERN.lastIndex = whitespace === -1 ? tag.length : whitespace;\n\t\t\tfor (let match = ATTRIBUTE_PATTERN.exec(tag); match; match = ATTRIBUTE_PATTERN.exec(tag)) {\n\t\t\t\tattributes.set(match[1], decodeEntities(match[2] ?? match[3] ?? \"\"));\n\t\t\t}\n\t\t\tconst pending = { name, attributes, children: [] as XmlNode[] };\n\t\t\tif (selfClosing) {\n\t\t\t\tstack[stack.length - 1].children.push({ kind: \"element\", ...pending });\n\t\t\t} else {\n\t\t\t\tstack.push(pending);\n\t\t\t}\n\t\t}\n\t\toffset = end + 1;\n\t}\n\tif (stack.length !== 1) throw new Error(`Invalid XML: unclosed tag ${stack[stack.length - 1].name}`);\n\tconst roots = synthetic.children.filter((node): node is XmlElement => node.kind === \"element\");\n\tif (roots.length !== 1) throw new Error(\"Invalid XML: expected one root element\");\n\treturn roots[0];\n}\n\n/** Return direct element children, optionally filtered by local name. */\nexport function childElements(element: XmlElement, name?: string): XmlElement[] {\n\treturn element.children.filter(\n\t\t(node): node is XmlElement => node.kind === \"element\" && (name === undefined || localName(node.name) === name),\n\t);\n}\n\n/** Return the first direct child with the given local name. */\nexport function firstChild(element: XmlElement | undefined, name: string): XmlElement | undefined {\n\tif (!element) return undefined;\n\treturn element.children.find((node): node is XmlElement => node.kind === \"element\" && localName(node.name) === name);\n}\n\n/** Return an attribute by qualified or namespace-independent name. */\nexport function attribute(element: XmlElement | undefined, name: string): string | undefined {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/docx/xml.ts#L112-L148","documentation":"A well-formed XML document must contain exactly one top-level element. parseXml attaches all parsed top-level elements to a synthetic root and, after a successful scan, requires that exactly one element child exists; zero or multiple roots throw this error. Text, comments, and declarations around the root are tolerated, but a second element sibling is not.","triggerScenarios":"Calling parseXml/root/document/parseFootnotes on input with zero elements (empty string or only text/whitespace), or two or more sibling top-level elements such as `<a/><b/>` or a concatenated pair of full documents `<doc1/><doc2/>`.","commonSituations":"Concatenating several DOCX fragments each having its own root; parsing an empty/blank response from a generator or template with no body; passing a document-fragment collection instead of a whole document.","solutions":["Wrap multiple top-level elements in a single container element (e.g. `<w:root>...</w:root>`).","If the input is empty, check upstream generation — an empty template or failed render often produces no root at all.","If you actually need to parse a fragment with multiple roots, parse each fragment separately or wrap it first.","Sanity-check the input with xmllint, which reports the same 'extra content' class of problem."],"exampleFix":"// before: two sibling roots\nconst xml = `<w:sectPr/><w:body/>`;\nparseXml(xml); // throws: expected one root element\n\n// after: single wrapped root\nconst xml = `<w:document><w:sectPr/><w:body/></w:document>`;\nparseXml(xml); // ok","handlingStrategy":"validation","validationCode":"function hasSingleRootElement(xml: string): boolean {\n  let depth = 0, roots = 0;\n  for (const m of xml.matchAll(/<\\s*(\\/?)\\s*([\\w:.-]+)(?:\\s[^>]*)?(\\/?)\\s*>/g)) {\n    if (m[3] === \"/\") { if (depth === 0) roots++; continue; }\n    if (m[1]) { depth--; } else { if (depth === 0) roots++; depth++; }\n  }\n  return roots === 1;\n}\n// call before: if (!hasSingleRootElement(xml)) xml = `<root>${xml}</root>`; else parseXml(xml);","typeGuard":null,"tryCatchPattern":"try {\n  return parseXml(xml);\n} catch (err) {\n  if (err instanceof Error && err.message === \"Invalid XML: expected one root element\") {\n    return parseXml(`<w:wrap>${xml}</w:wrap>`); // fallback: wrap fragments\n  }\n  throw err;\n}","preventionTips":["Wrap concatenated fragments in a single container element before parsing.","Reject or trim empty/whitespace-only input before calling parseXml.","Treat every parsed document as one root plus metadata — assert root name after parsing to catch swapped inputs."],"tags":["xml","parsing","docx","malformed-input"],"backgroundTag":"xml-multiple-root-elements","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}