1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.opensaml.xml.util;
18
19 import java.io.StringWriter;
20 import java.io.Writer;
21 import java.util.ArrayList;
22 import java.util.GregorianCalendar;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Locale;
26 import java.util.Map;
27 import java.util.StringTokenizer;
28
29 import javax.xml.datatype.DatatypeConfigurationException;
30 import javax.xml.datatype.DatatypeFactory;
31 import javax.xml.datatype.Duration;
32 import javax.xml.namespace.QName;
33 import javax.xml.transform.OutputKeys;
34 import javax.xml.transform.Transformer;
35 import javax.xml.transform.TransformerException;
36 import javax.xml.transform.TransformerFactory;
37 import javax.xml.transform.dom.DOMSource;
38 import javax.xml.transform.stream.StreamResult;
39
40 import org.opensaml.xml.XMLObject;
41 import org.opensaml.xml.parse.XMLParserException;
42 import org.w3c.dom.Attr;
43 import org.w3c.dom.DOMImplementation;
44 import org.w3c.dom.Document;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.NamedNodeMap;
47 import org.w3c.dom.Node;
48 import org.w3c.dom.NodeList;
49 import org.w3c.dom.Text;
50 import org.w3c.dom.ls.DOMImplementationLS;
51 import org.w3c.dom.ls.LSOutput;
52 import org.w3c.dom.ls.LSSerializer;
53
54
55
56
57 public final class XMLHelper {
58
59
60 private static DatatypeFactory dataTypeFactory;
61
62
63 private XMLHelper() {
64
65 }
66
67
68
69
70
71
72 public static DatatypeFactory getDataTypeFactory() {
73 if (dataTypeFactory == null) {
74 try {
75 dataTypeFactory = DatatypeFactory.newInstance();
76 } catch (DatatypeConfigurationException e) {
77
78 }
79 }
80
81 return dataTypeFactory;
82 }
83
84
85
86
87
88
89
90
91 public static boolean hasXSIType(Element e) {
92 if (e != null) {
93 if (e.getAttributeNodeNS(XMLConstants.XSI_NS, "type") != null) {
94 return true;
95 }
96 }
97
98 return false;
99 }
100
101
102
103
104
105
106
107
108 public static QName getXSIType(Element e) {
109 if (hasXSIType(e)) {
110 Attr attribute = e.getAttributeNodeNS(XMLConstants.XSI_NS, "type");
111 String attributeValue = attribute.getTextContent().trim();
112 StringTokenizer tokenizer = new StringTokenizer(attributeValue, ":");
113 String prefix = null;
114 String localPart;
115 if (tokenizer.countTokens() > 1) {
116 prefix = tokenizer.nextToken();
117 localPart = tokenizer.nextToken();
118 } else {
119 localPart = tokenizer.nextToken();
120 }
121
122 return constructQName(e.lookupNamespaceURI(prefix), localPart, prefix);
123 }
124
125 return null;
126 }
127
128
129
130
131
132
133
134
135 public static Attr getIdAttribute(Element domElement) {
136 if (!domElement.hasAttributes()) {
137 return null;
138 }
139
140 NamedNodeMap attributes = domElement.getAttributes();
141 Attr attribute;
142 for (int i = 0; i < attributes.getLength(); i++) {
143 attribute = (Attr) attributes.item(i);
144 if (attribute.isId()) {
145 return attribute;
146 }
147 }
148
149 return null;
150 }
151
152
153
154
155
156
157
158
159 public static QName getNodeQName(Node domNode) {
160 if (domNode != null) {
161 return constructQName(domNode.getNamespaceURI(), domNode.getLocalName(), domNode.getPrefix());
162 }
163
164 return null;
165 }
166
167
168
169
170
171
172
173
174
175
176 public static Locale getLanguage(Element element) {
177 String lang = DatatypeHelper.safeTrimOrNullString(element.getAttributeNS(XMLConstants.XML_NS, "lang"));
178 if(lang != null){
179 if(lang.contains("-")){
180 lang = lang.substring(0, lang.indexOf("-"));
181 }
182 return new Locale(lang.toUpperCase());
183 }else{
184 return Locale.getDefault();
185 }
186 }
187
188
189
190
191
192
193
194
195
196 public static Attr constructAttribute(Document owningDocument, QName attributeName) {
197 return constructAttribute(owningDocument, attributeName.getNamespaceURI(), attributeName.getLocalPart(),
198 attributeName.getPrefix());
199 }
200
201
202
203
204
205
206
207
208
209
210
211 public static Attr constructAttribute(Document document, String namespaceURI, String localName, String prefix) {
212 String trimmedLocalName = DatatypeHelper.safeTrimOrNullString(localName);
213
214 if (trimmedLocalName == null) {
215 throw new IllegalArgumentException("Local name may not be null or empty");
216 }
217
218 String qualifiedName;
219 String trimmedPrefix = DatatypeHelper.safeTrimOrNullString(prefix);
220 if (trimmedPrefix != null) {
221 qualifiedName = trimmedPrefix + ":" + DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
222 } else {
223 qualifiedName = DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
224 }
225
226 if (DatatypeHelper.isEmpty(namespaceURI)) {
227 return document.createAttribute(qualifiedName);
228 } else {
229 return document.createAttributeNS(namespaceURI, qualifiedName);
230 }
231 }
232
233
234
235
236
237
238
239
240 public static QName getAttributeValueAsQName(Attr attribute) {
241 if (attribute == null || DatatypeHelper.isEmpty(attribute.getValue())) {
242 return null;
243 }
244
245 String attributeValue = attribute.getTextContent();
246 String[] valueComponents = attributeValue.split(":");
247 if (valueComponents.length == 1) {
248 return constructQName(attribute.lookupNamespaceURI(null), valueComponents[0], null);
249 } else {
250 return constructQName(attribute.lookupNamespaceURI(valueComponents[0]), valueComponents[1],
251 valueComponents[0]);
252 }
253 }
254
255
256
257
258
259
260
261
262
263 public static Boolean getAttributeValueAsBoolean(Attr attribute) {
264 if(attribute == null){
265 return null;
266 }
267
268 String valueStr = attribute.getValue();
269 if (valueStr.equals("0") || valueStr.equals("false")) {
270 return Boolean.FALSE;
271 } else if (valueStr.equals("1") || valueStr.equals("true")) {
272 return Boolean.TRUE;
273 } else {
274 return null;
275 }
276 }
277
278
279
280
281
282
283
284
285 public static List<String> getAttributeValueAsList(Attr attribute) {
286 ArrayList<String> values = new ArrayList<String>();
287 if (attribute == null) {
288 return values;
289 }
290
291 StringTokenizer valueTokens = new StringTokenizer(DatatypeHelper.safeTrimOrNullString(attribute.getValue()));
292 while (valueTokens.hasMoreTokens()) {
293 values.add(valueTokens.nextToken());
294 }
295
296 return values;
297 }
298
299
300
301
302
303
304
305
306 public static QName getElementContentAsQName(Element element) {
307 if (element == null) {
308 return null;
309 }
310
311 String elementContent = null;
312 NodeList nodeList = element.getChildNodes();
313 for (int i = 0; i < nodeList.getLength(); i++) {
314 Node node = nodeList.item(i);
315 if (node.getNodeType() == Node.TEXT_NODE) {
316 elementContent = DatatypeHelper.safeTrimOrNullString(((Text) node).getWholeText());
317 break;
318 }
319 }
320
321 if (elementContent == null) {
322 return null;
323 }
324
325 String[] valueComponents = elementContent.split(":");
326 if (valueComponents.length == 1) {
327 return constructQName(element.lookupNamespaceURI(null), valueComponents[0], null);
328 } else {
329 return constructQName(element.lookupNamespaceURI(valueComponents[0]), valueComponents[1],
330 valueComponents[0]);
331 }
332 }
333
334
335
336
337
338
339
340
341 public static List<String> getElementContentAsList(Element element) {
342 ArrayList<String> values = new ArrayList<String>();
343 if (element == null) {
344 return values;
345 }
346
347 String elementContent = DatatypeHelper.safeTrimOrNullString(element.getTextContent());
348 StringTokenizer valueTokens = new StringTokenizer(elementContent);
349 while (valueTokens.hasMoreTokens()) {
350 values.add(valueTokens.nextToken());
351 }
352
353 return values;
354 }
355
356
357
358
359
360
361
362
363
364
365 public static QName constructQName(String namespaceURI, String localName, String prefix) {
366 if (DatatypeHelper.isEmpty(prefix)) {
367 return new QName(namespaceURI, localName);
368 } else if (DatatypeHelper.isEmpty(namespaceURI)) {
369 return new QName(localName);
370 }
371
372 return new QName(namespaceURI, localName, prefix);
373 }
374
375
376
377
378
379
380
381
382
383 public static QName constructQName(String qname, XMLObject owningObject) {
384 return constructQName(qname, owningObject.getDOM());
385 }
386
387
388
389
390
391
392
393
394
395 public static QName constructQName(String qname, Element owningElement) {
396 String nsURI;
397 String nsPrefix;
398 String name;
399
400 if (qname.indexOf(":") > -1) {
401 StringTokenizer qnameTokens = new StringTokenizer(qname, ":");
402 nsPrefix = qnameTokens.nextToken();
403 name = qnameTokens.nextToken();
404 } else {
405 nsPrefix = "";
406 name = qname;
407 }
408
409 nsURI = lookupNamespaceURI(owningElement, nsPrefix);
410 return constructQName(nsURI, name, nsPrefix);
411 }
412
413
414
415
416
417
418
419
420
421 public static Element constructElement(Document document, QName elementName) {
422 return constructElement(document, elementName.getNamespaceURI(), elementName.getLocalPart(), elementName
423 .getPrefix());
424 }
425
426
427
428
429
430
431
432
433
434
435
436 public static Element constructElement(Document document, String namespaceURI, String localName, String prefix) {
437 String trimmedLocalName = DatatypeHelper.safeTrimOrNullString(localName);
438
439 if (trimmedLocalName == null) {
440 throw new IllegalArgumentException("Local name may not be null or empty");
441 }
442
443 String qualifiedName;
444 String trimmedPrefix = DatatypeHelper.safeTrimOrNullString(prefix);
445 if (trimmedPrefix != null) {
446 qualifiedName = trimmedPrefix + ":" + DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
447 } else {
448 qualifiedName = DatatypeHelper.safeTrimOrNullString(trimmedLocalName);
449 }
450
451 if (!DatatypeHelper.isEmpty(namespaceURI)) {
452 return document.createElementNS(namespaceURI, qualifiedName);
453 } else {
454 return document.createElementNS(null, qualifiedName);
455 }
456 }
457
458
459
460
461
462
463
464 public static void appendChildElement(Element parentElement, Element childElement) {
465 Document parentDocument = parentElement.getOwnerDocument();
466 adoptElement(childElement, parentDocument);
467
468 parentElement.appendChild(childElement);
469 }
470
471
472
473
474
475
476
477 public static void adoptElement(Element adoptee, Document adopter) {
478 if (!(adoptee.getOwnerDocument().equals(adopter))) {
479 adopter.adoptNode(adoptee);
480 }
481 }
482
483
484
485
486
487
488
489 public static void appendTextContent(Element domElement, String textContent) {
490 if (textContent == null) {
491 return;
492 }
493 Document parentDocument = domElement.getOwnerDocument();
494 Text textNode = parentDocument.createTextNode(textContent);
495 domElement.appendChild(textNode);
496 }
497
498
499
500
501
502
503
504
505 public static void appendNamespaceDeclaration(Element domElement, String namespaceURI, String prefix) {
506 String nsURI = DatatypeHelper.safeTrimOrNullString(namespaceURI);
507 String nsPrefix = DatatypeHelper.safeTrimOrNullString(prefix);
508
509
510 if (nsURI == null && nsPrefix == null) {
511 return;
512 }
513
514 String attributeName;
515 if (nsPrefix == null) {
516 attributeName = XMLConstants.XMLNS_PREFIX;
517 } else {
518 attributeName = XMLConstants.XMLNS_PREFIX + ":" + nsPrefix;
519 }
520
521 String attributeValue;
522 if (nsURI == null) {
523 attributeValue = "";
524 } else {
525 attributeValue = nsURI;
526 }
527
528 domElement.setAttributeNS(XMLConstants.XMLNS_NS, attributeName, attributeValue);
529 }
530
531
532
533
534
535
536
537
538
539
540
541
542
543 public static String lookupNamespaceURI(Element startingElement, String prefix) {
544 return lookupNamespaceURI(startingElement, null, prefix);
545 }
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560 public static String lookupNamespaceURI(Element startingElement, Element stopingElement, String prefix) {
561 String namespaceURI;
562
563 if (startingElement == stopingElement) {
564 return null;
565 }
566
567
568 if (startingElement.hasAttributes()) {
569 NamedNodeMap map = startingElement.getAttributes();
570 int length = map.getLength();
571 for (int i = 0; i < length; i++) {
572 Node attr = map.item(i);
573 String attrPrefix = attr.getPrefix();
574 String value = attr.getNodeValue();
575 namespaceURI = attr.getNamespaceURI();
576 if (namespaceURI != null && namespaceURI.equals(XMLConstants.XMLNS_NS)) {
577
578 if (prefix == null && attr.getNodeName().equals(XMLConstants.XMLNS_PREFIX)) {
579
580 return value;
581 } else if (attrPrefix != null && attrPrefix.equals(XMLConstants.XMLNS_PREFIX)
582 && attr.getLocalName().equals(prefix)) {
583
584 return value;
585 }
586 }
587 }
588 }
589
590 Element ancestor = getElementAncestor(startingElement);
591
592 if (ancestor != null) {
593 return lookupNamespaceURI(ancestor, stopingElement, prefix);
594 }
595
596 return null;
597 }
598
599
600
601
602
603
604
605
606
607
608
609
610
611 public static String lookupPrefix(Element startingElement, String namespaceURI) {
612 return lookupPrefix(startingElement, null, namespaceURI);
613 }
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628 public static String lookupPrefix(Element startingElement, Element stopingElement, String namespaceURI) {
629 String namespace;
630
631 if (startingElement == stopingElement) {
632 return null;
633 }
634
635
636 if (startingElement.hasAttributes()) {
637 NamedNodeMap map = startingElement.getAttributes();
638 int length = map.getLength();
639 for (int i = 0; i < length; i++) {
640 Node attr = map.item(i);
641 String attrPrefix = attr.getPrefix();
642 String value = attr.getNodeValue();
643 namespace = attr.getNamespaceURI();
644 if (namespace != null && namespace.equals(XMLConstants.XMLNS_NS)) {
645
646 if (attr.getNodeName().equals(XMLConstants.XMLNS_PREFIX)
647 || (attrPrefix != null && attrPrefix.equals(XMLConstants.XMLNS_PREFIX))
648 && value.equals(namespaceURI)) {
649
650 String localname = attr.getLocalName();
651 String foundNamespace = startingElement.lookupNamespaceURI(localname);
652 if (foundNamespace != null && foundNamespace.equals(namespaceURI)) {
653 return localname;
654 }
655 }
656
657 }
658 }
659 }
660 Element ancestor = getElementAncestor(startingElement);
661
662 if (ancestor != null) {
663 return lookupPrefix(ancestor, stopingElement, namespaceURI);
664 }
665 return null;
666 }
667
668
669
670
671
672
673
674
675
676
677
678 public static List<Element> getChildElementsByTagNameNS(Element root, String namespaceURI, String localName) {
679 ArrayList<Element> children = new ArrayList<Element>();
680 NodeList childNodes = root.getChildNodes();
681
682 int numOfNodes = childNodes.getLength();
683 Node childNode;
684 Element e;
685 for (int i = 0; i < numOfNodes; i++) {
686 childNode = childNodes.item(i);
687 if (childNode.getNodeType() == Node.ELEMENT_NODE) {
688 e = (Element) childNode;
689 if (e.getNamespaceURI().equals(namespaceURI) && e.getLocalName().equals(localName)) {
690 children.add(e);
691 }
692 }
693 }
694
695 return children;
696 }
697
698
699
700
701
702
703
704
705
706
707 public static List<Element> getChildElementsByTagName(Element root, String localName) {
708 ArrayList<Element> children = new ArrayList<Element>();
709 NodeList childNodes = root.getChildNodes();
710
711 int numOfNodes = childNodes.getLength();
712 Node childNode;
713 Element e;
714 for (int i = 0; i < numOfNodes; i++) {
715 childNode = childNodes.item(i);
716 if (childNode.getNodeType() == Node.ELEMENT_NODE) {
717 e = (Element) childNode;
718 if (e.getLocalName().equals(localName)) {
719 children.add(e);
720 }
721 }
722 }
723
724 return children;
725 }
726
727
728
729
730
731
732
733
734 public static Map<QName, List<Element>> getChildElements(Element root) {
735 Map<QName, List<Element>> children = new HashMap<QName, List<Element>>();
736 NodeList childNodes = root.getChildNodes();
737
738 int numOfNodes = childNodes.getLength();
739 Node childNode;
740 Element e;
741 QName qname;
742 List<Element> elements;
743 for (int i = 0; i < numOfNodes; i++) {
744 childNode = childNodes.item(i);
745 if (childNode.getNodeType() == Node.ELEMENT_NODE) {
746 e = (Element) childNode;
747 qname = getNodeQName(e);
748 elements = children.get(qname);
749 if (elements == null) {
750 elements = new ArrayList<Element>();
751 children.put(qname, elements);
752 }
753
754 elements.add(e);
755 }
756 }
757
758 return children;
759 }
760
761
762
763
764
765
766
767
768 public static Element getElementAncestor(Node currentNode) {
769 Node parent = currentNode.getParentNode();
770 if (parent != null) {
771 short type = parent.getNodeType();
772 if (type == Node.ELEMENT_NODE) {
773 return (Element) parent;
774 }
775 return getElementAncestor(parent);
776 }
777 return null;
778 }
779
780
781
782
783
784
785
786
787 public static String nodeToString(Node node) {
788 StringWriter writer = new StringWriter();
789 writeNode(node, writer);
790 return writer.toString();
791 }
792
793
794
795
796
797
798
799
800 public static String prettyPrintXML(Node node) {
801 TransformerFactory tfactory = TransformerFactory.newInstance();
802 Transformer serializer;
803 try {
804 serializer = tfactory.newTransformer();
805 serializer.setOutputProperty(OutputKeys.INDENT, "yes");
806 serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3");
807
808 StringWriter output = new StringWriter();
809 serializer.transform(new DOMSource(node.getOwnerDocument()), new StreamResult(output));
810 return output.toString();
811 } catch (TransformerException e) {
812
813 e.printStackTrace();
814
815 throw new RuntimeException(e);
816 }
817 }
818
819
820
821
822
823
824
825
826 public static void writeNode(Node node, Writer output) {
827 DOMImplementation domImpl = node.getOwnerDocument().getImplementation();
828 DOMImplementationLS domImplLS = (DOMImplementationLS) domImpl.getFeature("LS", "3.0");
829 LSSerializer serializer = domImplLS.createLSSerializer();
830
831 LSOutput serializerOut = domImplLS.createLSOutput();
832 serializerOut.setCharacterStream(output);
833
834 serializer.write(node, serializerOut);
835 }
836
837
838
839
840
841
842
843
844 public static String qnameToContentString(QName qname) {
845 StringBuffer buf = new StringBuffer();
846
847 if (qname.getPrefix() != null) {
848 buf.append(qname.getPrefix());
849 buf.append(":");
850 }
851 buf.append(qname.getLocalPart());
852 return buf.toString();
853 }
854
855
856
857
858
859
860
861
862
863
864
865 public static void rootNamespaces(Element domElement) throws XMLParserException {
866 rootNamespaces(domElement, domElement);
867 }
868
869
870
871
872
873
874
875
876
877
878 private static void rootNamespaces(Element domElement, Element upperNamespaceSearchBound) throws XMLParserException {
879 String namespaceURI = null;
880 String namespacePrefix = null;
881
882
883 namespacePrefix = domElement.getPrefix();
884 if (DatatypeHelper.isEmpty(namespacePrefix)) {
885 namespaceURI = lookupNamespaceURI(upperNamespaceSearchBound, "");
886 } else {
887 namespaceURI = lookupNamespaceURI(domElement, upperNamespaceSearchBound, namespacePrefix);
888 }
889
890 if (namespaceURI == null) {
891 namespaceURI = lookupNamespaceURI(upperNamespaceSearchBound, null, namespacePrefix);
892 if (namespaceURI == null) {
893 throw new XMLParserException("Unable to resolve namespace prefix " + namespacePrefix
894 + " found on element " + getNodeQName(domElement));
895 }
896
897 appendNamespaceDeclaration(domElement, namespaceURI, namespacePrefix);
898 }
899
900
901 NamedNodeMap attributes = domElement.getAttributes();
902 Node attributeNode;
903 for (int i = 0; i < attributes.getLength(); i++) {
904 namespacePrefix = null;
905 namespaceURI = null;
906 attributeNode = attributes.item(i);
907
908
909 if (attributeNode.getNodeType() != Node.ATTRIBUTE_NODE) {
910 continue;
911 }
912
913 namespacePrefix = attributeNode.getPrefix();
914 if (!DatatypeHelper.isEmpty(namespacePrefix)) {
915
916
917 if (namespacePrefix.equals(XMLConstants.XMLNS_PREFIX)
918 || namespacePrefix.equals(XMLConstants.XML_PREFIX)) {
919 continue;
920 }
921
922
923 namespaceURI = lookupNamespaceURI(domElement, upperNamespaceSearchBound, namespacePrefix);
924 if (namespaceURI == null) {
925 namespaceURI = lookupNamespaceURI(upperNamespaceSearchBound, null, namespacePrefix);
926 if (namespaceURI == null) {
927 throw new XMLParserException("Unable to resolve namespace prefix " + namespacePrefix
928 + " found on attribute " + getNodeQName(attributeNode) + " found on element "
929 + getNodeQName(domElement));
930 }
931
932 appendNamespaceDeclaration(domElement, namespaceURI, namespacePrefix);
933 }
934 }
935 }
936
937
938
939 NodeList childNodes = domElement.getChildNodes();
940 Node childNode;
941 for (int i = 0; i < childNodes.getLength(); i++) {
942 childNode = childNodes.item(i);
943 if (childNode.getNodeType() == Node.ELEMENT_NODE) {
944 rootNamespaces((Element) childNode, upperNamespaceSearchBound);
945 }
946 }
947 }
948
949
950
951
952
953
954
955
956
957 public static boolean isElementNamed(Element e, String ns, String localName) {
958 return e != null && DatatypeHelper.safeEquals(ns, e.getNamespaceURI())
959 && DatatypeHelper.safeEquals(localName, e.getLocalName());
960 }
961
962
963
964
965
966
967
968 public static Element getFirstChildElement(Node n) {
969 Node child = n.getFirstChild();
970 while (child != null && child.getNodeType() != Node.ELEMENT_NODE) {
971 child = child.getNextSibling();
972 }
973
974 if (child != null) {
975 return (Element) child;
976 } else {
977 return null;
978 }
979 }
980
981
982
983
984
985
986
987 public static Element getNextSiblingElement(Node n) {
988 Node sib = n.getNextSibling();
989 while (sib != null && sib.getNodeType() != Node.ELEMENT_NODE) {
990 sib = sib.getNextSibling();
991 }
992
993 if (sib != null) {
994 return (Element) sib;
995 } else {
996 return null;
997 }
998 }
999
1000
1001
1002
1003
1004
1005
1006
1007 public static long durationToLong(String duration) {
1008 Duration xmlDuration = getDataTypeFactory().newDuration(duration);
1009 return xmlDuration.getTimeInMillis(new GregorianCalendar());
1010 }
1011
1012
1013
1014
1015
1016
1017
1018
1019 public static String longToDuration(long duration) {
1020 Duration xmlDuration = getDataTypeFactory().newDuration(duration);
1021 return xmlDuration.toString();
1022 }
1023 }