package xml;
import javax.xml.stream.EventFilter;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.Iterator;
/**
* A stream reader for xml parts.<br/>
* Uses StaX to read input stream.Reads from stream until end of tag is found.
* Then reads again when getNextXmlPart is called.
*/
public class XmlPartReaderImpl implements XmlPartReader {
private final String tag;
private final InputStream inputStream;
private long startPosition;
private XMLEventReader eventReader;
private XmlPartEventFilter eventFilter;
private boolean initialized = false;
private boolean isNamespaceAware = false;
public boolean isNamespaceAware() {
return isNamespaceAware;
}
public void setNamespaceAware(boolean namespaceAware) {
isNamespaceAware = namespaceAware;
}
private XmlPartReaderImpl(String tag, InputStream inputStream, long startPosition) throws IOException {
this.tag = tag;
this.inputStream = inputStream;
this.startPosition = startPosition;
}
@Override
public Iterator<String> iterator() {
return new XmlPartIterator(this);
}
public static XmlPartReaderImpl readerFor(String tag, InputStream inputStream, long startPosition) throws IOException {
return new XmlPartReaderImpl(tag, inputStream, startPosition);
}
/**
* Closes reader. Reads after calling close will cause an IOException.
* @throws IOException
*/
public void close() throws IOException {
withEventReader(FUNC_CLOSE_READER);
eventReader = null;
inputStream.close();
}
private <A> A withEventReader(EventReaderFunction<A> f) throws IOException {
if(eventReader != null) {
return f.apply(eventReader);
} else
throw new IOException("Reader not yet initialized or already closed.");
}
private interface EventReaderFunction<A> {
public A apply(XMLEventReader reader) throws IOException;
}
private final EventReaderFunction<Void> FUNC_CLOSE_READER = new EventReaderFunction<Void>() {
@Override
public Void apply(XMLEventReader reader) throws IOException {
try {
reader.close();
return null;
} catch (XMLStreamException e) {
throw new IOException(e);
}
}
};
public String getNextXmlPart() throws IOException {
if(hasNext()) {
return withEventReader(FUNC_READ_PART);
} else
return null;
}
private final EventReaderFunction<String> FUNC_READ_PART = new EventReaderFunction<String>() {
@Override
public String apply(XMLEventReader reader) throws IOException {
return readXmlPart(reader, eventFilter);
}
};
private String readXmlPart(XMLEventReader xmlEventReader, XmlPartEventFilter filter) throws IOException {
try {
return readPart(xmlEventReader, filter);
} catch (XMLStreamException e) {
throw new IOException(e);
}
}
private String readPart(XMLEventReader xmlEventReader, XmlPartEventFilter filter) throws XMLStreamException {
StringWriter writer = new StringWriter();
while (filter.isStartFound()) {
XMLEvent event = xmlEventReader.nextEvent();
event.writeAsEncodedUnicode(writer);
}
writer.flush();
return writer.toString();
}
public boolean hasNext() throws IOException {
initialize();
return withEventReader(FUNC_HAS_NEXT);
}
private void initialize() throws IOException {
if(!initialized) {
eventFilter = new XmlPartEventFilter(tag);
XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
xmlInputFactory.setProperty("javax.xml.stream.isNamespaceAware", isNamespaceAware);
try {
XMLEventReader xmlEventReader = xmlInputFactory.createXMLEventReader(inputStream);
if(startPosition > 0) {
readUntilPastStartPosition(xmlEventReader);
}
eventReader = xmlInputFactory.createFilteredReader(xmlEventReader, eventFilter);
initialized = true;
} catch (XMLStreamException e) {
throw new IOException(e);
}
}
}
private void readUntilPastStartPosition(XMLEventReader xmlEventReader) throws XMLStreamException {
XMLEvent xmlEvent = xmlEventReader.peek();
while(xmlEvent.getLocation().getCharacterOffset() < startPosition) {
xmlEvent = xmlEventReader.nextEvent();
}
}
private final EventReaderFunction<Boolean> FUNC_HAS_NEXT = new EventReaderFunction<Boolean>() {
@Override
public Boolean apply(XMLEventReader reader) throws IOException {
return reader.hasNext();
}
};
private static class XmlPartEventFilter implements EventFilter {
boolean startFound = false;
private String tag;
public XmlPartEventFilter(String tag) {
this.tag = tag;
}
public boolean isStartFound() {
return startFound;
}
private boolean isTag(StartElement element) {
return tag.equals(element.getName().getLocalPart());
}
private boolean isTag(EndElement element) {
return tag.equals(element.getName().getLocalPart());
}
@Override
public boolean accept(XMLEvent xmlEvent) {
if (!startFound && xmlEvent.isStartElement() && isTag(xmlEvent.asStartElement())) {
startFound = true;
}
// accept everything after start is found
// also accept closing tag
boolean accept = startFound;
// reset when end tag found
if (startFound && xmlEvent.isEndElement() && isTag(xmlEvent.asEndElement())) {
startFound = false;
}
return accept;
}
}
}