KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > test > xml > XmlDiff


1 /*
2   * JBoss, Home of Professional Open Source
3   * Copyright 2005, JBoss Inc., and individual contributors as indicated
4   * by the @authors tag. See the copyright.txt in the distribution for a
5   * full listing of individual contributors.
6   *
7   * This is free software; you can redistribute it and/or modify it
8   * under the terms of the GNU Lesser General Public License as
9   * published by the Free Software Foundation; either version 2.1 of
10   * the License, or (at your option) any later version.
11   *
12   * This software is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this software; if not, write to the Free
19   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21   */

22 package org.jboss.test.xml;
23
24 import java.io.StringReader JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.Collections JavaDoc;
27 import java.util.List JavaDoc;
28 import javax.xml.namespace.QName JavaDoc;
29 import javax.xml.parsers.DocumentBuilder JavaDoc;
30 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
31 import javax.xml.parsers.ParserConfigurationException JavaDoc;
32 import org.w3c.dom.Attr JavaDoc;
33 import org.w3c.dom.Document JavaDoc;
34 import org.w3c.dom.Element JavaDoc;
35 import org.w3c.dom.NamedNodeMap JavaDoc;
36 import org.w3c.dom.Node JavaDoc;
37 import org.w3c.dom.NodeList JavaDoc;
38 import org.w3c.dom.Text JavaDoc;
39 import org.xml.sax.EntityResolver JavaDoc;
40 import org.xml.sax.ErrorHandler JavaDoc;
41 import org.xml.sax.InputSource JavaDoc;
42 import org.xml.sax.SAXException JavaDoc;
43 import org.xml.sax.SAXParseException JavaDoc;
44 import org.jboss.xb.binding.Constants;
45
46 /**
47  * @author <a HREF="mailto:alex@jboss.org">Alexey Loubyansky</a>
48  * @version <tt>$Revision: 45494 $</tt>
49  */

50 public class XmlDiff
51 {
52    public static final ErrorHandler JavaDoc ERROR_HANDLER = new DefErrorHandler();
53
54    public static final byte PRINT_ELEMENT = 0;
55    public static final byte PRINT_PARENT = 1;
56    public static final byte PRINT_ALL = 2;
57
58    private static final DocumentBuilderFactory JavaDoc FACTORY = DocumentBuilderFactory.newInstance();
59    static
60    {
61       FACTORY.setNamespaceAware(true);
62       FACTORY.setValidating(true);
63    }
64
65    private static final String JavaDoc INDENT = " ";
66
67    public static void main(String JavaDoc[] args) throws Exception JavaDoc
68    {
69       String JavaDoc xml1 =
70          "<ns1:e xmlns:ns1='http://ns' attr1='attr1_val' ns1:attr2='attr2_val'>\n" +
71          " <ns1:child1>\n" +
72          " <ns2:child2 xmlns:ns2='http://ns2' child2_attr='child2_attr_val'>child2_val</ns2:child2>\n" +
73          " </ns1:child1>\n" +
74          " text\n" +
75          "</ns1:e>";
76
77       String JavaDoc xml2 =
78          "<e xmlns='http://ns' attr1='attr1_val'" +
79          " xmlns:ns='http://ns' ns:attr2='attr2_val'>text" +
80          " <child1>" +
81          " <child2 xmlns='http://ns2' child2_attr='child2_attr_val'>child2_val</child2>" +
82          " </child1>" +
83          "</e>";
84
85       System.out.println(new XmlDiff().diff(xml1, xml2));
86    }
87
88    public XmlDiff()
89    {
90       this(PRINT_ALL, true);
91    }
92
93    public XmlDiff(byte print, boolean reformat)
94    {
95       this.print = print;
96       this.reformat = reformat;
97    }
98
99    private byte print = PRINT_ALL;
100    private boolean reformat = true;
101
102    public byte getPrint()
103    {
104       return print;
105    }
106
107    public void setPrint(byte print)
108    {
109       this.print = print;
110    }
111
112    public boolean isReformat()
113    {
114       return reformat;
115    }
116
117    public void setReformat(boolean reformat)
118    {
119       this.reformat = reformat;
120    }
121
122    /**
123     * Compares two XML contents and returns a diff if they are different or null if they are equal.
124     *
125     * @param expected expected XML content
126     * @param was actual XML content
127     * @return difference between XML contents or null if the contents are equal
128     */

129    public String JavaDoc diff(String JavaDoc expected, String JavaDoc was)
130    {
131       return diff(expected, was, ERROR_HANDLER, null);
132    }
133
134    public String JavaDoc diff(String JavaDoc expected, String JavaDoc was, ErrorHandler JavaDoc eh)
135    {
136       return diff(expected, was, eh, null);
137    }
138
139    public String JavaDoc diff(String JavaDoc expected, String JavaDoc was, EntityResolver JavaDoc er)
140    {
141       return diff(expected, was, ERROR_HANDLER, er);
142    }
143
144    public String JavaDoc diff(String JavaDoc expected, String JavaDoc was, ErrorHandler JavaDoc eh, EntityResolver JavaDoc er)
145    {
146       DocumentBuilder JavaDoc documentBuilder = null;
147       try
148       {
149          documentBuilder = FACTORY.newDocumentBuilder();
150       }
151       catch(ParserConfigurationException JavaDoc e)
152       {
153          throw new IllegalStateException JavaDoc("Failed to create a document builder: " + e.getMessage());
154       }
155
156       if(eh != null)
157       {
158          documentBuilder.setErrorHandler(eh);
159       }
160
161       if(er != null)
162       {
163          documentBuilder.setEntityResolver(er);
164       }
165
166       Document JavaDoc expDoc = null;
167       try
168       {
169          expDoc = documentBuilder.parse(new InputSource JavaDoc(new StringReader JavaDoc(expected)));
170       }
171       catch(Exception JavaDoc e)
172       {
173          throw new IllegalStateException JavaDoc("Failed to parse expected XML\n" + expected + ": " + e.getMessage());
174       }
175
176       Document JavaDoc wasDoc = null;
177       try
178       {
179          wasDoc = documentBuilder.parse(new InputSource JavaDoc(new StringReader JavaDoc(was)));
180       }
181       catch(Exception JavaDoc e)
182       {
183          throw new IllegalStateException JavaDoc("Failed to parse XML\n" + was + ": " + e.getMessage());
184       }
185
186       Element JavaDoc expElement = expDoc.getDocumentElement();
187       Element JavaDoc wasElement = wasDoc.getDocumentElement();
188       return assertEquals(expElement, wasElement, expElement, wasElement);
189    }
190
191    private String JavaDoc assertEquals(Element JavaDoc exp, Element JavaDoc was, Element JavaDoc printAsExp, Element JavaDoc printAsWas)
192    {
193       QName JavaDoc expName = new QName JavaDoc(exp.getNamespaceURI(), exp.getLocalName());
194       QName JavaDoc wasName = new QName JavaDoc(was.getNamespaceURI(), was.getLocalName());
195
196       if(!expName.equals(wasName))
197       {
198          return fail("Expected name " + expName + " but was " + wasName, exp, was);
199       }
200
201       NamedNodeMap JavaDoc expAttrs = exp.getAttributes();
202       NamedNodeMap JavaDoc wasAttrs = was.getAttributes();
203
204       if(expAttrs == null && wasAttrs != null && hasNonIgnorableNs(wasAttrs))
205       {
206          return fail("Element " + expName + " doesn't have attributes", printAsExp, printAsWas);
207       }
208       else if(wasAttrs == null && expAttrs != null && hasNonIgnorableNs(expAttrs))
209       {
210          return fail("Element " + expName + " has attributes", printAsExp, printAsWas);
211       }
212       else if(expAttrs != null && wasAttrs != null)
213       {
214          String JavaDoc msg = assertAttrs(expAttrs, wasAttrs, printAsExp);
215          if(msg != null)
216          {
217             return fail(msg, printAsExp, printAsWas);
218          }
219       }
220
221       NodeList JavaDoc expChildren = exp.getChildNodes();
222       NodeList JavaDoc wasChildren = was.getChildNodes();
223
224       NodeList JavaDoc expTexts = getTextNodes(expChildren);
225       NodeList JavaDoc wasTexts = getTextNodes(wasChildren);
226       if(expTexts.getLength() > 0 && wasTexts.getLength() == 0)
227       {
228          return fail("Element " + expName + " has text content", printAsExp, printAsWas);
229       }
230       else if(expTexts.getLength() == 0 && wasTexts.getLength() > 0)
231       {
232          return fail("Element " + expName + " doesn't have text content", printAsExp, printAsWas);
233       }
234       // todo: should text content be concatenated before comparison?
235
else if(expTexts.getLength() != wasTexts.getLength())
236       {
237          return fail(
238             "Element " + expName + " has " + expTexts.getLength() + " text nodes (was " + wasTexts.getLength() + ")",
239             printAsExp,
240             printAsWas
241          );
242       }
243       else if(expTexts.getLength() > 0 && wasTexts.getLength() > 0)
244       {
245          for(int i = 0; i < expTexts.getLength(); ++i)
246          {
247             Text JavaDoc text = (Text JavaDoc)expTexts.item(i);
248             if(!containsText(text.getNodeValue(), wasTexts, i))
249             {
250                return fail("Element " + expName + " has text '" + text.getNodeValue() + "'", printAsExp, printAsWas);
251             }
252          }
253       }
254
255       NodeList JavaDoc expElems = sublist(expChildren, Node.ELEMENT_NODE);
256       NodeList JavaDoc wasElems = sublist(wasChildren, Node.ELEMENT_NODE);
257       if(expElems.getLength() > 0 && wasElems.getLength() == 0)
258       {
259          return fail("Element " + expName + " has child elements", printAsExp, printAsWas);
260       }
261       else if(expElems.getLength() == 0 && wasElems.getLength() > 0)
262       {
263          return fail("Element " + expName + " doesn't have child elements", printAsExp, printAsWas);
264       }
265       else if(expElems.getLength() != wasElems.getLength())
266       {
267          return fail("Element " +
268             expName +
269             " has " +
270             expElems.getLength() +
271             " child elements (was " +
272             wasElems.getLength() +
273             ")",
274             printAsExp,
275             printAsWas
276          );
277       }
278       else if(expElems.getLength() > 0 && wasElems.getLength() > 0)
279       {
280          if(print == PRINT_PARENT)
281          {
282             printAsExp = exp;
283             printAsWas = was;
284          }
285
286          for(int i = 0; i < expElems.getLength(); ++i)
287          {
288             Element JavaDoc expChild = (Element JavaDoc)expElems.item(i);
289             Element JavaDoc wasChild = getElement(expChild.getNamespaceURI(), expChild.getLocalName(), wasElems, i);
290             if(wasChild == null)
291             {
292                return fail("Element " +
293                   expName +
294                   " has child element " +
295                   new QName JavaDoc(expChild.getNamespaceURI(), expChild.getLocalName()),
296                   printAsExp,
297                   printAsWas
298                );
299             }
300
301             if(print == PRINT_ELEMENT)
302             {
303                printAsExp = expChild;
304                printAsWas = wasChild;
305             }
306
307             String JavaDoc diff = assertEquals(expChild, wasChild, printAsExp, printAsWas);
308             if(diff != null)
309             {
310                return diff;
311             }
312          }
313       }
314       return null;
315    }
316
317    private static Element JavaDoc getElement(String JavaDoc ns, String JavaDoc local, NodeList JavaDoc elements, int suggestedIndex)
318    {
319       if(suggestedIndex >= 0 && suggestedIndex < elements.getLength())
320       {
321          Element JavaDoc element = (Element JavaDoc)elements.item(suggestedIndex);
322          if((ns == null && element.getNamespaceURI() == null ||
323             ns != null && ns.equals(element.getNamespaceURI())
324             ) &&
325             local.equals(element.getLocalName()))
326          {
327             return element;
328          }
329       }
330
331       for(int i = 0; i < elements.getLength(); ++i)
332       {
333          Element JavaDoc element = (Element JavaDoc)elements.item(i);
334          if((ns == null && element.getNamespaceURI() == null ||
335             ns != null && ns.equals(element.getNamespaceURI())
336             ) &&
337             local.equals(element.getLocalName()))
338          {
339             return element;
340          }
341       }
342       return null;
343    }
344
345    private static boolean containsText(String JavaDoc text, NodeList JavaDoc textNodes, int suggestedIndex)
346    {
347       text = text.trim();
348       if(suggestedIndex >= 0)
349       {
350          Text JavaDoc textNode = (Text JavaDoc)textNodes.item(suggestedIndex);
351          String JavaDoc wasText = textNode.getNodeValue().trim();
352          if(text.equals(wasText))
353          {
354             return true;
355          }
356       }
357
358       for(int i = 0; i < textNodes.getLength(); ++i)
359       {
360          Text JavaDoc textNode = (Text JavaDoc)textNodes.item(i);
361          String JavaDoc wasText = textNode.getNodeValue().trim();
362          if(text.equals(wasText))
363          {
364             return true;
365          }
366       }
367       return false;
368    }
369
370    private static NodeList JavaDoc getTextNodes(NodeList JavaDoc list)
371    {
372       MutableNodeList result = new MutableNodeList();
373       for(int i = 0; i < list.getLength(); ++i)
374       {
375          Node JavaDoc node = list.item(i);
376          if(node.getNodeType() == Node.TEXT_NODE)
377          {
378             String JavaDoc text = node.getNodeValue();
379             if(text.trim().length() > 0)
380             {
381                result.add(node);
382             }
383          }
384       }
385       return result;
386    }
387
388    private static NodeList JavaDoc sublist(NodeList JavaDoc list, short nodeType)
389    {
390       MutableNodeList result = new MutableNodeList();
391       for(int i = 0; i < list.getLength(); ++i)
392       {
393          Node JavaDoc node = list.item(i);
394          if(node.getNodeType() == nodeType)
395          {
396             result.add(node);
397          }
398       }
399       return result;
400    }
401
402    private static String JavaDoc assertAttrs(NamedNodeMap JavaDoc attrsExp,
403                                      NamedNodeMap JavaDoc attrsWas,
404                                      Element JavaDoc printAsExp)
405    {
406       String JavaDoc result = assertSubset(attrsExp, attrsWas, printAsExp, true);
407       if(result == null)
408       {
409          result = assertSubset(attrsWas, attrsExp, printAsExp, false);
410       }
411       return result;
412    }
413
414    private static String JavaDoc assertSubset(NamedNodeMap JavaDoc attrsSubset,
415                                       NamedNodeMap JavaDoc attrsSet,
416                                       Element JavaDoc printAsExp,
417                                       boolean checkHave)
418    {
419       String JavaDoc msg = checkHave ? " has attribute " : " doesn't have attribute ";
420       QName JavaDoc expName = new QName JavaDoc(printAsExp.getNamespaceURI(), printAsExp.getLocalName());
421       for(int i = 0; i < attrsSubset.getLength(); ++i)
422       {
423          Attr JavaDoc attr = (Attr JavaDoc)attrsSubset.item(i);
424          String JavaDoc attrNs = attr.getNamespaceURI();
425          String JavaDoc localName = attr.getLocalName();
426          if(xsiNs(attrNs) && "type".equals(localName))
427          {
428             Attr JavaDoc wasAttr = (Attr JavaDoc)attrsSet.getNamedItemNS(attrNs, localName);
429             if(wasAttr == null)
430             {
431                return "Element " + expName + msg + new QName JavaDoc(attrNs, localName);
432             }
433
434             String JavaDoc typeName = attr.getValue();
435             int colon = typeName.indexOf(':');
436             if(colon != -1)
437             {
438                typeName = typeName.substring(colon);
439             }
440
441             if(!wasAttr.getValue().endsWith(typeName))
442             {
443                return "Element " + expName +
444                   (checkHave ? " has xsi:type " : " doesn't have xsi:type ") +
445                   attr.getValue();
446             }
447
448             //todo compare namespaces for xsi:types
449
}
450          else if(nonIgnorableNs(attrNs) || xsiNs(attrNs) && localName.equals("nil"))
451          {
452             Attr JavaDoc wasAttr = (Attr JavaDoc)attrsSet.getNamedItemNS(attrNs, localName);
453             if(wasAttr == null)
454             {
455                return "Element " + expName + msg + new QName JavaDoc(attrNs, localName);
456             }
457
458             if(!attr.getValue().equals(wasAttr.getValue()))
459             {
460                return "Attribute " +
461                   new QName JavaDoc(attrNs, localName) +
462                   " in element " +
463                   expName +
464                   " has value " + attr.getValue();
465             }
466          }
467       }
468       return null;
469    }
470    
471    private static boolean hasNonIgnorableNs(NamedNodeMap JavaDoc nodeMap)
472    {
473       for(int i = 0; i < nodeMap.getLength(); ++i)
474       {
475          Node JavaDoc node = nodeMap.item(i);
476          if(nonIgnorableNs(node.getNamespaceURI()))
477          {
478             return true;
479          }
480       }
481       return false;
482    }
483
484    private static boolean nonIgnorableNs(String JavaDoc ns)
485    {
486       return ns == null ||
487          !ns.equals(Constants.NS_XML_SCHEMA)
488          && !ns.equals(Constants.NS_XML_SCHEMA_INSTANCE)
489          && !ns.equals(Constants.NS_XML_XMLNS);
490    }
491
492    private static boolean xsiNs(String JavaDoc ns)
493    {
494       return Constants.NS_XML_SCHEMA_INSTANCE.equals(ns);
495    }
496
497    private String JavaDoc fail(String JavaDoc msg, Element JavaDoc exp, Element JavaDoc was)
498    {
499       return msg + ". Expected\n" + toString(exp) + "\nbut was\n" + toString(was);
500    }
501
502    private String JavaDoc toString(Element JavaDoc e)
503    {
504       return append(e, new StringBuffer JavaDoc(), 0).toString();
505    }
506
507    private StringBuffer JavaDoc append(Element JavaDoc e, StringBuffer JavaDoc buf, int depth)
508    {
509       if(reformat && depth > 0)
510       {
511          buf.append('\n');
512          for(int i = 0; i < depth; ++i)
513          {
514             buf.append(INDENT);
515          }
516       }
517
518       buf.append('<');
519       if(e.getPrefix() != null && e.getPrefix().length() > 0)
520       {
521          buf.append(e.getPrefix()).append(':');
522       }
523       buf.append(e.getLocalName());
524
525       NamedNodeMap JavaDoc attrs = e.getAttributes();
526       if(attrs != null && attrs.getLength() > 0)
527       {
528          for(int i = 0; i < attrs.getLength(); ++i)
529          {
530             Attr JavaDoc attr = (Attr JavaDoc)attrs.item(i);
531             buf.append(' ')
532                .append(attr.getName())
533                .append('=')
534                .append('\'')
535                .append(attr.getValue())
536                .append('\'');
537          }
538       }
539
540       buf.append('>');
541
542       NodeList JavaDoc childNodes = e.getChildNodes();
543       boolean childElements = false;
544       for(int i = 0; i < childNodes.getLength(); ++i)
545       {
546          Node JavaDoc child = childNodes.item(i);
547          switch(child.getNodeType())
548          {
549             case Node.TEXT_NODE:
550                String JavaDoc chars = child.getNodeValue();
551                if(chars.trim().length() > 0)
552                {
553                   buf.append(chars);
554                }
555                break;
556             case Node.ELEMENT_NODE:
557                append((Element JavaDoc)child, buf, depth + 1);
558                childElements = true;
559                break;
560             default:
561                throw new IllegalStateException JavaDoc("Node type is not supported: " + child.getNodeType());
562          }
563       }
564
565       if(reformat && childElements)
566       {
567          buf.append('\n');
568          for(int i = 0; i < depth; ++i)
569          {
570             buf.append(INDENT);
571          }
572       }
573
574       buf.append("</");
575       if(e.getPrefix() != null && e.getPrefix().length() > 0)
576       {
577          buf.append(e.getPrefix()).append(':');
578       }
579       buf.append(e.getLocalName())
580          .append('>');
581
582       return buf;
583    }
584
585    // Inner
586

587    private static final class MutableNodeList
588       implements NodeList JavaDoc
589    {
590       private List JavaDoc list = Collections.EMPTY_LIST;
591
592       public void add(Node JavaDoc node)
593       {
594          switch(list.size())
595          {
596             case 0:
597                list = Collections.singletonList(node);
598                break;
599             case 1:
600                list = new ArrayList JavaDoc(list);
601             default:
602                list.add(node);
603          }
604       }
605
606       public int getLength()
607       {
608          return list.size();
609       }
610
611       public Node JavaDoc item(int index)
612       {
613          return (Node JavaDoc)list.get(index);
614       }
615    }
616
617    public static final class DefErrorHandler
618       implements ErrorHandler JavaDoc
619    {
620       public static final byte IGNORE = 0;
621       public static final byte LOG = 1;
622       public static final byte FAIL = 3;
623
624       private byte warnEvent = IGNORE;
625       private byte errorEvent = IGNORE;
626       private byte fatalEvent = FAIL;
627
628       public void error(SAXParseException JavaDoc e) throws SAXException JavaDoc
629       {
630          handleEvent(warnEvent, e);
631       }
632
633       public void fatalError(SAXParseException JavaDoc e) throws SAXException JavaDoc
634       {
635          handleEvent(errorEvent, e);
636       }
637
638       public void warning(SAXParseException JavaDoc e) throws SAXException JavaDoc
639       {
640          handleEvent(fatalEvent, e);
641       }
642
643       private void handleEvent(byte event, SAXParseException JavaDoc e)
644          throws SAXException JavaDoc
645       {
646          switch(event)
647          {
648             case IGNORE:
649                break;
650             case LOG:
651                System.out.println(formatMessage(e));
652                break;
653             case FAIL:
654                String JavaDoc msg = formatMessage(e);
655                throw new SAXException JavaDoc(msg);
656          }
657       }
658    }
659
660    private static String JavaDoc formatMessage(SAXParseException JavaDoc exception)
661    {
662       StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(50);
663       buffer.append(exception.getMessage()).append(" @ ");
664       String JavaDoc location = exception.getPublicId();
665       if(location != null)
666       {
667          buffer.append(location);
668       }
669       else
670       {
671          location = exception.getSystemId();
672          if(location != null)
673          {
674             buffer.append(location);
675          }
676          else
677          {
678             buffer.append("*unknown*");
679          }
680       }
681       buffer.append('[');
682       buffer.append(exception.getLineNumber()).append(',');
683       buffer.append(exception.getColumnNumber()).append(']');
684       return buffer.toString();
685    }
686 }
687
Popular Tags