/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jkiss.utils.xml; import org.xml.sax.*; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import java.io.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * SAX document reader */ public final class SAXReader implements ContentHandler, EntityResolver, DTDHandler { public static final int DEFAULT_POOL_SIZE = 10; private static javax.xml.parsers.SAXParserFactory saxParserFactory = null; private static List<Parser> parsersPool = new ArrayList<>(); private org.xml.sax.InputSource inputSource; private Locator locator; private Map<String, Object> attributes = new HashMap<>(); private List<SAXListener> elementLayers = new ArrayList<>(); private SAXListener curListener; private StringBuilder textValue = new StringBuilder(); private int depth = 0; private boolean handleWhiteSpaces = false; /** * Private constructor. * Initialize parser. */ private SAXReader() { } /** * Standard constructor. * Initialize parser and prepare input stream for reading. */ public SAXReader(InputStream stream) { this(); inputSource = new org.xml.sax.InputSource(stream); } /** * Standard constructor. * Initialize parser and prepare input stream for reading. */ public SAXReader(Reader reader) { this(); inputSource = new org.xml.sax.InputSource(reader); } public boolean isHandleWhiteSpaces() { return handleWhiteSpaces; } public void setHandleWhiteSpaces( boolean flag) { handleWhiteSpaces = flag; } public Locator getLocator() { return locator; } /** * Parse input stream and handle XML tags. */ public void parse(SAXListener listener) throws IOException, XMLException { // Initialize SAX parser Parser parser = acquireParser(); // Get reader and parse using SAX2 API try { XMLReader saxReader = parser.getSAXParser().getXMLReader(); saxReader.setErrorHandler(new ParseErrorHandler()); saxReader.setContentHandler(this); saxReader.setEntityResolver(this); saxReader.setDTDHandler(this); curListener = listener; elementLayers.add(listener); saxReader.parse(inputSource); } catch (SAXParseException toCatch) { throw new XMLException( "Document parse error (line " + toCatch.getLineNumber() + ", pos " + toCatch.getColumnNumber(), toCatch); } catch (SAXException toCatch) { throw new XMLException( "Document reading SAX exception", XMLUtils.adaptSAXException(toCatch)); } finally { parser.close(); } } public synchronized static Parser acquireParser() throws XMLException { try { if (saxParserFactory == null) { try { saxParserFactory = javax.xml.parsers.SAXParserFactory.newInstance(); saxParserFactory.setNamespaceAware(true); saxParserFactory.setValidating(false); } catch (FactoryConfigurationError toCatch) { throw new XMLException( "SAX factory configuration error", toCatch); } } for (int i = 0; i < parsersPool.size(); i++) { Parser parser = parsersPool.get(i); if (parser != null) { if (!parser.isAcquired()) { parser.acquire(); return parser; } } else { parsersPool.remove(i); parser = new Parser(saxParserFactory.newSAXParser(), true); parsersPool.add(parser); return parser; } } if (parsersPool.size() == DEFAULT_POOL_SIZE) { throw new XMLException( "Maximum SAX Parsers Number Exceeded (" + DEFAULT_POOL_SIZE + ")"); } Parser parser = new Parser(saxParserFactory.newSAXParser(), true); parsersPool.add(parser); return parser; } catch (ParserConfigurationException toCatch) { throw new XMLException( "SAX Parser Configuration error", toCatch); } catch (SAXException toCatch) { throw new XMLException( "SAX Parser error", toCatch); } } /** * Closes parser and frees all resources. */ public void close() { if (elementLayers != null) { elementLayers.clear(); elementLayers = null; } inputSource = null; curListener = null; } /** * Set listener for next event. */ public void setListener( SAXListener listener) { curListener = listener; } public boolean hasAttribute( String name) { return attributes.get(name) != null; } public Object getAttribute( String name) { return attributes.get(name); } public void setAttribute( String name, Object value) { attributes.put(name, value); } public Object removeAttribute( String name) { return attributes.remove(name); } private void handleText() throws SAXException { curListener = elementLayers.get(elementLayers.size() - 1); try { String value = textValue.toString(); curListener.saxText(this, value); } catch (Exception toCatch) { throw new SAXException(toCatch); } finally { textValue.setLength(0); } } /////////////////////////////////////////////////////////////// // SAX Context Handler overrides /////////////////////////////////////////////////////////////// @Override public void startDocument() { // just do-nothing } @Override public void endDocument() { this.close(); } @Override public void startElement( String namespaceURI, String localName, String qName, org.xml.sax.Attributes attributes) throws SAXException { if (depth++ > 0) { this.handleText(); } curListener = elementLayers.get(elementLayers.size() - 1); try { curListener.saxStartElement(this, namespaceURI, localName, attributes); } catch (XMLException toCatch) { throw new SAXException(toCatch); } elementLayers.add(curListener); } @Override public void endElement( String namespaceURI, String localName, String qName) throws SAXException { this.handleText(); elementLayers.remove(elementLayers.size() - 1); curListener = elementLayers.get(elementLayers.size() - 1); try { curListener.saxEndElement(this, namespaceURI, localName); } catch (XMLException toCatch) { throw new SAXException(toCatch); } depth--; } @Override public void startPrefixMapping(String prefix, String uri) { // just do-nothing } @Override public void endPrefixMapping(String prefix) { // just do-nothing } @Override public void characters(char[] ch, int start, int length) { textValue.append(ch, start, length); } @Override public void ignorableWhitespace(char[] ch, int start, int length) { if (handleWhiteSpaces) { textValue.append(ch, start, length); } } @Override public void processingInstruction(String target, String data) { // just do-nothing } @Override public void setDocumentLocator(Locator locator) { this.locator = locator; } @Override public void skippedEntity(String name) { // just do-nothing } @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { // Return empty stream - we don't need entities by default return new InputSource(new StringReader("")); } @Override public void notationDecl(String name, String publicId, String systemId) throws SAXException { // do nothing } @Override public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException { // do nothing } static public class Parser { private javax.xml.parsers.SAXParser saxParser; private boolean isAcquired; public Parser(javax.xml.parsers.SAXParser saxParser, boolean isAcquired) { this.saxParser = saxParser; this.isAcquired = isAcquired; } public void setSAXParser(javax.xml.parsers.SAXParser saxParser) { this.saxParser = saxParser; } public void acquire() { isAcquired = true; } public void close() { isAcquired = false; } public javax.xml.parsers.SAXParser getSAXParser() { return saxParser; } public boolean isAcquired() { return isAcquired; } } static class ParseErrorHandler implements org.xml.sax.ErrorHandler { @Override public void error(SAXParseException exception) { } @Override public void fatalError(SAXParseException exception) { } @Override public void warning(SAXParseException exception) { } } }