{"record":{"id":"496ba56392f31d26","repo":"can1357/oh-my-pi","slug":"invalid-docx-missing-word-document-xml","errorCode":null,"errorMessage":"Invalid DOCX: missing word/document.xml","messagePattern":"Invalid DOCX: missing word/document\\.xml","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/utils/src/docx/converter.ts","lineNumber":660,"sourceCode":"\t\telse contents = `${contents.slice(0, lastParagraph)} ${backlink}${contents.slice(lastParagraph)}`;\n\t\thtml += `<li id=\"footnote-${escapeAttribute(note.id)}\">${contents}</li>`;\n\t}\n\treturn `${html}</ol>`;\n}\n\nfunction defaultImageConverter(): ImageConverter {\n\treturn images.imgElement(async image => ({\n\t\talt: image.altText,\n\t\tsrc: `data:${image.contentType};base64,${await image.read(\"base64\")}`,\n\t}));\n}\n\n/** Convert a DOCX buffer or path to mammoth-compatible HTML. */\nexport async function convertToHtml(input: DocxInput, options: ConvertToHtmlOptions = {}): Promise<DocxResult> {\n\tconst bytes = \"buffer\" in input && input.buffer ? input.buffer : await fs.readFile(input.path);\n\tconst entries = await readArchiveEntries({ bytes, format: \"zip\" });\n\tconst documentXml = archiveEntryText(entries, \"word/document.xml\");\n\tif (!documentXml) throw new Error(\"Invalid DOCX: missing word/document.xml\");\n\tconst context: ConversionContext = {\n\t\tentries,\n\t\trelationships: parseRelationships(archiveEntryText(entries, \"word/_rels/document.xml.rels\")),\n\t\tcontentTypes: parseContentTypes(archiveEntryText(entries, \"[Content_Types].xml\")),\n\t\tstyles: parseStyles(archiveEntryText(entries, \"word/styles.xml\")),\n\t\tnumbering: parseNumbering(archiveEntryText(entries, \"word/numbering.xml\")),\n\t\tmessages: [],\n\t\twarnedStyles: new Set(),\n\t\tcustomStyles: parseCustomStyles(options.styleMap),\n\t\tincludeDefaultStyleMap: options.includeDefaultStyleMap !== false,\n\t\tconvertImage: options.convertImage ?? defaultImageConverter(),\n\t\tfootnotes: parseFootnotes(archiveEntryText(entries, \"word/footnotes.xml\")),\n\t\tusedFootnotes: [],\n\t\tfootnoteOrdinals: new Map(),\n\t};\n\tconst document = parseXml(documentXml);\n\tconst body = firstChild(document, \"body\");\n\tif (!body) throw new Error(\"Invalid DOCX: missing document body\");","sourceCodeStart":642,"sourceCodeEnd":678,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/docx/converter.ts#L642-L678","documentation":"convertToHtml() reads the DOCX (a ZIP archive) and requires the core part word/document.xml, which holds the document content. If the archive has no such entry, the file is not a valid Office Open XML word-processing document, and the converter throws this error instead of attempting to parse.","triggerScenarios":"Calling convertToHtml() on a buffer or file that is not a real DOCX: a renamed .doc/.rtf/.txt/HTML file, a ZIP missing word/document.xml, a corrupt or truncated download, or an empty/password-protected archive.","commonSituations":"Users renaming report.doc or .rtf to .docx; passing the flat OPC XML (.xml) instead of the packaged .docx; partially uploaded files in a web pipeline; encrypted DOCX from enterprise templates.","solutions":["Verify the input is a genuine DOCX: it must be a ZIP containing word/document.xml (`unzip -l file.docx | grep word/document.xml`).","Re-export/re-save the file as .docx from Word/LibreOffice rather than renaming another format.","Handle legacy formats first: convert .doc/.rtf with an external tool (LibreOffice headless, pandoc) before calling convertToHtml.","Check file size and that the upload/download completed; re-fetch the file if it is truncated or 0 bytes."],"exampleFix":"// before\nconst html = await convertToHtml({ path: \"report.doc\" }); // renamed, throws\n\n// after\nconst out = await $`soffice --headless --convert-to docx report.doc`;\nconst html = await convertToHtml({ path: \"report.docx\" });","handlingStrategy":"validation","validationCode":"async function isLikelyDocx(path: string): Promise<boolean> {\n  try {\n    const fd = await fs.open(path, \"r\");\n    const buf = Buffer.alloc(4);\n    await fd.read(buf, 0, 4, 0);\n    await fd.close();\n    return buf.equals(Buffer.from(\"PK\\x03\\x04\"));\n  } catch { return false; }\n}\nif (!(await isLikelyDocx(inputPath))) throw new Error(\"not a DOCX (ZIP) file\");","typeGuard":null,"tryCatchPattern":"try {\n  const result = await convertToHtml(input);\n} catch (err) {\n  if (err instanceof Error && err.message === \"Invalid DOCX: missing word/document.xml\") {\n    // fall back to an external converter (libreoffice/pandoc) or surface a format error to the user\n  } else throw err;\n}","preventionTips":["Verify the input is a real ZIP (starts with PK\\x03\\x04) before conversion.","Convert legacy .doc/.rtf/.txt with LibreOffice/pandoc instead of renaming to .docx.","Validate uploaded files (extension + magic bytes + entry listing) at ingestion time.","Check for truncation: nonzero size and a complete ZIP central directory."],"tags":["docx","validation","zip","file-format"],"backgroundTag":"invalid-docx","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}