/* * citygml4j - The Open Source Java API for CityGML * https://github.com/citygml4j * * Copyright 2013-2017 Claus Nagel <claus.nagel@gmail.com> * * 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.citygml4j.util.xml; import javax.xml.XMLConstants; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; public class SAXEventBuffer implements ContentHandler { private final String END_PREFIX_MAPPING = "END_PREFIX_MAPPING"; private final Byte UNDEFINED = 0; private final Byte START_DOCUMENT = 1; private final Byte END_DOCUMENT = 2; private final Byte START_ELEMENT = 3; private final Byte END_ELEMENT = 4; private final Byte QUALIFIED_END_ELEMENT = 5; private final Byte CHARACTERS = 6; private final Byte NAMESPACE_PREFIX_MAPPING = 7; private final Byte ATTRIBUTE = 8; private ArrayBuffer<String> stringBuffer; private ArrayBuffer<char[]> charactersBuffer; private ArrayBuffer<Byte> eventBuffer; private ArrayBuffer<String> tmpBuffer; private AttributesImpl atts; private Byte previousElement = UNDEFINED; public SAXEventBuffer() { stringBuffer = new ArrayBuffer<String>(String.class); charactersBuffer = new ArrayBuffer<char[]>(char[].class); eventBuffer = new ArrayBuffer<Byte>(Byte.class); } public void reset() { stringBuffer = new ArrayBuffer<String>(String.class); charactersBuffer = new ArrayBuffer<char[]>(char[].class); eventBuffer = new ArrayBuffer<Byte>(Byte.class); tmpBuffer = null; atts = null; previousElement = UNDEFINED; } public boolean isEmpty() { return eventBuffer.currentPtr() == 0 && eventBuffer.previousBuffer() == null; } public void removeTrailingCharacters() { while (eventBuffer.peek() == CHARACTERS) { popEvent(); popCharacters(); } } public void addCharacters(char[] ch) { if (previousElement == START_ELEMENT) { pushEvent(CHARACTERS); pushCharacters(ch); } } public void addStartElement(String uri, String localName, String prefix) { if (previousElement == START_ELEMENT) removeTrailingCharacters(); pushEvent(START_ELEMENT); pushString(uri); pushString(localName); pushString(prefix); previousElement = START_ELEMENT; } public void addEndElement() { if (previousElement == END_ELEMENT) removeTrailingCharacters(); pushEvent(END_ELEMENT); previousElement = END_ELEMENT; } public void addEndElement(String uri, String localName) { if (previousElement == END_ELEMENT) removeTrailingCharacters(); pushEvent(QUALIFIED_END_ELEMENT); pushString(uri); pushString(localName); previousElement = END_ELEMENT; } public void addNamespacePrefixMapping(String uri, String prefix) { pushEvent(NAMESPACE_PREFIX_MAPPING); pushString(uri); pushString(prefix); } public void addAttribute(String uri, String localName, String prefix, String type, String value) { pushEvent(ATTRIBUTE); pushString(uri); pushString(localName); pushString(prefix); pushString(type); pushString(value); } public void addStartDocument() { pushEvent(START_DOCUMENT); } public void addEndDocument() { pushEvent(END_DOCUMENT); } public void send(ContentHandler handler, boolean release) throws SAXException { if (isEmpty()) throw new IllegalStateException("buffer is empty."); eventBuffer = eventBuffer.rewindToHeadBuffer(); stringBuffer = stringBuffer.rewindToHeadBuffer(); charactersBuffer = charactersBuffer.rewindToHeadBuffer(); tmpBuffer = new ArrayBuffer<String>(String.class); atts = new AttributesImpl(); Byte currentEvent = null; while (eventBuffer.peek() != null) { currentEvent = nextEvent(release); if (currentEvent == START_ELEMENT) sendStartElement(handler, release); else if (currentEvent == END_ELEMENT) sendEndElement(handler); else if (currentEvent == CHARACTERS) sendCharacters(handler, release); else if (currentEvent == NAMESPACE_PREFIX_MAPPING) sendStartPrefixMapping(handler, release); else if (currentEvent == START_DOCUMENT) handler.startDocument(); else if (currentEvent == END_DOCUMENT) handler.endDocument(); else if (currentEvent == QUALIFIED_END_ELEMENT) sendQualifiedEndElement(handler, release); } // clean up eventBuffer.decrementPtr(); stringBuffer.decrementPtr(); charactersBuffer.decrementPtr(); if (release) reset(); } private void sendCharacters(ContentHandler handler, boolean release) throws SAXException { char[] ch = nextCharacters(release); handler.characters(ch, 0, ch.length); } private void sendStartPrefixMapping(ContentHandler handler, boolean release) throws SAXException { String nsUri = nextString(release); String nsPrefix = nextString(release); if (nsPrefix == null) nsPrefix = XMLConstants.DEFAULT_NS_PREFIX; handler.startPrefixMapping(nsPrefix, nsUri); pushTmpString(nsPrefix); pushTmpString(END_PREFIX_MAPPING); } private void sendStartElement(ContentHandler handler, boolean release) throws SAXException { String elementUri = nextString(release); String elementLocalName = nextString(release); String elementPrefix = nextString(release); String elementQName = (elementPrefix == null || elementPrefix.length() == 0) ? elementLocalName : new StringBuffer(elementPrefix).append(':').append(elementLocalName).toString(); if (eventBuffer.peek() == ATTRIBUTE) { do { nextEvent(release); String attrUri = nextString(release); String attrLocalName = nextString(release); String attrPrefix = nextString(release); String attrType = nextString(release); String attrValue = nextString(release); String attrQName = (attrPrefix == null || attrPrefix.length() == 0) ? attrLocalName : new StringBuffer(attrPrefix).append(':').append(attrLocalName).toString(); if (attrUri == null) attrUri = ""; atts.addAttribute(attrUri, attrLocalName, attrQName, attrType, attrValue); } while (eventBuffer.peek() == ATTRIBUTE); } pushTmpString(elementUri); pushTmpString(elementLocalName); pushTmpString(elementPrefix); handler.startElement(elementUri, elementLocalName, elementQName, atts); atts.clear(); } private void sendEndElement(ContentHandler handler) throws SAXException { String elementPrefix = popTmpString(); String elementLocalName = popTmpString(); String elementUri = popTmpString(); String elementQName = elementPrefix == null ? elementLocalName : new StringBuffer(elementPrefix).append(':').append(elementLocalName).toString(); handler.endElement(elementUri, elementLocalName, elementQName); if (tmpBuffer.peek() == END_PREFIX_MAPPING) { do { popTmpString(); String nsPrefix = popTmpString(); handler.endPrefixMapping(nsPrefix); } while (tmpBuffer.peek() == END_PREFIX_MAPPING); } } private void sendQualifiedEndElement(ContentHandler handler, boolean release) throws SAXException { String elementUri = nextString(release); String elementLocalName = nextString(release); handler.endElement(elementUri, elementLocalName, elementLocalName); if (tmpBuffer.peek() == END_PREFIX_MAPPING) { do { popTmpString(); String nsPrefix = popTmpString(); handler.endPrefixMapping(nsPrefix); } while (tmpBuffer.peek() == END_PREFIX_MAPPING); } } private void pushEvent(byte event) { eventBuffer.push(event); if (eventBuffer.currentPtr() == eventBuffer.size()) eventBuffer = eventBuffer.appendBuffer(); } private void pushCharacters(char[] ch) { charactersBuffer.push(ch); if (charactersBuffer.currentPtr() == charactersBuffer.size()) charactersBuffer = charactersBuffer.appendBuffer(); } private void pushString(String s) { stringBuffer.push(s); if (stringBuffer.currentPtr() == stringBuffer.size()) stringBuffer = stringBuffer.appendBuffer(); } private void pushTmpString(String s) { tmpBuffer.push(s); if (tmpBuffer.currentPtr() == tmpBuffer.size()) tmpBuffer = tmpBuffer.appendBuffer(); } private void popEvent() { if (eventBuffer.currentPtr() == 0) eventBuffer = eventBuffer.dropBuffer(); eventBuffer.pop(); } private void popCharacters() { if (charactersBuffer.currentPtr() == 0) charactersBuffer = charactersBuffer.dropBuffer(); charactersBuffer.pop(); } private String popTmpString() { if (tmpBuffer.currentPtr() == 0) tmpBuffer = tmpBuffer.dropBuffer(); return tmpBuffer.pop(); } private Byte nextEvent(boolean release) { Byte event = eventBuffer.next(release); if (eventBuffer.currentPtr() > eventBuffer.size() && eventBuffer.nextBuffer() != null) { eventBuffer = eventBuffer.nextBuffer(); if (release) eventBuffer.dropPreviousBuffer(); } return event; } private String nextString(boolean release) { String s = stringBuffer.next(release); if (stringBuffer.currentPtr() > stringBuffer.size() && stringBuffer.nextBuffer() != null) { stringBuffer = stringBuffer.nextBuffer(); if (release) stringBuffer.dropPreviousBuffer(); } return s; } private char[] nextCharacters(boolean release) { char[] ch = charactersBuffer.next(release); if (charactersBuffer.currentPtr() > charactersBuffer.size() && charactersBuffer.nextBuffer() != null) { charactersBuffer = charactersBuffer.nextBuffer(); if (release) charactersBuffer.dropPreviousBuffer(); } return ch; } @Override public void setDocumentLocator(Locator locator) { // we do not record this event } @Override public void skippedEntity(String name) throws SAXException { // we do not record this event } @Override public void characters(char[] ch, int start, int length) throws SAXException { char[] tmp = new char[length]; System.arraycopy(ch, start, tmp, 0, length); addCharacters(tmp); } @Override public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { // we do not record this event } @Override public void processingInstruction(String target, String data) throws SAXException { // we do not record this event } @Override public void startDocument() throws SAXException { addStartDocument(); } @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { addStartElement(uri, localName, null); for (int i = 0; i < atts.getLength(); i++) addAttribute(atts.getURI(i), atts.getLocalName(i), null, atts.getType(i), atts.getValue(i)); } @Override public void startPrefixMapping(String prefix, String uri) throws SAXException { addNamespacePrefixMapping(uri, prefix); } @Override public void endDocument() throws SAXException { addEndDocument(); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { addEndElement(); } @Override public void endPrefixMapping(String prefix) throws SAXException { // we do not record this event } }