1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
47
48 public abstract class DomUtils {
49
50
51 private static Log log = LogFactory.getLog(DomUtils.class);
52
53
54
55 public static final int IGNORE_CASE = 1;
56
57 public static final int TRIM_STRING_VALUES = 2;
58
59 public static final int NORMALIZE_STRING_VALUES = 4 + TRIM_STRING_VALUES;
60
61 public static final int IGNORE_SEQUENCE_ORDER = 8;
62
63 public static final int EMPTY_EQUALS_NULL = 16;
64
65
66 private DomUtils() {
67 }
68
69
70
71
72
73
74
75
76
77
78
79
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
99
100
101
102
103
104
105
106
107
108
109
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
130
131
132
133
134
135
136
137
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
167
168
169
170
171
172
173
174
175
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
212
213
214
215
216
217
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
237
238
239
240
241
242
243
244
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
257
258
259
260
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
289
290
291
292
293
294
295
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
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
331
332
333
334
335
336
337
338
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
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
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
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
418 for (int i = 0; i < nbrChild; i++) {
419 if (!elementsAreEquals(children1.get(i), children2.get(i), flags)) return false;
420 }
421 } else {
422
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
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
457
458
459
460
461
462 public static String domToString(Document doc) {
463 return domToString(doc, 4, true);
464 }
465
466
467
468
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
486
487 public static String elementToString(Element elem) {
488 return elementToString(elem, 4, true);
489 }
490
491
492
493
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
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
581
582
583
584
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
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
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("&");
679 break;
680 case '<':
681 wr.write("<");
682 break;
683 case '>':
684 wr.write(">");
685 break;
686 case '"':
687 wr.write(""");
688 break;
689 case '\'':
690 wr.write("'");
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 }