1 /* 2 * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.stream; 27 28 import com.sun.xml.internal.stream.events.XMLEventAllocatorImpl; 29 import java.util.NoSuchElementException; 30 import javax.xml.stream.XMLInputFactory; 31 import javax.xml.stream.XMLStreamConstants; 32 import javax.xml.stream.XMLStreamException; 33 import javax.xml.stream.XMLStreamReader; 34 import javax.xml.stream.events.EntityReference; 35 import javax.xml.stream.events.XMLEvent; 36 import javax.xml.stream.util.XMLEventAllocator; 37 38 /** 39 * @author @author Neeraj Bajaj Sun Microsystems 40 * 41 */ 42 43 public class XMLEventReaderImpl implements javax.xml.stream.XMLEventReader{ 44 45 protected XMLStreamReader fXMLReader ; 46 protected XMLEventAllocator fXMLEventAllocator; 47 48 //only constructor will do because we delegate everything to underlying XMLStreamReader 49 public XMLEventReaderImpl(XMLStreamReader reader) throws XMLStreamException { 50 fXMLReader = reader ; 51 fXMLEventAllocator = (XMLEventAllocator)reader.getProperty(XMLInputFactory.ALLOCATOR); 52 if(fXMLEventAllocator == null){ 53 fXMLEventAllocator = new XMLEventAllocatorImpl(); 54 } 55 fPeekedEvent = fXMLEventAllocator.allocate(fXMLReader); 56 } 57 58 59 public boolean hasNext() { 60 //if we have the peeked event return 'true' 61 if(fPeekedEvent != null)return true; 62 //this is strange XMLStreamReader throws XMLStreamException 63 //XMLEventReader doesn't throw XMLStreamException 64 boolean next = false ; 65 try{ 66 next = fXMLReader.hasNext(); 67 }catch(XMLStreamException ex){ 68 return false; 69 } 70 return next ; 71 } 72 73 74 public XMLEvent nextEvent() throws XMLStreamException { 75 //if application peeked return the peeked event 76 if(fPeekedEvent != null){ 77 fLastEvent = fPeekedEvent ; 78 fPeekedEvent = null; 79 return fLastEvent ; 80 } 81 else if(fXMLReader.hasNext()){ 82 //advance the reader to next state. 83 fXMLReader.next(); 84 return fLastEvent = fXMLEventAllocator.allocate(fXMLReader); 85 } 86 else{ 87 fLastEvent = null; 88 throw new NoSuchElementException(); 89 } 90 } 91 92 public void remove(){ 93 //remove of the event is not supported. 94 throw new java.lang.UnsupportedOperationException(); 95 } 96 97 98 public void close() throws XMLStreamException { 99 fXMLReader.close(); 100 } 101 102 /** Reads the content of a text-only element. Precondition: 103 * the current event is START_ELEMENT. Postcondition: 104 * The current event is the corresponding END_ELEMENT. 105 * @throws XMLStreamException if the current event is not a START_ELEMENT 106 * or if a non text element is encountered 107 */ 108 public String getElementText() throws XMLStreamException { 109 //we have to keep reference to the 'last event' of the stream to be able 110 //to make this check - is there another way ? - nb. 111 if(fLastEvent.getEventType() != XMLEvent.START_ELEMENT){ 112 throw new XMLStreamException( 113 "parser must be on START_ELEMENT to read next text", fLastEvent.getLocation()); 114 } 115 116 // STag content ETag 117 //[43] content ::= CharData? ((element | Reference | CDSect | PI | Comment) CharData?)* 118 119 //<foo>....some long text say in KB and underlying parser reports multiple character 120 // but getElementText() events....</foo> 121 122 String data = null; 123 //having a peeked event makes things really worse -- we have to test the first event 124 if(fPeekedEvent != null){ 125 XMLEvent event = fPeekedEvent ; 126 fPeekedEvent = null; 127 int type = event.getEventType(); 128 129 if( type == XMLEvent.CHARACTERS || type == XMLEvent.SPACE || 130 type == XMLEvent.CDATA){ 131 data = event.asCharacters().getData(); 132 } 133 else if(type == XMLEvent.ENTITY_REFERENCE){ 134 data = ((EntityReference)event).getDeclaration().getReplacementText(); 135 } 136 else if(type == XMLEvent.COMMENT || type == XMLEvent.PROCESSING_INSTRUCTION){ 137 //ignore 138 } else if(type == XMLEvent.START_ELEMENT) { 139 throw new XMLStreamException( 140 "elementGetText() function expects text only elment but START_ELEMENT was encountered.", event.getLocation()); 141 }else if(type == XMLEvent.END_ELEMENT){ 142 return ""; 143 } 144 145 //create the string buffer and add initial data 146 StringBuffer buffer = new StringBuffer(); 147 if(data != null && data.length() > 0 ) { 148 buffer.append(data); 149 } 150 //get the next event -- we should stop at END_ELEMENT but it can be any thing 151 //things are worse when implementing this function in XMLEventReader because 152 //there isn't any function called getText() which can get values for 153 //space, cdata, characters and entity reference 154 //nextEvent() would also set the last event. 155 event = nextEvent(); 156 while(event.getEventType() != XMLEvent.END_ELEMENT){ 157 if( type == XMLEvent.CHARACTERS || type == XMLEvent.SPACE || 158 type == XMLEvent.CDATA){ 159 data = event.asCharacters().getData(); 160 } 161 else if(type == XMLEvent.ENTITY_REFERENCE){ 162 data = ((EntityReference)event).getDeclaration().getReplacementText(); 163 } 164 else if(type == XMLEvent.COMMENT || type == XMLEvent.PROCESSING_INSTRUCTION){ 165 //ignore 166 } else if(type == XMLEvent.END_DOCUMENT) { 167 throw new XMLStreamException("unexpected end of document when reading element text content"); 168 } else if(type == XMLEvent.START_ELEMENT) { 169 throw new XMLStreamException( 170 "elementGetText() function expects text only elment but START_ELEMENT was encountered.", event.getLocation()); 171 } else { 172 throw new XMLStreamException( 173 "Unexpected event type "+ type, event.getLocation()); 174 } 175 //add the data to the buffer 176 if(data != null && data.length() > 0 ) { 177 buffer.append(data); 178 } 179 event = nextEvent(); 180 } 181 return buffer.toString(); 182 }//if (fPeekedEvent != null) 183 184 //if there was no peeked, delegate everything to fXMLReader 185 //update the last event before returning the text 186 data = fXMLReader.getElementText(); 187 fLastEvent = fXMLEventAllocator.allocate(fXMLReader); 188 return data; 189 } 190 191 /** Get the value of a feature/property from the underlying implementation 192 * @param name The name of the property 193 * @return The value of the property 194 * @throws IllegalArgumentException if the property is not supported 195 */ 196 public Object getProperty(java.lang.String name) throws java.lang.IllegalArgumentException { 197 return fXMLReader.getProperty(name) ; 198 } 199 200 /** Skips any insignificant space events until a START_ELEMENT or 201 * END_ELEMENT is reached. If anything other than space characters are 202 * encountered, an exception is thrown. This method should 203 * be used when processing element-only content because 204 * the parser is not able to recognize ignorable whitespace if 205 * the DTD is missing or not interpreted. 206 * @throws XMLStreamException if anything other than space characters are encountered 207 */ 208 public XMLEvent nextTag() throws XMLStreamException { 209 //its really a pain if there is peeked event before calling nextTag() 210 if(fPeekedEvent != null){ 211 //check the peeked event first. 212 XMLEvent event = fPeekedEvent; 213 fPeekedEvent = null ; 214 int eventType = event.getEventType(); 215 //if peeked event is whitespace move to the next event 216 //if peeked event is PI or COMMENT move to the next event 217 if( (event.isCharacters() && event.asCharacters().isWhiteSpace()) 218 || eventType == XMLStreamConstants.PROCESSING_INSTRUCTION 219 || eventType == XMLStreamConstants.COMMENT 220 || eventType == XMLStreamConstants.START_DOCUMENT){ 221 event = nextEvent(); 222 eventType = event.getEventType(); 223 } 224 225 //we have to have the while loop because there can be many PI or comment event in sucession 226 while((event.isCharacters() && event.asCharacters().isWhiteSpace()) 227 || eventType == XMLStreamConstants.PROCESSING_INSTRUCTION 228 || eventType == XMLStreamConstants.COMMENT){ 229 230 event = nextEvent(); 231 eventType = event.getEventType(); 232 } 233 234 if (eventType != XMLStreamConstants.START_ELEMENT && eventType != XMLStreamConstants.END_ELEMENT) { 235 throw new XMLStreamException("expected start or end tag", event.getLocation()); 236 } 237 return event; 238 } 239 240 //if there is no peeked event -- delegate the work of getting next event to fXMLReader 241 fXMLReader.nextTag(); 242 return (fLastEvent = fXMLEventAllocator.allocate(fXMLReader)); 243 } 244 245 public Object next() { 246 Object object = null; 247 try{ 248 object = nextEvent(); 249 }catch(XMLStreamException streamException){ 250 fLastEvent = null ; 251 //xxx: what should be done in this case ? 252 throw new NoSuchElementException(); 253 } 254 return object; 255 } 256 257 public XMLEvent peek() throws XMLStreamException{ 258 //if someone call peek() two times we should just return the peeked event 259 //this is reset if we call next() or nextEvent() 260 if(fPeekedEvent != null) return fPeekedEvent; 261 262 if(hasNext()){ 263 //revisit: we can implement peek() by calling underlying reader to advance 264 // the stream and returning the event without the knowledge of the user 265 // that the stream was advanced but the point is we are advancing the stream 266 //here. -- nb. 267 268 // Is there any application that relies on this behavior ? 269 //Can it be an application knows that there is particularly very large 'comment' section 270 //or character data which it doesn't want to read or to be returned as event 271 //But as of now we are creating every event but it can be optimized not to create 272 // the event. 273 fXMLReader.next(); 274 fPeekedEvent = fXMLEventAllocator.allocate(fXMLReader); 275 return fPeekedEvent; 276 }else{ 277 return null; 278 } 279 }//peek() 280 281 private XMLEvent fPeekedEvent; 282 private XMLEvent fLastEvent; 283 284 }//XMLEventReaderImpl