ReadonlydeadlineReadonlyoriginalReadonlyprofileReadonlyschemasReadonlyunpackedReadonlyverboseReadonlyxmlParts 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.
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.
The named checks this validator executed, in the order they ran.
Reset at the entry of every validate().
Align the threaded-comment parts, preflighting the encoding and writability of every part to be written before changing any of them. A failed or refused write returns only the repairs already written (#909, #920).
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).
Delete _rels/*.rels parts whose <Relationships> element has zero
children. Mirrors what Word does on save — see
validateNoEmptyRelsParts for the spec citation.
Returns the count of files deleted. The deleted files are also
removed from this.xmlFiles so subsequent passes (and validation
re-runs in the same instance) don't see them.
Repair mc:Ignorable entries that name an undeclared namespace
prefix. For each undeclared prefix:
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.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).
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:
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.
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).
Run every check. compareParagraphCounts is informational only and
does not affect the merged validity.
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.
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.
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):
<w:comment> in comments.xml that carries a w14:paraId
on its first paragraph must have a matching <w15:commentEx>
entry in commentsExtended.xml.<w15:commentEx> must have a w15:paraId matching the
paraId of some <w:comment> first paragraph (the inverse of #1).<w15:commentEx> entries may share the same w15:paraId
(this was the surface symptom of jubarte's #153 regression).w15:paraIdParent must resolve to a paraId
present somewhere else in commentsExtended.xml.w:commentRangeStart, w:commentRangeEnd, and
w:commentReference elements in document.xml must each equal
the number of comments in comments.xml.w16cid:commentId/@paraId in commentsIds.xml must resolve
to a paragraph in comments.xml.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.
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).
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.
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).
Check the font-table part the main document relates
(by name in its folder when none does, #690, #699) for empty
<w:fonts> (no <w:font> children).
An empty font table is XSD-valid but almost always indicates a repairer bug
that discards all font metadata.
Reports font-table-empty at "warning" severity.
Check the styles part the main document relates (by name in its
folder when none does, #688, #699) for the presence of <w:latentStyles>.
Missing latent styles is XSD-valid but unusual for Word-generated files.
Reports latent-styles-missing at "info" severity.
Every XML part should NOT begin with a UTF-8 BOM (U+FEFF, bytes EF BB
BF). XML 1.0 §4.3.3 permits the BOM and Microsoft Office emits it
routinely, so this is reported at warning severity under EVERY
profile — it never fails a document on its own. The finding stays
visible in result.issues so callers who care about canonical bytes can
audit it.
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.
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.
Check every .rels file and verify that each internal <Relationship>
target resolves to an existing file in the unpacked directory.
External (http/https) targets are skipped.
Reports rels-target-missing (error) when a target file is absent,
or when the target names a folder (#975).
Check for cell borders (<w:tcBorders>) that duplicate the enclosing
table's <w:tblBorders> defaults. Redundant borders override style-level
conditional formatting (e.g. <w:tblLook> banded-row suppression).
Reports cell-borders-redundant at "info" severity.
Check for explicit <w:rFonts> on runs that duplicate the document
defaults (<w:docDefaults>/<w:rPrDefault>/<w:rPr>/<w:rFonts>).
Redundant explicit properties make the document harder to re-style.
Reports run-props-redundant at "info" severity.
Check relationship .rels files for the rId1-rIdN sequential pattern.
Sequential IDs are valid but indicate the repairer may have regenerated
relationship IDs rather than preserving the originals.
Reports rel-ids-sequential at "info" severity.
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.
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.
Check every <w:tbl> in word/document.xml that references a
<w:tblStyle> for the presence of <w:tblLook>. Missing <w:tblLook>
prevents conditional formatting bands from applying.
Reports tbl-look-missing at "info" severity.
Check tracked-change elements (<w:ins>, <w:del>) for sequential w:id
values starting from 1. A sequential-from-1 pattern is a strong heuristic
signal that the repairer regenerated IDs rather than preserving originals.
Reports tracked-change-ids-regenerated at "info" severity.
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).
StaticassertEagerly 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).
The run's wall-clock budget, when it has one (#630).