• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xmlunit / xmlunit / 667

pending completion
667

push

travis-ci-com

bodewig
add Cyclone DX SBOM generation to build

5824 of 6326 relevant lines covered (92.06%)

3.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.73
/xmlunit-core/src/main/java/org/xmlunit/diff/DOMDifferenceEngine.java
1
/*
2
  This file is licensed to You under the Apache License, Version 2.0
3
  (the "License"); you may not use this file except in compliance with
4
  the License.  You may obtain a copy of the License at
5

6
  http://www.apache.org/licenses/LICENSE-2.0
7

8
  Unless required by applicable law or agreed to in writing, software
9
  distributed under the License is distributed on an "AS IS" BASIS,
10
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
  See the License for the specific language governing permissions and
12
  limitations under the License.
13
*/
14

15
package org.xmlunit.diff;
16

17
import java.util.Collections;
18
import java.util.HashMap;
19
import java.util.HashSet;
20
import java.util.LinkedList;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.Set;
24
import javax.xml.XMLConstants;
25
import javax.xml.namespace.QName;
26
import javax.xml.parsers.DocumentBuilderFactory;
27
import javax.xml.transform.Source;
28
import org.xmlunit.XMLUnitException;
29
import org.xmlunit.util.Convert;
30
import org.xmlunit.util.DocumentBuilderFactoryConfigurer;
31
import org.xmlunit.util.IterableNodeList;
32
import org.xmlunit.util.Linqy;
33
import org.xmlunit.util.Mapper;
34
import org.xmlunit.util.Nodes;
35
import org.w3c.dom.Attr;
36
import org.w3c.dom.CharacterData;
37
import org.w3c.dom.Document;
38
import org.w3c.dom.DocumentType;
39
import org.w3c.dom.Element;
40
import org.w3c.dom.NamedNodeMap;
41
import org.w3c.dom.Node;
42
import org.w3c.dom.ProcessingInstruction;
43

44
/**
45
 * Difference engine based on DOM.
46
 */
47
public final class DOMDifferenceEngine extends AbstractDifferenceEngine {
48

49
    /**
50
     * Maps Nodes to their QNames.
51
     */
52
    private static final Mapper<Node, QName> QNAME_MAPPER =
4✔
53
        new Mapper<Node, QName>() {
4✔
54
            @Override
55
            public QName apply(Node n) { return Nodes.getQName(n); }
4✔
56
        };
57

58
    private DocumentBuilderFactory documentBuilderFactory;
59

60
    /**
61
     * Creates a new DOMDifferenceEngine using the default {@link DocumentBuilderFactory}.
62
     */
63
    public DOMDifferenceEngine() {
64
        this(DocumentBuilderFactoryConfigurer.Default.configure(DocumentBuilderFactory.newInstance()));
4✔
65
    }
4✔
66

67
    /**
68
     * Creates a new DOMDifferenceEngine.
69
     *
70
     * <p>The {@link DocumentBuilderFactory} is only used if the
71
     * {@code Source} passed to {@link #compare} is not already a
72
     * {@link javax.xml.transform.dom.DOMSource}.</p>
73
     *
74
     * @param f {@code DocumentBuilderFactory} to use when creating a
75
     * {@link Document} from the {@link Source}s to compare.
76
     *
77
     * @since XMLUnit 2.7.0
78
     */
79
    public DOMDifferenceEngine(final DocumentBuilderFactory f) {
4✔
80
        if (f == null) {
4✔
81
            throw new IllegalArgumentException("factory must not be null");
4✔
82
        }
83
        documentBuilderFactory = f;
4✔
84
    }
4✔
85

86
    /**
87
     * Sets the {@link DocumentBuilderFactory} to use when creating a
88
     * {@link Document} from the {@link Source}s to compare.
89
     *
90
     * <p>This is only used if the {@code Source} passed to {@link #compare}
91
     * is not already a {@link javax.xml.transform.dom.DOMSource}.</p>
92
     *
93
     * @param f {@code DocumentBuilderFactory} to use when creating a
94
     * {@link Document} from the {@link Source}s to compare.
95
     *
96
     * @since XMLUnit 2.2.0
97
     * @deprecated use the one-arg constructor instead
98
     */
99
    public void setDocumentBuilderFactory(DocumentBuilderFactory f) {
100
        if (f == null) {
4✔
101
            throw new IllegalArgumentException("factory must not be null");
4✔
102
        }
103
        documentBuilderFactory = f;
×
104
    }
×
105

106
    @Override
107
    public void compare(Source control, Source test) {
108
        if (control == null) {
4✔
109
            throw new IllegalArgumentException("control must not be null");
×
110
        }
111
        if (test == null) {
4✔
112
            throw new IllegalArgumentException("test must not be null");
×
113
        }
114
        try {
115
            Node controlNode = Convert.toNode(control, documentBuilderFactory);
4✔
116
            Node testNode = Convert.toNode(test, documentBuilderFactory);
4✔
117
            compareNodes(controlNode, xpathContextFor(controlNode),
4✔
118
                         testNode, xpathContextFor(testNode));
4✔
119
        } catch (Exception ex) {
4✔
120
            throw new XMLUnitException("Caught exception during comparison",
4✔
121
                                       ex);
122
        }
4✔
123
    }
4✔
124

125
    private XPathContext xpathContextFor(Node n) {
126
        return new XPathContext(getNamespaceContext(), n);
4✔
127
    }
128

129
    /**
130
     * Recursively compares two XML nodes.
131
     *
132
     * <p>Performs comparisons common to all node types, then performs
133
     * the node type specific comparisons and finally recurses into
134
     * the node's child lists.</p>
135
     *
136
     * <p>Stops as soon as any comparison returns
137
     * ComparisonResult.CRITICAL.</p>
138
     *
139
     * <p>package private to support tests.</p>
140
     */
141
    ComparisonState compareNodes(final Node control, final XPathContext controlContext,
142
                                 final Node test, final XPathContext testContext) {
143
        final Iterable<Node> allControlChildren =
4✔
144
            new IterableNodeList(control.getChildNodes());
4✔
145
        final Iterable<Node> controlChildren =
4✔
146
            Linqy.filter(allControlChildren, getNodeFilter());
4✔
147
        final Iterable<Node> allTestChildren =
4✔
148
            new IterableNodeList(test.getChildNodes());
4✔
149
        final Iterable<Node> testChildren =
4✔
150
            Linqy.filter(allTestChildren, getNodeFilter());
4✔
151

152
        return compare(new Comparison(ComparisonType.NODE_TYPE,
4✔
153
                                      control, getXPath(controlContext),
4✔
154
                                      control.getNodeType(), getParentXPath(controlContext),
4✔
155
                                      test, getXPath(testContext),
4✔
156
                                      test.getNodeType(), getParentXPath(testContext)))
4✔
157
            .andThen(new Comparison(ComparisonType.NAMESPACE_URI,
4✔
158
                                    control, getXPath(controlContext),
4✔
159
                                    control.getNamespaceURI(), getParentXPath(controlContext),
4✔
160
                                    test, getXPath(testContext),
4✔
161
                                    test.getNamespaceURI(), getParentXPath(testContext)))
4✔
162
            .andThen(new Comparison(ComparisonType.NAMESPACE_PREFIX,
4✔
163
                                    control, getXPath(controlContext),
4✔
164
                                    control.getPrefix(), getParentXPath(controlContext),
4✔
165
                                    test, getXPath(testContext),
4✔
166
                                    test.getPrefix(), getParentXPath(testContext)))
4✔
167
            .andIfTrueThen(control.getNodeType() != Node.ATTRIBUTE_NODE,
4✔
168
                           new Comparison(ComparisonType.CHILD_NODELIST_LENGTH,
169
                                          control, getXPath(controlContext),
4✔
170
                                          Linqy.count(controlChildren), getParentXPath(controlContext),
4✔
171
                                          test, getXPath(testContext),
4✔
172
                                          Linqy.count(testChildren), getParentXPath(testContext)))
4✔
173
            .andThen(new DeferredComparison() {
4✔
174
                    @Override
175
                    public ComparisonState apply() {
176
                        return nodeTypeSpecificComparison(control, controlContext,
4✔
177
                                                          test, testContext);
178
                    }
179
                })
180
            // and finally recurse into children
181
            .andIfTrueThen(control.getNodeType() != Node.ATTRIBUTE_NODE,
4✔
182
                           compareChildren(controlContext,
4✔
183
                                           allControlChildren,
184
                                           controlChildren,
185
                                           testContext,
186
                                           allTestChildren,
187
                                           testChildren));
188
    }
189

190
    /**
191
     * Dispatches to the node type specific comparison if one is
192
     * defined for the given combination of nodes.
193
     */
194
    private ComparisonState nodeTypeSpecificComparison(Node control,
195
                                                       XPathContext controlContext,
196
                                                       Node test,
197
                                                       XPathContext testContext) {
198
        switch (control.getNodeType()) {
4✔
199
        case Node.CDATA_SECTION_NODE:
200
        case Node.COMMENT_NODE:
201
        case Node.TEXT_NODE:
202
            if (test instanceof CharacterData) {
4✔
203
                return compareCharacterData((CharacterData) control,
4✔
204
                                            controlContext,
205
                                            (CharacterData) test, testContext);
206
            }
207
            break;
208
        case Node.DOCUMENT_NODE:
209
            if (test instanceof Document) {
4✔
210
                return compareDocuments((Document) control, controlContext,
4✔
211
                                        (Document) test, testContext);
212
            }
213
            break;
214
        case Node.ELEMENT_NODE:
215
            if (test instanceof Element) {
4✔
216
                return compareElements((Element) control, controlContext,
4✔
217
                                       (Element) test, testContext);
218
            }
219
            break;
220
        case Node.PROCESSING_INSTRUCTION_NODE:
221
            if (test instanceof ProcessingInstruction) {
4✔
222
                return
4✔
223
                    compareProcessingInstructions((ProcessingInstruction) control,
4✔
224
                                                  controlContext,
225
                                                  (ProcessingInstruction) test,
226
                                                  testContext);
227
            }
228
            break;
229
        case Node.DOCUMENT_TYPE_NODE:
230
            if (test instanceof DocumentType) {
4✔
231
                return compareDocTypes((DocumentType) control, controlContext,
4✔
232
                                       (DocumentType) test, testContext);
233
            }
234
            break;
235
        case Node.ATTRIBUTE_NODE:
236
            if (test instanceof Attr) {
4✔
237
                return compareAttributes((Attr) control, controlContext,
4✔
238
                                         (Attr) test, testContext);
239
            }
240
            break;
241
        default:
242
            break;
243
        }
244
        return new OngoingComparisonState();
×
245
    }
246

247
    private DeferredComparison compareChildren(final XPathContext controlContext,
248
                                               final Iterable<Node> allControlChildren,
249
                                               final Iterable<Node> controlChildren,
250
                                               final XPathContext testContext,
251
                                               final Iterable<Node> allTestChildren,
252
                                               final Iterable<Node> testChildren) {
253
        return new DeferredComparison() {
4✔
254
            @Override
255
            public ComparisonState apply() {
256
                controlContext
4✔
257
                    .setChildren(Linqy.map(allControlChildren, ElementSelectors.TO_NODE_INFO));
4✔
258
                testContext
4✔
259
                    .setChildren(Linqy.map(allTestChildren, ElementSelectors.TO_NODE_INFO));
4✔
260
                return compareNodeLists(allControlChildren, controlChildren, controlContext,
4✔
261
                                        allTestChildren, testChildren, testContext);
262
            }
263
        };
264
    }
265

266
    /**
267
     * Compares textual content.
268
     */
269
    private ComparisonState compareCharacterData(CharacterData control,
270
                                                 XPathContext controlContext,
271
                                                 CharacterData test,
272
                                                 XPathContext testContext) {
273
        return compare(new Comparison(ComparisonType.TEXT_VALUE, control,
4✔
274
                                      getXPath(controlContext),
4✔
275
                                      control.getData(), getParentXPath(controlContext),
4✔
276
                                      test, getXPath(testContext),
4✔
277
                                      test.getData(), getParentXPath(testContext)));
4✔
278
    }
279

280
    /**
281
     * Compares document node, doctype and XML declaration properties
282
     */
283
    private ComparisonState compareDocuments(final Document control,
284
                                             final XPathContext controlContext,
285
                                             final Document test,
286
                                             final XPathContext testContext) {
287
        final DocumentType controlDt = filterNode(control.getDoctype());
4✔
288
        final DocumentType testDt = filterNode(test.getDoctype());
4✔
289

290
        return compare(new Comparison(ComparisonType.HAS_DOCTYPE_DECLARATION,
4✔
291
                                      control, getXPath(controlContext),
4✔
292
                                      Boolean.valueOf(controlDt != null), getParentXPath(controlContext),
4✔
293
                                      test, getXPath(testContext),
4✔
294
                                      Boolean.valueOf(testDt != null), getParentXPath(testContext)))
4✔
295
            .andIfTrueThen(controlDt != null && testDt != null,
4✔
296
                           new DeferredComparison() {
4✔
297
                               @Override
298
                               public ComparisonState apply() {
299
                                   return compareNodes(controlDt, controlContext,
4✔
300
                                                       testDt, testContext);
301
                               }
302
                           })
303
            .andThen(compareDeclarations(control, controlContext,
4✔
304
                                         test, testContext));
305
    }
306

307
    private <T extends Node> T filterNode(T n) {
308
        return n != null && getNodeFilter().test(n) ? n : null;
4✔
309
    }
310

311
    /**
312
     * Compares properties of the doctype declaration.
313
     */
314
    private ComparisonState
315
        compareDocTypes(DocumentType control,
316
                        XPathContext controlContext,
317
                        DocumentType test,
318
                        XPathContext testContext) {
319
        return compare(new Comparison(ComparisonType.DOCTYPE_NAME,
4✔
320
                                      control, getXPath(controlContext),
4✔
321
                                      control.getName(), getParentXPath(controlContext),
4✔
322
                                      test, getXPath(testContext),
4✔
323
                                      test.getName(), getParentXPath(testContext)))
4✔
324
            .andThen(new Comparison(ComparisonType.DOCTYPE_PUBLIC_ID,
4✔
325
                                    control, getXPath(controlContext),
4✔
326
                                    control.getPublicId(), getParentXPath(controlContext),
4✔
327
                                    test, getXPath(testContext),
4✔
328
                                    test.getPublicId(), getParentXPath(testContext)))
4✔
329
            .andThen(new Comparison(ComparisonType.DOCTYPE_SYSTEM_ID,
4✔
330
                                    control, null, control.getSystemId(), null,
4✔
331
                                    test, null, test.getSystemId(), null));
4✔
332
    }
333

334
    /**
335
     * Compares properties of XML declaration.
336
     */
337
    private DeferredComparison compareDeclarations(final Document control,
338
                                                   final XPathContext controlContext,
339
                                                   final Document test,
340
                                                   final XPathContext testContext) {
341
        return new DeferredComparison() {
4✔
342
            @Override
343
            public ComparisonState apply() {
344
                return
4✔
345
                    compare(new Comparison(ComparisonType.XML_VERSION,
4✔
346
                                           control, getXPath(controlContext),
4✔
347
                                           control.getXmlVersion(), getParentXPath(controlContext),
4✔
348
                                           test, getXPath(testContext),
4✔
349
                                           test.getXmlVersion(), getParentXPath(testContext)))
4✔
350
                    .andThen(new Comparison(ComparisonType.XML_STANDALONE,
4✔
351
                                            control, getXPath(controlContext),
4✔
352
                                            control.getXmlStandalone(), getParentXPath(controlContext),
4✔
353
                                            test, getXPath(testContext),
4✔
354
                                            test.getXmlStandalone(), getParentXPath(testContext)))
4✔
355
                    .andThen(new Comparison(ComparisonType.XML_ENCODING,
4✔
356
                                            control, getXPath(controlContext),
4✔
357
                                            control.getXmlEncoding(), getParentXPath(controlContext),
4✔
358
                                            test, getXPath(testContext),
4✔
359
                                            test.getXmlEncoding(), getParentXPath(testContext)));
4✔
360
            }
361
        };
362
    }
363

364
    /**
365
     * Compares elements node properties, in particular the element's
366
     * name and its attributes.
367
     */
368
    private ComparisonState compareElements(final Element control,
369
                                            final XPathContext controlContext,
370
                                            final Element test,
371
                                            final XPathContext testContext) {
372
        return
4✔
373
            compare(new Comparison(ComparisonType.ELEMENT_TAG_NAME,
4✔
374
                                   control, getXPath(controlContext),
4✔
375
                                   Nodes.getQName(control).getLocalPart(), getParentXPath(controlContext),
4✔
376
                                   test, getXPath(testContext),
4✔
377
                                   Nodes.getQName(test).getLocalPart(), getParentXPath(testContext)))
4✔
378
            .andThen(new DeferredComparison() {
4✔
379
                    @Override
380
                    public ComparisonState apply() {
381
                        return compareElementAttributes(control, controlContext,
4✔
382
                                                        test, testContext);
383
                    }
384
                });
385
    }
386

387
    /**
388
     * Compares element's attributes.
389
     */
390
    private ComparisonState compareElementAttributes(final Element control,
391
                                                     final XPathContext controlContext,
392
                                                     final Element test,
393
                                                     final XPathContext testContext) {
394
        final Attributes controlAttributes = splitAttributes(control.getAttributes());
4✔
395
        controlContext
4✔
396
            .addAttributes(Linqy.map(controlAttributes.remainingAttributes,
4✔
397
                                     QNAME_MAPPER));
398
        final Attributes testAttributes = splitAttributes(test.getAttributes());
4✔
399
        testContext
4✔
400
            .addAttributes(Linqy.map(testAttributes.remainingAttributes,
4✔
401
                                     QNAME_MAPPER));
402

403
        return compare(new Comparison(ComparisonType.ELEMENT_NUM_ATTRIBUTES,
4✔
404
                                      control, getXPath(controlContext),
4✔
405
                                      controlAttributes.remainingAttributes.size(), getParentXPath(controlContext),
4✔
406
                                      test, getXPath(testContext),
4✔
407
                                      testAttributes.remainingAttributes.size(), getParentXPath(testContext)))
4✔
408
            .andThen(new DeferredComparison() {
4✔
409
                    @Override
410
                    public ComparisonState apply() {
411
                        return compareXsiType(controlAttributes.type, controlContext,
4✔
412
                                              testAttributes.type, testContext);
4✔
413
                    }
414
                })
415
            .andThen(new Comparison(ComparisonType.SCHEMA_LOCATION,
4✔
416
                                    control, getXPath(controlContext),
4✔
417
                                    controlAttributes.schemaLocation != null
4✔
418
                                    ? controlAttributes.schemaLocation.getValue() : null, getParentXPath(controlContext),
4✔
419
                                    test, getXPath(testContext),
4✔
420
                                    testAttributes.schemaLocation != null
4✔
421
                                    ? testAttributes.schemaLocation.getValue() : null, getParentXPath(testContext)))
4✔
422
            .andThen(new Comparison(ComparisonType.NO_NAMESPACE_SCHEMA_LOCATION,
4✔
423
                                    control, getXPath(controlContext),
4✔
424
                                    controlAttributes.noNamespaceSchemaLocation != null ?
4✔
425
                                    controlAttributes.noNamespaceSchemaLocation.getValue()
4✔
426
                                    : null, getParentXPath(controlContext),
4✔
427
                                    test, getXPath(testContext),
4✔
428
                                    testAttributes.noNamespaceSchemaLocation != null
4✔
429
                                    ? testAttributes.noNamespaceSchemaLocation.getValue()
4✔
430
                                    : null, getParentXPath(testContext)))
4✔
431
            .andThen(new NormalAttributeComparer(control, controlContext,
4✔
432
                                                 controlAttributes, test,
433
                                                 testContext, testAttributes));
434
    }
435

436
    private class NormalAttributeComparer implements DeferredComparison {
437
        private final Set<Attr> foundTestAttributes = new HashSet<Attr>();
4✔
438
        private final Element control, test;
439
        private final XPathContext controlContext, testContext;
440
        private final Attributes controlAttributes, testAttributes;
441

442
        private NormalAttributeComparer(Element control,
443
                                        XPathContext controlContext,
444
                                        Attributes controlAttributes,
445
                                        Element test,
446
                                        XPathContext testContext,
447
                                        Attributes testAttributes) {
4✔
448
            this.control = control;
4✔
449
            this.controlContext = controlContext;
4✔
450
            this.controlAttributes = controlAttributes;
4✔
451
            this.test = test;
4✔
452
            this.testContext = testContext;
4✔
453
            this.testAttributes = testAttributes;
4✔
454
        }
4✔
455

456
        @Override
457
        public ComparisonState apply() {
458
            ComparisonState chain = new OngoingComparisonState();
4✔
459
            for (final Attr controlAttr : controlAttributes.remainingAttributes) {
4✔
460
                final QName controlAttrName = Nodes.getQName(controlAttr);
4✔
461
                final Attr testAttr =
4✔
462
                    findMatchingAttr(testAttributes.remainingAttributes,
4✔
463
                                     controlAttr);
464
                final QName testAttrName = testAttr != null
4✔
465
                    ? Nodes.getQName(testAttr) : null;
4✔
466

467
                controlContext.navigateToAttribute(controlAttrName);
4✔
468
                try {
469
                    chain = chain.andThen(
4✔
470
                        new Comparison(ComparisonType.ATTR_NAME_LOOKUP,
471
                                                control, getXPath(controlContext),
4✔
472
                                                controlAttrName, getParentXPath(controlContext),
4✔
473
                                                test, getXPath(testContext),
4✔
474
                                                testAttrName, getParentXPath(testContext)));
4✔
475

476
                    if (testAttr != null) {
4✔
477
                        testContext.navigateToAttribute(testAttrName);
4✔
478
                        try {
479
                            chain = chain.andThen(new DeferredComparison() {
4✔
480
                                    @Override
481
                                    public ComparisonState apply() {
482
                                        return compareNodes(controlAttr, controlContext,
4✔
483
                                                            testAttr, testContext);
4✔
484
                                    }
485
                                });
486
                            foundTestAttributes.add(testAttr);
4✔
487
                        } finally {
488
                            testContext.navigateToParent();
4✔
489
                        }
490
                    }
491
                } finally {
492
                    controlContext.navigateToParent();
4✔
493
                }
494
            }
4✔
495
            return chain.andThen(new ControlAttributePresentComparer(control,
4✔
496
                                                                     controlContext,
497
                                                                     test, testContext,
498
                                                                     testAttributes,
499
                                                                     foundTestAttributes));
500
        }
501
    }
502

503
    private class ControlAttributePresentComparer implements DeferredComparison {
504

505
        private final Set<Attr> foundTestAttributes;
506
        private final Element control, test;
507
        private final XPathContext controlContext, testContext;
508
        private final Attributes testAttributes;
509

510
        private ControlAttributePresentComparer(Element control,
511
                                                XPathContext controlContext,
512
                                                Element test,
513
                                                XPathContext testContext,
514
                                                Attributes testAttributes,
515
                                                Set<Attr> foundTestAttributes) {
4✔
516
            this.control = control;
4✔
517
            this.controlContext = controlContext;
4✔
518
            this.test = test;
4✔
519
            this.testContext = testContext;
4✔
520
            this.testAttributes = testAttributes;
4✔
521
            this.foundTestAttributes = foundTestAttributes;
4✔
522
        }
4✔
523

524
        @Override
525
        public ComparisonState apply() {
526
            ComparisonState chain = new OngoingComparisonState();
4✔
527
            for (Attr testAttr : testAttributes.remainingAttributes) {
4✔
528
                if (!foundTestAttributes.contains(testAttr)) {
4✔
529
                    QName testAttrName = Nodes.getQName(testAttr);
4✔
530
                    testContext.navigateToAttribute(testAttrName);
4✔
531
                    try {
532
                        chain =
4✔
533
                            chain.andThen(new Comparison(ComparisonType.ATTR_NAME_LOOKUP,
4✔
534
                                                         control,
535
                                                         getXPath(controlContext),
4✔
536
                                                         null, getParentXPath(controlContext),
4✔
537
                                                         test, getXPath(testContext),
4✔
538
                                                         testAttrName, getParentXPath(testContext)));
4✔
539
                    } finally {
540
                        testContext.navigateToParent();
4✔
541
                    }
542
                }
543
            }
4✔
544
            return chain;
4✔
545
        }
546

547
    }
548

549
    /**
550
     * Compares properties of a processing instruction.
551
     */
552
    private ComparisonState compareProcessingInstructions(ProcessingInstruction control,
553
                                                          XPathContext controlContext,
554
                                                          ProcessingInstruction test,
555
                                                          XPathContext testContext) {
556
        return compare(new Comparison(ComparisonType.PROCESSING_INSTRUCTION_TARGET,
4✔
557
                                      control, getXPath(controlContext),
4✔
558
                                      control.getTarget(), getParentXPath(controlContext),
4✔
559
                                      test, getXPath(testContext),
4✔
560
                                      test.getTarget(), getParentXPath(testContext)))
4✔
561
            .andThen(new Comparison(ComparisonType.PROCESSING_INSTRUCTION_DATA,
4✔
562
                                    control, getXPath(controlContext),
4✔
563
                                    control.getData(), getParentXPath(controlContext),
4✔
564
                                    test, getXPath(testContext),
4✔
565
                                    test.getData(), getParentXPath(testContext)));
4✔
566
    }
567

568
    /**
569
     * Matches nodes of two node lists and invokes compareNode on each pair.
570
     *
571
     * <p>Also performs CHILD_LOOKUP comparisons for each node that
572
     * couldn't be matched to one of the "other" list.</p>
573
     */
574
    private ComparisonState compareNodeLists(Iterable<Node> allControlChildren,
575
                                             Iterable<Node> controlSeq,
576
                                             final XPathContext controlContext,
577
                                             Iterable<Node> allTestChildren,
578
                                             Iterable<Node> testSeq,
579
                                             final XPathContext testContext) {
580
        ComparisonState chain = new OngoingComparisonState();
4✔
581

582
        Iterable<Map.Entry<Node, Node>> matches =
4✔
583
            getNodeMatcher().match(controlSeq, testSeq);
4✔
584
        List<Node> controlList = Linqy.asList(controlSeq);
4✔
585
        List<Node> testList = Linqy.asList(testSeq);
4✔
586

587
        final Map<Node, Integer> controlListForXpathIndex = index(allControlChildren);
4✔
588
        final Map<Node, Integer> testListForXpathIndex = index(allTestChildren);
4✔
589
        final Map<Node, Integer> controlListIndex = index(controlList);
4✔
590
        final Map<Node, Integer> testListIndex = index(testList);
4✔
591

592
        Set<Node> seen = new HashSet<Node>();
4✔
593
        for (Map.Entry<Node, Node> pair : matches) {
4✔
594
            final Node control = pair.getKey();
4✔
595
            seen.add(control);
4✔
596
            final Node test = pair.getValue();
4✔
597
            seen.add(test);
4✔
598
            Integer controlIndexForXpath = controlListForXpathIndex.get(control);
4✔
599
            Integer testIndexForXpath = testListForXpathIndex.get(test);
4✔
600
            Integer controlIndex = controlListIndex.get(control);
4✔
601
            Integer testIndex = testListIndex.get(test);
4✔
602
            if (controlIndexForXpath == null || testIndexForXpath == null
4✔
603
                || controlIndex == null || testIndex == null) {
604
                throw new NullPointerException("failed to look up index for pair " + pair);
×
605
            }
606

607
            controlContext.navigateToChild(controlIndexForXpath);
4✔
608
            testContext.navigateToChild(testIndexForXpath);
4✔
609
            try {
610
                chain =
4✔
611
                    chain.andThen(new Comparison(ComparisonType.CHILD_NODELIST_SEQUENCE,
4✔
612
                                                 control, getXPath(controlContext),
4✔
613
                                                 Integer.valueOf(controlIndex), getParentXPath(controlContext),
4✔
614
                                                 test, getXPath(testContext),
4✔
615
                                                 Integer.valueOf(testIndex), getParentXPath(testContext)))
4✔
616
                    .andThen(new DeferredComparison() {
4✔
617
                            @Override
618
                            public ComparisonState apply() {
619
                                return compareNodes(control, controlContext,
4✔
620
                                                    test, testContext);
621
                            }
622
                        });
623
            } finally {
624
                testContext.navigateToParent();
4✔
625
                controlContext.navigateToParent();
4✔
626
            }
627
        }
4✔
628

629
        return chain.andThen(new UnmatchedControlNodes(controlListForXpathIndex, controlList,
4✔
630
                controlContext, seen, testContext))
631
            .andThen(new UnmatchedTestNodes(testListForXpathIndex, testList,
4✔
632
                testContext, seen, controlContext));
633
    }
634

635
    private class UnmatchedControlNodes implements DeferredComparison {
636
        private final Map<Node, Integer> controlListForXpathIndex;
637
        private final List<Node> controlList;
638
        private final XPathContext controlContext;
639
        private final Set<Node> seen;
640
        private final XPathContext testContext;
641

642
        private UnmatchedControlNodes(Map<Node, Integer> controlListForXpathIndex,
643
                                      List<Node> controlList,
644
                                      XPathContext controlContext,
645
                                      Set<Node> seen, XPathContext testContext) {
4✔
646
            this.controlListForXpathIndex = controlListForXpathIndex;
4✔
647
            this.controlList = controlList;
4✔
648
            this.controlContext = controlContext;
4✔
649
            this.seen = seen;
4✔
650
            this.testContext = testContext;
4✔
651
        }
4✔
652

653
        @Override
654
        public ComparisonState apply() {
655
            ComparisonState chain = new OngoingComparisonState();
4✔
656
            final int controlSize = controlList.size();
4✔
657
            for (int i = 0; i < controlSize; i++) {
4✔
658
                if (!seen.contains(controlList.get(i))) {
4✔
659
                    controlContext.navigateToChild(controlListForXpathIndex.get(controlList.get(i)));
4✔
660
                    try {
661
                        chain =
4✔
662
                            chain.andThen(new Comparison(ComparisonType.CHILD_LOOKUP,
4✔
663
                                                         controlList.get(i),
4✔
664
                                                         getXPath(controlContext),
4✔
665
                                                         Nodes.getQName(controlList.get(i)), getParentXPath(controlContext),
4✔
666
                                                         null, null, null, getXPath(testContext)));
4✔
667
                    } finally {
668
                        controlContext.navigateToParent();
4✔
669
                    }
670
                }
671
            }
672
            return chain;
4✔
673
        }
674
    }
675

676
    private class UnmatchedTestNodes implements DeferredComparison {
677
        private final Map<Node, Integer> testListForXpathIndex;
678
        private final List<Node> testList;
679
        private final XPathContext testContext;
680
        private final Set<Node> seen;
681
        private final XPathContext controlContext;
682

683
        private UnmatchedTestNodes(Map<Node, Integer> testListForXpathIndex,
684
                                   List<Node> testList, XPathContext testContext,
685
                                   Set<Node> seen, XPathContext controlContext) {
4✔
686
            this.testListForXpathIndex = testListForXpathIndex;
4✔
687
            this.testList = testList;
4✔
688
            this.testContext = testContext;
4✔
689
            this.seen = seen;
4✔
690
            this.controlContext = controlContext;
4✔
691
        }
4✔
692

693
        @Override
694
        public ComparisonState apply() {
695
            ComparisonState chain = new OngoingComparisonState();
4✔
696
            final int testSize = testList.size();
4✔
697
            for (int i = 0; i < testSize; i++) {
4✔
698
                if (!seen.contains(testList.get(i))) {
4✔
699
                    testContext.navigateToChild(testListForXpathIndex.get(testList.get(i)));
4✔
700
                    try {
701
                        chain =
4✔
702
                            chain.andThen(new Comparison(ComparisonType.CHILD_LOOKUP,
4✔
703
                                                         null, null, null, getXPath(controlContext),
4✔
704
                                                         testList.get(i),
4✔
705
                                                         getXPath(testContext),
4✔
706
                                                         Nodes.getQName(testList.get(i)), getParentXPath(testContext)));
4✔
707
                    } finally {
708
                        testContext.navigateToParent();
4✔
709
                    }
710
                }
711
            }
712
            return chain;
4✔
713
        }
714
    }
715

716
    /**
717
     * Compares xsi:type attribute values
718
     */
719
    private ComparisonState compareXsiType(Attr controlAttr,
720
                                           XPathContext controlContext,
721
                                           Attr testAttr,
722
                                           XPathContext testContext) {
723
        boolean mustChangeControlContext = controlAttr != null;
4✔
724
        boolean mustChangeTestContext = testAttr != null;
4✔
725
        if (!mustChangeControlContext && !mustChangeTestContext) {
4✔
726
            return new OngoingComparisonState();
4✔
727
        }
728
        boolean attributePresentOnBothSides = mustChangeControlContext
4✔
729
            && mustChangeTestContext;
730

731
        try {
732
            QName controlAttrName = null;
4✔
733
            if (mustChangeControlContext) {
4✔
734
                controlAttrName = Nodes.getQName(controlAttr);
4✔
735
                controlContext.addAttribute(controlAttrName);
4✔
736
                controlContext.navigateToAttribute(controlAttrName);
4✔
737
            }
738
            QName testAttrName = null;
4✔
739
            if (mustChangeTestContext) {
4✔
740
                testAttrName = Nodes.getQName(testAttr);
4✔
741
                testContext.addAttribute(testAttrName);
4✔
742
                testContext.navigateToAttribute(testAttrName);
4✔
743
            }
744
            return
4✔
745
                compare(new Comparison(ComparisonType.ATTR_NAME_LOOKUP,
4✔
746
                                       controlAttr, getXPath(controlContext),
4✔
747
                                       controlAttrName, getParentXPath(controlContext),
4✔
748
                                       testAttr, getXPath(testContext),
4✔
749
                                       testAttrName, getParentXPath(testContext)))
4✔
750
                .andIfTrueThen(attributePresentOnBothSides,
4✔
751
                               compareAttributeExplicitness(controlAttr, controlContext,
4✔
752
                                                            testAttr, testContext))
753
                .andIfTrueThen(attributePresentOnBothSides,
4✔
754
                               new Comparison(ComparisonType.ATTR_VALUE,
755
                                              controlAttr, getXPath(controlContext),
4✔
756
                                              valueAsQName(controlAttr), getParentXPath(controlContext),
4✔
757
                                              testAttr, getXPath(testContext),
4✔
758
                                              valueAsQName(testAttr), getParentXPath(testContext)));
4✔
759
        } finally {
760
            if (mustChangeControlContext) {
4✔
761
                controlContext.navigateToParent();
4✔
762
            }
763
            if (mustChangeTestContext) {
4✔
764
                testContext.navigateToParent();
4✔
765
            }
766
        }
767
    }
768

769
    /**
770
     * Compares properties of an attribute.
771
     */
772
    private ComparisonState compareAttributes(Attr control,
773
                                              XPathContext controlContext,
774
                                              Attr test,
775
                                              XPathContext testContext) {
776
        return compareAttributeExplicitness(control, controlContext, test,
4✔
777
                                            testContext).apply()
4✔
778
            .andThen(new Comparison(ComparisonType.ATTR_VALUE,
4✔
779
                                    control, getXPath(controlContext),
4✔
780
                                    control.getValue(), getParentXPath(controlContext),
4✔
781
                                    test, getXPath(testContext),
4✔
782
                                    test.getValue(), getParentXPath(testContext)));
4✔
783
    }
784

785
    /**
786
     * Compares whether two attributes are specified explicitly.
787
     */
788
    private DeferredComparison compareAttributeExplicitness(final Attr control,
789
                                                            final XPathContext controlContext,
790
                                                            final Attr test,
791
                                                            final XPathContext testContext) {
792
        return new DeferredComparison() {
4✔
793
            @Override
794
            public ComparisonState apply() {
795
                return compare(new Comparison(ComparisonType.ATTR_VALUE_EXPLICITLY_SPECIFIED,
4✔
796
                                              control, getXPath(controlContext),
4✔
797
                                              control.getSpecified(), getParentXPath(controlContext),
4✔
798
                                              test, getXPath(testContext),
4✔
799
                                              test.getSpecified(), getParentXPath(testContext)));
4✔
800
            }
801
        };
802
    }
803

804
    /**
805
     * Separates XML namespace related attributes from "normal" attributes.xb
806
     */
807
    private Attributes splitAttributes(final NamedNodeMap map) {
808
        Attr sLoc = (Attr) map.getNamedItemNS(XMLConstants
4✔
809
                                              .W3C_XML_SCHEMA_INSTANCE_NS_URI,
810
                                              "schemaLocation");
811
        Attr nNsLoc = (Attr) map.getNamedItemNS(XMLConstants
4✔
812
                                                .W3C_XML_SCHEMA_INSTANCE_NS_URI,
813
                                                "noNamespaceSchemaLocation");
814
        Attr type = (Attr) map.getNamedItemNS(XMLConstants
4✔
815
                                                .W3C_XML_SCHEMA_INSTANCE_NS_URI,
816
                                                "type");
817
        List<Attr> rest = new LinkedList<Attr>();
4✔
818
        final int len = map.getLength();
4✔
819
        for (int i = 0; i < len; i++) {
4✔
820
            Attr a = (Attr) map.item(i);
4✔
821
            if (!XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(a.getNamespaceURI())
4✔
822
                && a != sLoc && a != nNsLoc && a != type
823
                && getAttributeFilter().test(a)) {
4✔
824
                rest.add(a);
4✔
825
            }
826
        }
827
        return new Attributes(sLoc, nNsLoc, type, rest);
4✔
828
    }
829

830
    private static QName valueAsQName(Attr attribute) {
831
        if (attribute == null) {
4✔
832
            return null;
4✔
833
        }
834
        // split QName into prefix and local name
835
        String[] pieces = attribute.getValue().split(":");
4✔
836
        if (pieces.length < 2) {
4✔
837
            // unprefixed name
838
            pieces = new String[] { null, pieces[0] };
4✔
839
        } else if (pieces.length > 2) {
4✔
840
            // actually, this is not a valid QName - be lenient
841
            pieces = new String[] {
×
842
                pieces[0],
843
                attribute.getValue().substring(pieces[0].length() + 1)
×
844
            };
845
        }
846
        if ("".equals(pieces[0])) {
4✔
847
            pieces[0] = null;
×
848
        }
849
        return new QName(attribute.lookupNamespaceURI(pieces[0]), pieces[1]);
4✔
850
    }
851

852
    private static class Attributes {
853
        private final Attr schemaLocation;
854
        private final Attr noNamespaceSchemaLocation;
855
        private final Attr type;
856
        private final List<Attr> remainingAttributes;
857
        private Attributes(Attr schemaLocation, Attr noNamespaceSchemaLocation,
858
                           Attr type, List<Attr> remainingAttributes) {
4✔
859
            this.schemaLocation = schemaLocation;
4✔
860
            this.noNamespaceSchemaLocation = noNamespaceSchemaLocation;
4✔
861
            this.type = type;
4✔
862
            this.remainingAttributes = remainingAttributes;
4✔
863
        }
4✔
864
    }
865

866
    /**
867
     * Find the attribute with the same namespace and local name as a
868
     * given attribute in a list of attributes.
869
     */
870
    private static Attr findMatchingAttr(final List<Attr> attrs,
871
                                         final Attr attrToMatch) {
872
        final boolean hasNs = attrToMatch.getNamespaceURI() != null;
4✔
873
        final String nsToMatch = attrToMatch.getNamespaceURI();
4✔
874
        final String nameToMatch = hasNs ? attrToMatch.getLocalName()
4✔
875
            : attrToMatch.getName();
4✔
876
        for (Attr a : attrs) {
4✔
877
            if (((!hasNs && a.getNamespaceURI() == null)
4✔
878
                 ||
879
                 (hasNs && nsToMatch.equals(a.getNamespaceURI())))
4✔
880
                &&
881
                ((hasNs && nameToMatch.equals(a.getLocalName()))
4✔
882
                 ||
883
                 (!hasNs && nameToMatch.equals(a.getName())))
4✔
884
                ) {
885
                return a;
4✔
886
            }
887
        }
4✔
888
        return null;
4✔
889
    }
890

891
    private static Map<Node, Integer> index(final Iterable<Node> nodes) {
892
        Map<Node, Integer> indices = new HashMap<Node, Integer>();
4✔
893
        int idx = 0;
4✔
894
        for (Node n : nodes) {
4✔
895
            indices.put(n, idx++);
4✔
896
        }
4✔
897
        return Collections.unmodifiableMap(indices);
4✔
898
    }
899
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc