1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.betwixt.xmlunit;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.StringReader;
23 import java.io.StringWriter;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.Comparator;
27 import java.util.Iterator;
28 import java.util.List;
29
30 import javax.xml.parsers.DocumentBuilder;
31 import javax.xml.parsers.DocumentBuilderFactory;
32 import javax.xml.parsers.ParserConfigurationException;
33
34 import junit.framework.AssertionFailedError;
35 import junit.framework.TestCase;
36
37 import org.apache.xerces.parsers.SAXParser;
38 import org.w3c.dom.Attr;
39 import org.w3c.dom.DOMException;
40 import org.w3c.dom.Document;
41 import org.w3c.dom.NamedNodeMap;
42 import org.w3c.dom.Node;
43 import org.w3c.dom.NodeList;
44 import org.xml.sax.InputSource;
45 import org.xml.sax.SAXException;
46 import org.xml.sax.SAXParseException;
47 import org.xml.sax.helpers.DefaultHandler;
48
49
50
51
52
53
54
55
56 public class XmlTestCase extends TestCase {
57
58 private static final String NAMESPACES_FEATURE_ID
59 = "http://xml.org/sax/features/namespaces";
60
61 private static final String NAMESPACE_PREFIXES_FEATURE_ID
62 = "http://xml.org/sax/features/namespace-prefixes";
63
64 private static final String VALIDATION_FEATURE_ID
65 = "http://xml.org/sax/features/validation";
66
67 private static final String SCHEMA_VALIDATION_FEATURE_ID
68 = "http://apache.org/xml/features/validation/schema";
69
70 private static final String SCHEMA_FULL_CHECKING_FEATURE_ID
71 = "http://apache.org/xml/features/validation/schema-full-checking";
72
73 private static final String DYNAMIC_VALIDATION_FEATURE_ID
74 = "http://apache.org/xml/features/validation/dynamic";
75
76 private static final String NONAMESPACE_SCHEMA_LOCATION_PROPERTY_ID
77 = "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation";
78
79
80 protected static boolean debug = false;
81
82 DocumentBuilderFactory domFactory;
83
84
85 public XmlTestCase(String testName) {
86 super(testName);
87 }
88
89
90 public void xmlAssertIsomorphicContent(
91 org.w3c.dom.Document documentOne,
92 org.w3c.dom.Document documentTwo)
93 throws
94 AssertionFailedError {
95 log("Testing documents:" + documentOne.getDocumentElement().getNodeName()
96 + " and " + documentTwo.getDocumentElement().getNodeName());
97 xmlAssertIsomorphicContent(documentOne, documentTwo, false);
98 }
99
100 public void xmlAssertIsomorphicContent(
101 org.w3c.dom.Document documentOne,
102 org.w3c.dom.Document documentTwo,
103 boolean orderIndependent)
104 throws
105 AssertionFailedError {
106 xmlAssertIsomorphicContent(null, documentOne, documentTwo, orderIndependent);
107 }
108
109 public void xmlAssertIsomorphicContent(
110 String message,
111 org.w3c.dom.Document documentOne,
112 org.w3c.dom.Document documentTwo)
113 throws
114 AssertionFailedError {
115
116 xmlAssertIsomorphicContent(message, documentOne, documentTwo, false);
117 }
118
119 public void xmlAssertIsomorphicContent(
120 String message,
121 org.w3c.dom.Document documentOne,
122 org.w3c.dom.Document documentTwo,
123 boolean orderIndependent)
124 throws
125 AssertionFailedError
126 {
127
128
129 xmlAssertIsomorphic(
130 message,
131 documentOne.getDocumentElement(),
132 documentTwo.getDocumentElement(),
133 orderIndependent);
134 }
135
136
137 public void xmlAssertIsomorphic(
138 org.w3c.dom.Node rootOne,
139 org.w3c.dom.Node rootTwo)
140 throws
141 AssertionFailedError {
142 xmlAssertIsomorphic(rootOne, rootTwo, false);
143 }
144
145 public void xmlAssertIsomorphic(
146 org.w3c.dom.Node rootOne,
147 org.w3c.dom.Node rootTwo,
148 boolean orderIndependent)
149 throws
150 AssertionFailedError
151 {
152 xmlAssertIsomorphic(null, rootOne, rootTwo, orderIndependent);
153 }
154
155 public void xmlAssertIsomorphic(
156 String message,
157 org.w3c.dom.Node rootOne,
158 org.w3c.dom.Node rootTwo) {
159
160 xmlAssertIsomorphic(message, rootOne, rootTwo, false);
161
162 }
163
164 public void xmlAssertIsomorphic(
165 String message,
166 org.w3c.dom.Node rootOne,
167 org.w3c.dom.Node rootTwo,
168 boolean orderIndependent)
169 throws
170 AssertionFailedError
171 {
172
173 rootOne.normalize();
174 rootTwo.normalize();
175
176 testIsomorphic(message, rootOne, rootTwo, orderIndependent);
177 }
178
179
180 private void testIsomorphic(
181 String message,
182 org.w3c.dom.Node nodeOne,
183 org.w3c.dom.Node nodeTwo)
184 throws
185 AssertionFailedError {
186
187 testIsomorphic(message, nodeOne, nodeTwo, false);
188 }
189
190
191 private void testIsomorphic(
192 String message,
193 org.w3c.dom.Node nodeOne,
194 org.w3c.dom.Node nodeTwo,
195 boolean orderIndependent)
196 throws
197 AssertionFailedError
198 {
199 try {
200 if (debug) {
201 log(
202 "node 1 name=" + nodeOne.getNodeName()
203 + " qname=" + nodeOne.getLocalName());
204 log(
205 "node 2 name=" + nodeTwo.getNodeName()
206 + " qname=" + nodeTwo.getLocalName());
207 }
208
209
210 log("Comparing node properties");
211 assertEquals(
212 (null == message ? "(Unequal node types)" : message + "(Unequal node types)"),
213 nodeOne.getNodeType(),
214 nodeTwo.getNodeType());
215 assertEquals(
216 (null == message ? "(Unequal node names)" : message + "(Unequal node names)"),
217 nodeOne.getNodeName(),
218 nodeTwo.getNodeName());
219 assertEquals(
220 (null == message ? "(Unequal node values)" : message + "(Unequal node values)"),
221 trim(nodeOne.getNodeValue()),
222 trim(nodeTwo.getNodeValue()));
223 assertEquals(
224 (null == message ? "(Unequal local names)" : message + "(Unequal local names)"),
225 nodeOne.getLocalName(),
226 nodeTwo.getLocalName());
227 assertEquals(
228 (null == message ? "(Unequal namespace)" : message + "(Unequal namespace)"),
229 nodeOne.getNamespaceURI(),
230 nodeTwo.getNamespaceURI());
231
232
233
234 log("Comparing attributes");
235
236 assertEquals(
237 (null == message ? "(Unequal attributes)" : message + "(Unequal attributes)"),
238 nodeOne.hasAttributes(),
239 nodeTwo.hasAttributes());
240 if (nodeOne.hasAttributes()) {
241
242
243
244
245
246 NamedNodeMap attributesOne = nodeOne.getAttributes();
247 NamedNodeMap attributesTwo = nodeTwo.getAttributes();
248
249 assertEquals(
250 (null == message ? "(Unequal attributes)" : message + "(Unequal attributes)"),
251 attributesOne.getLength(),
252 attributesTwo.getLength());
253
254 for (int i=0, size=attributesOne.getLength(); i<size; i++) {
255 Attr attributeOne = (Attr) attributesOne.item(i);
256 Attr attributeTwo = (Attr) attributesTwo.getNamedItemNS(
257 attributeOne.getNamespaceURI(),
258 attributeOne.getLocalName());
259 if (attributeTwo == null) {
260 attributeTwo = (Attr) attributesTwo.getNamedItem(attributeOne.getName());
261 }
262
263
264 if (attributeTwo == null) {
265 String diagnosis = "[Missing attribute (" + attributeOne.getName() + ")]";
266 fail((null == message ? diagnosis : message + diagnosis));
267 }
268
269
270 assertEquals(
271 (null == message ? "(Unequal attribute values)" : message + "(Unequal attribute values)"),
272 attributeOne.getValue(),
273 attributeTwo.getValue());
274 }
275 }
276
277
278
279 log("Comparing children");
280
281
282 List listOne = sanitize(nodeOne.getChildNodes());
283 List listTwo = sanitize(nodeTwo.getChildNodes());
284
285 if (orderIndependent) {
286 log("[Order Independent]");
287 Comparator nodeByName = new NodeByNameComparator();
288 Collections.sort(listOne, nodeByName);
289 Collections.sort(listTwo, nodeByName);
290 }
291
292 Iterator it = listOne.iterator();
293 Iterator iter2 = listTwo.iterator();
294 while (it.hasNext() & iter2.hasNext()) {
295 Node nextOne = ((Node)it.next());
296 Node nextTwo = ((Node)iter2.next());
297 log(nextOne.getNodeName() + ":" + nextOne.getNodeValue());
298 log(nextTwo.getNodeName() + ":" + nextTwo.getNodeValue());
299 }
300
301 assertEquals(
302 (null == message ? "(Unequal child nodes@" + nodeOne.getNodeName() +")":
303 message + "(Unequal child nodes @" + nodeOne.getNodeName() +")"),
304 listOne.size(),
305 listTwo.size());
306
307 it = listOne.iterator();
308 iter2 = listTwo.iterator();
309 while (it.hasNext() & iter2.hasNext()) {
310 Node nextOne = ((Node)it.next());
311 Node nextTwo = ((Node)iter2.next());
312 log(nextOne.getNodeName() + " vs " + nextTwo.getNodeName());
313 testIsomorphic(message, nextOne, nextTwo, orderIndependent);
314
315 }
316
317 } catch (DOMException ex) {
318 fail((null == message ? "" : message + " ") + "DOM exception" + ex.toString());
319 }
320 }
321
322
323 protected DocumentBuilder createDocumentBuilder() {
324 try {
325
326 return getDomFactory().newDocumentBuilder();
327
328 } catch (ParserConfigurationException e) {
329 fail("Cannot create DOM builder: " + e.toString());
330
331 }
332
333 return null;
334 }
335
336 protected DocumentBuilderFactory getDomFactory() {
337
338 if (domFactory == null) {
339 domFactory = DocumentBuilderFactory.newInstance();
340 }
341
342 return domFactory;
343 }
344
345 protected Document parseString(StringWriter writer) {
346 try {
347
348 return createDocumentBuilder().parse(new InputSource(new StringReader(writer.getBuffer().toString())));
349
350 } catch (SAXException e) {
351 fail("Cannot create parse string: " + e.toString());
352
353 } catch (IOException e) {
354 fail("Cannot create parse string: " + e.toString());
355
356 }
357
358 return null;
359 }
360
361 protected Document parseString(String string) {
362 try {
363
364 return createDocumentBuilder().parse(new InputSource(new StringReader(string)));
365
366 } catch (SAXException e) {
367 fail("Cannot create parse string: " + e.toString());
368
369 } catch (IOException e) {
370 fail("Cannot create parse string: " + e.toString());
371
372 }
373
374 return null;
375 }
376
377
378 protected Document parseFile(String path) {
379 try {
380
381 return createDocumentBuilder().parse(new File(path));
382
383 } catch (SAXException e) {
384 fail("Cannot create parse file: " + e.toString());
385
386 } catch (IOException e) {
387 fail("Cannot create parse file: " + e.toString());
388
389 }
390
391 return null;
392 }
393
394 private void log(String message)
395 {
396 if (debug) {
397 System.out.println("[XmlTestCase]" + message);
398 }
399 }
400
401
402 private void log(String message, Exception e)
403 {
404 if (debug) {
405 System.out.println("[XmlTestCase]" + message);
406 e.printStackTrace();
407 }
408 }
409
410 private String trim(String trimThis)
411 {
412 if (trimThis == null) {
413 return trimThis;
414 }
415
416 return trimThis.trim();
417 }
418
419 private List sanitize(NodeList nodes) {
420 ArrayList list = new ArrayList();
421
422 for (int i=0, size=nodes.getLength(); i<size; i++) {
423 if ( nodes.item(i).getNodeType() == Node.TEXT_NODE ) {
424 if ( !( nodes.item(i).getNodeValue() == null ||
425 nodes.item(i).getNodeValue().trim().length() == 0 )) {
426 list.add(nodes.item(i));
427 } else {
428 log("Ignoring text node:" + nodes.item(i).getNodeValue());
429 }
430 } else {
431 list.add(nodes.item(i));
432 }
433 }
434 return list;
435 }
436
437 private class NodeByNameComparator implements Comparator {
438 public int compare(Object objOne, Object objTwo) {
439 String nameOne = ((Node) objOne).getNodeName();
440 String nameTwo = ((Node) objTwo).getNodeName();
441
442 if (nameOne == null) {
443 if (nameTwo == null) {
444 return 0;
445 }
446 return -1;
447 }
448
449 if (nameTwo == null) {
450 return 1;
451 }
452
453 return nameOne.compareTo(nameTwo);
454 }
455 }
456
457
458 public void validateWithSchema(InputSource documentSource, final InputSource schemaSource)
459 throws ParserConfigurationException, SAXException, IOException
460 {
461 class XMLUnitHandler extends DefaultHandler {
462 ArrayList errors = new ArrayList();
463 ArrayList warnings = new ArrayList();
464 InputSource schemaSource;
465
466 XMLUnitHandler(InputSource schemaSource) {
467 this.schemaSource = schemaSource;
468 schemaSource.setSystemId("schema.xsd");
469 }
470
471 public InputSource resolveEntity(String publicId, String systemId) {
472 return schemaSource;
473 }
474
475 public void error(SAXParseException ex) {
476 errors.add(ex);
477 }
478
479 public void warning(SAXParseException ex) {
480 warnings.add(ex);
481 }
482
483 void reportErrors() throws SAXException {
484 if (errors.size() > 0) {
485 throw (SAXException) errors.get(0);
486 }
487 }
488
489 }
490
491
492
493
494
495
496 SAXParser parser = new SAXParser();
497
498
499 parser.setFeature(NAMESPACES_FEATURE_ID, true);
500 parser.setFeature(NAMESPACE_PREFIXES_FEATURE_ID, false);
501 parser.setFeature(VALIDATION_FEATURE_ID, true);
502 parser.setFeature(SCHEMA_VALIDATION_FEATURE_ID, true);
503 parser.setFeature(SCHEMA_FULL_CHECKING_FEATURE_ID, false);
504 parser.setFeature(DYNAMIC_VALIDATION_FEATURE_ID, false);
505
506
507 parser.setProperty(NONAMESPACE_SCHEMA_LOCATION_PROPERTY_ID, "schema.xsd");
508
509 XMLUnitHandler handler = new XMLUnitHandler(schemaSource);
510
511
512 parser.setContentHandler(handler);
513 parser.setErrorHandler(handler);
514 parser.setEntityResolver(handler);
515
516
517 parser.parse(documentSource);
518 handler.reportErrors();
519 }
520
521 public boolean isValid(InputSource documentSource, InputSource schemaSource)
522 throws ParserConfigurationException, IOException
523 {
524 boolean result = false;
525 try
526 {
527 validateWithSchema(documentSource, schemaSource);
528 result = true;
529 }
530 catch (SAXException se)
531 {
532 log("Validation failed.", se);
533 }
534
535 return result;
536 }
537
538
539 public void xmlAssertIsValid(String document, String schema)
540 throws ParserConfigurationException, IOException
541 {
542 xmlAssertIsValid(new InputSource(new StringReader(document)), new InputSource(new StringReader(schema)));
543 }
544
545 public void xmlAssertIsValid(InputSource documentSource, InputSource schemaSource)
546 throws ParserConfigurationException, IOException
547 {
548 try
549 {
550 validateWithSchema(documentSource, schemaSource);
551 }
552 catch (SAXException se)
553 {
554 se.printStackTrace();
555 fail("Validation failure: " + se.getMessage());
556 }
557 }
558 }
559