Added various XML helper classes, for use with OXM

This commit is contained in:
Arjen Poutsma
2009-01-06 16:24:42 +00:00
parent 2eca3aee06
commit 5dfe6775d4
13 changed files with 1881 additions and 115 deletions

View File

@@ -0,0 +1,208 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.util.xml;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;
import org.springframework.util.StringUtils;
/**
* Abstract base class for SAX <code>XMLReader</code> implementations that use StAX as a basis.
*
* @author Arjen Poutsma
* @see #setContentHandler(org.xml.sax.ContentHandler)
* @see #setDTDHandler(org.xml.sax.DTDHandler)
* @see #setEntityResolver(org.xml.sax.EntityResolver)
* @see #setErrorHandler(org.xml.sax.ErrorHandler)
* @since 3.0
*/
abstract class AbstractStaxXmlReader extends AbstractXmlReader {
private static final String NAMESPACES_FEATURE_NAME = "http://xml.org/sax/features/namespaces";
private static final String NAMESPACE_PREFIXES_FEATURE_NAME = "http://xml.org/sax/features/namespace-prefixes";
private static final String IS_STANDALONE_FEATURE_NAME = "http://xml.org/sax/features/is-standalone";
private boolean namespacesFeature = true;
private boolean namespacePrefixesFeature = false;
private Boolean isStandalone;
@Override
public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
if (NAMESPACES_FEATURE_NAME.equals(name)) {
return namespacesFeature;
}
else if (NAMESPACE_PREFIXES_FEATURE_NAME.equals(name)) {
return namespacePrefixesFeature;
}
else if (IS_STANDALONE_FEATURE_NAME.equals(name)) {
if (isStandalone != null) {
return isStandalone;
}
else {
throw new SAXNotSupportedException("startDocument() callback not completed yet");
}
}
else {
return super.getFeature(name);
}
}
@Override
public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
if (NAMESPACES_FEATURE_NAME.equals(name)) {
this.namespacesFeature = value;
}
else if (NAMESPACE_PREFIXES_FEATURE_NAME.equals(name)) {
this.namespacePrefixesFeature = value;
}
else {
super.setFeature(name, value);
}
}
/** Indicates whether the SAX feature <code>http://xml.org/sax/features/namespaces</code> is turned on. */
protected boolean hasNamespacesFeature() {
return namespacesFeature;
}
/** Indicates whether the SAX feature <code>http://xml.org/sax/features/namespaces-prefixes</code> is turned on. */
protected boolean hasNamespacePrefixesFeature() {
return namespacePrefixesFeature;
}
protected void setStandalone(boolean standalone) {
isStandalone = (standalone) ? Boolean.TRUE : Boolean.FALSE;
}
/**
* Parses the StAX XML reader passed at construction-time. <p/> <strong>Note</strong> that the given
* <code>InputSource</code> is not read, but ignored.
*
* @param ignored is ignored
* @throws SAXException A SAX exception, possibly wrapping a <code>XMLStreamException</code>
*/
public final void parse(InputSource ignored) throws SAXException {
parse();
}
/**
* Parses the StAX XML reader passed at construction-time. <p/> <strong>Note</strong> that the given system identifier
* is not read, but ignored.
*
* @param ignored is ignored
* @throws SAXException A SAX exception, possibly wrapping a <code>XMLStreamException</code>
*/
public final void parse(String ignored) throws SAXException {
parse();
}
private void parse() throws SAXException {
try {
parseInternal();
}
catch (XMLStreamException ex) {
Locator locator = null;
if (ex.getLocation() != null) {
locator = new StaxLocator(ex.getLocation());
}
SAXParseException saxException = new SAXParseException(ex.getMessage(), locator, ex);
if (getErrorHandler() != null) {
getErrorHandler().fatalError(saxException);
}
else {
throw saxException;
}
}
}
/**
* Sets the SAX <code>Locator</code> based on the given StAX <code>Location</code>.
*
* @param location the location
* @see ContentHandler#setDocumentLocator(org.xml.sax.Locator)
*/
protected void setLocator(Location location) {
if (getContentHandler() != null) {
getContentHandler().setDocumentLocator(new StaxLocator(location));
}
}
/** Template-method that parses the StAX reader passed at construction-time. */
protected abstract void parseInternal() throws SAXException, XMLStreamException;
/**
* Convert a <code>QName</code> to a qualified name, as used by DOM and SAX. The returned string has a format of
* <code>prefix:localName</code> if the prefix is set, or just <code>localName</code> if not.
*
* @param qName the <code>QName</code>
* @return the qualified name
*/
protected String toQualifiedName(QName qName) {
String prefix = qName.getPrefix();
if (!StringUtils.hasLength(prefix)) {
return qName.getLocalPart();
}
else {
return prefix + ":" + qName.getLocalPart();
}
}
/**
* Implementation of the <code>Locator</code> interface that is based on a StAX <code>Location</code>.
*
* @see Locator
* @see Location
*/
private static class StaxLocator implements Locator {
private Location location;
protected StaxLocator(Location location) {
this.location = location;
}
public String getPublicId() {
return location.getPublicId();
}
public String getSystemId() {
return location.getSystemId();
}
public int getLineNumber() {
return location.getLineNumber();
}
public int getColumnNumber() {
return location.getColumnNumber();
}
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.util.xml;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
/**
* Abstract base class for SAX <code>XMLReader</code> implementations. Contains properties as defined in {@link
* XMLReader}, and does not recognize any features.
*
* @author Arjen Poutsma
* @see #setContentHandler(org.xml.sax.ContentHandler)
* @see #setDTDHandler(org.xml.sax.DTDHandler)
* @see #setEntityResolver(org.xml.sax.EntityResolver)
* @see #setErrorHandler(org.xml.sax.ErrorHandler)
* @since 3.0
*/
abstract class AbstractXmlReader implements XMLReader {
private DTDHandler dtdHandler;
private ContentHandler contentHandler;
private EntityResolver entityResolver;
private ErrorHandler errorHandler;
private LexicalHandler lexicalHandler;
public ContentHandler getContentHandler() {
return contentHandler;
}
public void setContentHandler(ContentHandler contentHandler) {
this.contentHandler = contentHandler;
}
public void setDTDHandler(DTDHandler dtdHandler) {
this.dtdHandler = dtdHandler;
}
public DTDHandler getDTDHandler() {
return dtdHandler;
}
public EntityResolver getEntityResolver() {
return entityResolver;
}
public void setEntityResolver(EntityResolver entityResolver) {
this.entityResolver = entityResolver;
}
public ErrorHandler getErrorHandler() {
return errorHandler;
}
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
protected LexicalHandler getLexicalHandler() {
return lexicalHandler;
}
/**
* Throws a <code>SAXNotRecognizedException</code> exception.
*
* @throws org.xml.sax.SAXNotRecognizedException
* always
*/
public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
throw new SAXNotRecognizedException(name);
}
/**
* Throws a <code>SAXNotRecognizedException</code> exception.
*
* @throws SAXNotRecognizedException always
*/
public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
throw new SAXNotRecognizedException(name);
}
/**
* Throws a <code>SAXNotRecognizedException</code> exception when the given property does not signify a lexical
* handler. The property name for a lexical handler is <code>http://xml.org/sax/properties/lexical-handler</code>.
*/
public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
return lexicalHandler;
}
else {
throw new SAXNotRecognizedException(name);
}
}
/**
* Throws a <code>SAXNotRecognizedException</code> exception when the given property does not signify a lexical
* handler. The property name for a lexical handler is <code>http://xml.org/sax/properties/lexical-handler</code>.
*/
public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
lexicalHandler = (LexicalHandler) value;
}
else {
throw new SAXNotRecognizedException(name);
}
}
}

View File

@@ -0,0 +1,289 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.util.xml;
import java.util.Iterator;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.Comment;
import javax.xml.stream.events.DTD;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.EntityDeclaration;
import javax.xml.stream.events.EntityReference;
import javax.xml.stream.events.Namespace;
import javax.xml.stream.events.NotationDeclaration;
import javax.xml.stream.events.ProcessingInstruction;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.springframework.util.StringUtils;
/**
* SAX <code>XMLReader</code> that reads from a StAX <code>XMLEventReader</code>. Consumes <code>XMLEvents</code> from
* an <code>XMLEventReader</code>, and calls the corresponding methods on the SAX callback interfaces.
*
* @author Arjen Poutsma
* @see XMLEventReader
* @see #setContentHandler(org.xml.sax.ContentHandler)
* @see #setDTDHandler(org.xml.sax.DTDHandler)
* @see #setEntityResolver(org.xml.sax.EntityResolver)
* @see #setErrorHandler(org.xml.sax.ErrorHandler)
* @since 1.0.0
*/
class StaxEventXmlReader extends AbstractStaxXmlReader {
private final XMLEventReader reader;
/**
* Constructs a new instance of the <code>StaxEventXmlReader</code> that reads from the given
* <code>XMLEventReader</code>. The supplied event reader must be in <code>XMLStreamConstants.START_DOCUMENT</code> or
* <code>XMLStreamConstants.START_ELEMENT</code> state.
*
* @param reader the <code>XMLEventReader</code> to read from
* @throws IllegalStateException if the reader is not at the start of a document or element
*/
StaxEventXmlReader(XMLEventReader reader) {
try {
XMLEvent event = reader.peek();
if (event == null || !(event.isStartDocument() || event.isStartElement())) {
throw new IllegalStateException("XMLEventReader not at start of document or element");
}
}
catch (XMLStreamException ex) {
throw new IllegalStateException("Could not read first element: " + ex.getMessage());
}
this.reader = reader;
}
@Override
protected void parseInternal() throws SAXException, XMLStreamException {
boolean documentStarted = false;
boolean documentEnded = false;
int elementDepth = 0;
while (reader.hasNext() && elementDepth >= 0) {
XMLEvent event = reader.nextEvent();
if (!event.isStartDocument() && !event.isEndDocument() && !documentStarted) {
handleStartDocument();
documentStarted = true;
}
switch (event.getEventType()) {
case XMLStreamConstants.START_ELEMENT:
elementDepth++;
handleStartElement(event.asStartElement());
break;
case XMLStreamConstants.END_ELEMENT:
elementDepth--;
if (elementDepth >= 0) {
handleEndElement(event.asEndElement());
}
break;
case XMLStreamConstants.PROCESSING_INSTRUCTION:
handleProcessingInstruction((ProcessingInstruction) event);
break;
case XMLStreamConstants.CHARACTERS:
case XMLStreamConstants.SPACE:
case XMLStreamConstants.CDATA:
handleCharacters(event.asCharacters());
break;
case XMLStreamConstants.START_DOCUMENT:
setLocator(event.getLocation());
handleStartDocument();
documentStarted = true;
break;
case XMLStreamConstants.END_DOCUMENT:
handleEndDocument();
documentEnded = true;
break;
case XMLStreamConstants.NOTATION_DECLARATION:
handleNotationDeclaration((NotationDeclaration) event);
break;
case XMLStreamConstants.ENTITY_DECLARATION:
handleEntityDeclaration((EntityDeclaration) event);
break;
case XMLStreamConstants.COMMENT:
handleComment((Comment) event);
break;
case XMLStreamConstants.DTD:
handleDtd((DTD) event);
break;
case XMLStreamConstants.ENTITY_REFERENCE:
handleEntityReference((EntityReference) event);
break;
}
}
if (!documentEnded) {
handleEndDocument();
}
}
private void handleStartElement(StartElement startElement) throws SAXException {
if (getContentHandler() != null) {
QName qName = startElement.getName();
if (hasNamespacesFeature()) {
for (Iterator i = startElement.getNamespaces(); i.hasNext();) {
Namespace namespace = (Namespace) i.next();
getContentHandler().startPrefixMapping(namespace.getPrefix(), namespace.getNamespaceURI());
}
getContentHandler().startElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName),
getAttributes(startElement));
}
else {
getContentHandler().startElement("", "", toQualifiedName(qName), getAttributes(startElement));
}
}
}
private void handleCharacters(Characters characters) throws SAXException {
char[] data = characters.getData().toCharArray();
if (getContentHandler() != null && characters.isIgnorableWhiteSpace()) {
getContentHandler().ignorableWhitespace(data, 0, data.length);
return;
}
if (characters.isCData() && getLexicalHandler() != null) {
getLexicalHandler().startCDATA();
}
if (getContentHandler() != null) {
getContentHandler().characters(data, 0, data.length);
}
if (characters.isCData() && getLexicalHandler() != null) {
getLexicalHandler().endCDATA();
}
}
private void handleEndDocument() throws SAXException {
if (getContentHandler() != null) {
getContentHandler().endDocument();
}
}
private void handleEndElement(EndElement endElement) throws SAXException {
if (getContentHandler() != null) {
QName qName = endElement.getName();
if (hasNamespacesFeature()) {
getContentHandler().endElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName));
for (Iterator i = endElement.getNamespaces(); i.hasNext();) {
Namespace namespace = (Namespace) i.next();
getContentHandler().endPrefixMapping(namespace.getPrefix());
}
}
else {
getContentHandler().endElement("", "", toQualifiedName(qName));
}
}
}
private void handleNotationDeclaration(NotationDeclaration declaration) throws SAXException {
if (getDTDHandler() != null) {
getDTDHandler().notationDecl(declaration.getName(), declaration.getPublicId(), declaration.getSystemId());
}
}
private void handleEntityDeclaration(EntityDeclaration entityDeclaration) throws SAXException {
if (getDTDHandler() != null) {
getDTDHandler().unparsedEntityDecl(entityDeclaration.getName(), entityDeclaration.getPublicId(),
entityDeclaration.getSystemId(), entityDeclaration.getNotationName());
}
}
private void handleProcessingInstruction(ProcessingInstruction pi) throws SAXException {
if (getContentHandler() != null) {
getContentHandler().processingInstruction(pi.getTarget(), pi.getData());
}
}
private void handleStartDocument() throws SAXException {
if (getContentHandler() != null) {
getContentHandler().startDocument();
}
}
private void handleComment(Comment comment) throws SAXException {
if (getLexicalHandler() != null) {
char[] ch = comment.getText().toCharArray();
getLexicalHandler().comment(ch, 0, ch.length);
}
}
private void handleDtd(DTD dtd) throws SAXException {
if (getLexicalHandler() != null) {
javax.xml.stream.Location location = dtd.getLocation();
getLexicalHandler().startDTD(null, location.getPublicId(), location.getSystemId());
}
if (getLexicalHandler() != null) {
getLexicalHandler().endDTD();
}
}
private void handleEntityReference(EntityReference reference) throws SAXException {
if (getLexicalHandler() != null) {
getLexicalHandler().startEntity(reference.getName());
}
if (getLexicalHandler() != null) {
getLexicalHandler().endEntity(reference.getName());
}
}
private Attributes getAttributes(StartElement event) {
AttributesImpl attributes = new AttributesImpl();
for (Iterator i = event.getAttributes(); i.hasNext();) {
Attribute attribute = (Attribute) i.next();
QName qName = attribute.getName();
String namespace = qName.getNamespaceURI();
if (namespace == null || !hasNamespacesFeature()) {
namespace = "";
}
String type = attribute.getDTDType();
if (type == null) {
type = "CDATA";
}
attributes
.addAttribute(namespace, qName.getLocalPart(), toQualifiedName(qName), type, attribute.getValue());
}
if (hasNamespacePrefixesFeature()) {
for (Iterator i = event.getNamespaces(); i.hasNext();) {
Namespace namespace = (Namespace) i.next();
String prefix = namespace.getPrefix();
String namespaceUri = namespace.getNamespaceURI();
String qName;
if (StringUtils.hasLength(prefix)) {
qName = "xmlns:" + prefix;
}
else {
qName = "xmlns";
}
attributes.addAttribute("", "", qName, "CDATA", namespaceUri);
}
}
return attributes;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.util.xml;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.sax.SAXSource;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
/**
* Implementation of the <code>Source</code> tagging interface for StAX readers. Can be constructed with a
* <code>XMLEventReader</code> or a <code>XMLStreamReader</code>.
*
* <p>This class is necessary because there is no implementation of <code>Source</code> for StAX Readers in JAXP 1.3.
* There is a <code>StAXSource</code> in JAXP 1.4 (JDK 1.6), but this class is kept around for back-ward compatibility
* reasons.
*
* <p>Even though <code>StaxSource</code> extends from <code>SAXSource</code>, calling the methods of
* <code>SAXSource</code> is <strong>not supported</strong>. In general, the only supported operation on this class is
* to use the <code>XMLReader</code> obtained via {@link #getXMLReader()} to parse the input source obtained via {@link
* #getInputSource()}. Calling {@link #setXMLReader(XMLReader)} or {@link #setInputSource(InputSource)} will result in
* <code>UnsupportedOperationException</code>s.
*
* @author Arjen Poutsma
* @see XMLEventReader
* @see XMLStreamReader
* @see javax.xml.transform.Transformer
* @since 3.0
*/
public class StaxSource extends SAXSource {
private XMLEventReader eventReader;
private XMLStreamReader streamReader;
/**
* Constructs a new instance of the <code>StaxSource</code> with the specified <code>XMLStreamReader</code>. The
* supplied stream reader must be in <code>XMLStreamConstants.START_DOCUMENT</code> or
* <code>XMLStreamConstants.START_ELEMENT</code> state.
*
* @param streamReader the <code>XMLStreamReader</code> to read from
* @throws IllegalStateException if the reader is not at the start of a document or element
*/
public StaxSource(XMLStreamReader streamReader) {
super(new StaxStreamXmlReader(streamReader), new InputSource());
this.streamReader = streamReader;
}
/**
* Constructs a new instance of the <code>StaxSource</code> with the specified <code>XMLEventReader</code>. The
* supplied event reader must be in <code>XMLStreamConstants.START_DOCUMENT</code> or
* <code>XMLStreamConstants.START_ELEMENT</code> state.
*
* @param eventReader the <code>XMLEventReader</code> to read from
* @throws IllegalStateException if the reader is not at the start of a document or element
*/
public StaxSource(XMLEventReader eventReader) {
super(new StaxEventXmlReader(eventReader), new InputSource());
this.eventReader = eventReader;
}
/**
* Returns the <code>XMLEventReader</code> used by this <code>StaxSource</code>. If this <code>StaxSource</code> was
* created with an <code>XMLStreamReader</code>, the result will be <code>null</code>.
*
* @return the StAX event reader used by this source
* @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader)
*/
public XMLEventReader getXMLEventReader() {
return eventReader;
}
/**
* Returns the <code>XMLStreamReader</code> used by this <code>StaxSource</code>. If this <code>StaxSource</code> was
* created with an <code>XMLEventReader</code>, the result will be <code>null</code>.
*
* @return the StAX event reader used by this source
* @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader)
*/
public XMLStreamReader getXMLStreamReader() {
return streamReader;
}
/**
* Throws a <code>UnsupportedOperationException</code>.
*
* @throws UnsupportedOperationException always
*/
@Override
public void setInputSource(InputSource inputSource) {
throw new UnsupportedOperationException("setInputSource is not supported");
}
/**
* Throws a <code>UnsupportedOperationException</code>.
*
* @throws UnsupportedOperationException always
*/
@Override
public void setXMLReader(XMLReader reader) {
throw new UnsupportedOperationException("setXMLReader is not supported");
}
}

View File

@@ -0,0 +1,259 @@
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.util.xml;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.springframework.util.StringUtils;
/**
* SAX <code>XMLReader</code> that reads from a StAX <code>XMLStreamReader</code>. Reads from an
* <code>XMLStreamReader</code>, and calls the corresponding methods on the SAX callback interfaces.
*
* @author Arjen Poutsma
* @see XMLStreamReader
* @see #setContentHandler(org.xml.sax.ContentHandler)
* @see #setDTDHandler(org.xml.sax.DTDHandler)
* @see #setEntityResolver(org.xml.sax.EntityResolver)
* @see #setErrorHandler(org.xml.sax.ErrorHandler)
* @since 1.0.0
*/
class StaxStreamXmlReader extends AbstractStaxXmlReader {
private final XMLStreamReader reader;
/**
* Constructs a new instance of the <code>StaxStreamXmlReader</code> that reads from the given
* <code>XMLStreamReader</code>. The supplied stream reader must be in <code>XMLStreamConstants.START_DOCUMENT</code>
* or <code>XMLStreamConstants.START_ELEMENT</code> state.
*
* @param reader the <code>XMLEventReader</code> to read from
* @throws IllegalStateException if the reader is not at the start of a document or element
*/
StaxStreamXmlReader(XMLStreamReader reader) {
int event = reader.getEventType();
if (!(event == XMLStreamConstants.START_DOCUMENT || event == XMLStreamConstants.START_ELEMENT)) {
throw new IllegalStateException("XMLEventReader not at start of document or element");
}
this.reader = reader;
}
@Override
protected void parseInternal() throws SAXException, XMLStreamException {
boolean documentStarted = false;
boolean documentEnded = false;
int elementDepth = 0;
int eventType = reader.getEventType();
while (true) {
if (eventType != XMLStreamConstants.START_DOCUMENT && eventType != XMLStreamConstants.END_DOCUMENT &&
!documentStarted) {
handleStartDocument();
documentStarted = true;
}
switch (eventType) {
case XMLStreamConstants.START_ELEMENT:
elementDepth++;
handleStartElement();
break;
case XMLStreamConstants.END_ELEMENT:
elementDepth--;
if (elementDepth >= 0) {
handleEndElement();
}
break;
case XMLStreamConstants.PROCESSING_INSTRUCTION:
handleProcessingInstruction();
break;
case XMLStreamConstants.CHARACTERS:
case XMLStreamConstants.SPACE:
case XMLStreamConstants.CDATA:
handleCharacters();
break;
case XMLStreamConstants.START_DOCUMENT:
setLocator(reader.getLocation());
handleStartDocument();
documentStarted = true;
break;
case XMLStreamConstants.END_DOCUMENT:
handleEndDocument();
documentEnded = true;
break;
case XMLStreamConstants.COMMENT:
handleComment();
break;
case XMLStreamConstants.DTD:
handleDtd();
break;
case XMLStreamConstants.ENTITY_REFERENCE:
handleEntityReference();
break;
}
if (reader.hasNext() && elementDepth >= 0) {
eventType = reader.next();
}
else {
break;
}
}
if (!documentEnded) {
handleEndDocument();
}
}
private void handleStartDocument() throws SAXException {
if (getContentHandler() != null) {
getContentHandler().startDocument();
if (reader.standaloneSet()) {
setStandalone(reader.isStandalone());
}
}
}
private void handleStartElement() throws SAXException {
if (getContentHandler() != null) {
QName qName = reader.getName();
if (hasNamespacesFeature()) {
for (int i = 0; i < reader.getNamespaceCount(); i++) {
String prefix = reader.getNamespacePrefix(i);
if (prefix == null) {
prefix = "";
}
getContentHandler().startPrefixMapping(prefix, reader.getNamespaceURI(i));
}
getContentHandler().startElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName),
getAttributes());
}
else {
getContentHandler().startElement("", "", toQualifiedName(qName), getAttributes());
}
}
}
private void handleEndElement() throws SAXException {
if (getContentHandler() != null) {
QName qName = reader.getName();
if (hasNamespacesFeature()) {
getContentHandler().endElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName));
for (int i = 0; i < reader.getNamespaceCount(); i++) {
String prefix = reader.getNamespacePrefix(i);
if (prefix == null) {
prefix = "";
}
getContentHandler().endPrefixMapping(prefix);
}
}
else {
getContentHandler().endElement("", "", toQualifiedName(qName));
}
}
}
private void handleCharacters() throws SAXException {
if (getContentHandler() != null && reader.isWhiteSpace()) {
getContentHandler()
.ignorableWhitespace(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength());
return;
}
if (XMLStreamConstants.CDATA == reader.getEventType() && getLexicalHandler() != null) {
getLexicalHandler().startCDATA();
}
if (getContentHandler() != null) {
getContentHandler().characters(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength());
}
if (XMLStreamConstants.CDATA == reader.getEventType() && getLexicalHandler() != null) {
getLexicalHandler().endCDATA();
}
}
private void handleComment() throws SAXException {
if (getLexicalHandler() != null) {
getLexicalHandler().comment(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength());
}
}
private void handleDtd() throws SAXException {
if (getLexicalHandler() != null) {
javax.xml.stream.Location location = reader.getLocation();
getLexicalHandler().startDTD(null, location.getPublicId(), location.getSystemId());
}
if (getLexicalHandler() != null) {
getLexicalHandler().endDTD();
}
}
private void handleEntityReference() throws SAXException {
if (getLexicalHandler() != null) {
getLexicalHandler().startEntity(reader.getLocalName());
}
if (getLexicalHandler() != null) {
getLexicalHandler().endEntity(reader.getLocalName());
}
}
private void handleEndDocument() throws SAXException {
if (getContentHandler() != null) {
getContentHandler().endDocument();
}
}
private void handleProcessingInstruction() throws SAXException {
if (getContentHandler() != null) {
getContentHandler().processingInstruction(reader.getPITarget(), reader.getPIData());
}
}
private Attributes getAttributes() {
AttributesImpl attributes = new AttributesImpl();
for (int i = 0; i < reader.getAttributeCount(); i++) {
String namespace = reader.getAttributeNamespace(i);
if (namespace == null || !hasNamespacesFeature()) {
namespace = "";
}
String type = reader.getAttributeType(i);
if (type == null) {
type = "CDATA";
}
attributes.addAttribute(namespace, reader.getAttributeLocalName(i),
toQualifiedName(reader.getAttributeName(i)), type, reader.getAttributeValue(i));
}
if (hasNamespacePrefixesFeature()) {
for (int i = 0; i < reader.getNamespaceCount(); i++) {
String prefix = reader.getNamespacePrefix(i);
String namespaceUri = reader.getNamespaceURI(i);
String qName;
if (StringUtils.hasLength(prefix)) {
qName = "xmlns:" + prefix;
}
else {
qName = "xmlns";
}
attributes.addAttribute("", "", qName, "CDATA", namespaceUri);
}
}
return attributes;
}
}