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 int getDepth() {
078        return depth;
079    }
080
081    @Override
082    public String getPositionDescription() {
083        Location location = xmlStreamReader.getLocation();
084        return location.toString();
085    }
086
087    @Override
088    public int getLineNumber() {
089        Location location = xmlStreamReader.getLocation();
090        return location.getLineNumber();
091    }
092
093    @Override
094    public int getColumnNumber() {
095        Location location = xmlStreamReader.getLocation();
096        return location.getColumnNumber();
097    }
098
099    @Override
100    public boolean isWhiteSpace() {
101        return xmlStreamReader.isWhiteSpace();
102    }
103
104    @Override
105    public String getText() {
106        return xmlStreamReader.getText();
107    }
108
109    @Override
110    public String getNamespace() {
111        NamespaceContext namespaceContext = xmlStreamReader.getNamespaceContext();
112        String prefix = getPrefix();
113        return namespaceContext.getNamespaceURI(prefix);
114    }
115
116    @Override
117    public String getName() {
118        QName qname = getQName();
119        return qname.getLocalPart();
120    }
121
122    @Override
123    public QName getQName() {
124        return xmlStreamReader.getName();
125    }
126
127    @Override
128    public String getPrefix() {
129        return xmlStreamReader.getPrefix();
130    }
131
132    @Override
133    public int getAttributeCount() {
134        return xmlStreamReader.getAttributeCount();
135    }
136
137    @Override
138    public String getAttributeNamespace(int index) {
139        return xmlStreamReader.getAttributeNamespace(index);
140    }
141
142    @Override
143    public String getAttributeName(int index) {
144        QName qname = getAttributeQName(index);
145        if (qname == null) {
146            return null;
147        }
148        return qname.getLocalPart();
149    }
150
151    @Override
152    public QName getAttributeQName(int index) {
153        return xmlStreamReader.getAttributeName(index);
154    }
155
156    @Override
157    public String getAttributePrefix(int index) {
158        return xmlStreamReader.getAttributePrefix(index);
159    }
160
161    @Override
162    public String getAttributeType(int index) {
163        return xmlStreamReader.getAttributeType(index);
164    }
165
166    @Override
167    public String getAttributeValue(int index) {
168        return xmlStreamReader.getAttributeValue(index);
169    }
170
171    @Override
172    public String getAttributeValue(String namespace, String name) {
173        String namespaceURI = namespace;
174        String localName = name;
175        return xmlStreamReader.getAttributeValue(namespaceURI, localName);
176    }
177
178    @Override
179    public Event getEventType() {
180        int staxEventInt = xmlStreamReader.getEventType();
181        return staxEventIntegerToEvent(staxEventInt);
182    }
183
184    private boolean delayedDepthDecrement;
185
186    @Override
187    public Event next() throws XmlPullParserException {
188        preNextEvent();
189
190        int staxEventInt;
191        try {
192            staxEventInt = xmlStreamReader.next();
193        } catch (XMLStreamException e) {
194            throw new XmlPullParserException(e);
195        }
196
197        Event event = staxEventIntegerToEvent(staxEventInt);
198        switch (event) {
199        case START_ELEMENT:
200            depth++;
201            break;
202        case END_ELEMENT:
203            delayedDepthDecrement = true;
204            break;
205        default:
206            // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
207            break;
208        }
209        return event;
210    }
211
212    @Override
213    public String nextText() throws IOException, XmlPullParserException {
214        final String nextText;
215        try {
216            nextText = xmlStreamReader.getElementText();
217        } catch (XMLStreamException e) {
218            throw new XmlPullParserException(e);
219        }
220
221        // XMLStreamReader.getElementText() will forward to the next END_ELEMENT, hence we need to set
222        // delayedDepthDecrement to true.
223        delayedDepthDecrement = true;
224
225        return nextText;
226    }
227
228    @Override
229    public TagEvent nextTag() throws IOException, XmlPullParserException {
230        preNextEvent();
231
232        int staxEventInt;
233        try {
234            staxEventInt = xmlStreamReader.nextTag();
235        } catch (XMLStreamException e) {
236            throw new XmlPullParserException(e);
237        }
238
239        switch (staxEventInt) {
240        case XMLStreamConstants.START_ELEMENT:
241            depth++;
242            return TagEvent.START_ELEMENT;
243        case XMLStreamConstants.END_ELEMENT:
244            delayedDepthDecrement = true;
245            return TagEvent.END_ELEMENT;
246        default:
247            throw new AssertionError();
248        }
249    }
250
251    private void preNextEvent() {
252        if (delayedDepthDecrement) {
253            depth--;
254            delayedDepthDecrement = false;
255            assert depth >= 0;
256        }
257    }
258
259    private static Event staxEventIntegerToEvent(int staxEventInt) {
260        switch (staxEventInt) {
261        case XMLStreamConstants.START_ELEMENT:
262            return Event.START_ELEMENT;
263        case XMLStreamConstants.END_ELEMENT:
264            return Event.END_ELEMENT;
265        case XMLStreamConstants.PROCESSING_INSTRUCTION:
266            return Event.PROCESSING_INSTRUCTION;
267        case XMLStreamConstants.CHARACTERS:
268            return Event.TEXT_CHARACTERS;
269        case XMLStreamConstants.COMMENT:
270            return Event.COMMENT;
271        case XMLStreamConstants.SPACE:
272            return Event.IGNORABLE_WHITESPACE;
273        case XMLStreamConstants.START_DOCUMENT:
274            return Event.START_DOCUMENT;
275        case XMLStreamConstants.END_DOCUMENT:
276            return Event.END_DOCUMENT;
277        case XMLStreamConstants.ENTITY_REFERENCE:
278            return Event.ENTITY_REFERENCE;
279        case XMLStreamConstants.ATTRIBUTE:
280            return Event.OTHER;
281        case XMLStreamConstants.DTD:
282            return Event.OTHER;
283        case XMLStreamConstants.CDATA:
284            return Event.OTHER;
285        case XMLStreamConstants.NAMESPACE:
286            return Event.OTHER;
287        case XMLStreamConstants.NOTATION_DECLARATION:
288            return Event.OTHER;
289        case XMLStreamConstants.ENTITY_DECLARATION:
290            return Event.OTHER;
291        default:
292            throw new IllegalArgumentException("Unknown Stax event integer: " + staxEventInt);
293        }
294    }
295
296    @Override
297    public boolean supportsRoundtrip() {
298        // TODO: Is there a StAX parser implementation which does support roundtrip?
299        return false;
300    }
301}