0
# XML Document Comparison
1
2
Comprehensive XML comparison functionality for detecting structural differences, content variations, and attribute changes between XML documents. XMLUnit provides flexible comparison strategies with customizable difference evaluation and detailed reporting.
3
4
## Capabilities
5
6
### DiffBuilder
7
8
The main entry point for XML comparison operations, providing a fluent API for configuring and executing comparisons.
9
10
```java { .api }
11
/**
12
* Creates a new DiffBuilder to compare XML documents
13
* @param control - The control (expected) XML document as any supported input type
14
* @returns DiffBuilder instance for configuration
15
*/
16
public static DiffBuilder compare(Object control);
17
18
public class DiffBuilder implements DifferenceEngineConfigurer<DiffBuilder> {
19
/** Set the test (actual) document to compare against control */
20
public DiffBuilder withTest(Object test);
21
22
/** Ignore whitespace-only text nodes */
23
public DiffBuilder ignoreWhitespace();
24
25
/** Normalize whitespace in text content */
26
public DiffBuilder normalizeWhitespace();
27
28
/** Ignore whitespace between elements */
29
public DiffBuilder ignoreElementContentWhitespace();
30
31
/** Ignore XML comments during comparison */
32
public DiffBuilder ignoreComments();
33
34
/** Ignore XML comments using specific XSLT version */
35
public DiffBuilder ignoreCommentsUsingXSLTVersion(String xsltVersion);
36
37
/** Check for similarity rather than identical match */
38
public DiffBuilder checkForSimilar();
39
40
/** Check for identical match (default) */
41
public DiffBuilder checkForIdentical();
42
43
/** Configure custom DocumentBuilderFactory */
44
public DiffBuilder withDocumentBuilderFactory(DocumentBuilderFactory f);
45
46
/** Build the final Diff instance */
47
public Diff build();
48
}
49
```
50
51
**Usage Examples:**
52
53
```java
54
import org.xmlunit.builder.Input;
55
import org.xmlunit.builder.DiffBuilder;
56
import org.xmlunit.diff.Diff;
57
58
// Basic XML comparison
59
Source control = Input.fromString("<users><user id='1'>John</user></users>").build();
60
Source test = Input.fromString("<users><user id='1'>Jane</user></users>").build();
61
62
Diff diff = DiffBuilder.compare(control)
63
.withTest(test)
64
.build();
65
66
if (diff.hasDifferences()) {
67
System.out.println("Documents differ: " + diff.toString());
68
}
69
70
// Ignore whitespace differences
71
Diff lenientDiff = DiffBuilder.compare(control)
72
.withTest(test)
73
.ignoreWhitespace()
74
.normalizeWhitespace()
75
.ignoreComments()
76
.build();
77
78
// Check for similarity instead of identical
79
Diff similarityDiff = DiffBuilder.compare(control)
80
.withTest(test)
81
.checkForSimilar()
82
.build();
83
```
84
85
### Advanced Configuration
86
87
DiffBuilder extends `DifferenceEngineConfigurer` providing advanced customization options.
88
89
```java { .api }
90
public interface DifferenceEngineConfigurer<D> {
91
/** Configure custom node matching strategy */
92
D withNodeMatcher(NodeMatcher nodeMatcher);
93
94
/** Configure custom difference evaluation */
95
D withDifferenceEvaluator(DifferenceEvaluator differenceEvaluator);
96
97
/** Configure comparison control flow */
98
D withComparisonController(ComparisonController comparisonController);
99
100
/** Add comparison event listeners */
101
D withComparisonListeners(ComparisonListener... listeners);
102
103
/** Add difference event listeners */
104
D withDifferenceListeners(ComparisonListener... listeners);
105
106
/** Configure namespace prefixes for XPath */
107
D withNamespaceContext(Map<String, String> prefix2Uri);
108
109
/** Filter attributes during comparison */
110
D withAttributeFilter(Predicate<Attr> attributeFilter);
111
112
/** Filter nodes during comparison */
113
D withNodeFilter(Predicate<Node> nodeFilter);
114
115
/** Configure comparison output formatting */
116
D withComparisonFormatter(ComparisonFormatter formatter);
117
}
118
```
119
120
**Usage Examples:**
121
122
```java
123
import org.xmlunit.diff.*;
124
125
// Custom difference evaluation
126
DifferenceEvaluator customEvaluator = new DifferenceEvaluator() {
127
@Override
128
public ComparisonResult evaluate(Comparison comparison, ComparisonResult outcome) {
129
// Treat attribute order differences as similar, not different
130
if (comparison.getType() == ComparisonType.ATTR_SEQUENCE) {
131
return ComparisonResult.SIMILAR;
132
}
133
return outcome;
134
}
135
};
136
137
Diff customDiff = DiffBuilder.compare(control)
138
.withTest(test)
139
.withDifferenceEvaluator(customEvaluator)
140
.build();
141
142
// Custom node matching
143
NodeMatcher elementMatcher = new DefaultNodeMatcher(
144
ElementSelectors.byNameAndText,
145
ElementSelectors.byName
146
);
147
148
Diff matchedDiff = DiffBuilder.compare(control)
149
.withTest(test)
150
.withNodeMatcher(elementMatcher)
151
.build();
152
153
// Namespace context for XPath evaluation
154
Map<String, String> namespaces = new HashMap<>();
155
namespaces.put("ns", "http://example.com/namespace");
156
157
Diff namespaceAwareDiff = DiffBuilder.compare(control)
158
.withTest(test)
159
.withNamespaceContext(namespaces)
160
.build();
161
```
162
163
### Diff Results
164
165
The result of a comparison operation containing all detected differences.
166
167
```java { .api }
168
public class Diff {
169
/** Check if any differences were found */
170
public boolean hasDifferences();
171
172
/** Get all differences found during comparison */
173
public Iterable<Difference> getDifferences();
174
175
/** Get the control (expected) XML source */
176
public Source getControlSource();
177
178
/** Get the test (actual) XML source */
179
public Source getTestSource();
180
181
/** Get complete description of all differences */
182
public String fullDescription();
183
184
/** Get complete description using custom formatter */
185
public String fullDescription(ComparisonFormatter formatter);
186
187
/** String representation of first difference */
188
@Override
189
public String toString();
190
191
/** String representation using custom formatter */
192
public String toString(ComparisonFormatter formatter);
193
}
194
```
195
196
### Individual Differences
197
198
Detailed information about specific differences found during comparison.
199
200
```java { .api }
201
public class Difference {
202
/** Get the comparison that was performed */
203
public Comparison getComparison();
204
205
/** Get the result of the comparison */
206
public ComparisonResult getResult();
207
}
208
209
public class Comparison {
210
/** Get the type of comparison performed */
211
public ComparisonType getType();
212
213
/** Get details of the control (expected) side */
214
public Detail getControlDetails();
215
216
/** Get details of the test (actual) side */
217
public Detail getTestDetails();
218
219
/** Formatted string representation */
220
public String toString(ComparisonFormatter formatter);
221
}
222
223
public static class Detail {
224
/** Get the target node being compared */
225
public Node getTarget();
226
227
/** Get XPath to the target node */
228
public String getXPath();
229
230
/** Get the value being compared */
231
public Object getValue();
232
233
/** Get XPath to the parent node */
234
public String getParentXPath();
235
}
236
```
237
238
### Comparison Types and Results
239
240
Enumerations defining what is being compared and the outcome.
241
242
```java { .api }
243
public enum ComparisonType {
244
ATTR_NAME_LOOKUP, ATTR_VALUE, CHILD_LOOKUP, CHILD_NODELIST_LENGTH,
245
CHILD_NODELIST_SEQUENCE, ELEMENT_NAME, ELEMENT_TAG_NAME, HAS_DOCTYPE_DECLARATION,
246
NAMESPACE_PREFIX, NAMESPACE_URI, NODE_TYPE, NO_NAMESPACE_SCHEMA_LOCATION,
247
PROCESSING_INSTRUCTION_DATA, PROCESSING_INSTRUCTION_TARGET, SCHEMA_LOCATION,
248
TEXT_VALUE, XML_VERSION, XML_STANDALONE, XML_ENCODING, DOCTYPE_NAME,
249
DOCTYPE_PUBLIC_ID, DOCTYPE_SYSTEM_ID, COMMENT_VALUE, CDATA_VALUE
250
}
251
252
public enum ComparisonResult {
253
/** Content is identical */
254
EQUAL,
255
256
/** Content is similar but not identical */
257
SIMILAR,
258
259
/** Content is completely different */
260
DIFFERENT
261
}
262
```
263
264
### Node Matching Strategies
265
266
Configure how control and test nodes are matched during comparison.
267
268
```java { .api }
269
public interface NodeMatcher {
270
/** Match child nodes between control and test parents */
271
Iterable<Map.Entry<Node, Node>> match(Iterable<Node> controlNodes,
272
Iterable<Node> testNodes);
273
}
274
275
public class DefaultNodeMatcher implements NodeMatcher {
276
/** Create matcher with element selector strategies */
277
public DefaultNodeMatcher(ElementSelector... selectors);
278
}
279
280
// Built-in element selectors
281
public class ElementSelectors {
282
public static final ElementSelector byName;
283
public static final ElementSelector byNameAndText;
284
public static final ElementSelector byNameAndAllAttributes;
285
public static final ElementSelector byNameAndAttributes(String... attributeNames);
286
287
/** Chain multiple selectors with fallback behavior */
288
public static ElementSelector conditionalBuilder()
289
.whenElementIsNamed(String expectedName)
290
.thenUse(ElementSelector selector)
291
.elseUse(ElementSelector selector)
292
.build();
293
}
294
```
295
296
### Difference Evaluation
297
298
Customize how comparison results are interpreted and handled.
299
300
```java { .api }
301
public interface DifferenceEvaluator {
302
/** Evaluate a comparison and potentially change its result */
303
ComparisonResult evaluate(Comparison comparison, ComparisonResult outcome);
304
}
305
306
// Built-in evaluators
307
public class DifferenceEvaluators {
308
/** Default evaluator that accepts all results as-is */
309
public static final DifferenceEvaluator Default;
310
311
/** Ignore specific comparison types */
312
public static DifferenceEvaluator ignorePaths(String... xPaths);
313
314
/** Chain multiple evaluators */
315
public static DifferenceEvaluator chain(DifferenceEvaluator... evaluators);
316
317
/** Upgrade SIMILAR results to EQUAL */
318
public static final DifferenceEvaluator upgradeDifferencesToEqual;
319
320
/** Downgrade DIFFERENT results to SIMILAR */
321
public static final DifferenceEvaluator downgradeDifferencesToSimilar;
322
}
323
```
324
325
**Usage Examples:**
326
327
```java
328
// Ignore attribute order differences
329
DifferenceEvaluator ignoreAttrSequence = (comparison, outcome) -> {
330
if (comparison.getType() == ComparisonType.ATTR_SEQUENCE) {
331
return ComparisonResult.SIMILAR;
332
}
333
return outcome;
334
};
335
336
// Chain multiple evaluators
337
DifferenceEvaluator chained = DifferenceEvaluators.chain(
338
ignoreAttrSequence,
339
DifferenceEvaluators.ignorePaths("/users/user/@created"),
340
DifferenceEvaluators.downgradeDifferencesToSimilar
341
);
342
343
Diff customEvaluatedDiff = DiffBuilder.compare(control)
344
.withTest(test)
345
.withDifferenceEvaluator(chained)
346
.build();
347
```
348
349
### Comparison Formatting
350
351
Customize how comparison results are displayed and reported.
352
353
```java { .api }
354
public interface ComparisonFormatter {
355
/** Format comparison details for display */
356
String getDescription(Comparison difference);
357
358
/** Get short summary of comparison */
359
String getDetails(Detail details, ComparisonType type, boolean formatting);
360
}
361
362
public class DefaultComparisonFormatter implements ComparisonFormatter {
363
/** Default formatting implementation */
364
}
365
```
366
367
### Low-Level Difference Engine
368
369
Direct access to the comparison engine for advanced use cases.
370
371
```java { .api }
372
public interface DifferenceEngine {
373
/** Add listener for comparison events */
374
void addDifferenceListener(ComparisonListener listener);
375
376
/** Add listener that can control comparison flow */
377
void addComparisonController(ComparisonController controller);
378
379
/** Add listener for completed comparisons */
380
void addMatchListener(ComparisonListener listener);
381
382
/** Perform comparison between control and test sources */
383
void compare(Source control, Source test);
384
}
385
386
public class DOMDifferenceEngine extends AbstractDifferenceEngine {
387
/** DOM-based difference engine implementation */
388
}
389
390
public interface ComparisonListener {
391
/** Called for each comparison performed */
392
void comparisonPerformed(Comparison comparison, ComparisonResult outcome);
393
}
394
395
public interface ComparisonController extends ComparisonListener {
396
/** Control whether comparison should continue */
397
boolean stopDiffing(Comparison comparison);
398
}
399
```
400
401
**Usage Examples:**
402
403
```java
404
// Direct engine usage with listeners
405
DOMDifferenceEngine engine = new DOMDifferenceEngine();
406
407
engine.addDifferenceListener((comparison, outcome) -> {
408
if (outcome == ComparisonResult.DIFFERENT) {
409
System.out.println("Found difference: " + comparison);
410
}
411
});
412
413
engine.addComparisonController(new ComparisonController() {
414
private int differenceCount = 0;
415
416
@Override
417
public void comparisonPerformed(Comparison comparison, ComparisonResult outcome) {
418
if (outcome != ComparisonResult.EQUAL) {
419
differenceCount++;
420
}
421
}
422
423
@Override
424
public boolean stopDiffing(Comparison comparison) {
425
// Stop after finding 10 differences
426
return differenceCount >= 10;
427
}
428
});
429
430
engine.compare(controlSource, testSource);
431
```