Compare two documents
CompareTo gives the same element-by-element comparison as a failing snapshot test, for any two documents and without
snapshot files. It suits one-off checks such as before and after a library upgrade, or a customer's file against
yours.
PdfUaDocument before = PdfUaDocument.Load("guide-v1.pdf");
PdfUaDocument after = PdfUaDocument.Load("guide-v2.pdf");
// The same options as a snapshot: here, only the tag structure and text matter.
DocumentDiff diff = before.CompareTo(after, options => options.Ignore(PdfSection.Layout, PdfSection.Metadata));
DocumentDiff.Compare(before, after) does the same with the earlier document
first, and works for Word documents too, as does DocxDocument.CompareTo.
DocumentDiff.CompareSnapshots compares two snapshot texts, such as the
contents of two .verified.pdfua.txt files, and DocumentDiff.CompareFiles
compares two files the way pdiffy before after does, reading a PDF or Word document, whatever its extension, and
taking any other text as a snapshot.
Assert on the changes
A DocumentDiff lists its Changes. Each
DocumentChange has:
| Property | Holds | Example |
|---|---|---|
Kind |
A ChangeKind: Added, Removed, Moved, AttributeChanged, TextChanged or Changed |
TextChanged |
SectionName |
The section as the snapshot prints it, as in the JSON output; null for text without sections | "structure" |
PdfSection |
The PdfSection it's in; null outside PDF snapshots, and for the snapshot's first lines and whitespace-only changes |
PdfSection.Structure |
DocxSection |
The DocxSection it's in; null outside Word snapshots, and for the snapshot's first lines and whitespace-only changes |
DocxSection.Body |
Path |
Where it is | structure > Document > Table > TR[2] > TD[1] |
Page |
The page, when it's on one | 2 |
Detail |
What changed | "Go [-with-] {+without+} a guide" |
AffectsAccessibility |
Whether it can change what a screen reader gets; null for text, which PDiffy doesn't assess | true |
// No element changed place in reading order.
Assert.DoesNotContain(diff.Changes, change => change.Kind == ChangeKind.Moved);
// Any change is fine except one that can affect what a screen reader gets.
Assert.DoesNotContain(diff.Changes, change => change.AffectsAccessibility == true);
// Nothing changed at all.
Assert.True(diff.IsEmpty);
Tip
To fail a test only on accessibility changes, compare with CompareTo and assert on AffectsAccessibility. A
snapshot test always fails on any change; the flag only orders and marks the explanation.
New and fixed problems
When both snapshots have a checks section, its changes
read as progress or regression rather than as lines added and removed. A finding that only the later snapshot has is a
new problem: an Added change in the checks section whose path is the rule and whose detail is the finding's path. A
finding that only the earlier snapshot has is fixed: a Removed change. A new problem from an accessibility rule
(rather than a house rule) can affect accessibility, so it's listed first:
2 change(s): 1 new problem, 1 fixed
by section: checks 2
1 can affect accessibility, listed first
+ new problem FigureAltText (p4) [accessibility]
structure > Document > Figure[3]
- fixed TableHeaders (p3)
structure > Document > Table > TR > TH[2] > scope
A fixed problem still fails the test until the snapshot is accepted, so it can't come back unnoticed. When an upgrade
adds rules to the set, the summary says how many, so new problems can be told apart from changes to the document.
If you leave the section out with Ignore(PdfSection.Checks), every finding the earlier snapshot recorded is reported
as fixed, next to the header change that says the section is now ignored.
Print or save the result
| Method | Gives |
|---|---|
Describe(maxChanges) |
The summary a failing test shows: counts by kind and section, then up to maxChanges changes, accessibility ones first, with layout lines that moved together shown as one shift |
ToHtml() |
The side-by-side HTML report |
ToJson() |
The changes as JSON, for scripts and tools outside .NET |
Console.WriteLine(diff.Describe());
File.WriteAllText("report.html", diff.ToHtml());
File.WriteAllText("changes.json", diff.ToJson());
JSON output
ToJson(), or pdiffy before.pdf after.pdf --json on the command line, writes
a small, versioned shape:
{
"version": 1,
"changes": [
{
"kind": "Changed",
"section": "structure",
"path": "structure > Document > P[1]",
"page": 1,
"detail": "type: H1 -> P",
"affectsAccessibility": true,
"wcagCriteria": ["1.3.1"],
"needsReview": false
}
]
}
| Field | Holds |
|---|---|
version |
Goes up only when a field is renamed, removed or changes meaning. New fields can be added without changing it. |
kind |
A ChangeKind name |
section |
The section as the snapshot prints it, such as structure or body; header for the snapshot's first lines, whitespace when only blank lines or line endings differ, and null for text without sections, such as JSON |
path |
Where the change is |
page |
The page, or null when the change isn't on a page |
detail |
What changed |
affectsAccessibility |
Whether it can change what a screen reader gets, or null for text snapshots |
needsReview |
Whether a specialist should read it although it doesn't affect accessibility, such as reworded alt text (which ones), or null for text snapshots |
wcagCriteria |
The WCAG 2.2 success criteria a flagged change could affect, such as 1.1.1. Empty when the change isn't flagged or no criterion clearly covers it (which ones) |
Compare a PDF with its remediated version
Remediating a PDF should change only its accessibility layer: tags, alt text, artifacts, tab order and accessible
names. RemediationDiff.Compare lists the changes to the content, which a remediation
isn't expected to make, separately from the full diff:
PdfUaDocument original = PdfUaDocument.Load("brochure.pdf");
PdfUaDocument remediated = PdfUaDocument.Load("brochure-remediated.pdf");
RemediationDiff diff = RemediationDiff.Compare(original, remediated, options => options.Check(PdfCheckSet.Latest));
// Text, images, paths, page sizes, link targets, comments and form values the remediation changed.
Assert.True(diff.ContentChanges.IsEmpty, diff.ContentChanges.Describe());
// Every change, with the problems the remediation fixed and any it introduced in the checks section.
Console.WriteLine(diff.AllChanges.Describe());
- Tagging text splits its layout runs, and some tools reorder the content stream to match the reading order, so
ContentChangesjoins text across tags and compares content in page order. ContentChangesleaves out metadata, bookmarks, fonts and settings, which a remediation often sets.AllChangeshas them.- An empty
ContentChangesonly means the content didn't change: it doesn't say the tags are right. The checks section and a specialist's review cover that. - From the command line,
pdiffy original.pdf remediated.pdf --remediationprints the same two lists (Compare).
Compare a Word document with its PDF
Exporting a Word document to PDF can lose what a screen reader needs.
DocumentDiff.CompareExport compares what a screen reader announces in
each (its transcript): the title and language, the text in
reading order, heading levels, alt text, tables with their header rows, lists and links.
DocxDocument word = DocxDocument.Load("guide.docx");
PdfUaDocument pdf = PdfUaDocument.Load("guide.pdf");
DocumentDiff diff = DocumentDiff.CompareExport(word, pdf);
// ~ text line 2
// [-heading level 1:-] Trail Guide
Assert.True(diff.IsEmpty, diff.Describe());
- Layout and fonts aren't compared, since the two formats record them differently.
- The transcripts are compared line by line, so a change shows as the line it's on, and changes that can affect accessibility aren't flagged as they are when comparing snapshots.
- From the command line,
pdiffy guide.docx guide.pdfcompares them the same way (Compare).
See also
- Changes that affect accessibility
- Command line: compare two files without writing code