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