/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2010, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.xml; import java.io.*; import javax.xml.transform.TransformerFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.Result; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.dom.DOMSource; import org.w3c.dom.Node; import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import static javax.xml.stream.XMLStreamReader.*; import javax.xml.stream.util.StreamReaderDelegate; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.stax.StAXSource; import org.geotoolkit.nio.IOUtilities; import org.w3c.dom.Document; import org.xml.sax.SAXException; /** * An abstract class for all stax parser.<br/> * Readers for a given specification should extend this class and * provide appropriate read methods.<br/> * <br/> * Example : <br/> * <pre> * {@code * public class UserReader extends StaxStreamReader{ * * public User read() throws XMLStreamException{ * //casual stax reading operations * return user; * } * } * } * </pre> * And should be used like :<br/> * <pre> * {@code * final UserReader instance = new UserReader(); * try{ * instance.setInput(stream); * user = instance.read(); * }finally{ * instance.dispose(); * } * </pre> * * @author Johann Sorel (Geomatys) * @module */ public abstract class StaxStreamReader extends AbstractConfigurable { protected XMLStreamReader reader; /** * Store the input stream if it was generated by the parser itself. * It will closed on the dispose method or when a new input is set. */ private InputStream sourceStream; public StaxStreamReader(){ } /** * close potentiel previous stream and cache if there are some. * This way the reader can be reused for a different input later. * The underlying stax reader will be closed. */ public void reset() throws IOException, XMLStreamException{ if(sourceStream != null){ sourceStream.close(); sourceStream = null; } if(reader != null){ reader.close(); reader = null; } } /** * Release potentiel locks or opened stream. * Must be called when the reader is not needed anymore. * It should not be used after this method has been called. */ public void dispose() throws IOException, XMLStreamException{ reset(); } /** * Set the input for this reader.<br/> * Handle types are :<br/> * - java.io.File<br/> * - java.io.Reader<br/> * - java.io.InputStream<br/> * - java.net.URL<br/> * - java.net.URI<br/> * - javax.xml.stream.XMLStreamReader<br/> * - javax.xml.transform.Source<br/> * * @param input * @throws IOException * @throws XMLStreamException */ public void setInput(Object input) throws IOException, XMLStreamException{ reset(); if(input instanceof XMLStreamReader){ reader = (XMLStreamReader) input; return; } if(input instanceof File){ sourceStream = new FileInputStream((File)input); input = sourceStream; }else if(input instanceof Path){ sourceStream = Files.newInputStream((Path)input, StandardOpenOption.READ); input = sourceStream; }else if(input instanceof URL){ sourceStream = ((URL)input).openStream(); input = sourceStream; }else if(input instanceof URI){ sourceStream = IOUtilities.open(input); input = sourceStream; }else if(input instanceof String){ input = new StringReader((String) input); } reader = toReader(input); } /** * Iterator on the reader until it reachs the end of the given tag name. * @param tagName tag name to search * @throws XMLStreamException */ protected void toTagEnd(final String tagName) throws XMLStreamException{ while (reader.hasNext()) { if(END_ELEMENT == reader.next() && tagName.equalsIgnoreCase(reader.getLocalName())) return; } throw new XMLStreamException("Error in xml file, Could not find end of tag "+tagName+" ."); } /** * Creates a new XMLStreamReader. * @param input * @return XMLStreamReader * @throws XMLStreamException if the input is not handled */ private static final XMLStreamReader toReader(final Object input) throws XMLStreamException { final XMLInputFactory XMLfactory = XMLInputFactory.newInstance(); XMLfactory.setProperty("http://java.sun.com/xml/stream/properties/report-cdata-event", Boolean.TRUE); XMLfactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.FALSE); if (input instanceof InputStream) { return XMLfactory.createXMLStreamReader((InputStream) input); } else if (input instanceof Source) { return XMLfactory.createXMLStreamReader((Source) input); } else if (input instanceof Node) { /* here we can think that we can use a DOMSource and pass it directly to the * method XMLfactory.createXMLStreamReader(Source) but it lead to a NPE * during the geometry unmarshall. */ try { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); Result outputTarget = new StreamResult(outputStream); Transformer t = TransformerFactory.newInstance().newTransformer(); t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); t.transform(new DOMSource((Node) input), outputTarget); return XMLfactory.createXMLStreamReader(new ByteArrayInputStream(outputStream.toByteArray())); } catch (TransformerException ex) { throw new XMLStreamException(ex); } } else if (input instanceof Reader) { return XMLfactory.createXMLStreamReader((Reader) input); } else { throw new XMLStreamException("Input type is not supported : " + input); } } /** * <p>XML language provides two notations for boolean type : * "true" can be written "1" and "0" significates "false". * This method considers all this values as CharSequences and return its boolean value.</p> * * @param bool The String to parse * @return true if bool is equal to "true" or "1". */ protected static boolean parseBoolean(final String candidate) { if (candidate.length() == 1) { return !candidate.equals("0"); } return Boolean.parseBoolean(candidate); } /** * <p>This method reads doubles with coma separated.</p> * * @param candidate Can not be null. * @return */ protected static double parseDouble(final String candidate) { return Double.parseDouble(candidate.replace(',', '.')); } /** * Iterator on the reader until it reachs the end of the given tag name. * Return the read elements as dom. * * TODO : not supported yet, needed for GML antype nodes, waiting for the new Feature Model. * * @return Element */ protected Document readAsDom(final String tagName) throws XMLStreamException{ final XMLStreamReader limitedReader = new StreamReaderDelegate(reader){ boolean finished = false; @Override public boolean hasNext() throws XMLStreamException { if(finished) return false; return super.hasNext(); } @Override public int next() throws XMLStreamException { int t = super.next(); finished = END_ELEMENT == t && tagName.equalsIgnoreCase(reader.getLocalName()); return t; } }; final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(false); final TransformerFactory trsFactory = TransformerFactory.newInstance(); try { final DocumentBuilder builder = factory.newDocumentBuilder(); final Transformer idTransform = trsFactory.newTransformer(); final Source input = new StAXSource(limitedReader); final ByteArrayOutputStream out = new ByteArrayOutputStream(); final Result output = new StreamResult(out); idTransform.transform(input, output); final Document doc = builder.parse(new ByteArrayInputStream(out.toByteArray())); return doc; } catch (TransformerConfigurationException e) { throw new XMLStreamException(e.getMessage()); } catch (TransformerFactoryConfigurationError e) { throw new XMLStreamException(e.getMessage()); } catch (IOException e) { throw new XMLStreamException(e.getMessage()); } catch (TransformerException e) { throw new XMLStreamException(e.getMessage()); } catch (SAXException e) { throw new XMLStreamException(e.getMessage()); } catch (ParserConfigurationException e) { throw new XMLStreamException(e.getMessage()); } } }