[Aprog]
[aprog.git] / Aprog / src / net / sourceforge / aprog / xml / XMLTools.java
blob271d70cc2f9487f78ebcfdfa12f236867bc72821
1 /*
2 * The MIT License
3 *
4 * Copyright 2010 Codist Monk.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
25 package net.sourceforge.aprog.xml;
27 import java.io.IOException;
28 import java.util.logging.Logger;
29 import javax.xml.transform.TransformerConfigurationException;
30 import javax.xml.transform.TransformerException;
31 import static net.sourceforge.aprog.tools.Tools.*;
33 import java.io.ByteArrayInputStream;
34 import java.io.ByteArrayOutputStream;
35 import java.io.File;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.logging.Level;
41 import javax.xml.XMLConstants;
42 import javax.xml.namespace.QName;
44 import javax.xml.parsers.DocumentBuilderFactory;
45 import javax.xml.parsers.ParserConfigurationException;
46 import javax.xml.parsers.SAXParserFactory;
47 import javax.xml.transform.OutputKeys;
48 import javax.xml.transform.Result;
49 import javax.xml.transform.Source;
50 import javax.xml.transform.Transformer;
51 import javax.xml.transform.TransformerFactory;
52 import javax.xml.transform.dom.DOMSource;
53 import javax.xml.transform.stream.StreamResult;
54 import javax.xml.transform.stream.StreamSource;
55 import javax.xml.validation.SchemaFactory;
56 import javax.xml.validation.Validator;
57 import javax.xml.xpath.XPathConstants;
58 import javax.xml.xpath.XPathFactory;
60 import net.sourceforge.aprog.tools.IllegalInstantiationException;
62 import org.w3c.dom.Document;
63 import org.w3c.dom.Node;
64 import org.w3c.dom.NodeList;
65 import org.xml.sax.InputSource;
66 import org.xml.sax.SAXException;
67 import org.xml.sax.SAXParseException;
68 import org.xml.sax.helpers.DefaultHandler;
70 /**
72 * @author codistmonk (creation 2010-07-02)
74 public final class XMLTools {
76 /**
77 * @throws IllegalInstantiationException To prevent instantiation
79 private XMLTools() {
80 throw new IllegalInstantiationException();
83 public static final String XML_1_UTF8_NOT_STANDALONE =
84 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>";
86 public static final String XML_1_UTF8_STANDALONE =
87 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
89 /**
91 * @param xmlInput
92 * <br>Not null
93 * @return
94 * <br>Not null
95 * <br>New
96 * @throws RuntimeException if an error occurs
98 public static final Document parse(final String xmlInput) {
99 return parse(new ByteArrayInputStream(xmlInput.getBytes()));
104 * @param xmlInputStream
105 * <br>Not null
106 * <br>Input-output
107 * @return
108 * <br>Not null
109 * <br>New
110 * @throws RuntimeException if an error occurs
112 public static final Document parse(final InputStream xmlInputStream) {
113 return parse(new InputSource(xmlInputStream));
118 * @param inputSource
119 * <br>Not null
120 * <br>Input-output
121 * @return
122 * <br>Not null
123 * <br>New
124 * @throws RuntimeException if an error occurs
126 public static final Document parse(final InputSource inputSource) {
127 try {
128 return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputSource);
129 } catch (final Exception exception) {
130 throw unchecked(exception);
136 * @return
137 * <br>Not null
138 * <br>New
140 public static final Document newDocument() {
141 try {
142 final Document result = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
144 result.setXmlStandalone(true);
146 return result;
147 } catch (final ParserConfigurationException exception) {
148 throw unchecked(exception);
154 * @param document
155 * <br>Not null
156 * <br>Input-output
157 * @return {@code document}
158 * <br>Not null
160 public static final Document normalize(final Document document) {
161 document.normalize();
163 return document;
168 * @param document
169 * <br>Not null
170 * <br>Input-output
171 * @return {@code document}
172 * <br>Not null
174 public static final Document standalone(final Document document) {
175 document.setXmlStandalone(true);
177 return document;
182 * @param node
183 * <br>Not null
184 * @param outputFile
185 * <br>Not null
186 * <br>Input-output
187 * @param indent
188 * <br>Range: {@code [0 .. Integer.MAX_VALUE]}
190 public static final void write(final Node node, final File outputFile, final int indent) {
191 write(node, new StreamResult(outputFile.getAbsolutePath()), indent);
196 * @param node
197 * <br>Not null
198 * @param output
199 * <br>Not null
200 * <br>Input-output
201 * @param indent
202 * <br>Range: {@code [0 .. Integer.MAX_VALUE]}
204 public static final void write(final Node node, final OutputStream output, final int indent) {
205 write(node, new StreamResult(output), indent);
210 * @param node
211 * <br>Not null
212 * @param output
213 * <br>Not null
214 * <br>Input-output
215 * @param indent
216 * <br>Range: {@code [0 .. Integer.MAX_VALUE]}
218 public static final void write(final Node node, final Result output, final int indent) {
219 try {
220 final TransformerFactory transformerFactory = TransformerFactory.newInstance();
222 transformerFactory.setAttribute("indent-number", indent);
224 final Transformer transformer = transformerFactory.newTransformer();
226 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
227 transformer.setOutputProperty(OutputKeys.INDENT, indent != 0 ? "yes" : "no");
228 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, node instanceof Document ? "no" : "yes");
229 transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
231 transformer.transform(new DOMSource(node), output);
232 } catch (final Exception exception) {
233 throw unchecked(exception);
239 * @param resourceName
240 * <br>Not null
241 * @return
242 * <br>Not null
243 * <br>New
245 public static final Source getResourceAsSource(final String resourceName) {
246 final ClassLoader classLoader = getCallerClass().getClassLoader();
247 final Source result = new StreamSource(classLoader.getResourceAsStream(resourceName));
249 result.setSystemId(classLoader.getResource(resourceName).toString());
251 return result;
256 * @param nodeList
257 * <br>Not null
258 * @return
259 * <br>Not null
260 * <br>New
262 public static final List<Node> newList(final NodeList nodeList) {
263 final List<Node> result = new ArrayList<Node>();
265 for (int i = 0; i < nodeList.getLength(); ++i) {
266 result.add(nodeList.item(i));
269 return result;
274 * @param xmlInputStream IN NOT_NULL
275 * @param dtdOrSchema IN NOT_NULL
276 * @return NEW NOT_NULL
278 public static final List<Throwable> validate(final InputStream xmlInputStream, final Source dtdOrSchema) {
279 final List<Throwable> exceptions = new ArrayList<Throwable>();
280 final String schemaLanguage = getSchemaLanguage(dtdOrSchema);
282 try {
283 if (schemaLanguage != null) {
284 final Validator validator = SchemaFactory.newInstance(schemaLanguage).newSchema(dtdOrSchema).newValidator();
286 validator.validate(new StreamSource(xmlInputStream));
287 } else {
288 final Transformer addDoctypeInformation = TransformerFactory.newInstance().newTransformer();
290 addDoctypeInformation.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, dtdOrSchema.getSystemId());
292 final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
294 addDoctypeInformation.transform(new StreamSource(xmlInputStream), new StreamResult(buffer));
296 final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
298 saxParserFactory.setValidating(true);
299 saxParserFactory.newSAXParser().parse(new ByteArrayInputStream(buffer.toByteArray()), new DefaultHandler() {
301 @Override
302 public final void error(final SAXParseException exception) throws SAXException {
303 exceptions.add(exception);
305 getLoggerForThisMethod().log(Level.WARNING, "", exceptions);
308 @Override
309 public final void fatalError(final SAXParseException exception) throws SAXException {
310 exceptions.add(exception);
312 getLoggerForThisMethod().log(Level.WARNING, "", exceptions);
314 throw exception;
317 @Override
318 public final void warning(final SAXParseException exception) throws SAXException {
319 getLoggerForThisMethod().log(Level.WARNING, "", exceptions);
324 } catch (final TransformerConfigurationException exception) {
325 throw unchecked(exception);
326 } catch (final TransformerException exception) {
327 exceptions.add(exception);
328 } catch (final SAXException exception) {
329 exceptions.add(exception);
330 } catch (final Exception exception) {
331 throw unchecked(exception);
334 return exceptions;
339 * @param dtdOrSchema
340 * <br>Not null
341 * @return {@code null} for a DTD
342 * <br>Maybe null
343 * <br>Range: { {@code null}, {@link XMLConstants#W3C_XML_SCHEMA_NS_URI}, {@link XMLConstants#RELAXNG_NS_URI} }
344 * @throws IllegalArgumentException If {@code dtdOrSchema}'s system id does not indicate a DTD or a schema (XSD or RNG)
346 public static final String getSchemaLanguage(final Source dtdOrSchema) {
347 final String dtdOrSchemaId = dtdOrSchema.getSystemId().toLowerCase();
349 if (dtdOrSchemaId.endsWith(".xsd")) {
350 return XMLConstants.W3C_XML_SCHEMA_NS_URI;
351 } else if (dtdOrSchemaId.endsWith(".rng") || dtdOrSchemaId.endsWith(".rnc")) {
352 return XMLConstants.RELAXNG_NS_URI;
353 } else if (dtdOrSchemaId.endsWith(".dtd")) {
354 return null;
357 throw new IllegalArgumentException("Unsupported extension for " + dtdOrSchema +
358 " (the name must end with .xsd, .rng, .rnc or .dtd (case-insensitive))");
363 * @param context
364 * <br>Maybe null
365 * @param xPath
366 * <br>Not null
367 * @return
368 * <br>Maybe null
369 * <br>Not New
371 public static final Node getNode(final Node context, final String xPath) {
372 return getNode(context, xPath);
377 * @param context
378 * <br>Not null
379 * @param xPath
380 * <br>Not null
381 * @return
382 * <br>Maybe null
383 * <br>Maybe New
385 public static final Node getOrCreateNode(final Node context, final String xPath) {
386 return getOrCreateNode(context, xPath);
391 * @param context
392 * <br>Maybe null
393 * @param xPath
394 * <br>Not null
395 * @return
396 * <br>Maybe null
397 * <br>Not New
399 public static final NodeList getNodes(final Node context, final String xPath) {
400 return getNodes(context, xPath);
405 * @param <N> The expected node type
406 * @param context
407 * <br>Maybe null
408 * @param xPath
409 * <br>Not null
410 * @return
411 * <br>Maybe null
412 * <br>Not New
414 @SuppressWarnings("unchecked")
415 public static final <N> N getNode(final Object context, final String xPath) {
416 return (N) get(context, xPath, XPathConstants.NODE);
421 * @param <S> The expected node set type
422 * @param context
423 * <br>Maybe null
424 * @param xPath
425 * <br>Not null
426 * @return
427 * <br>Maybe null
428 * <br>Not New
430 @SuppressWarnings("unchecked")
431 public static final <S> S getNodes(final Object context, final String xPath) {
432 return (S) get(context, xPath, XPathConstants.NODESET);
437 * @param context
438 * <br>Maybe null
439 * @param xPath
440 * <br>Not null
441 * @return
442 * <br>Maybe null
443 * <br>Not New
445 public static final Boolean getBoolean(final Object context, final String xPath) {
446 return (Boolean) get(context, xPath, XPathConstants.BOOLEAN);
451 * @param <T> The expected return type
452 * @param context
453 * <br>Maybe null
454 * @param xPath
455 * <br>Not null
456 * @param returnType
457 * <br>Not null
458 * <br>Range: {
459 * {@link XPathConstants#BOOLEAN},
460 * {@link XPathConstants#NODE},
461 * {@link XPathConstants#NODESET},
462 * {@link XPathConstants#NUMBER},
463 * {@link XPathConstants#STRING}
465 * @return
466 * <br>Maybe null
467 * @throws RuntimeException If an error occurs
469 @SuppressWarnings("unchecked")
470 public static final <T> T get(final Object context, final String xPath, final QName returnType) {
471 try {
472 return (T) XPathFactory.newInstance().newXPath().compile(xPath)
473 .evaluate(context, returnType);
474 } catch (final Exception exception) {
475 throw unchecked(exception);
481 * @param <N> The expected node type
482 * @param context
483 * <br>Not null
484 * @param xPath
485 * <br>Not null
486 * @return
487 * <br>Maybe null
488 * <br>Maybe new
490 public static final <N> N getOrCreateNode(final Object context, final String xPath) {
491 return null; // TODO