package com.revolsys.record.io.format.xml;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.stream.util.StreamReaderDelegate;
import org.apache.commons.io.input.XmlStreamReader;
import com.revolsys.io.BaseCloseable;
import com.revolsys.spring.resource.Resource;
import com.revolsys.util.Exceptions;
import com.revolsys.util.Property;
public class StaxReader extends StreamReaderDelegate implements BaseCloseable {
private static final XMLInputFactory FACTORY = XMLInputFactory.newInstance();
public static StaxReader newXmlReader(final InputStream inputStream) {
return newXmlReader(FACTORY, inputStream);
}
public static StaxReader newXmlReader(final Reader reader) {
return newXmlReader(FACTORY, reader);
}
public static StaxReader newXmlReader(final Resource resource) {
final InputStream inputStream = resource.getInputStream();
return newXmlReader(inputStream);
}
public static StaxReader newXmlReader(final XMLInputFactory factory,
final InputStream inputStream) {
try {
final XmlStreamReader reader = new XmlStreamReader(inputStream);
return newXmlReader(factory, reader);
} catch (final IOException e) {
return Exceptions.throwUncheckedException(e);
}
}
public static StaxReader newXmlReader(final XMLInputFactory factory, final Reader reader) {
try {
final XMLStreamReader xmlReader = factory.createXMLStreamReader(reader);
return new StaxReader(xmlReader);
} catch (final Throwable e) {
return Exceptions.throwUncheckedException(e);
}
}
private int depth = 0;
public StaxReader(final XMLStreamReader reader) {
super(reader);
}
private void appendAttribute(final StringBuilder builder, final int index) {
final String prefix = getAttributePrefix(index);
final String namespace = getAttributeNamespace(index);
final String localName = getAttributeLocalName(index);
final String value = getAttributeValue(index);
builder.append(' ');
appendName(builder, prefix, namespace, localName);
builder.append("='" + value + "'");
}
private void appendAttributes(final StringBuilder builder) {
for (int i = 0; i < getAttributeCount(); i++) {
appendAttribute(builder, i);
}
}
private void appendName(final StringBuilder builder) {
if (hasName()) {
final String prefix = getPrefix();
final String uri = getNamespaceURI();
final String localName = getLocalName();
appendName(builder, prefix, uri, localName);
}
}
private void appendName(final StringBuilder builder, final String prefix, final String uri,
final String localName) {
if (uri != null && !"".equals(uri)) {
builder.append("['");
builder.append(uri);
builder.append("']:");
}
if (prefix != null) {
builder.append(prefix + ":");
}
if (localName != null) {
builder.append(localName);
}
}
private void appendNamespace(final StringBuilder builder, final int index) {
final String prefix = getNamespacePrefix(index);
final String uri = getNamespaceURI(index);
builder.append(' ');
if (prefix == null) {
builder.append("xmlns='" + uri + "'");
} else {
builder.append("xmlns:" + prefix + "='" + uri + "'");
}
}
private void appendNamespaces(final StringBuilder builder) {
for (int i = 0; i < getNamespaceCount(); i++) {
appendNamespace(builder, i);
}
}
@Override
public void close() {
try {
super.close();
} catch (final XMLStreamException e) {
}
}
public String getAttribute(final QName typePath) {
final String namespaceURI = typePath.getNamespaceURI();
final String localPart = typePath.getLocalPart();
final String value = getAttributeValue(namespaceURI, localPart);
return value;
}
public boolean getBooleanAttribute(final String namespaceUri, final String name) {
final String value = getAttributeValue(namespaceUri, name);
if (value != null) {
return Boolean.parseBoolean(value);
} else {
return false;
}
}
public int getDepth() {
return this.depth;
}
public double getDoubleAttribute(final String namespaceUri, final String name) {
final String value = getAttributeValue(namespaceUri, name);
if (value != null) {
return Double.parseDouble(value);
} else {
return Double.NaN;
}
}
@Override
public String getElementText() {
final StringBuilder text = new StringBuilder();
if (getEventType() == XMLStreamConstants.START_ELEMENT) {
final int depth = this.depth;
while (next() != XMLStreamConstants.END_ELEMENT && getDepth() >= depth) {
switch (getEventType()) {
case XMLStreamConstants.CHARACTERS:
text.append(getText());
break;
}
}
}
return text.toString();
}
public double getElementTextDouble(final double defaultValue) {
final String text = getElementText();
if (Property.hasValue(text)) {
try {
return Double.parseDouble(text);
} catch (final Throwable e) {
return defaultValue;
}
}
return defaultValue;
}
public int getElementTextInt(final int defaultValue) {
final String text = getElementText();
if (Property.hasValue(text)) {
try {
return Integer.parseInt(text);
} catch (final Throwable e) {
return defaultValue;
}
}
return defaultValue;
}
public int getIntAttribute(final String namespaceUri, final String name) {
final String value = getAttributeValue(namespaceUri, name);
if (value != null) {
return Integer.parseInt(value);
} else {
return 0;
}
}
public String getLocalPart() {
final QName name = getName();
return name.getLocalPart();
}
public long getLongAttribute(final String namespaceUri, final String name) {
final String value = getAttributeValue(namespaceUri, name);
if (value != null) {
return Long.parseLong(value);
} else {
return 0;
}
}
public QName getQNameAttribute(final QName fieldName) {
final String value = getAttribute(fieldName);
final NamespaceContext namespaceContext = getNamespaceContext();
final QName qName = getXmlQName(namespaceContext, value);
return qName;
}
public QName getXmlQName(final NamespaceContext context, final String value) {
if (value == null) {
return null;
} else {
final int colonIndex = value.indexOf(':');
if (colonIndex == -1) {
return new QName(value);
} else {
final String prefix = value.substring(0, colonIndex);
final String name = value.substring(colonIndex + 1);
final String namespaceUri = context.getNamespaceURI(prefix);
return new QName(namespaceUri, name, prefix);
}
}
}
public boolean isEndElementLocalName(final QName name) {
if (isEndElement()) {
final String currentLocalName = getLocalName();
final String requiredLocalName = name.getLocalPart();
if (currentLocalName.equals(requiredLocalName)) {
return true;
}
}
return false;
}
/**
* Check that the parser is currently at the specified XML element.
*
* @param parser The STAX XML
* @param element The expected XML element.
* @throws XMLStreamException If an exception processing the XML occurs.
*/
public boolean isStartElementLocalName(final QName name) {
final String requiredLocalName = name.getLocalPart();
return isStartElementLocalName(requiredLocalName);
}
public boolean isStartElementLocalName(final String requiredLocalName) {
if (isStartElement()) {
final String currentLocalName = getLocalName();
if (currentLocalName.equals(requiredLocalName)) {
return true;
}
}
return false;
}
@Override
public int next() {
try {
final int next = super.next();
switch (next) {
case XMLStreamConstants.START_ELEMENT:
this.depth++;
break;
case XMLStreamConstants.END_ELEMENT:
this.depth--;
break;
default:
break;
}
return next;
} catch (final XMLStreamException e) {
return Exceptions.throwUncheckedException(e);
}
}
@Override
public int nextTag() {
try {
return super.nextTag();
} catch (final XMLStreamException e) {
return (Integer)Exceptions.throwUncheckedException(e);
}
}
@Override
public void require(final int type, final String namespaceURI, final String localPart) {
try {
super.require(type, namespaceURI, localPart);
} catch (final XMLStreamException e) {
Exceptions.throwUncheckedException(e);
}
}
/**
* Check that the parser is currently at the specified XML element.
*
* @param parser The STAX XML
* @param element The expected XML element.
* @throws XMLStreamException If an exception processing the XML occurs.
*/
public void require(final QName element) {
final String namespaceURI = element.getNamespaceURI();
final String localPart = element.getLocalPart();
require(XMLStreamConstants.START_ELEMENT, namespaceURI, localPart);
}
/**
* Check that the parser is currently at the specified XML element.
*
* @param parser The STAX XML
* @param element The expected XML element.
* @throws XMLStreamException If an exception processing the XML occurs.
*/
public void requireLocalName(final QName element) {
final String localPart = element.getLocalPart();
require(XMLStreamConstants.START_ELEMENT, null, localPart);
}
/**
* Skip all elements and content until the end of the current element.
*
* @param parser The STAX XML
* @throws XMLStreamException If an exception processing the XML occurs.
*/
public void skipSubTree() {
require(XMLStreamConstants.START_ELEMENT, null, null);
int level = 1;
while (level > 0) {
final int eventType = next();
if (eventType == XMLStreamConstants.END_ELEMENT) {
--level;
} else if (eventType == XMLStreamConstants.START_ELEMENT) {
++level;
}
}
}
/**
* Skip all events until the next start element which is a child of the
* current element has one of the elementNames.
*
* @param parser The STAX XML
* @param elementNames The names of the elements to find
* @return True if one of the elements was found.
* @throws XMLStreamException If an exception processing the XML occurs.
*/
public boolean skipToChildStartElements(final Collection<QName> elementNames) {
int count = 0;
QName elementName = null;
if (isEndElement()) {
elementName = getName();
if (elementNames.contains(elementName)) {
nextTag();
} else {
return false;
}
}
if (isStartElement()) {
elementName = getName();
if (elementNames.contains(elementName)) {
return true;
}
}
do {
while (next() != XMLStreamConstants.START_ELEMENT) {
if (getEventType() == XMLStreamConstants.END_DOCUMENT) {
return false;
} else if (isEndElement()) {
if (count == 0) {
return false;
}
count--;
}
}
count++;
elementName = getName();
} while (!elementNames.contains(elementName));
return true;
}
/**
* Skip all events until the next start element which is a child of the
* current element has one of the elementNames.
*
* @param parser The STAX XML
* @param elementNames The names of the elements to find
* @return True if one of the elements was found.
* @throws XMLStreamException If an exception processing the XML occurs.
*/
public boolean skipToChildStartElements(final QName... elementNames) {
final List<QName> names = Arrays.asList(elementNames);
return skipToChildStartElements(names);
}
/**
* Skip all events until the next end element event.
*
* @param parser The STAX XML
* @throws XMLStreamException If an exception processing the XML occurs.
*/
public int skipToEndElement() {
while (next() != XMLStreamConstants.END_ELEMENT) {
if (getEventType() == XMLStreamConstants.END_DOCUMENT) {
return getEventType();
}
}
return getEventType();
}
/**
* Skip all events until the next end element event.
*
* @param parser The STAX XML
* @throws XMLStreamException If an exception processing the XML occurs.
*/
public void skipToEndElement(final QName name) {
while (!isEndElement() || !getName().equals(name)) {
next();
}
next();
}
/**
* Skip all events until the next end element event.
*
* @param parser The STAX XML
*/
public void skipToEndElementByLocalName(final QName name) {
while (!isEndElement() || !getName().getLocalPart().equals(name.getLocalPart())) {
next();
if (getEventType() == XMLStreamConstants.START_ELEMENT
|| getEventType() == XMLStreamConstants.END_ELEMENT) {
}
}
skipWhitespace();
}
/**
* Skip all events until the next start element event.
*
* @param parser The STAX XML
*/
public boolean skipToStartElement() {
while (next() != XMLStreamConstants.START_ELEMENT) {
if (getEventType() == XMLStreamConstants.END_DOCUMENT) {
return false;
}
}
return true;
}
public boolean skipToStartElement(final int depth, final QName elementName) {
while (this.depth >= depth) {
next();
if (this.depth < depth) {
return false;
} else if (getEventType() == END_DOCUMENT) {
return false;
} else if (getEventType() == START_ELEMENT) {
final QName currentElement = getName();
if (currentElement.equals(elementName)) {
return true;
}
}
}
return false;
}
public boolean skipToStartElement(final int depth, final String localName) {
while (this.depth >= depth) {
next();
if (this.depth < depth) {
return false;
} else if (getEventType() == END_DOCUMENT) {
return false;
} else if (getEventType() == START_ELEMENT) {
final QName currentElement = getName();
if (isStartElementLocalName(localName)) {
return true;
}
}
}
return false;
}
public boolean skipToStartElement(final String localName) {
String currentName = null;
do {
while (next() != XMLStreamConstants.START_ELEMENT) {
if (getEventType() == XMLStreamConstants.END_DOCUMENT) {
return false;
}
}
currentName = getLocalName();
} while (!currentName.equals(localName));
return true;
}
public boolean skipToStartElements(final int depth, final Collection<QName> elementNames) {
try {
while (this.depth >= depth) {
next();
if (this.depth < depth) {
return false;
} else if (getEventType() == END_DOCUMENT) {
return false;
} else if (getEventType() == START_ELEMENT) {
final QName elementName = getName();
if (elementNames.contains(elementName)) {
return true;
}
}
}
} catch (final NoSuchElementException e) {
}
return false;
}
public boolean skipToStartElements(final int depth, final QName... elementNames) {
return skipToStartElements(depth, Arrays.asList(elementNames));
}
public void skipToStartOrEndElement() {
require(XMLStreamConstants.END_ELEMENT, null, null);
while (true) {
final int eventType = next();
if (eventType == XMLStreamConstants.END_ELEMENT) {
return;
} else if (eventType == XMLStreamConstants.START_ELEMENT) {
return;
} else if (eventType == XMLStreamConstants.END_DOCUMENT) {
return;
}
}
}
/**
* Skip any whitespace until an start or end of element is found.
*
* @param parser The STAX XML
* @throws XMLStreamException If an exception processing the XML occurs.
*/
public int skipWhitespace() {
while (next() == XMLStreamConstants.CHARACTERS && isWhiteSpace()) {
switch (getEventType()) {
case XMLStreamConstants.END_DOCUMENT:
case XMLStreamConstants.START_ELEMENT:
return getEventType();
}
}
return getEventType();
}
public void startElement(final XMLStreamWriter writer, final QName element) {
try {
writer.writeStartElement(element.getPrefix(), element.getLocalPart(),
element.getNamespaceURI());
} catch (final XMLStreamException e) {
Exceptions.throwUncheckedException(e);
}
}
@Override
public String toString() {
final int eventType = getEventType();
final StringBuilder builder = new StringBuilder();
switch (eventType) {
case START_ELEMENT:
builder.append("<");
appendName(builder);
appendNamespaces(builder);
appendAttributes(builder);
builder.append(">");
break;
case END_ELEMENT:
builder.append("</");
appendName(builder);
builder.append(">");
break;
case SPACE:
case CHARACTERS:
int start = getTextStart();
int length = getTextLength();
builder.append(new String(getTextCharacters(), start, length));
break;
case PROCESSING_INSTRUCTION:
builder.append("<?");
if (hasText()) {
builder.append(getText());
}
builder.append("?>");
break;
case CDATA:
builder.append("<![CDATA[");
start = getTextStart();
length = getTextLength();
builder.append(new String(getTextCharacters(), start, length));
builder.append("]]>");
break;
case COMMENT:
builder.append("<!--");
if (hasText()) {
builder.append(getText());
}
builder.append("-->");
break;
case ENTITY_REFERENCE:
builder.append(getLocalName() + "=");
if (hasText()) {
builder.append("[" + getText() + "]");
}
break;
case START_DOCUMENT:
builder.append("<?xml");
builder.append(" version='" + getVersion() + "'");
builder.append(" encoding='" + getCharacterEncodingScheme() + "'");
if (isStandalone()) {
builder.append(" standalone='yes'");
} else {
builder.append(" standalone='no'");
}
builder.append("?>");
break;
case ATTRIBUTE:
return "";
case DTD:
return "";
case END_DOCUMENT:
return "";
case ENTITY_DECLARATION:
return "";
case NAMESPACE:
return "";
case NOTATION_DECLARATION:
return "";
default:
return "Unknown XML event: " + eventType;
}
return builder.toString();
}
}