feat(docxdiff): Word Compare "Comparison settings" parity (white space, textboxes, fields, tables, character granularity) - #337
Open
rey-ms wants to merge 9 commits into
Open
Conversation
WmlComparer had no whitespace toggle, so re-spaced text always produced revisions. Both inputs are canonicalized before comparison rather than making the comparison whitespace-blind, because FlattenToComparisonUnitAtomList zips an Equal sequence's two atom streams and a length mismatch there misaligns the reassembled content. WASM/npm ripple deliberately deferred; DocxDiff has no equivalent toggle yet.
DocxDiff is the default engine since v8, so the WmlComparer-only flag was invisible on the CLI/WASM/npm default paths. Both engines now share one input canonicalizer, hooked into DocxDiff.PreAccept so every entry point and every consolidate reviewer folds alike. A token-level fold was rejected: IrTokenDiffer's edit stream is 1:1 per token, so a length-asymmetric Equal would ripple through every op consumer.
A granularity switch: off, a changed textbox is del/ins'd wholesale by its host paragraph instead of inner-diffed, so the change stays in the markup and the round trip holds. A footnotes/endnotes twin was implemented and reverted — note references are inline body content, so gating the note-store diff dangles a right-added note's reference and emits an invalid package.
A changed drawing renders as del+ins copies carrying the source ids, so two revisions of one document emitted duplicates and the package was schema-invalid (wp:docPr/@id, the v:shape id space, v:shapetype/@id). NormalizeDrawingIds re-issues the later copies per story. Which copy keeps a REFERENCED id is chosen by revision survival, not document order: a bare shapetype reference cannot be rebound, so the accepted document must keep the original id.
Reported from real documents: two files differing only in one textbox text compared to an output that looked undetected. It was detected — the edit script carried textboxDiffs — but the markup renderer bailed out of fine rendering for textbox-bearing paragraphs, del/ins-ing the whole drawing, which for an anchored box stacks two boxes at one position. It now rebuilds each emitted txbxContent from the nested ops, matching Word.
The engine already compared field codes; this adds the off switch, gating FieldEnvelopeDigest in StructuralCarrierDiffers. Off, an uncompared code follows the scope's output source - right in the body, left in a header/footer story or note - so only one round-trip direction is exact.
Character level narrows a change to the differing characters instead of marking the whole word, as a refinement post-pass over both rendered surfaces - alignment stays word-grained, and a character shared by the del and ins texts survives accept and reject wherever it sits, so the round trip is untouched. Consolidate stays word level.
Gates IrTableDiffer at its single call site. Off, a content-differing table pair emits EqualBlock flagged Uncompared - the flag matters because the numbering-evidence zip and the edit-script verifier both assume an EqualBlock's sides correspond. Shell-only pairs route to FormatOnlyBlock so table formatting stays on its own axis; moves and add/remove survive.
# Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the missing Word Compare "Comparison settings" options to the
DocxDiffengine, plus two rendering-correctness fixes found while building them. Every option defaults to Word's own default, so existing output is byte-identical unless a caller opts in.docs/architecture/wml_comparer_gaps.mdgained a table mapping Word's Compare dialog onto our settings; this series fills in five of its rows.Added
WmlComparerSettings.CompareWhitespace/DocxDiffSettings.CompareWhitespacetrueDocxDiffSettings.CompareTextboxestrueDocxDiffSettings.CompareFieldstrueDocxDiffSettings.CompareTablestrueDocxDiffSettings.ChangeGranularityWordInternal/WhitespaceCanonicalizer.cs, used by both engines), so whitespace-only differences produce no revisions. A tab is never equated with a space.w:fldChar/w:fldDatascaffolding) are compared. Field results are ordinary text either way. On, a code change is one reversible whole-paragraphw:del/w:inspair carryingw:delInstrText.FormatOnlyBlockso table formatting stays on its own axis (TrackBlockFormatChanges).Characternarrows a change to the differing characters (colour→colorrenders retainedcolo+ deletedu+ retainedr) in both the markup and the revision list. Alignment stays word-grained; the edit script is identical under both values.Fixed
Sem_UniqueAttributeValue) acrosswp:docPr/@id, the VML shape-id space,v:shapetype/@idando:spid. A newNormalizeDrawingIdspass re-ids the non-surviving copies, choosing the keeper by revision side so the id lands on the copy that survives accept.Round-trip contract
accept ≡ rightandreject ≡ lefthold for every option at its default. Where an option is turned off, the uncompared difference is by definition not reversible — one side's content rides through untracked, and which side depends on the scope (body renders from the right; a header/footer story or note definition keeps its left part). This is stated per option in the XML docs and indocs/architecture/ir_diff_engine.md.Commits
The series has real internal dependencies and is ordered accordingly:
feat(comparer)White space (CompareWhitespace) — introduces the shared canonicalizer and the parity tablefeat(docxdiff)White space on theDocxDiffenginefeat(docxdiff)Textboxes (CompareTextboxes)fix(docxdiff)unique drawing ids in compare outputfeat(docxdiff)track a textbox change inside the box — builds on 4feat(docxdiff)Fields (CompareFields)feat(docxdiff)Show changes at (ChangeGranularity) — builds on 4feat(docxdiff)Tables (CompareTables)Verification
mainat44dac09(DS310–DS312,MD018,MD030,IrCorpusTests DB007-Notes.docx,MCP137) — confirmed by running them on an unmodified checkout.dotnet build -c Release Docxodus.sln(warnings-as-errors): 0 errors../scripts/build-wasm.sh: exit 0.npx tsc --noEmitinnpm/: clean.DocxDiffOpswire keys,npm/src/types.ts,python/src/docx_scalpel/.