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}