View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.model;
17  
18  import org.w3c.dom.*;
19  
20  import java.util.HashMap;
21  import java.util.HashSet;
22  import java.util.Map;
23  import java.util.Set;
24  
25  /**
26   * XML handling and printing shortcuts.
27   * <p>
28   * This class exists because Android 2.1 does not offer any way to print an <code>org.w3c.dom.Document</code>,
29   * and it also doesn't implement the most trivial methods to build a DOM (although the API is provided, they
30   * fail at runtime). We might be able to remove this class once compatibility for Android 2.1 can be
31   * dropped.
32   * </p>
33   *
34   * @author Christian Bauer
35   */
36  public class XMLUtil {
37  
38      /* TODO: How it should be done (nice API, eh?)
39      public static String documentToString(Document document) throws Exception {
40          TransformerFactory transFactory = TransformerFactory.newInstance();
41          transFactory.setAttribute("indent-number", 4);
42          Transformer transformer = transFactory.newTransformer();
43          transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
44          transformer.setOutputProperty(OutputKeys.INDENT, "yes");
45          transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
46          StringWriter out = new StringWriter();
47          transformer.transform(new DOMSource(d), new StreamResult(out));
48          return out.toString();
49      }
50      */
51  
52      // TODO: Evil methods to print XML on Android 2.1 (there is no TransformerFactory)
53  
54      public static String documentToString(Document document) throws Exception {
55          return documentToString(document, true);
56      }
57  
58      public static String documentToString(Document document, boolean standalone) throws Exception {
59          String prol = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"" + (standalone ? "yes" : "no") + "\"?>";
60          return prol + nodeToString(document.getDocumentElement(), new HashSet<String>(), document.getDocumentElement().getNamespaceURI());
61      }
62  
63      public static String documentToFragmentString(Document document) throws Exception {
64          return nodeToString(document.getDocumentElement(), new HashSet<String>(), document.getDocumentElement().getNamespaceURI());
65      }
66  
67      protected static String nodeToString(Node node, Set<String> parentPrefixes, String namespaceURI) throws Exception {
68          StringBuilder b = new StringBuilder();
69  
70          if (node == null) {
71              return "";
72          }
73  
74          if (node instanceof Element) {
75              Element element = (Element) node;
76              b.append("<");
77              b.append(element.getNodeName());
78  
79              Map<String, String> thisLevelPrefixes = new HashMap<>();
80              if (element.getPrefix() != null && !parentPrefixes.contains(element.getPrefix())) {
81                  thisLevelPrefixes.put(element.getPrefix(), element.getNamespaceURI());
82              }
83  
84              if (element.hasAttributes()) {
85                  NamedNodeMap map = element.getAttributes();
86                  for (int i = 0; i < map.getLength(); i++) {
87                      Node attr = map.item(i);
88                      if (attr.getNodeName().startsWith("xmlns")) continue;
89                      if (attr.getPrefix() != null && !parentPrefixes.contains(attr.getPrefix())) {
90                          thisLevelPrefixes.put(attr.getPrefix(), element.getNamespaceURI());
91                      }
92                      b.append(" ");
93                      b.append(attr.getNodeName());
94                      b.append("=\"");
95                      b.append(attr.getNodeValue());
96                      b.append("\"");
97                  }
98              }
99  
100             if (namespaceURI != null && !thisLevelPrefixes.containsValue(namespaceURI) &&
101                     !namespaceURI.equals(element.getParentNode().getNamespaceURI())) {
102                 b.append(" xmlns=\"").append(namespaceURI).append("\"");
103             }
104 
105             for (Map.Entry<String, String> entry : thisLevelPrefixes.entrySet()) {
106                 b.append(" xmlns:").append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"");
107                 parentPrefixes.add(entry.getKey());
108             }
109 
110             NodeList children = element.getChildNodes();
111             boolean hasOnlyAttributes = true;
112             for (int i = 0; i < children.getLength(); i++) {
113                 Node child = children.item(i);
114                 if (child.getNodeType() != Node.ATTRIBUTE_NODE) {
115                     hasOnlyAttributes = false;
116                     break;
117                 }
118             }
119             if (!hasOnlyAttributes) {
120                 b.append(">");
121                 for (int i = 0; i < children.getLength(); i++) {
122                     b.append(nodeToString(children.item(i), parentPrefixes, children.item(i).getNamespaceURI()));
123                 }
124                 b.append("</");
125                 b.append(element.getNodeName());
126                 b.append(">");
127             } else {
128                 b.append("/>");
129             }
130 
131             for (String thisLevelPrefix : thisLevelPrefixes.keySet()) {
132                 parentPrefixes.remove(thisLevelPrefix);
133             }
134 
135         } else if (node.getNodeValue() != null) {
136             b.append(encodeText(node.getNodeValue(), node instanceof Attr));
137         }
138 
139         return b.toString();
140     }
141 
142     public static String encodeText(String s) {
143         return encodeText(s, true);
144     }
145 
146     public static String encodeText(String s, boolean encodeQuotes) {
147         s = s.replaceAll("&", "&amp;");
148         s = s.replaceAll("<", "&lt;");
149         s = s.replaceAll(">", "&gt;");
150         if(encodeQuotes) {
151         	s = s.replaceAll("'", "&apos;");
152         	s = s.replaceAll("\"", "&quot;");
153         }
154         return s;
155     }
156 
157     public static Element appendNewElement(Document document, Element parent, Enum el) {
158         return appendNewElement(document, parent, el.toString());
159     }
160 
161     public static Element appendNewElement(Document document, Element parent, String element) {
162         Element child = document.createElement(element);
163         parent.appendChild(child);
164         return child;
165     }
166 
167     public static Element appendNewElementIfNotNull(Document document, Element parent, Enum el, Object content) {
168         return appendNewElementIfNotNull(document, parent, el, content, null);
169     }
170 
171     public static Element appendNewElementIfNotNull(Document document, Element parent, Enum el, Object content, String namespace) {
172         return appendNewElementIfNotNull(document, parent, el.toString(), content, namespace);
173     }
174 
175     public static Element appendNewElementIfNotNull(Document document, Element parent, String element, Object content) {
176         return appendNewElementIfNotNull(document, parent, element, content, null);
177     }
178 
179     public static Element appendNewElementIfNotNull(Document document, Element parent, String element, Object content, String namespace) {
180         if (content == null) return parent;
181         return appendNewElement(document, parent, element, content, namespace);
182     }
183 
184     public static Element appendNewElement(Document document, Element parent, String element, Object content) {
185         return appendNewElement(document, parent, element, content, null);
186     }
187 
188     public static Element appendNewElement(Document document, Element parent, String element, Object content, String namespace) {
189         Element childElement;
190         if (namespace != null) {
191             childElement = document.createElementNS(namespace, element);
192         } else {
193             childElement = document.createElement(element);
194         }
195 
196         if (content != null) {
197             // TODO: We'll have that on Android 2.2:
198             // childElement.setTextContent(content.toString());
199             // Meanwhile:
200             childElement.appendChild(document.createTextNode(content.toString()));
201         }
202 
203         parent.appendChild(childElement);
204         return childElement;
205     }
206 
207     // TODO: Of course, there is no Element.getTextContent() either...
208     public static String getTextContent(Node node) {
209         StringBuffer buffer = new StringBuffer();
210         NodeList childList = node.getChildNodes();
211         for (int i = 0; i < childList.getLength(); i++) {
212             Node child = childList.item(i);
213             if (child.getNodeType() != Node.TEXT_NODE)
214                 continue; // skip non-text nodes
215             buffer.append(child.getNodeValue());
216         }
217         return buffer.toString();
218     }
219 
220 }