/** * * Copyright 2019 Florian Schmaus * * 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.jivesoftware.smack.xml.stax; import java.io.IOException; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.stream.Location; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; public final class StaxXmlPullParser implements XmlPullParser { private final XMLStreamReader xmlStreamReader; private int depth; StaxXmlPullParser(XMLStreamReader xmlStreamReader) { this.xmlStreamReader = xmlStreamReader; } @Override public Object getProperty(String name) { return xmlStreamReader.getProperty(name); } @Override public String getInputEncoding() { return xmlStreamReader.getEncoding(); } @Override public int getNamespaceCount() { return xmlStreamReader.getNamespaceCount(); } @Override public String getNamespacePrefix(int pos) { return xmlStreamReader.getNamespacePrefix(pos); } @Override public String getNamespaceUri(int pos) { return xmlStreamReader.getNamespaceURI(pos); } @Override public String getNamespace(String prefix) { if (prefix == null) { prefix = XMLConstants.DEFAULT_NS_PREFIX; } NamespaceContext namespaceContext = xmlStreamReader.getNamespaceContext(); return namespaceContext.getNamespaceURI(prefix); } @Override public String getNamespace() { String prefix = getPrefix(); return getNamespace(prefix); } @Override public int getDepth() { return depth; } @Override public String getPositionDescription() { Location location = xmlStreamReader.getLocation(); return location.toString(); } @Override public int getLineNumber() { Location location = xmlStreamReader.getLocation(); return location.getLineNumber(); } @Override public int getColumnNumber() { Location location = xmlStreamReader.getLocation(); return location.getColumnNumber(); } @Override public boolean isWhiteSpace() { return xmlStreamReader.isWhiteSpace(); } @Override public String getText() { return xmlStreamReader.getText(); } @Override public String getName() { QName qname = getQName(); return qname.getLocalPart(); } @Override public QName getQName() { return xmlStreamReader.getName(); } @Override public String getPrefix() { return xmlStreamReader.getPrefix(); } @Override public int getAttributeCount() { return xmlStreamReader.getAttributeCount(); } @Override public String getAttributeNamespace(int index) { return xmlStreamReader.getAttributeNamespace(index); } @Override public String getAttributeName(int index) { QName qname = getAttributeQName(index); if (qname == null) { return null; } return qname.getLocalPart(); } @Override public QName getAttributeQName(int index) { return xmlStreamReader.getAttributeName(index); } @Override public String getAttributePrefix(int index) { return xmlStreamReader.getAttributePrefix(index); } @Override public String getAttributeType(int index) { return xmlStreamReader.getAttributeType(index); } @Override public String getAttributeValue(int index) { return xmlStreamReader.getAttributeValue(index); } @Override public String getAttributeValue(String namespace, String name) { String namespaceURI = namespace; String localName = name; return xmlStreamReader.getAttributeValue(namespaceURI, localName); } @Override public Event getEventType() { int staxEventInt = xmlStreamReader.getEventType(); return staxEventIntegerToEvent(staxEventInt); } private boolean delayedDepthDecrement; @Override public Event next() throws XmlPullParserException { preNextEvent(); int staxEventInt; try { staxEventInt = xmlStreamReader.next(); } catch (XMLStreamException e) { throw new XmlPullParserException(e); } Event event = staxEventIntegerToEvent(staxEventInt); switch (event) { case START_ELEMENT: depth++; break; case END_ELEMENT: delayedDepthDecrement = true; break; default: // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement. break; } return event; } @Override public String nextText() throws IOException, XmlPullParserException { final String nextText; try { nextText = xmlStreamReader.getElementText(); } catch (XMLStreamException e) { throw new XmlPullParserException(e); } // XMLStreamReader.getElementText() will forward to the next END_ELEMENT, hence we need to set // delayedDepthDecrement to true. delayedDepthDecrement = true; return nextText; } @Override public TagEvent nextTag() throws IOException, XmlPullParserException { preNextEvent(); int staxEventInt; try { staxEventInt = xmlStreamReader.nextTag(); } catch (XMLStreamException e) { throw new XmlPullParserException(e); } switch (staxEventInt) { case XMLStreamConstants.START_ELEMENT: depth++; return TagEvent.START_ELEMENT; case XMLStreamConstants.END_ELEMENT: delayedDepthDecrement = true; return TagEvent.END_ELEMENT; default: throw new AssertionError(); } } private void preNextEvent() { if (delayedDepthDecrement) { depth--; delayedDepthDecrement = false; assert depth >= 0; } } private static Event staxEventIntegerToEvent(int staxEventInt) { switch (staxEventInt) { case XMLStreamConstants.START_ELEMENT: return Event.START_ELEMENT; case XMLStreamConstants.END_ELEMENT: return Event.END_ELEMENT; case XMLStreamConstants.PROCESSING_INSTRUCTION: return Event.PROCESSING_INSTRUCTION; case XMLStreamConstants.CHARACTERS: return Event.TEXT_CHARACTERS; case XMLStreamConstants.COMMENT: return Event.COMMENT; case XMLStreamConstants.SPACE: return Event.IGNORABLE_WHITESPACE; case XMLStreamConstants.START_DOCUMENT: return Event.START_DOCUMENT; case XMLStreamConstants.END_DOCUMENT: return Event.END_DOCUMENT; case XMLStreamConstants.ENTITY_REFERENCE: return Event.ENTITY_REFERENCE; case XMLStreamConstants.ATTRIBUTE: return Event.OTHER; case XMLStreamConstants.DTD: return Event.OTHER; case XMLStreamConstants.CDATA: return Event.OTHER; case XMLStreamConstants.NAMESPACE: return Event.OTHER; case XMLStreamConstants.NOTATION_DECLARATION: return Event.OTHER; case XMLStreamConstants.ENTITY_DECLARATION: return Event.OTHER; default: throw new IllegalArgumentException("Unknown Stax event integer: " + staxEventInt); } } @Override public boolean supportsRoundtrip() { // TODO: Is there a StAX parser implementation which does support roundtrip? return false; } }