can1357/oh-my-pi · error · Error

Invalid XML: unterminated declaration

Error message

Invalid XML: unterminated declaration

What it means

Declarations and doctypes ("<!DOCTYPE ...>", "<![...]") are skipped by scanning to the next ">". If no ">" exists after "<!" before end-of-input, the parser throws this unterminated-declaration error.

Source

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

			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");
		const raw = source.slice(lessThan + 1, end).trim();
		if (raw.startsWith("/")) {
			if (stack.length === 1) throw new Error("Invalid XML: unexpected closing tag");
			const closingName = raw.slice(1).trim();
			const completed = stack.pop();
			if (!completed || completed.name !== closingName)
				throw new Error(`Invalid XML: mismatched closing tag ${closingName}`);
			stack[stack.length - 1].children.push({
				kind: "element",
				name: completed.name,
				attributes: completed.attributes,
				children: completed.children,
			});

View on GitHub (pinned to 9690622007)

Solutions

  1. Close the declaration/doctype with ">" in the source XML.
  2. Verify the input isn't truncated; re-extract the DOCX entry or re-download the file.
  3. Remove unnecessary DOCTYPE declarations entirely if your generator doesn't need them.
  4. Validate the XML with a conforming parser before conversion.

Example fix

// before
const xml = `<!DOCTYPE document [ <!ENTITY x "y" `;

// after
const xml = `<!DOCTYPE document [ <!ENTITY x "y" ]>`;
parseXml(xml);
Defensive patterns

Strategy: validation

Validate before calling

function hasUnterminatedDeclaration(xml: string): boolean {
  const m = /<![^>]*$/.exec(xml);
  return m !== null;
}
if (hasUnterminatedDeclaration(xml)) throw new Error("XML has an unterminated declaration/doctype");

Try / catch

try {
  const doc = parseXml(xml);
} catch (err) {
  if (err instanceof Error && err.message === "Invalid XML: unterminated declaration") {
    // strip the broken doctype line or reject the document
  } else throw err;
}

Prevention

When it happens

Trigger: Calling parseXml() on XML containing "<!" (DOCTYPE or other declaration) with no closing ">" — truncated doctype, hand-written XML missing the closing angle bracket, or content where the doctype was cut during preprocessing.

Common situations: Files truncated mid-DOCTYPE; do-it-yourself XML assembly that emits "<!DOCTYPE document [...]" and forgets the final ">"; regex-based edits that removed the closing bracket.

Related errors


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