001/**
002 *
003 * Copyright 2019 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.xml.stax;
018
019import java.io.IOException;
020
021import javax.xml.XMLConstants;
022import javax.xml.namespace.NamespaceContext;
023import javax.xml.namespace.QName;
024import javax.xml.stream.Location;
025import javax.xml.stream.XMLStreamConstants;
026import javax.xml.stream.XMLStreamException;
027import javax.xml.stream.XMLStreamReader;
028
029import org.jivesoftware.smack.xml.XmlPullParser;
030import org.jivesoftware.smack.xml.XmlPullParserException;
031
032public final class StaxXmlPullParser implements XmlPullParser {
033
034    private final XMLStreamReader xmlStreamReader;
035
036    private int depth;
037
038    StaxXmlPullParser(XMLStreamReader xmlStreamReader) {
039        this.xmlStreamReader = xmlStreamReader;
040    }
041
042    @Override
043    public Object getProperty(String name) {
044        return xmlStreamReader.getProperty(name);
045    }
046
047    @Override
048    public String getInputEncoding() {
049        return xmlStreamReader.getEncoding();
050    }
051
052    @Override
053    public int getNamespaceCount() {
054        return xmlStreamReader.getNamespaceCount();
055    }
056
057    @Override
058    public String getNamespacePrefix(int pos) {
059        return xmlStreamReader.getNamespacePrefix(pos);
060    }
061
062    @Override
063    public String getNamespaceUri(int pos) {
064        return xmlStreamReader.getNamespaceURI(pos);
065    }
066
067    @Override
068    public String getNamespace(String prefix) {
069        if (prefix == null) {
070            prefix = XMLConstants.DEFAULT_NS_PREFIX;
071        }
072        NamespaceContext namespaceContext = xmlStreamReader.getNamespaceContext();
073        return namespaceContext.getNamespaceURI(prefix);
074    }
075
076    @Override
077    public String getNamespace() {
078        String prefix = getPrefix();
079        return getNamespace(prefix);
080    }
081
082    @Override
083    public int getDepth() {
084        return depth;
085    }
086
087    @Override
088    public String getPositionDescription() {
089        Location location = xmlStreamReader.getLocation();
090        return location.toString();
091    }
092
093    @Override
094    public int getLineNumber() {
095        Location location = xmlStreamReader.getLocation();
096        return location.getLineNumber();
097    }
098
099    @Override
100    public int getColumnNumber() {
101        Location location = xmlStreamReader.getLocation();
102        return location.getColumnNumber();
103    }
104
105    @Override
106    public boolean isWhiteSpace() {
107        return xmlStreamReader.isWhiteSpace();
108    }
109
110    @Override
111    public String getText() {
112        return xmlStreamReader.getText();
113    }
114
115    @Override
116    public String getName() {
117        QName qname = getQName();
118        return qname.getLocalPart();
119    }
120
121    @Override
122    public QName getQName() {
123        return xmlStreamReader.getName();
124    }
125
126    @Override
127    public String getPrefix() {
128        return xmlStreamReader.getPrefix();
129    }
130
131    @Override
132    public int getAttributeCount() {
133        return xmlStreamReader.getAttributeCount();
134    }
135
136    @Override
137    public String getAttributeNamespace(int index) {
138        return xmlStreamReader.getAttributeNamespace(index);
139    }
140
141    @Override
142    public String getAttributeName(int index) {
143        QName qname = getAttributeQName(index);
144        if (qname == null) {
145            return null;
146        }
147        return qname.getLocalPart();
148    }
149
150    @Override
151    public QName getAttributeQName(int index) {
152        return xmlStreamReader.getAttributeName(index);
153    }
154
155    @Override
156    public String getAttributePrefix(int index) {
157        return xmlStreamReader.getAttributePrefix(index);
158    }
159
160    @Override
161    public String getAttributeType(int index) {
162        return xmlStreamReader.getAttributeType(index);
163    }
164
165    @Override
166    public String getAttributeValue(int index) {
167        return xmlStreamReader.getAttributeValue(index);
168    }
169
170    @Override
171    public String getAttributeValue(String namespace, String name) {
172        String namespaceURI = namespace;
173        String localName = name;
174        return xmlStreamReader.getAttributeValue(namespaceURI, localName);
175    }
176
177    @Override
178    public Event getEventType() {
179        int staxEventInt = xmlStreamReader.getEventType();
180        return staxEventIntegerToEvent(staxEventInt);
181    }
182
183    private boolean delayedDepthDecrement;
184
185    @Override
186    public Event next() throws XmlPullParserException {
187        preNextEvent();
188
189        int staxEventInt;
190        try {
191            staxEventInt = xmlStreamReader.next();
192        } catch (XMLStreamException e) {
193            throw new XmlPullParserException(e);
194        }
195
196        Event event = staxEventIntegerToEvent(staxEventInt);
197        switch (event) {
198        case START_ELEMENT:
199            depth++;
200            break;
201        case END_ELEMENT:
202            delayedDepthDecrement = true;
203            break;
204        default:
205            // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
206            break;
207        }
208        return event;
209    }
210
211    @Override
212    public String nextText() throws IOException, XmlPullParserException {
213        final String nextText;
214        try {
215            nextText = xmlStreamReader.getElementText();
216        } catch (XMLStreamException e) {
217            throw new XmlPullParserException(e);
218        }
219
220        // XMLStreamReader.getElementText() will forward to the next END_ELEMENT, hence we need to set
221        // delayedDepthDecrement to true.
222        delayedDepthDecrement = true;
223
224        return nextText;
225    }
226
227    @Override
228    public TagEvent nextTag() throws IOException, XmlPullParserException {
229        preNextEvent();
230
231        int staxEventInt;
232        try {
233            staxEventInt = xmlStreamReader.nextTag();
234        } catch (XMLStreamException e) {
235            throw new XmlPullParserException(e);
236        }
237
238        switch (staxEventInt) {
239        case XMLStreamConstants.START_ELEMENT:
240            depth++;
241            return TagEvent.START_ELEMENT;
242        case XMLStreamConstants.END_ELEMENT:
243            delayedDepthDecrement = true;
244            return TagEvent.END_ELEMENT;
245        default:
246            throw new AssertionError();
247        }
248    }
249
250    private void preNextEvent() {
251        if (delayedDepthDecrement) {
252            depth--;
253            delayedDepthDecrement = false;
254            assert depth >= 0;
255        }
256    }
257
258    private static Event staxEventIntegerToEvent(int staxEventInt) {
259        switch (staxEventInt) {
260        case XMLStreamConstants.START_ELEMENT:
261            return Event.START_ELEMENT;
262        case XMLStreamConstants.END_ELEMENT:
263            return Event.END_ELEMENT;
264        case XMLStreamConstants.PROCESSING_INSTRUCTION:
265            return Event.PROCESSING_INSTRUCTION;
266        case XMLStreamConstants.CHARACTERS:
267            return Event.TEXT_CHARACTERS;
268        case XMLStreamConstants.COMMENT:
269            return Event.COMMENT;
270        case XMLStreamConstants.SPACE:
271            return Event.IGNORABLE_WHITESPACE;
272        case XMLStreamConstants.START_DOCUMENT:
273            return Event.START_DOCUMENT;
274        case XMLStreamConstants.END_DOCUMENT:
275            return Event.END_DOCUMENT;
276        case XMLStreamConstants.ENTITY_REFERENCE:
277            return Event.ENTITY_REFERENCE;
278        case XMLStreamConstants.ATTRIBUTE:
279            return Event.OTHER;
280        case XMLStreamConstants.DTD:
281            return Event.OTHER;
282        case XMLStreamConstants.CDATA:
283            return Event.OTHER;
284        case XMLStreamConstants.NAMESPACE:
285            return Event.OTHER;
286        case XMLStreamConstants.NOTATION_DECLARATION:
287            return Event.OTHER;
288        case XMLStreamConstants.ENTITY_DECLARATION:
289            return Event.OTHER;
290        default:
291            throw new IllegalArgumentException("Unknown Stax event integer: " + staxEventInt);
292        }
293    }
294
295    @Override
296    public boolean supportsRoundtrip() {
297        // TODO: Is there a StAX parser implementation which does support roundtrip?
298        return false;
299    }
300}