001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.betwixt.xmlunit;
019:
020: import java.io.File;
021: import java.io.IOException;
022: import java.io.StringReader;
023: import java.io.StringWriter;
024: import java.util.ArrayList;
025: import java.util.Collections;
026: import java.util.Comparator;
027: import java.util.Iterator;
028: import java.util.List;
029:
030: import javax.xml.parsers.DocumentBuilder;
031: import javax.xml.parsers.DocumentBuilderFactory;
032: import javax.xml.parsers.ParserConfigurationException;
033:
034: import junit.framework.AssertionFailedError;
035: import junit.framework.TestCase;
036:
037: import org.apache.xerces.parsers.SAXParser;
038: import org.w3c.dom.Attr;
039: import org.w3c.dom.DOMException;
040: import org.w3c.dom.Document;
041: import org.w3c.dom.NamedNodeMap;
042: import org.w3c.dom.Node;
043: import org.w3c.dom.NodeList;
044: import org.xml.sax.InputSource;
045: import org.xml.sax.SAXException;
046: import org.xml.sax.SAXParseException;
047: import org.xml.sax.helpers.DefaultHandler;
048:
049: /**
050: * Provides xml test utilities.
051: * Hopefully, these might be moved into [xmlunit] sometime.
052: *
053: * @author Robert Burrell Donkin
054: * @author Khaled Noaman, IBM (some portions derived from test code originally contributed to the Apache Xerces project)
055: */
056: public class XmlTestCase extends TestCase {
057:
058: private static final String NAMESPACES_FEATURE_ID = "http://xml.org/sax/features/namespaces";
059:
060: private static final String NAMESPACE_PREFIXES_FEATURE_ID = "http://xml.org/sax/features/namespace-prefixes";
061:
062: private static final String VALIDATION_FEATURE_ID = "http://xml.org/sax/features/validation";
063:
064: private static final String SCHEMA_VALIDATION_FEATURE_ID = "http://apache.org/xml/features/validation/schema";
065:
066: private static final String SCHEMA_FULL_CHECKING_FEATURE_ID = "http://apache.org/xml/features/validation/schema-full-checking";
067:
068: private static final String DYNAMIC_VALIDATION_FEATURE_ID = "http://apache.org/xml/features/validation/dynamic";
069:
070: private static final String NONAMESPACE_SCHEMA_LOCATION_PROPERTY_ID = "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation";
071:
072: protected static boolean debug = false;
073:
074: DocumentBuilderFactory domFactory;
075:
076: public XmlTestCase(String testName) {
077: super (testName);
078: }
079:
080: public void xmlAssertIsomorphicContent(
081: org.w3c.dom.Document documentOne,
082: org.w3c.dom.Document documentTwo)
083: throws AssertionFailedError {
084: log("Testing documents:"
085: + documentOne.getDocumentElement().getNodeName()
086: + " and "
087: + documentTwo.getDocumentElement().getNodeName());
088: xmlAssertIsomorphicContent(documentOne, documentTwo, false);
089: }
090:
091: public void xmlAssertIsomorphicContent(
092: org.w3c.dom.Document documentOne,
093: org.w3c.dom.Document documentTwo, boolean orderIndependent)
094: throws AssertionFailedError {
095: xmlAssertIsomorphicContent(null, documentOne, documentTwo,
096: orderIndependent);
097: }
098:
099: public void xmlAssertIsomorphicContent(String message,
100: org.w3c.dom.Document documentOne,
101: org.w3c.dom.Document documentTwo)
102: throws AssertionFailedError {
103:
104: xmlAssertIsomorphicContent(message, documentOne, documentTwo,
105: false);
106: }
107:
108: public void xmlAssertIsomorphicContent(String message,
109: org.w3c.dom.Document documentOne,
110: org.w3c.dom.Document documentTwo, boolean orderIndependent)
111: throws AssertionFailedError {
112: // two documents have isomorphic content iff their root elements
113: // are isomophic
114: xmlAssertIsomorphic(message, documentOne.getDocumentElement(),
115: documentTwo.getDocumentElement(), orderIndependent);
116: }
117:
118: public void xmlAssertIsomorphic(org.w3c.dom.Node rootOne,
119: org.w3c.dom.Node rootTwo) throws AssertionFailedError {
120: xmlAssertIsomorphic(rootOne, rootTwo, false);
121: }
122:
123: public void xmlAssertIsomorphic(org.w3c.dom.Node rootOne,
124: org.w3c.dom.Node rootTwo, boolean orderIndependent)
125: throws AssertionFailedError {
126: xmlAssertIsomorphic(null, rootOne, rootTwo, orderIndependent);
127: }
128:
129: public void xmlAssertIsomorphic(String message,
130: org.w3c.dom.Node rootOne, org.w3c.dom.Node rootTwo) {
131:
132: xmlAssertIsomorphic(message, rootOne, rootTwo, false);
133:
134: }
135:
136: public void xmlAssertIsomorphic(String message,
137: org.w3c.dom.Node rootOne, org.w3c.dom.Node rootTwo,
138: boolean orderIndependent) throws AssertionFailedError {
139: // first normalize the xml
140: rootOne.normalize();
141: rootTwo.normalize();
142: // going to use recursion so avoid normalizing each time
143: testIsomorphic(message, rootOne, rootTwo, orderIndependent);
144: }
145:
146: private void testIsomorphic(String message,
147: org.w3c.dom.Node nodeOne, org.w3c.dom.Node nodeTwo)
148: throws AssertionFailedError {
149:
150: testIsomorphic(message, nodeOne, nodeTwo, false);
151: }
152:
153: private void testIsomorphic(String message,
154: org.w3c.dom.Node nodeOne, org.w3c.dom.Node nodeTwo,
155: boolean orderIndependent) throws AssertionFailedError {
156: try {
157: if (debug) {
158: log("node 1 name=" + nodeOne.getNodeName() + " qname="
159: + nodeOne.getLocalName());
160: log("node 2 name=" + nodeTwo.getNodeName() + " qname="
161: + nodeTwo.getLocalName());
162: }
163:
164: // compare node properties
165: log("Comparing node properties");
166: assertEquals((null == message ? "(Unequal node types)"
167: : message + "(Unequal node types)"), nodeOne
168: .getNodeType(), nodeTwo.getNodeType());
169: assertEquals((null == message ? "(Unequal node names)"
170: : message + "(Unequal node names)"), nodeOne
171: .getNodeName(), nodeTwo.getNodeName());
172: assertEquals((null == message ? "(Unequal node values)"
173: : message + "(Unequal node values)"), trim(nodeOne
174: .getNodeValue()), trim(nodeTwo.getNodeValue()));
175: assertEquals((null == message ? "(Unequal local names)"
176: : message + "(Unequal local names)"), nodeOne
177: .getLocalName(), nodeTwo.getLocalName());
178: assertEquals((null == message ? "(Unequal namespace)"
179: : message + "(Unequal namespace)"), nodeOne
180: .getNamespaceURI(), nodeTwo.getNamespaceURI());
181:
182: // compare attributes
183: log("Comparing attributes");
184: // make sure both have them first
185: assertEquals((null == message ? "(Unequal attributes)"
186: : message + "(Unequal attributes)"), nodeOne
187: .hasAttributes(), nodeTwo.hasAttributes());
188: if (nodeOne.hasAttributes()) {
189: // do the actual comparison
190: // first we check the number of attributes are equal
191: // we then check that for every attribute of node one,
192: // a corresponding attribute exists in node two
193: // (this should be sufficient to prove equality)
194: NamedNodeMap attributesOne = nodeOne.getAttributes();
195: NamedNodeMap attributesTwo = nodeTwo.getAttributes();
196:
197: assertEquals((null == message ? "(Unequal attributes)"
198: : message + "(Unequal attributes)"),
199: attributesOne.getLength(), attributesTwo
200: .getLength());
201:
202: for (int i = 0, size = attributesOne.getLength(); i < size; i++) {
203: Attr attributeOne = (Attr) attributesOne.item(i);
204: Attr attributeTwo = (Attr) attributesTwo
205: .getNamedItemNS(attributeOne
206: .getNamespaceURI(), attributeOne
207: .getLocalName());
208: if (attributeTwo == null) {
209: attributeTwo = (Attr) attributesTwo
210: .getNamedItem(attributeOne.getName());
211: }
212:
213: // check attribute two exists
214: if (attributeTwo == null) {
215: String diagnosis = "[Missing attribute ("
216: + attributeOne.getName() + ")]";
217: fail((null == message ? diagnosis : message
218: + diagnosis));
219: }
220:
221: // now check attribute values
222: assertEquals(
223: (null == message ? "(Unequal attribute values)"
224: : message
225: + "(Unequal attribute values)"),
226: attributeOne.getValue(), attributeTwo
227: .getValue());
228: }
229: }
230:
231: // compare children
232: log("Comparing children");
233: // this time order is important
234: // so we can just go down the list and compare node-wise using recursion
235: List listOne = sanitize(nodeOne.getChildNodes());
236: List listTwo = sanitize(nodeTwo.getChildNodes());
237:
238: if (orderIndependent) {
239: log("[Order Independent]");
240: Comparator nodeByName = new NodeByNameComparator();
241: Collections.sort(listOne, nodeByName);
242: Collections.sort(listTwo, nodeByName);
243: }
244:
245: Iterator it = listOne.iterator();
246: Iterator iter2 = listTwo.iterator();
247: while (it.hasNext() & iter2.hasNext()) {
248: Node nextOne = ((Node) it.next());
249: Node nextTwo = ((Node) iter2.next());
250: log(nextOne.getNodeName() + ":"
251: + nextOne.getNodeValue());
252: log(nextTwo.getNodeName() + ":"
253: + nextTwo.getNodeValue());
254: }
255:
256: assertEquals((null == message ? "(Unequal child nodes@"
257: + nodeOne.getNodeName() + ")" : message
258: + "(Unequal child nodes @" + nodeOne.getNodeName()
259: + ")"), listOne.size(), listTwo.size());
260:
261: it = listOne.iterator();
262: iter2 = listTwo.iterator();
263: while (it.hasNext() & iter2.hasNext()) {
264: Node nextOne = ((Node) it.next());
265: Node nextTwo = ((Node) iter2.next());
266: log(nextOne.getNodeName() + " vs "
267: + nextTwo.getNodeName());
268: testIsomorphic(message, nextOne, nextTwo,
269: orderIndependent);
270:
271: }
272:
273: } catch (DOMException ex) {
274: fail((null == message ? "" : message + " ")
275: + "DOM exception" + ex.toString());
276: }
277: }
278:
279: protected DocumentBuilder createDocumentBuilder() {
280: try {
281:
282: return getDomFactory().newDocumentBuilder();
283:
284: } catch (ParserConfigurationException e) {
285: fail("Cannot create DOM builder: " + e.toString());
286:
287: }
288: // just to keep the compiler happy
289: return null;
290: }
291:
292: protected DocumentBuilderFactory getDomFactory() {
293: // lazy creation
294: if (domFactory == null) {
295: domFactory = DocumentBuilderFactory.newInstance();
296: }
297:
298: return domFactory;
299: }
300:
301: protected Document parseString(StringWriter writer) {
302: try {
303:
304: return createDocumentBuilder().parse(
305: new InputSource(new StringReader(writer.getBuffer()
306: .toString())));
307:
308: } catch (SAXException e) {
309: fail("Cannot create parse string: " + e.toString());
310:
311: } catch (IOException e) {
312: fail("Cannot create parse string: " + e.toString());
313:
314: }
315: // just to keep the compiler happy
316: return null;
317: }
318:
319: protected Document parseString(String string) {
320: try {
321:
322: return createDocumentBuilder().parse(
323: new InputSource(new StringReader(string)));
324:
325: } catch (SAXException e) {
326: fail("Cannot create parse string: " + e.toString());
327:
328: } catch (IOException e) {
329: fail("Cannot create parse string: " + e.toString());
330:
331: }
332: // just to keep the compiler happy
333: return null;
334: }
335:
336: protected Document parseFile(String path) {
337: try {
338:
339: return createDocumentBuilder().parse(new File(path));
340:
341: } catch (SAXException e) {
342: fail("Cannot create parse file: " + e.toString());
343:
344: } catch (IOException e) {
345: fail("Cannot create parse file: " + e.toString());
346:
347: }
348: // just to keep the compiler happy
349: return null;
350: }
351:
352: private void log(String message) {
353: if (debug) {
354: System.out.println("[XmlTestCase]" + message);
355: }
356: }
357:
358: private void log(String message, Exception e) {
359: if (debug) {
360: System.out.println("[XmlTestCase]" + message);
361: e.printStackTrace();
362: }
363: }
364:
365: private String trim(String trimThis) {
366: if (trimThis == null) {
367: return trimThis;
368: }
369:
370: return trimThis.trim();
371: }
372:
373: private List sanitize(NodeList nodes) {
374: ArrayList list = new ArrayList();
375:
376: for (int i = 0, size = nodes.getLength(); i < size; i++) {
377: if (nodes.item(i).getNodeType() == Node.TEXT_NODE) {
378: if (!(nodes.item(i).getNodeValue() == null || nodes
379: .item(i).getNodeValue().trim().length() == 0)) {
380: list.add(nodes.item(i));
381: } else {
382: log("Ignoring text node:"
383: + nodes.item(i).getNodeValue());
384: }
385: } else {
386: list.add(nodes.item(i));
387: }
388: }
389: return list;
390: }
391:
392: private class NodeByNameComparator implements Comparator {
393: public int compare(Object objOne, Object objTwo) {
394: String nameOne = ((Node) objOne).getNodeName();
395: String nameTwo = ((Node) objTwo).getNodeName();
396:
397: if (nameOne == null) {
398: if (nameTwo == null) {
399: return 0;
400: }
401: return -1;
402: }
403:
404: if (nameTwo == null) {
405: return 1;
406: }
407:
408: return nameOne.compareTo(nameTwo);
409: }
410: }
411:
412: public void validateWithSchema(InputSource documentSource,
413: final InputSource schemaSource)
414: throws ParserConfigurationException, SAXException,
415: IOException {
416: class XMLUnitHandler extends DefaultHandler {
417: ArrayList errors = new ArrayList();
418: ArrayList warnings = new ArrayList();
419: InputSource schemaSource;
420:
421: XMLUnitHandler(InputSource schemaSource) {
422: this .schemaSource = schemaSource;
423: schemaSource.setSystemId("schema.xsd");
424: }
425:
426: public InputSource resolveEntity(String publicId,
427: String systemId) {
428: return schemaSource;
429: }
430:
431: public void error(SAXParseException ex) {
432: errors.add(ex);
433: }
434:
435: public void warning(SAXParseException ex) {
436: warnings.add(ex);
437: }
438:
439: void reportErrors() throws SAXException {
440: if (errors.size() > 0) {
441: throw (SAXException) errors.get(0);
442: }
443: }
444:
445: }
446:
447: // it's not all that good to have a concrete dependency on Xerces
448: // and a particular version, at that.
449: // but schema support in the Xerces series of parsers is variable
450: // and some of the configuration details differ.
451: // At least this way seems reliable
452: SAXParser parser = new SAXParser();
453:
454: // Set features
455: parser.setFeature(NAMESPACES_FEATURE_ID, true);
456: parser.setFeature(NAMESPACE_PREFIXES_FEATURE_ID, false);
457: parser.setFeature(VALIDATION_FEATURE_ID, true);
458: parser.setFeature(SCHEMA_VALIDATION_FEATURE_ID, true);
459: parser.setFeature(SCHEMA_FULL_CHECKING_FEATURE_ID, false);
460: parser.setFeature(DYNAMIC_VALIDATION_FEATURE_ID, false);
461:
462: // Set properties
463: parser.setProperty(NONAMESPACE_SCHEMA_LOCATION_PROPERTY_ID,
464: "schema.xsd");
465:
466: XMLUnitHandler handler = new XMLUnitHandler(schemaSource);
467:
468: // Set handlers
469: parser.setContentHandler(handler);
470: parser.setErrorHandler(handler);
471: parser.setEntityResolver(handler);
472:
473: // parse document
474: parser.parse(documentSource);
475: handler.reportErrors();
476: }
477:
478: public boolean isValid(InputSource documentSource,
479: InputSource schemaSource)
480: throws ParserConfigurationException, IOException {
481: boolean result = false;
482: try {
483: validateWithSchema(documentSource, schemaSource);
484: result = true;
485: } catch (SAXException se) {
486: log("Validation failed.", se);
487: }
488:
489: return result;
490: }
491:
492: public void xmlAssertIsValid(String document, String schema)
493: throws ParserConfigurationException, IOException {
494: xmlAssertIsValid(new InputSource(new StringReader(document)),
495: new InputSource(new StringReader(schema)));
496: }
497:
498: public void xmlAssertIsValid(InputSource documentSource,
499: InputSource schemaSource)
500: throws ParserConfigurationException, IOException {
501: try {
502: validateWithSchema(documentSource, schemaSource);
503: } catch (SAXException se) {
504: se.printStackTrace();
505: fail("Validation failure: " + se.getMessage());
506: }
507: }
508: }
|