View Javadoc

1   /*-------------------------------------------------------------------------
2    Copyright 2006 Olivier Berlanger
3   
4    Licensed under the Apache License, Version 2.0 (the "License");
5    you may not use this file except in compliance with the License.
6    You may obtain a copy of the License at
7   
8    http://www.apache.org/licenses/LICENSE-2.0
9   
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15   -------------------------------------------------------------------------*/
16  package net.sf.xolite.dom;
17  
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.StringWriter;
23  import java.io.UnsupportedEncodingException;
24  import java.io.Writer;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import javax.xml.parsers.DocumentBuilder;
29  import javax.xml.parsers.DocumentBuilderFactory;
30  
31  import net.sf.xolite.XMLSerializable;
32  import net.sf.xolite.XMLSerializeException;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.w3c.dom.Attr;
37  import org.w3c.dom.Document;
38  import org.w3c.dom.Element;
39  import org.w3c.dom.NamedNodeMap;
40  import org.w3c.dom.Node;
41  import org.w3c.dom.NodeList;
42  import org.w3c.dom.Text;
43  
44  
45  /**
46   * Utilities for DOM.
47   */
48  public abstract class DomUtils {
49  
50  
51      private static Log log = LogFactory.getLog(DomUtils.class);
52  
53  
54      /** Flag for comparison: ignore case for string values. */
55      public static final int IGNORE_CASE = 1;
56      /** Flag for comparison: trim string values. */
57      public static final int TRIM_STRING_VALUES = 2;
58      /** Flag for comparison: normalize whitespaces in string values . */
59      public static final int NORMALIZE_STRING_VALUES = 4 + TRIM_STRING_VALUES;
60      /** Flag for comparison: ignore order of elements in sequences. */
61      public static final int IGNORE_SEQUENCE_ORDER = 8;
62      /** Flag for comparison: empty elements (like <el/> or <el></el>) are equivalent to no-existent elements. */
63      public static final int EMPTY_EQUALS_NULL = 16;
64  
65  
66      private DomUtils() {
67      }
68  
69  
70      /**
71       * Get the list of direct child of an element matching the given namespace+tag.
72       * 
73       * @param parent
74       *            parent node when the child are searched.
75       * @param namespaceURI
76       *            requested element namespace URI.
77       * @param localName
78       *            requested element local name (tag).
79       * @return List a list with all the direct children matching the given namespace+localName.
80       */
81      public static List<Node> getDirectChildElementsByTagNameNS(Element parent, String namespaceURI, String localName) {
82          List<Node> matchedChilds = null;
83          NodeList allChilds = parent.getChildNodes();
84          int len = (allChilds == null) ? 0 : allChilds.getLength();
85          Node nod;
86          for (int i = 0; i < len; i++) {
87              nod = allChilds.item(i);
88              if (matchElement(nod, namespaceURI, localName)) {
89                  if (matchedChilds == null) matchedChilds = new ArrayList<Node>();
90                  matchedChilds.add(nod);
91              }
92          }
93          return matchedChilds;
94      }
95  
96  
97      /**
98       * Get the direct child of an element matching the given namespace+tag+index.
99       * 
100      * @param parent
101      *            parent node when the child are searched.
102      * @param namespaceURI
103      *            requested element namespace URI.
104      * @param localName
105      *            requested element local name (tag).
106      * @param index
107      *            the child element index (among the elements matching the namespace+localName). Note: Unlike XPath standard,
108      *            this index is zero-based.
109      * @return List a list with all the direct childs matching the given namespace+localName.
110      */
111     public static Element getDirectChild(Element parent, String namespaceURI, String localName, int index) {
112         Element matchedElement = null;
113         int matchedCount = 0;
114         NodeList allChilds = parent.getChildNodes();
115         int len = (allChilds == null) ? 0 : allChilds.getLength();
116         Node nod;
117         for (int i = 0; (i < len) && (matchedElement == null); i++) {
118             nod = allChilds.item(i);
119             if (matchElement(nod, namespaceURI, localName)) {
120                 if ((index >= 0) && (matchedCount < index)) matchedCount++;
121                 else matchedElement = (Element) nod;
122             }
123         }
124         return matchedElement;
125     }
126 
127 
128     /**
129      * Get the prefix already defined in the DOM at the node location for the given namespace URI. Unlike the DOM implementation
130      * this method returns "" to denote that the default prefix is used (with a xmlns="...." declaration). This method returns
131      * null when no prefix is found in the given context for the URI.
132      * 
133      * @param startNode
134      *            the context node where we search for prefix.
135      * @param uri
136      *            the namespace URI to search.
137      * @return the prefix already used to define the URI or null if the URI is not yet defined in this context.
138      */
139     public static String getDefinedPerfix(Node startNode, String uri) {
140         if (uri == null) return null;
141         Node nod = startNode;
142         while (nod != null) {
143             if (nod.getNodeType() == Node.ELEMENT_NODE) {
144                 NamedNodeMap nnm = nod.getAttributes();
145                 int len = nnm.getLength();
146                 for (int i = 0; i < len; i++) {
147                     Node attr = nnm.item(i);
148                     String attrName = attr.getNodeName();
149                     if (attrName.startsWith("xmlns")) {
150                         if (attrName.length() == 5) {
151                             if (uri.equals(attr.getNodeValue())) return "";
152                         } else if (attrName.charAt(6) == ':') {
153                             if (uri.equals(attr.getNodeValue())) return attrName.substring(6);
154                         }
155                     }
156                 }
157 
158             }
159             nod = nod.getParentNode();
160         }
161         return null;
162     }
163 
164 
165     /**
166      * Get the list of prefixs already defined in the DOM at the node location for the given namespace URI. Unlike the DOM
167      * implementation this method returns "" to denote that the default prefix is used (with a xmlns="...." declaration). This
168      * method returns an empty list when no prefix is found in the given context for the URI.
169      * 
170      * @param startNode
171      *            the context node where we search for prefix.
172      * @param uri
173      *            the namespace URI to search.
174      * @return the list of all prefix already used to define the URI or empty list if the URI is not yet defined in this
175      *         context.
176      */
177     public static List<String> getDefinedPerfixes(Node startNode, String uri) {
178         List<String> result = new ArrayList<String>();
179         if (uri != null) {
180             Node nod = startNode;
181             while (nod != null) {
182                 if (nod.getNodeType() == Node.ELEMENT_NODE) {
183                     NamedNodeMap nnm = nod.getAttributes();
184                     int len = nnm.getLength();
185                     for (int i = 0; i < len; i++) {
186                         Node attr = nnm.item(i);
187                         String attrName = attr.getNodeName();
188                         if (attrName.startsWith("xmlns")) {
189                             if (attrName.length() == 5) {
190                                 if (uri.equals(attr.getNodeValue())) {
191                                     if (!result.contains("")) result.add("");
192                                 }
193                             } else if (attrName.charAt(6) == ':') {
194                                 if (uri.equals(attr.getNodeValue())) {
195                                     String prefix = attrName.substring(6);
196                                     if (!result.contains(prefix)) result.add(prefix);
197                                 }
198                             }
199                         }
200                     }
201 
202                 }
203                 nod = nod.getParentNode();
204             }
205         }
206         return result;
207     }
208 
209 
210     /**
211      * Get the URI already defined in the DOM and associated to the given prefix.
212      * 
213      * @param startNode
214      *            the context node where we search for prefix.
215      * @param prefix
216      *            the prefix to search.
217      * @return the URI already defined in the DOM or null if no URI is mapped to the prefix in this context.
218      */
219     public static String getDefinedUri(Node startNode, String prefix) {
220         if (prefix == null) throw new IllegalArgumentException("Prefix cannot be null");
221         String attrName = (prefix.equals("")) ? "xmlns" : "xmlns:" + prefix;
222         Node nod = startNode;
223         while (nod != null) {
224             if (prefix.equals(nod.getPrefix())) return nod.getNamespaceURI();
225             if (nod.getNodeType() == Node.ELEMENT_NODE) {
226                 String uri = ((Element) nod).getAttribute(attrName);
227                 if ((uri != null) && !uri.equals("")) return uri;
228             }
229             nod = nod.getParentNode();
230         }
231         return null;
232     }
233 
234 
235     /**
236      * Check if the given node is an element matching the given namespace+tag.
237      * 
238      * @param nod
239      *            the node
240      * @param uri
241      *            namespace for the element
242      * @param tag
243      *            local name for the element
244      * @return boolean true iff the given element match the given namespace+tag.
245      */
246     public static boolean matchElement(Node nod, String uri, String tag) {
247         if (nod.getNodeType() != Node.ELEMENT_NODE) return false;
248         if (!tag.equals(nod.getLocalName())) return false;
249         String elUri = nod.getNamespaceURI();
250         if ((uri == null) || uri.equals("")) return (elUri == null) || elUri.equals("");
251         return uri.equals(elUri);
252     }
253 
254 
255     /**
256      * Get the text value of an element or attribute node. The value of all the text nodes of an element are concatenated.
257      * 
258      * @param nod
259      *            The element or attribute node.
260      * @return String it's text value.
261      */
262     public static String getTextValue(Node nod) {
263         String text = null;
264         if (nod != null) {
265             if (nod.getNodeType() == Node.ELEMENT_NODE) {
266                 NodeList lst = nod.getChildNodes();
267                 int len = lst.getLength();
268                 Node child;
269                 for (int i = 0; i < len; i++) {
270                     child = lst.item(i);
271                     if (child.getNodeType() == Node.TEXT_NODE) {
272                         if (text == null) text = ((Text) child).getData();
273                         else text += ((Text) child).getData();
274                     }
275                 }
276             } else if (nod.getNodeType() == Node.ATTRIBUTE_NODE) {
277                 Attr attrNode = (Attr) nod;
278                 text = attrNode.getValue();
279             } else {
280                 throw new IllegalArgumentException("GetTextValue only works for element and attribute nodes, not for: " + nod);
281             }
282         }
283         return text;
284     }
285 
286 
287     /**
288      * Set the text value of an element or attribute node. For elements, the value of the first the text node is replaced and
289      * any other text node are removed. If the element did not contain text node previously, a new text node is added with given
290      * text value.
291      * 
292      * @param nod
293      *            The element or attribute node.
294      * @param text
295      *            it's new text value.
296      */
297     public static void setTextValue(Node nod, String text) {
298         if (nod == null) throw new NullPointerException("Cannot set text value on a null node");
299         if (nod.getNodeType() == Node.ELEMENT_NODE) {
300             NodeList lst = nod.getChildNodes();
301             int len = lst.getLength();
302             Node child;
303             boolean textSet = false;
304             for (int i = 0; i < len; i++) {
305                 child = lst.item(i);
306                 if (child.getNodeType() == Node.TEXT_NODE) {
307                     if (textSet) {
308                         nod.removeChild(child);
309                     } else {
310                         ((Text) child).setData(text);
311                         textSet = true;
312                     }
313                 }
314             }
315             if (!textSet) {
316                 // append a new text node if the element did not contain any text nodes.
317                 Text newText = nod.getOwnerDocument().createTextNode(text);
318                 nod.appendChild(newText);
319             }
320         } else if (nod.getNodeType() == Node.ELEMENT_NODE) {
321             Attr attrNode = (Attr) nod;
322             attrNode.setValue(text);
323         } else {
324             throw new IllegalArgumentException("GetTextValue only works for element and attribute nodes, not for: " + nod);
325         }
326     }
327 
328 
329     /**
330      * Check that result of the given XMLSerializable serialisation is the same XML as the given inputStream content. <br>
331      * Both are transformed to DOM documents and then compared, this way ignorable whitespaces, prefixes, attribute order, ...
332      * are ignored.
333      * 
334      * @param src
335      *            a XML serializable object
336      * @param is
337      *            a stream containing the expected serialisation result.
338      * @return true if the serialisation match the expected result.
339      */
340     public static boolean checkObjectSerialisation(XMLSerializable src, InputStream is) {
341         if (log.isDebugEnabled()) log.debug("------------ Check serialisation of " + src);
342         boolean result = checkObjectSerialisation(src, is, TRIM_STRING_VALUES);
343         if (log.isDebugEnabled()) log.debug("            --> " + result);
344         return result;
345     }
346 
347 
348     public static boolean checkObjectSerialisation(XMLSerializable src, InputStream is, int flags) {
349         try {
350             DomXMLSerializer serializer = new DomXMLSerializer();
351             Document doc1 = serializer.serializeToDOM(src);
352             Document doc2 = streamToDom(is);
353             return documentsAreEquals(doc1, doc2, flags);
354         } catch (XMLSerializeException e) {
355             throw new RuntimeException("Failed to serialize object " + src);
356         }
357     }
358 
359 
360     public static boolean documentsAreEquals(Document doc1, Document doc2) {
361         if (log.isDebugEnabled()) log.debug("------------ Compare DOM documents");
362         boolean result = documentsAreEquals(doc1, doc2, TRIM_STRING_VALUES);
363         if (log.isDebugEnabled()) log.debug("            --> " + result);
364         return result;
365     }
366 
367 
368     public static boolean documentsAreEquals(Document doc1, Document doc2, int flags) {
369         if (doc1 == null) return (doc2 == null);
370         if (doc2 == null) return false;
371         return elementsAreEquals(doc1.getDocumentElement(), doc2.getDocumentElement(), flags);
372     }
373 
374 
375     public static boolean elementsAreEquals(Element elem1, Element elem2, int flags) {
376         // compare namespace + tag
377         if (elem1 == null) {
378             if (elem2 == null) return true;
379             if (log.isDebugEnabled()) log.debug("Compare elements = [" + null + "] to <" + elem2.getLocalName() + ">");
380             return false;
381         }
382         if (elem2 == null) {
383             if (log.isDebugEnabled()) log.debug("Compare elements = <" + elem1.getLocalName() + "> to [" + null + "]");
384             return false;
385         }
386         if (log.isDebugEnabled())
387             log.debug("Compare elements = <" + elem1.getLocalName() + "> to <" + elem2.getLocalName() + ">");
388         if (!stringAreEquals(elem1.getNamespaceURI(), elem2.getNamespaceURI(), 0)) {
389             if (log.isDebugEnabled())
390                 log.debug("Namespaces are diffrent: " + elem1.getNamespaceURI() + " != " + elem2.getNamespaceURI());
391             return false;
392         }
393         if (!elem1.getLocalName().equals(elem2.getLocalName())) {
394             if (log.isDebugEnabled())
395                 log.debug("Local names are diffrent: " + elem1.getLocalName() + " != " + elem2.getLocalName());
396             return false;
397         }
398         // compare attributes
399         NamedNodeMap attrs1 = elem1.getAttributes();
400         NamedNodeMap attrs2 = elem2.getAttributes();
401         if (!attributeMapsAreEquals(attrs1, attrs2, flags)) return false;
402         if (!attributeMapsAreEquals(attrs2, attrs1, flags)) return false;
403         // compare children
404         List<Element> children1 = getChildElementNodes(elem1, flags);
405         List<Element> children2 = getChildElementNodes(elem2, flags);
406         int nbrChild = children1.size();
407         if (nbrChild != children2.size()) {
408             if (log.isDebugEnabled())
409                 log.debug("Child count of element <" + elem1.getLocalName() + "> differs: " + nbrChild + " != "
410                         + children2.size());
411             return false;
412         }
413         if (nbrChild == 0) {
414             if (!stringAreEquals(getTextValue(elem1), getTextValue(elem2), flags)) return false;
415         } else {
416             if ((flags & IGNORE_SEQUENCE_ORDER) == 0) {
417                 // sub elements must be in the same sequence order
418                 for (int i = 0; i < nbrChild; i++) {
419                     if (!elementsAreEquals(children1.get(i), children2.get(i), flags)) return false;
420                 }
421             } else {
422                 // sub-elements can be in different orders
423                 boolean found;
424                 Element child1;
425                 for (int i = 0; i < nbrChild; i++) {
426                     child1 = children1.get(i);
427                     found = false;
428                     for (int j = 0; j < (nbrChild - i); j++) {
429                         if (elementsAreEquals(child1, children2.get(j), flags)) {
430                             found = true;
431                             children2.remove(j);
432                             break;
433                         }
434                     }
435                     if (!found) return false;
436                 }
437             }
438         }
439         return true;
440     }
441 
442 
443     /**
444      * An empty element means an element without attributes and without text or with white text.
445      */
446     public static boolean isEmpty(Element el) {
447         NamedNodeMap attrs = el.getAttributes();
448         if (attrs.getLength() > 0) return false;
449         List<Element> subElements = getChildElementNodes(el, 0);
450         if (subElements.size() > 0) return false;
451         String text = getTextValue(el);
452         return isEmpty(text);
453     }
454 
455 
456     // ------------------ DOM to string -----------------------------------------------
457 
458 
459     /**
460      * Serialize a DOM document to a String with multiple lines and 4 indentation spaces. Note: Don't support mixed content.
461      */
462     public static String domToString(Document doc) {
463         return domToString(doc, 4, true);
464     }
465 
466 
467     /**
468      * Serialize a DOM document to a String. Note: Don't support mixed content.
469      */
470     public static String domToString(Document doc, int nbrIndentationSpace, boolean multiLine) {
471         StringWriter wr = new StringWriter();
472         try {
473             wr.write("<?xml version=\"1.0\" ?>");
474             if (multiLine) wr.write("\n");
475             serializeElement(doc.getDocumentElement(), wr, 0, nbrIndentationSpace, multiLine);
476             wr.close();
477         } catch (IOException ioe) {
478             throw new IllegalStateException("domToString failed: " + ioe);
479         }
480         return wr.toString();
481     }
482 
483 
484     /**
485      * Serialize a DOM document to a String with multiple lines and 4 indentation spaces. Note: Don't support mixed content.
486      */
487     public static String elementToString(Element elem) {
488         return elementToString(elem, 4, true);
489     }
490 
491 
492     /**
493      * Serialize a DOM element (and all it's sub-elements) to a String. Note: Don't support mixed content.
494      */
495     public static String elementToString(Element elem, int nbrIndentationSpace, boolean multiLine) {
496         StringWriter wr = new StringWriter();
497         try {
498             serializeElement(elem, wr, 0, nbrIndentationSpace, multiLine);
499             wr.close();
500         } catch (IOException ioe) {
501             throw new IllegalStateException("elementToString failed: " + ioe);
502         }
503         return wr.toString();
504     }
505 
506 
507     public static Document stringToDom(String xml) {
508         InputStream is;
509         String encoding = "???";
510         try {
511             encoding = getEncoding(xml);
512             is = new ByteArrayInputStream(xml.getBytes(encoding));
513         } catch (UnsupportedEncodingException e) {
514             throw new IllegalArgumentException("Unsupported encoding defined in XML header: " + encoding, e);
515         }
516         return streamToDom(is);
517     }
518 
519 
520     /**
521      * Get the encoding declared in the given XML or "UTF-8" by default. <br>
522      */
523     public static String getEncoding(String xml) {
524         String encoding = "UTF-8";
525         if (xml != null) {
526             if (xml.startsWith("<?xml")) {
527                 int headerEnd = xml.indexOf("?>");
528                 if (headerEnd > 10) {
529                     String header = xml.substring(0, headerEnd);
530                     int len = header.length();
531                     int encodingStart = header.indexOf("encoding");
532                     if (encodingStart > 0) {
533                         int i = encodingStart + "encoding".length();
534                         while ((header.charAt(i) != '\'') && (header.charAt(i) != '"') && (i < len)) {
535                             i++;
536                         }
537                         if (i < len) {
538                             char quote = header.charAt(i);
539                             int start = i + 1;
540                             int end = 0;
541                             for (i = start; i < len; i++) {
542                                 if (header.charAt(i) == quote) {
543                                     end = i;
544                                     break;
545                                 }
546                             }
547                             if (end > 0) encoding = header.substring(start, end);
548                         }
549                     }
550                 }
551             }
552         }
553         return encoding;
554     }
555 
556 
557     public static Document resourceToDom(Class<?> resolver, String resourcePath) {
558         InputStream is = resolver.getResourceAsStream(resourcePath);
559         if (is == null)
560             throw new IllegalArgumentException("Ressource not found '" + resourcePath + "' relative to " + resolver);
561         return streamToDom(is);
562     }
563 
564 
565     public static Document streamToDom(InputStream is) {
566         Document doc = null;
567         try {
568             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
569             factory.setNamespaceAware(true);
570             DocumentBuilder builder = factory.newDocumentBuilder();
571             if (is == null) throw new NullPointerException("Input stream is null");
572             doc = builder.parse(is);
573         } catch (Exception e) {
574             throw new RuntimeException("parsing to Dom failed", e);
575         }
576         return doc;
577     }
578 
579 
580     // ------------------ Implementation -----------------------------------------------
581 
582 
583     /**
584      * Get al the direct-child elements as a list.
585      */
586     private static List<Element> getChildElementNodes(Element parent, int flags) {
587         List<Element> children = new ArrayList<Element>();
588         NodeList nodes = parent.getChildNodes();
589         Node nod;
590         int len = nodes.getLength();
591         boolean keepEmptyElements = (flags & EMPTY_EQUALS_NULL) == 0;
592         for (int i = 0; i < len; i++) {
593             nod = nodes.item(i);
594             if (nod instanceof Element) {
595                 if (keepEmptyElements || !isEmpty((Element) nod)) {
596                     children.add((Element) nod);
597                 }
598             }
599         }
600         return children;
601     }
602 
603 
604     private static void serializeElement(Element el, Writer wr, int indentation, int nbrIndentationSpace, boolean multiLine)
605             throws IOException {
606         int len;
607         int indentCount = indentation * nbrIndentationSpace;
608         for (int j = 0; j < indentCount; j++)
609             wr.write(' ');
610         wr.write("<");
611         wr.write(el.getNodeName());
612         // write attributes
613         NamedNodeMap allAttributes = el.getAttributes();
614         int attrCount = allAttributes.getLength();
615         Attr attribute;
616         for (int i = 0; i < attrCount; i++) {
617             attribute = (Attr) allAttributes.item(i);
618             wr.write(" ");
619             wr.write(attribute.getName());
620             wr.write("=\"");
621             writeEscaped(attribute.getValue(), wr);
622             wr.write("\"");
623         }
624         // write sub-elements or text
625         if (isComposite(el)) {
626             NodeList childs = el.getChildNodes();
627             len = childs.getLength();
628             wr.write(">");
629             if (multiLine) wr.write("\n");
630             int newIndentation = indentation + 1;
631             Node child;
632             for (int i = 0; i < len; i++) {
633                 child = childs.item(i);
634                 if (child instanceof Element) {
635                     serializeElement((Element) child, wr, newIndentation, nbrIndentationSpace, multiLine);
636                 }
637             }
638             for (int j = 0; j < indentCount; j++)
639                 wr.write(' ');
640             wr.write("</");
641             wr.write(el.getNodeName());
642             wr.write(">");
643             if (multiLine) wr.write("\n");
644         } else {
645             String text = getTextValue(el);
646             if ((text == null) || (text.trim().equals(""))) {
647                 wr.write("/>");
648                 if (multiLine) wr.write("\n");
649             } else {
650                 wr.write(">");
651                 writeEscaped(text, wr);
652                 wr.write("</");
653                 wr.write(el.getNodeName());
654                 wr.write(">");
655                 if (multiLine) wr.write("\n");
656             }
657         }
658     }
659 
660 
661     private static boolean isComposite(Element el) {
662         NodeList lst = el.getChildNodes();
663         int len = lst.getLength();
664         for (int i = 0; i < len; i++) {
665             if (lst.item(i).getNodeType() == Node.ELEMENT_NODE) return true;
666         }
667         return false;
668     }
669 
670 
671     private static void writeEscaped(String value, Writer wr) throws IOException {
672         int len = (value == null) ? 0 : value.length();
673         char ch;
674         for (int i = 0; i < len; i++) {
675             ch = value.charAt(i);
676             switch (ch) {
677                 case '&':
678                     wr.write("&amp;");
679                     break;
680                 case '<':
681                     wr.write("&lt;");
682                     break;
683                 case '>':
684                     wr.write("&gt;");
685                     break;
686                 case '"':
687                     wr.write("&quot;");
688                     break;
689                 case '\'':
690                     wr.write("&apos;");
691                     break;
692                 default:
693                     wr.write(ch);
694             }
695         }
696     }
697 
698 
699     private static boolean attributeMapsAreEquals(NamedNodeMap attrMap1, NamedNodeMap attrMap2, int flags) {
700         if (attrMap1 == null) return (attrMap2 == null);
701         if (attrMap2 == null) return false;
702         int len = attrMap1.getLength();
703         Attr attr1;
704         Attr attr2;
705         String attrName;
706         for (int i = 0; i < len; i++) {
707             attr1 = (Attr) attrMap1.item(i);
708             attrName = attr1.getName();
709             if ((!attrName.equals("xmlns")) && (!attrName.startsWith("xmlns:"))) {
710                 attr2 = (Attr) attrMap2.getNamedItem(attrName);
711                 if (attr2 == null) {
712                     log.debug("attribute " + attrName + " not found in other element");
713                     return false;
714                 }
715                 if (!stringAreEquals(attr1.getValue(), attr2.getValue(), flags)) {
716                     log.debug("attribute " + attrName + "='" + attr1.getValue() + "' has value '" + attr2.getValue()
717                             + "' in other element");
718                     return false;
719                 }
720             }
721         }
722         return true;
723     }
724 
725 
726     private static boolean stringAreEquals(String str1, String str2, int flags) {
727         if (isEmpty(str1)) return isEmpty(str2);
728         if (isEmpty(str2)) return false;
729         if ((flags & TRIM_STRING_VALUES) != 0) {
730             str1 = str1.trim();
731             str2 = str2.trim();
732         }
733         if ((flags & NORMALIZE_STRING_VALUES) != 0) {
734             str1 = str1.replaceAll("\\s+", " ");
735             str2 = str2.replaceAll("\\s+", " ");
736         }
737         boolean equals = ((flags & IGNORE_CASE) != 0) ? str1.equalsIgnoreCase(str2) : str1.equals(str2);
738         return equals;
739     }
740 
741 
742     private static boolean isEmpty(String str) {
743         if (str == null) return true;
744         return str.trim().equals("");
745     }
746 
747 }