docx-validate
    Preparing search index...

    Class DOCXSchemaValidator

    Hierarchy (View Summary)

    Index
    deadline: RunDeadline | undefined

    The run's wall-clock budget, when it has one (#630).

    originalFile: string | null
    profile: Profile
    schemasDir: string
    unpackedDir: string
    verbose: boolean
    xmlFiles: string[]
    xsdEngineFailures: number = 0

    Parts whose schema the engine could not load, or whose XSD call threw, in the walk that finished last. Each validateAgainstXsd owns its count, resets the instance at entry and publishes its count at exit, even on a throw, so a nested walk cannot erase the outer count (#943). A call outside every walk's active calls (a direct call, or a late task after its call settled) counts on the instance, and a walk still running replaces that count when it publishes its own: the failure is not the walk's, and its own outcome reports it.

    xsdWalk.parts.length === xsdWalk.eligible only proves every eligible part was offered to the engine; this counter is what proves the engine actually answered for it. A schema that fails to compile still counts as a completed walk, which is why the top-level validate() also requires this to be 0 before reporting schemaCompleted: true.

    xsdWalk: XsdWalkTrace = ...

    Engine trace owned by one validateAgainstXsd walk on this instance. Published at entry so a lone walk is observable while running; the walk that finishes last publishes its own trace again, even on a throw, so nested walks do not mix their parts or counts (#943). DOCXSchemaValidator.validate resets it at entry. mergeResults drops it, so the public validate() copies it off the instance afterwards.

    • Repair every over-cap w16cid:durableId (>= 0x7FFFFFFF) and propagate the renumbering into the threaded-comments extension, as repairParaId does for paraIds.

      Two-pass implementation:

      Pass 1: scan all XML files, build a Map<oldId, newId> for every over-cap hex w16cid:durableId outside numbering.xml. Each old value gets one fresh in-range value that collides with no durableId in use — w16cid anywhere outside numbering.xml, and w16cex in commentsExtensible.xml — so two over-cap ids can't land on the same fresh value and none can land on a value an extensible entry already names. Pass 2: walk every XML file again and rewrite (a) every w16cid:durableId named in the map, AND (b) every w16cex:durableId in any part that names a remapped value. Without (b), commentsExtensible.xml's references still name the old values and every extensible entry is left orphaned — surfaced by validateCommentThreading's comment-thread-durableid-orphan check.

      The map is keyed by the upper-cased value: both attributes are xsd:hexBinary, whose value ignores the case of its hex letters, so ffffffff in one part and FFFFFFFF in the other name one id and get one new value (#904).

      The decimal durableIds of numbering.xml are outside the remap: they are self-contained (no part references them), so each keeps its own per-element renumbering, independent of the preflight below. A hex durableId stored in numbering.xml is renamed like any other, which makes it a part carrying a hex rename (#942). Every part the hex rewrite would write must be decodable and writable; otherwise only the decimal renumbering runs. A failed or refused write of a part carrying a hex rename stops the remaining hex renames and counts only the repairs already written (#909, #920). A failed or refused write of a part carrying only the decimal renumbering counts nothing and stops nothing, and that renumbering runs whether the hex rewrite is refused by the preflight or stopped by a failed write (#925).

      Returns Promise<number>

    • Repair mc:Ignorable entries that name an undeclared namespace prefix. For each undeclared prefix:

      • If the prefix is in KNOWN_OOXML_PREFIX_URIS (the table of well-known Word/OOXML namespaces), declare it on the document root: xmlns:prefix="<canonical-uri>". Mirrors what Word does on save and preserves the tolerated-extension semantics for any <prefix:*> elements present elsewhere in the document.
      • Otherwise (truly unknown prefix), drop it from the mc:Ignorable token list. If that empties the attribute, the attribute is removed entirely.

      Returns the count of mutations made (one per declaration added or Ignorable token dropped).

      Returns Promise<number>

    • Stamp a fresh in-range w14:paraId (and the matching w14:textId) on every <w:p> and <w:tr> that needs one. The new paraIds are random and avoid colliding with any paraId already present on either side (incl. the renumbered ones from repairParaId); textIds are sampled from the same space and likewise deduped within their own pool (paraId and textId namespaces are independent in Word's model — Word's saves often emit the placeholder 77777777 for textId, but the spec only requires it be a hex digit string).

      Four cases:

      1. Element has neither → stamp both (Word's tracked-changes infrastructure expects them paired).
      2. Element has paraId but no textId → stamp textId. This is the asymmetric shape that triggers Word's "Document Recovery — Table Properties" dialog on open.
      3. Element has textId but no paraId → stamp paraId (the inverse asymmetric shape — unusual but handled for completeness so Word doesn't see a dangling textId).
      4. Element has both → leave alone.

      Returns Promise<number>

    • Inject canonical definitions into each story root's styles part — the main document's (by name in its folder when none is related, #699), and a reached glossary's own for the references its story makes (#698) — for every well-known style ID referenced by document XML but not defined. Only handles the small set of styles writers commonly leak — unknown style IDs are NOT auto-defined (we don't know what they mean). Pairs with validateStyleReferences.

      Returns Promise<number>

    • Repair every w14:paraId whose value is >= 0x80000000 (the spec cap), and propagate the renumbering into the threaded-comments extension.

      Two-pass implementation:

      Pass 1: scan all XML files, build a Map<oldId, newId> for every over-cap w14:paraId. Each old ID gets one new value (deterministic per-run via random allocation, with a collision-avoidance set so two different over-cap IDs can't both land on the same fresh value). Pass 2: walk every XML file again and rewrite (a) every w14:paraId referenced in the map, AND (b) every w15:paraId / w15:paraIdParent in commentsExtended.xml that names an old value. Without (b), the cross-reference between comments.xml and commentsExtended.xml breaks and Word's threading view shows no replies — surfaced by validateCommentThreading's comment-thread-paraid-orphan check.

      Every part to be written must be decodable and writable before any write starts. A failed or refused write returns only the repairs already written (#909, #920).

      Returns Promise<number>

    • Profile-aware: every <w:p> AND every <w:tr> should carry a w14:paraId attribute, AND any element that has paraId must also have a w14:textId. The OOXML schema makes both optional, but Word's tracked-changes machinery treats them as a pair — empirically, across 226 real-world SuperDoc fixtures, ZERO <w:tr> elements were observed with paraId present and textId absent. Elements with NEITHER attribute are common and Word handles them silently. The asymmetric "paraId without textId" shape is what trips Word's "Document Recovery — Table Properties" dialog on open, because the row's paraId cannot be linked to any text-content fingerprint for change tracking.

      Severity:

      • strict → error (spec-purist + Word-parity).
      • lenient → warning (ours-vs-Word stylistic difference; the schema allows it, but downstream collaboration tooling may need it).

      Surfaced by the comparison against Word's reallyrepaired.docx for the sample-document fixture: jubarte's writer leaves table rows without paraId/textId; Word stamps both on all 13 of them.

      Returns Promise<ValidationResult>

    • Reports every mismatched r:id it finds, under every profile: this method knows nothing about the registered Word checks, so a caller composing it directly gets the full legacy finding set (#612). A full DOCXSchemaValidator.validate() run hands the story reference elements its Word rule already reported back to that rule (withoutRepeatedHeaderFooterFindings in docx.ts), through the structured story reference identity this method registers on each headerReference/footerReference mismatch (#616) and on each such rels-id-missing (#619), and the same hand-off pairs a dangling c:chart/o:OLEObject r:id with word-part-reference-relationship (#705), a wne:toolbarData with word-attached-toolbar-relationship (#716) and a refused w:hyperlink with word-hyperlink-relationship (#1089). A full non-lenient PPTXSchemaValidator.validate() run hands a slide master's p:sldLayoutId to sldlayout-missing-rid the same way (#1093). Each pairing reads the registered identity, never the message prose.

      Returns Promise<ValidationResult>

    • Verify the cross-references between word/comments.xml and word/commentsExtended.xml (issue #153 in jubarte was a regression in this exact integrity contract).

      This is a STRICT validator. Some Word builds emit a comments.xml paraId that has no matching <w15:commentEx> entry (notably for non-threaded comments) — we still flag that here, because callers that want to ignore Word's output divergences should run validation with profile: "lenient" (where the strict-only code is downgraded to a warning).

      Rules enforced (severity = error in strict, warning in lenient):

      1. Every <w:comment> in comments.xml that carries a w14:paraId on its first paragraph must have a matching <w15:commentEx> entry in commentsExtended.xml.
      2. Every <w15:commentEx> must have a w15:paraId matching the paraId of some <w:comment> first paragraph (the inverse of #1).
      3. No two <w15:commentEx> entries may share the same w15:paraId (this was the surface symptom of jubarte's #153 regression).
      4. Every non-null w15:paraIdParent must resolve to a paraId present somewhere else in commentsExtended.xml.
      5. The number of w:commentRangeStart, w:commentRangeEnd, and w:commentReference elements in document.xml must each equal the number of comments in comments.xml.
      6. Every w16cid:commentId/@paraId in commentsIds.xml must resolve to a paragraph in comments.xml.
      7. Every w16cex:commentExtensible/@durableId in commentsExtensible.xml must resolve to a durableId in commentsIds.xml.

      validateCommentMarkers already covers orphan range start/end and missing-comment references — this validator is strictly about the threaded-comments extension surface.

      Returns Promise<ValidationResult>

    • Detect docProps/app.xml boolean extended-properties (ScaleCrop, LinksUpToDate, SharedDoc, HyperlinksChanged) whose value is not a canonical xsd:boolean (true/false/0/1) — in practice, whitespace around the value, e.g. <ScaleCrop>false\n </ScaleCrop>. Word reports "unreadable content" and offers recovery.

      Root-caused against REAL Word: stripping ONLY the boolean whitespace flips every affected file to clean, and 0/350 Word-clean files carry it. Integer properties with whitespace (e.g. <TotalTime>0\n </TotalTime>) are TOLERATED by Word and DO occur in Word-clean files, so they are deliberately NOT flagged here (that would re-introduce false positives).

      Returns Promise<ValidationResult>

    • Detect unresolved OpenXmlPowerTools DocumentBuilder <Insert> directives (namespace http://powertools.codeplex.com/documentbuilder/2011/insert, the only URI PtOpenXml.ptOpenXml publishes) left in a WordprocessingML content part. Word cannot open a document whose header/footer/body content model contains this foreign element — it reports "Word experienced an error trying to open the file" (a hard OPEN_ERROR).

      Root-caused against REAL Word over the word-invalid-fixtures corpus (Docxodus DB/HeadersFooters/Dest/{Fax,Letter,…}): removing only the <Insert> element flips every affected file to clean, and 0/350 Word-clean files carry the namespace. Word-blocking in every profile.

      Returns Promise<ValidationResult>

    • Validate one part against the schema the routing picks for it. The schema walk calls it for each part it validates, so a subclass that overrides it sees every one. Only the first base call the walk's own active call makes for this part claims the root its eligibility check read, once (#902, #941). Each call owns its roots: the claiming call takes that one entry from its walk at claim time, while every other call starts empty and reads afresh (#910, #926). A nested call or walk cannot replace the root the claiming call routes by (#949). The call's scope stops serving reads once the call is done — after _validateSingleFileXsd and the --original baseline read — so a task that outlives it reads afresh (#964).

      Parameters

      • xmlFile: string

      Returns Promise<XsdValidationOutcome>

    • Profile-aware check: every _rels/*.rels part must contain at least one <Relationship> child. Per OPC §9.3 / ISO 29500-2, a relationship part exists because its source part has outgoing relationships — an empty <Relationships/> is non-canonical and Word strips them on save.

      Severity:

      • strict → error (spec-purist: flag the non-canonical state).
      • lenient → warning (real-world unpack→pack pipelines and third-party writers routinely emit empty rels parts; visible but doesn't fail validation).

      This is the canonical example of a "strict-only" gap surfaced by the comparison against Word's own save output: jubarte / our pack helper preserves empty rels sidecars from the input, while Word strips them. The repaired-file regression of the sample-document.id-overflow.docx fixture surfaces here.

      Returns Promise<ValidationResult>

    • Detect HTML-to-DOCX wire-format tracking tokens (e.g. [[DOCX_INS_START:{...}]]) that leaked into the OOXML output. These are placeholder strings emitted by html-to-docx-style pipelines for tracked-changes round-tripping; they MUST be expanded into proper <w:ins> / <w:del> / comment markers before serialisation. A leak is a writer-pipeline bug — the document will render the literal [[DOCX_…]] text in Word.

      Only scans the document XML files (document.xml + headers / footers / footnotes / endnotes), not the relationship sidecars or content types — those parts can never legitimately contain user-visible text.

      Returns Promise<ValidationResult>

    • EG_RunInnerContent elements (<w:t>, <w:tab>, <w:br>, <w:cr>, <w:drawing>, <w:pict>, <w:object>, <w:sym>, <w:softHyphen>, <w:noBreakHyphen>, <w:ptab>, <w:fldChar>, <w:instrText>, <w:delText>, <w:lastRenderedPageBreak>, the note/comment references, …) are valid ONLY inside a <w:r>. When emitted as a DIRECT child of <w:p> — a sibling of runs, e.g. <w:r>…</w:r><w:tab/><w:r>… produced by a foreign pipeline that forgot to wrap them — they are not in EG_PContent (invalid OOXML). A real Word probe shows Word OPENS such files cleanly and silently relocates/drops the misplaced content — so this is structural invalidity + silent data loss, NOT a Word-openability failure: an error in lenient/strict, tolerated by the word-valid profile. The bare XSD validator surfaces it only generically ("this element is not expected"); this dedicated rule names the misplacement.

      Reports run-content-misplaced at "error" severity.

      Returns Promise<ValidationResult>

    • Verify the styles part the main document relates (by name in its folder when none does, #688, #699) defines the four "implied default" styles required by ECMA-376 §17.7.4.4 — Normal (paragraph), DefaultParagraphFont (character), TableNormal (table), NoList (numbering). Word looks these up by name when an element omits an explicit style and pops "Document Recovery" with a "Table Properties" complaint if TableNormal (the most visible default) is missing — even when no <w:tblStyle> references it.

      Profile-aware: strict reports error, lenient downgrades to warning (many real-world templates omit one or more of the implied defaults and Word still opens them — the dialog only fires for TableNormal-on-tables, not for absence in general). The repair pass repairMissingStyleDefinitions injects canonical definitions for any missing default, so --auto-repair resolves this without manual fixes regardless of profile.

      Returns Promise<ValidationResult>

    • Well-formedness of every XML part, checked on the ORIGINAL BYTES.

      libxml2 parses bytes, so the part's own BOM and encoding declaration decide how it decodes (a UTF-16 part read as a UTF-8 string is garbage) and every diagnostic carries a 1-based line/column. Parsing runs WITHOUT XML_PARSE_RECOVER so content @xmldom silently recovers from (a bare &, an unquoted attribute value) is reported instead of accepted, and WITH XML_PARSE_NONET | XML_PARSE_NO_XXE so external entities are never fetched. See src/lib/xml-syntax.ts.

      Codes: xml-syntax (not well-formed), xml-undeclared-prefix (undeclared namespace prefix), xml-encoding (declared encoding unsupported / bytes do not decode in it).

      Returns Promise<ValidationResult>

    • Eagerly verify that the wasm-backed XSD engine is loadable and that XSD validation is available on this host. Call this once at program startup if you want loud failures when the engine is broken — otherwise the per-file pipeline silently turns the same condition into per-file validation errors that look like document corruption.

      Throws an Error prefixed with XSD engine unavailable when validation cannot be performed. Returns silently on success.

      Successor of the libxmljs2-era assertLibxmljsAvailable, retired with the native addon (no Node 26 / ABI-147 prebuild, no source build).

      Returns Promise<void>