can1357/oh-my-pi · error · Error

Invalid XML: expected one root element

Error message

Invalid XML: expected one root element

What it means

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.

Source

Thrown at packages/utils/src/docx/xml.ts:130

			const whitespace = tag.search(/\s/);
			const name = whitespace === -1 ? tag : tag.slice(0, whitespace);
			const attributes = new Map<string, string>();
			ATTRIBUTE_PATTERN.lastIndex = whitespace === -1 ? tag.length : whitespace;
			for (let match = ATTRIBUTE_PATTERN.exec(tag); match; match = ATTRIBUTE_PATTERN.exec(tag)) {
				attributes.set(match[1], decodeEntities(match[2] ?? match[3] ?? ""));
			}
			const pending = { name, attributes, children: [] as XmlNode[] };
			if (selfClosing) {
				stack[stack.length - 1].children.push({ kind: "element", ...pending });
			} else {
				stack.push(pending);
			}
		}
		offset = end + 1;
	}
	if (stack.length !== 1) throw new Error(`Invalid XML: unclosed tag ${stack[stack.length - 1].name}`);
	const roots = synthetic.children.filter((node): node is XmlElement => node.kind === "element");
	if (roots.length !== 1) throw new Error("Invalid XML: expected one root element");
	return roots[0];
}

/** Return direct element children, optionally filtered by local name. */
export function childElements(element: XmlElement, name?: string): XmlElement[] {
	return element.children.filter(
		(node): node is XmlElement => node.kind === "element" && (name === undefined || localName(node.name) === name),
	);
}

/** Return the first direct child with the given local name. */
export function firstChild(element: XmlElement | undefined, name: string): XmlElement | undefined {
	if (!element) return undefined;
	return element.children.find((node): node is XmlElement => node.kind === "element" && localName(node.name) === name);
}

/** Return an attribute by qualified or namespace-independent name. */
export function attribute(element: XmlElement | undefined, name: string): string | undefined {

View on GitHub (pinned to 9690622007)

Solutions

  1. Wrap multiple top-level elements in a single container element (e.g. `<w:root>...</w:root>`).
  2. If the input is empty, check upstream generation — an empty template or failed render often produces no root at all.
  3. If you actually need to parse a fragment with multiple roots, parse each fragment separately or wrap it first.
  4. Sanity-check the input with xmllint, which reports the same 'extra content' class of problem.

Example fix

// before: two sibling roots
const xml = `<w:sectPr/><w:body/>`;
parseXml(xml); // throws: expected one root element

// after: single wrapped root
const xml = `<w:document><w:sectPr/><w:body/></w:document>`;
parseXml(xml); // ok
Defensive patterns

Strategy: validation

Validate before calling

function hasSingleRootElement(xml: string): boolean {
  let depth = 0, roots = 0;
  for (const m of xml.matchAll(/<\s*(\/?)\s*([\w:.-]+)(?:\s[^>]*)?(\/?)\s*>/g)) {
    if (m[3] === "/") { if (depth === 0) roots++; continue; }
    if (m[1]) { depth--; } else { if (depth === 0) roots++; depth++; }
  }
  return roots === 1;
}
// call before: if (!hasSingleRootElement(xml)) xml = `<root>${xml}</root>`; else parseXml(xml);

Try / catch

try {
  return parseXml(xml);
} catch (err) {
  if (err instanceof Error && err.message === "Invalid XML: expected one root element") {
    return parseXml(`<w:wrap>${xml}</w:wrap>`); // fallback: wrap fragments
  }
  throw err;
}

Prevention

When it happens

Trigger: 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/>`.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/0aa5bf0c337a1ac2. Report an issue: GitHub.