001 /*
002 * Java GPX Library (jpx-3.1.0).
003 * Copyright (c) 2016-2023 Franz Wilhelmstötter
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 * Author:
018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019 */
020 package io.jenetics.jpx;
021
022 import static java.lang.String.format;
023 import static java.nio.charset.StandardCharsets.UTF_8;
024 import static javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION;
025 import static javax.xml.transform.OutputKeys.VERSION;
026
027 import java.io.ByteArrayInputStream;
028 import java.io.ByteArrayOutputStream;
029 import java.io.IOException;
030 import java.io.OutputStream;
031 import java.io.UncheckedIOException;
032 import java.util.ArrayList;
033 import java.util.List;
034 import java.util.Objects;
035
036 import javax.xml.parsers.DocumentBuilder;
037 import javax.xml.parsers.ParserConfigurationException;
038 import javax.xml.stream.XMLStreamException;
039 import javax.xml.stream.XMLStreamReader;
040 import javax.xml.stream.XMLStreamWriter;
041 import javax.xml.transform.Result;
042 import javax.xml.transform.Source;
043 import javax.xml.transform.Transformer;
044 import javax.xml.transform.TransformerException;
045 import javax.xml.transform.TransformerFactory;
046 import javax.xml.transform.dom.DOMResult;
047 import javax.xml.transform.dom.DOMSource;
048 import javax.xml.transform.stax.StAXResult;
049 import javax.xml.transform.stax.StAXSource;
050 import javax.xml.transform.stream.StreamResult;
051 import javax.xml.transform.stream.StreamSource;
052
053 import org.w3c.dom.DOMException;
054 import org.w3c.dom.Document;
055 import org.w3c.dom.Element;
056 import org.w3c.dom.NamedNodeMap;
057 import org.w3c.dom.Node;
058 import org.w3c.dom.NodeList;
059
060 /**
061 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
062 * @version 1.5
063 * @since 1.5
064 */
065 final class XML {
066
067 private static final class TFHolder {
068 private static final TFHolder INSTANCE = new TFHolder();
069
070 final TransformerFactory factory;
071
072 private TFHolder() {
073 factory = TransformerFactory.newInstance();
074 }
075 }
076
077 private XML() {
078 }
079
080
081 private static void __copy(final Source source, final Result sink)
082 throws XMLStreamException
083 {
084 try {
085 final Transformer transformer = TFHolder.INSTANCE.factory
086 .newTransformer();
087
088 transformer.setOutputProperty(OMIT_XML_DECLARATION, "yes");
089 transformer.setOutputProperty(VERSION, "1.0");
090
091 transformer.transform(source, sink);
092 } catch (TransformerException e) {
093 throw new XMLStreamException(e);
094 }
095 }
096
097 static void copy(final XMLStreamReader source, final Document sink)
098 throws XMLStreamException
099 {
100 __copy(new StAXSource(source), new DOMResult(sink));
101 }
102
103 static void copy(final Node source, final XMLStreamWriter sink)
104 throws XMLStreamException
105 {
106 __copy(new DOMSource(source), new StAXResult(sink));
107 }
108
109 static void copy(final Node source, final OutputStream sink)
110 throws IOException
111 {
112 try {
113 __copy(new DOMSource(source), new StreamResult(sink));
114 } catch (XMLStreamException e) {
115 throw new IOException(e);
116 }
117 }
118
119 static String toString(final Node source) {
120 final ByteArrayOutputStream out = new ByteArrayOutputStream();
121 try {
122 copy(source, out);
123 } catch (IOException e) {
124 throw new UncheckedIOException(e);
125 }
126 return out.toString();
127 }
128
129 static DocumentBuilder builder()
130 throws XMLStreamException
131 {
132 try {
133 return XMLProvider
134 .provider()
135 .documentBuilderFactory()
136 .newDocumentBuilder();
137 } catch (ParserConfigurationException e) {
138 throw new XMLStreamException(e);
139 }
140 }
141
142 static Document parse(final String xml) {
143 try {
144 final Document doc = builder().newDocument();
145
146 final ByteArrayInputStream in = new ByteArrayInputStream(xml.getBytes(UTF_8));
147 __copy(new StreamSource(in), new DOMResult(doc));
148 return clean(doc);
149 } catch (Exception e) {
150 throw new IllegalArgumentException(e);
151 }
152 }
153
154 static Document clone(final Document doc) {
155 if (doc == null) return null;
156
157 try {
158 final Transformer transformer = TFHolder.INSTANCE.factory
159 .newTransformer();
160
161 final DOMSource source = new DOMSource(doc);
162 final DOMResult result = new DOMResult();
163 transformer.transform(source, result);
164 return (Document)result.getNode();
165 } catch (TransformerException e) {
166 throw (DOMException)
167 new DOMException(DOMException.NOT_SUPPORTED_ERR, e.getMessage())
168 .initCause(e);
169 }
170 }
171
172 static boolean equals(final Node n1, final Node n2) {
173 if (n1 == n2) return true;
174 if (n1 == null || n2 == null) return false;
175 if (!Objects.equals(n1.getNodeValue(), n2.getNodeValue())) return false;
176 if (!equals(n1.getAttributes(), n2.getAttributes())) return false;
177
178 final NodeList nl1 = n1.getChildNodes();
179 final NodeList nl2 = n2.getChildNodes();
180 if (nl1.getLength() != nl2.getLength()) return false;
181 for (int i = 0; i < nl1.getLength(); ++i) {
182 if (!equals(nl1.item(i), nl2.item(i))) return false;
183 }
184
185 return true;
186 }
187
188 private static boolean equals(final NamedNodeMap a1, final NamedNodeMap a2) {
189 if (a1 == null && a2 == null) return true;
190 if (a1 == null || a2 == null) return false;
191 if (a1.getLength() != a2.getLength()) return false;
192
193 for (int i = 0; i < a1.getLength(); ++i) {
194 final String name = a1.item(i).getNodeName();
195 if (!"xmlns".equals(name)) {
196 final String v1 = a1.item(i).getNodeValue();
197 final String v2 = a2.getNamedItem(a1.item(i).getNodeName()).getNodeValue();
198
199 if (!Objects.equals(v1, v2)) return false;
200 }
201 }
202
203 return true;
204 }
205
206 private static boolean isEmpty(final Document doc) {
207 return doc == null ||
208 doc.getDocumentElement().getChildNodes().getLength() == 0;
209 }
210
211 private static <T extends Node> T clean(final T node) {
212 if (node == null) return null;
213
214 node.normalize();
215 final List<Node> remove = new ArrayList<>();
216 clean(node, remove);
217 for (Node n : remove) {
218 if (n.getParentNode() != null) {
219 n.getParentNode().removeChild(n);
220 }
221 }
222
223 return node;
224 }
225
226 private static void clean(final Node node, final List<Node> remove) {
227 if (node.getNodeType() == Node.TEXT_NODE
228 && isEmpty(node.getTextContent()))
229 {
230 remove.add(node);
231 }
232
233 final NodeList list = node.getChildNodes();
234 for (int i = 0; i < list.getLength(); i++) {
235 clean(list.item(i), remove);
236 }
237 }
238
239 private static boolean isEmpty(final String text) {
240 if (text == null) return true;
241 if (text.isEmpty()) return true;
242 for (int i = 0; i < text.length(); ++i) {
243 if (!Character.isWhitespace(text.charAt(i))) {
244 return false;
245 }
246 }
247 return true;
248 }
249
250 static Document removeNS(final Document doc) {
251 if (doc == null) return null;
252
253 final Node root = doc.getDocumentElement();
254 final Element newRoot = doc.createElement(root.getNodeName());
255
256 final NodeList children = root.getChildNodes();
257 for (int i = 0; i < children.getLength(); ++i) {
258 newRoot.appendChild(children.item(i).cloneNode(true));
259 }
260
261 doc.replaceChild(newRoot, root);
262 return doc;
263 }
264
265 static Document extensions(final Document extensions) {
266 final Document doc = XML.clean(extensions);
267 return XML.isEmpty(doc) ? null : doc;
268 }
269
270 static Document checkExtensions(final Document extensions) {
271 if (extensions != null) {
272 final Element root = extensions.getDocumentElement();
273
274 if (root == null) {
275 throw new IllegalArgumentException(
276 "'extensions' has no document element."
277 );
278 }
279
280 if (!"extensions".equals(root.getNodeName())) {
281 throw new IllegalArgumentException(format(
282 "Expected 'extensions' root element, but got '%s'.",
283 root.getNodeName()
284 ));
285 }
286
287 if (root.getNamespaceURI() != null) {
288 final String ns = root.getNamespaceURI();
289 if (!ns.isEmpty() &&
290 !ns.startsWith("http://www.topografix.com/GPX/1/1") &&
291 !ns.startsWith("http://www.topografix.com/GPX/1/0"))
292 {
293 throw new IllegalArgumentException(format(
294 "Invalid document namespace: '%s'.", ns
295 ));
296 }
297 }
298 }
299
300 return extensions;
301 }
302
303 }
|