can1357/oh-my-pi · error · Error

Invalid XML: unterminated CDATA section

Error message

Invalid XML: unterminated CDATA section

What it means

The XML parser handles "<![CDATA[ ... ]]>" sections as raw text. When a CDATA opener lacks its closing "]]>" before end-of-input, the parser cannot determine the section boundary and throws this error from parseXml.

Source

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

		const lessThan = source.indexOf("<", offset);
		if (lessThan === -1) {
			const value = decodeEntities(source.slice(offset));
			if (value) stack[stack.length - 1].children.push({ kind: "text", value });
			break;
		}
		if (lessThan > offset) {
			const value = decodeEntities(source.slice(offset, lessThan));
			if (value) stack[stack.length - 1].children.push({ kind: "text", value });
		}
		if (source.startsWith("<!--", lessThan)) {
			const end = source.indexOf("-->", lessThan + 4);
			if (end === -1) throw new Error("Invalid XML: unterminated comment");
			offset = end + 3;
			continue;
		}
		if (source.startsWith("<![CDATA[", lessThan)) {
			const end = source.indexOf("]]>", lessThan + 9);
			if (end === -1) throw new Error("Invalid XML: unterminated CDATA section");
			stack[stack.length - 1].children.push({ kind: "text", value: source.slice(lessThan + 9, end) });
			offset = end + 3;
			continue;
		}
		if (source.startsWith("<?", lessThan)) {
			const end = source.indexOf("?>", lessThan + 2);
			if (end === -1) throw new Error("Invalid XML: unterminated processing instruction");
			offset = end + 2;
			continue;
		}
		if (source.startsWith("<!", lessThan)) {
			const end = source.indexOf(">", lessThan + 2);
			if (end === -1) throw new Error("Invalid XML: unterminated declaration");
			offset = end + 1;
			continue;
		}
		const end = source.indexOf(">", lessThan + 1);
		if (end === -1) throw new Error("Invalid XML: unterminated tag");

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure every "<![CDATA[" section ends with "]]>" in the source XML.
  2. Re-extract or re-download the file — verify the XML part isn't truncated.
  3. If embedding arbitrary text, escape XML entities instead of relying on CDATA, or split text containing "]]>" across two CDATA sections.
  4. Validate the XML part with a strict parser before feeding it to the converter to get a clearer diagnostic.

Example fix

// before
const xml = `<doc><![CDATA[ 1 < 2 `;
parseXml(xml); // throws

// after
const xml = `<doc><![CDATA[ 1 < 2 ]]></doc>`;
parseXml(xml);
Defensive patterns

Strategy: validation

Validate before calling

function hasUnterminatedCdata(xml: string): boolean {
  return (xml.match(/<!\[CDATA\[/g)?.length ?? 0) > (xml.match(/\]\]>/g)?.length ?? 0);
}
if (hasUnterminatedCdata(xml)) throw new Error("XML has an unterminated CDATA section");

Try / catch

try {
  const doc = parseXml(xml);
} catch (err) {
  if (err instanceof Error && err.message === "Invalid XML: unterminated CDATA section") {
    // reject the document or repair by appending "]]>" before the next tag
  } else throw err;
}

Prevention

When it happens

Trigger: Calling parseXml() (directly or via convertToHtml) on XML with "<![CDATA[" without a closing "]]>" — truncated files, generated XML with unterminated CDATA, or content containing literal "]]>" handling mistakes that ate the closer.

Common situations: Escaped code/sample text embedded in DOCX documents where the closing bracket was lost in preprocessing; XML assembled by concatenation that dropped the terminator; truncated entries in a damaged archive.

Related errors


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