From 81b1d4e5720cc58e0c679289ccc5f587a45878bd Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Tue, 6 Jan 2009 16:24:42 +0000 Subject: [PATCH] Added various XML helper classes, for use with OXM git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@514 50f2f4bb-b051-0410-bef5-90022cba6387 --- org.springframework.core/core.iml | 396 +++++++++++++----- org.springframework.core/ivy.xml | 22 +- .../util/xml/AbstractStaxXmlReader.java | 208 +++++++++ .../util/xml/AbstractXmlReader.java | 131 ++++++ .../util/xml/StaxEventXmlReader.java | 289 +++++++++++++ .../springframework/util/xml/StaxSource.java | 119 ++++++ .../util/xml/StaxStreamXmlReader.java | 259 ++++++++++++ .../xml/AbstractStaxXmlReaderTestCase.java | 363 ++++++++++++++++ .../util/xml/StaxEventXmlReaderTest.java | 61 +++ .../util/xml/StaxSourceTest.java | 68 +++ .../util/xml/StaxStreamXmlReaderTest.java | 70 ++++ .../util/xml/testContentHandler.xml | 2 + .../util/xml/testLexicalHandler.xml | 8 + 13 files changed, 1881 insertions(+), 115 deletions(-) create mode 100644 org.springframework.core/src/main/java/org/springframework/util/xml/AbstractStaxXmlReader.java create mode 100644 org.springframework.core/src/main/java/org/springframework/util/xml/AbstractXmlReader.java create mode 100644 org.springframework.core/src/main/java/org/springframework/util/xml/StaxEventXmlReader.java create mode 100644 org.springframework.core/src/main/java/org/springframework/util/xml/StaxSource.java create mode 100644 org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamXmlReader.java create mode 100644 org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxXmlReaderTestCase.java create mode 100644 org.springframework.core/src/test/java/org/springframework/util/xml/StaxEventXmlReaderTest.java create mode 100644 org.springframework.core/src/test/java/org/springframework/util/xml/StaxSourceTest.java create mode 100644 org.springframework.core/src/test/java/org/springframework/util/xml/StaxStreamXmlReaderTest.java create mode 100644 org.springframework.core/src/test/resources/org/springframework/util/xml/testContentHandler.xml create mode 100644 org.springframework.core/src/test/resources/org/springframework/util/xml/testLexicalHandler.xml diff --git a/org.springframework.core/core.iml b/org.springframework.core/core.iml index c38f24baa4b..cb07f548806 100644 --- a/org.springframework.core/core.iml +++ b/org.springframework.core/core.iml @@ -1,109 +1,287 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.core/ivy.xml b/org.springframework.core/ivy.xml index 218231af423..f1774c6a35b 100644 --- a/org.springframework.core/ivy.xml +++ b/org.springframework.core/ivy.xml @@ -22,16 +22,26 @@ - - - - + + + + - - + + + + + diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractStaxXmlReader.java b/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractStaxXmlReader.java new file mode 100644 index 00000000000..cc6560811c1 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractStaxXmlReader.java @@ -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 XMLReader 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 http://xml.org/sax/features/namespaces is turned on. */ + protected boolean hasNamespacesFeature() { + return namespacesFeature; + } + + /** Indicates whether the SAX feature http://xml.org/sax/features/namespaces-prefixes 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.

Note that the given + * InputSource is not read, but ignored. + * + * @param ignored is ignored + * @throws SAXException A SAX exception, possibly wrapping a XMLStreamException + */ + public final void parse(InputSource ignored) throws SAXException { + parse(); + } + + /** + * Parses the StAX XML reader passed at construction-time.

Note that the given system identifier + * is not read, but ignored. + * + * @param ignored is ignored + * @throws SAXException A SAX exception, possibly wrapping a XMLStreamException + */ + 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 Locator based on the given StAX Location. + * + * @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 QName to a qualified name, as used by DOM and SAX. The returned string has a format of + * prefix:localName if the prefix is set, or just localName if not. + * + * @param qName the QName + * @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 Locator interface that is based on a StAX Location. + * + * @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(); + } + } +} diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractXmlReader.java b/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractXmlReader.java new file mode 100644 index 00000000000..2c914b9bea2 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractXmlReader.java @@ -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 XMLReader 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 SAXNotRecognizedException exception. + * + * @throws org.xml.sax.SAXNotRecognizedException + * always + */ + public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException { + throw new SAXNotRecognizedException(name); + } + + /** + * Throws a SAXNotRecognizedException exception. + * + * @throws SAXNotRecognizedException always + */ + public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException { + throw new SAXNotRecognizedException(name); + } + + /** + * Throws a SAXNotRecognizedException exception when the given property does not signify a lexical + * handler. The property name for a lexical handler is http://xml.org/sax/properties/lexical-handler. + */ + 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 SAXNotRecognizedException exception when the given property does not signify a lexical + * handler. The property name for a lexical handler is http://xml.org/sax/properties/lexical-handler. + */ + 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); + } + } +} diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/StaxEventXmlReader.java b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxEventXmlReader.java new file mode 100644 index 00000000000..73c3034af85 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxEventXmlReader.java @@ -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 XMLReader that reads from a StAX XMLEventReader. Consumes XMLEvents from + * an XMLEventReader, 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 StaxEventXmlReader that reads from the given + * XMLEventReader. The supplied event reader must be in XMLStreamConstants.START_DOCUMENT or + * XMLStreamConstants.START_ELEMENT state. + * + * @param reader the XMLEventReader 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; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/StaxSource.java b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxSource.java new file mode 100644 index 00000000000..e69b0172042 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxSource.java @@ -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 Source tagging interface for StAX readers. Can be constructed with a + * XMLEventReader or a XMLStreamReader. + * + *

This class is necessary because there is no implementation of Source for StAX Readers in JAXP 1.3. + * There is a StAXSource in JAXP 1.4 (JDK 1.6), but this class is kept around for back-ward compatibility + * reasons. + * + *

Even though StaxSource extends from SAXSource, calling the methods of + * SAXSource is not supported. In general, the only supported operation on this class is + * to use the XMLReader obtained via {@link #getXMLReader()} to parse the input source obtained via {@link + * #getInputSource()}. Calling {@link #setXMLReader(XMLReader)} or {@link #setInputSource(InputSource)} will result in + * UnsupportedOperationExceptions. + * + * @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 StaxSource with the specified XMLStreamReader. The + * supplied stream reader must be in XMLStreamConstants.START_DOCUMENT or + * XMLStreamConstants.START_ELEMENT state. + * + * @param streamReader the XMLStreamReader 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 StaxSource with the specified XMLEventReader. The + * supplied event reader must be in XMLStreamConstants.START_DOCUMENT or + * XMLStreamConstants.START_ELEMENT state. + * + * @param eventReader the XMLEventReader 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 XMLEventReader used by this StaxSource. If this StaxSource was + * created with an XMLStreamReader, the result will be null. + * + * @return the StAX event reader used by this source + * @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader) + */ + public XMLEventReader getXMLEventReader() { + return eventReader; + } + + /** + * Returns the XMLStreamReader used by this StaxSource. If this StaxSource was + * created with an XMLEventReader, the result will be null. + * + * @return the StAX event reader used by this source + * @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader) + */ + public XMLStreamReader getXMLStreamReader() { + return streamReader; + } + + /** + * Throws a UnsupportedOperationException. + * + * @throws UnsupportedOperationException always + */ + @Override + public void setInputSource(InputSource inputSource) { + throw new UnsupportedOperationException("setInputSource is not supported"); + } + + /** + * Throws a UnsupportedOperationException. + * + * @throws UnsupportedOperationException always + */ + @Override + public void setXMLReader(XMLReader reader) { + throw new UnsupportedOperationException("setXMLReader is not supported"); + } +} diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamXmlReader.java b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamXmlReader.java new file mode 100644 index 00000000000..2f8a529d9af --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamXmlReader.java @@ -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 XMLReader that reads from a StAX XMLStreamReader. Reads from an + * XMLStreamReader, 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 StaxStreamXmlReader that reads from the given + * XMLStreamReader. The supplied stream reader must be in XMLStreamConstants.START_DOCUMENT + * or XMLStreamConstants.START_ELEMENT state. + * + * @param reader the XMLEventReader 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; + } + +} diff --git a/org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxXmlReaderTestCase.java b/org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxXmlReaderTestCase.java new file mode 100644 index 00000000000..c96dee49c67 --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxXmlReaderTestCase.java @@ -0,0 +1,363 @@ +/* + * 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.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; + +import org.easymock.AbstractMatcher; +import org.easymock.MockControl; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.XMLReaderFactory; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +public abstract class AbstractStaxXmlReaderTestCase { + + protected static XMLInputFactory inputFactory; + + private XMLReader standardReader; + + private MockControl contentHandlerControl; + + private ContentHandler contentHandler; + + @Before + public void setUp() throws Exception { + inputFactory = XMLInputFactory.newInstance(); + standardReader = XMLReaderFactory.createXMLReader(); + contentHandlerControl = MockControl.createStrictControl(ContentHandler.class); + contentHandlerControl.setDefaultMatcher(new SaxArgumentMatcher()); + ContentHandler contentHandlerMock = (ContentHandler) contentHandlerControl.getMock(); + contentHandler = new CopyingContentHandler(contentHandlerMock); + standardReader.setContentHandler(contentHandler); + } + + private InputStream createTestInputStream() { + return getClass().getResourceAsStream("testContentHandler.xml"); + } + + @Test + public void contentHandlerNamespacesNoPrefixes() throws SAXException, IOException, XMLStreamException { + standardReader.setFeature("http://xml.org/sax/features/namespaces", true); + standardReader.setFeature("http://xml.org/sax/features/namespace-prefixes", false); + + standardReader.parse(new InputSource(createTestInputStream())); + contentHandlerControl.replay(); + + AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(createTestInputStream()); + staxXmlReader.setFeature("http://xml.org/sax/features/namespaces", true); + staxXmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", false); + + staxXmlReader.setContentHandler(contentHandler); + staxXmlReader.parse(new InputSource()); + contentHandlerControl.verify(); + } + + @Test + public void contentHandlerNamespacesPrefixes() throws SAXException, IOException, XMLStreamException { + standardReader.setFeature("http://xml.org/sax/features/namespaces", true); + standardReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); + + standardReader.parse(new InputSource(createTestInputStream())); + contentHandlerControl.replay(); + + AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(createTestInputStream()); + staxXmlReader.setFeature("http://xml.org/sax/features/namespaces", true); + staxXmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); + + staxXmlReader.setContentHandler(contentHandler); + staxXmlReader.parse(new InputSource()); + contentHandlerControl.verify(); + } + + @Test + public void contentHandlerNoNamespacesPrefixes() throws SAXException, IOException, XMLStreamException { + standardReader.setFeature("http://xml.org/sax/features/namespaces", false); + standardReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); + + standardReader.parse(new InputSource(createTestInputStream())); + contentHandlerControl.replay(); + + AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(createTestInputStream()); + staxXmlReader.setFeature("http://xml.org/sax/features/namespaces", false); + staxXmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); + + staxXmlReader.setContentHandler(contentHandler); + staxXmlReader.parse(new InputSource()); + contentHandlerControl.verify(); + } + + @Test + @Ignore + public void lexicalHandler() throws SAXException, IOException, XMLStreamException { + MockControl lexicalHandlerControl = MockControl.createControl(LexicalHandler.class); + lexicalHandlerControl.setDefaultMatcher(new SaxArgumentMatcher()); + LexicalHandler lexicalHandlerMock = (LexicalHandler) lexicalHandlerControl.getMock(); + LexicalHandler lexicalHandler = new CopyingLexicalHandler(lexicalHandlerMock); + + Resource testLexicalHandlerXml = new ClassPathResource("testLexicalHandler.xml", getClass()); + + standardReader.setContentHandler(null); + standardReader.setProperty("http://xml.org/sax/properties/lexical-handler", lexicalHandler); + standardReader.parse(new InputSource(createTestInputStream())); + lexicalHandlerControl.replay(); + + inputFactory.setProperty("javax.xml.stream.isCoalescing", Boolean.FALSE); + inputFactory.setProperty("http://java.sun.com/xml/stream/properties/report-cdata-event", Boolean.TRUE); + inputFactory.setProperty("javax.xml.stream.isReplacingEntityReferences", Boolean.FALSE); + inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", Boolean.FALSE); + + AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(testLexicalHandlerXml.getInputStream()); + + staxXmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", lexicalHandler); + staxXmlReader.parse(new InputSource()); + lexicalHandlerControl.verify(); + } + + protected abstract AbstractStaxXmlReader createStaxXmlReader(InputStream inputStream) throws XMLStreamException; + + /** Easymock ArgumentMatcher implementation that matches SAX arguments. */ + protected static class SaxArgumentMatcher extends AbstractMatcher { + + @Override + public boolean matches(Object[] expected, Object[] actual) { + if (expected == actual) { + return true; + } + if (expected == null || actual == null) { + return false; + } + if (expected.length != actual.length) { + throw new IllegalArgumentException("Expected and actual arguments must have the same size"); + } + if (expected.length == 3 && expected[0] instanceof char[] && expected[1] instanceof Integer && + expected[2] instanceof Integer) { + // handling of the character(char[], int, int) methods + String expectedString = new String((char[]) expected[0], (Integer) expected[1], (Integer) expected[2]); + String actualString = new String((char[]) actual[0], (Integer) actual[1], (Integer) actual[2]); + return expectedString.equals(actualString); + } + else if (expected.length == 1 && (expected[0] instanceof Locator)) { + return true; + } + else { + return super.matches(expected, actual); + } + } + + @Override + protected boolean argumentMatches(Object expected, Object actual) { + if (expected instanceof char[]) { + return Arrays.equals((char[]) expected, (char[]) actual); + } + else if (expected instanceof Attributes) { + Attributes expectedAttributes = (Attributes) expected; + Attributes actualAttributes = (Attributes) actual; + if (expectedAttributes.getLength() != actualAttributes.getLength()) { + return false; + } + for (int i = 0; i < expectedAttributes.getLength(); i++) { + boolean found = false; + for (int j = 0; j < actualAttributes.getLength(); j++) { + if (expectedAttributes.getURI(i).equals(actualAttributes.getURI(j)) && + expectedAttributes.getQName(i).equals(actualAttributes.getQName(j)) && + expectedAttributes.getType(i).equals(actualAttributes.getType(j)) && + expectedAttributes.getValue(i).equals(actualAttributes.getValue(j))) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + else { + return super.argumentMatches(expected, actual); + } + } + + @Override + public String toString(Object[] arguments) { + if (arguments != null && arguments.length == 3 && arguments[0] instanceof char[] && + arguments[1] instanceof Integer && arguments[2] instanceof Integer) { + return new String((char[]) arguments[0], (Integer) arguments[1], (Integer) arguments[2]); + } + else { + return super.toString(arguments); + } + } + + @Override + protected String argumentToString(Object argument) { + if (argument instanceof char[]) { + char[] array = (char[]) argument; + StringBuilder buffer = new StringBuilder(); + for (char anArray : array) { + buffer.append(anArray); + } + return buffer.toString(); + } + else if (argument instanceof Attributes) { + Attributes attributes = (Attributes) argument; + StringBuilder buffer = new StringBuilder("["); + for (int i = 0; i < attributes.getLength(); i++) { + if (attributes.getURI(i).length() != 0) { + buffer.append('{'); + buffer.append(attributes.getURI(i)); + buffer.append('}'); + } + if (attributes.getQName(i).length() != 0) { + buffer.append(attributes.getQName(i)); + } + buffer.append('='); + buffer.append(attributes.getValue(i)); + if (i < attributes.getLength() - 1) { + buffer.append(", "); + } + } + buffer.append(']'); + return buffer.toString(); + } + else if (argument instanceof Locator) { + Locator locator = (Locator) argument; + StringBuilder buffer = new StringBuilder("["); + buffer.append(locator.getLineNumber()); + buffer.append(','); + buffer.append(locator.getColumnNumber()); + buffer.append(']'); + return buffer.toString(); + } + else { + return super.argumentToString(argument); + } + } + } + + private static class CopyingContentHandler implements ContentHandler { + + private final ContentHandler wrappee; + + private CopyingContentHandler(ContentHandler wrappee) { + this.wrappee = wrappee; + } + + public void setDocumentLocator(Locator locator) { + wrappee.setDocumentLocator(locator); + } + + public void startDocument() throws SAXException { + wrappee.startDocument(); + } + + public void endDocument() throws SAXException { + wrappee.endDocument(); + } + + public void startPrefixMapping(String prefix, String uri) throws SAXException { + wrappee.startPrefixMapping(prefix, uri); + } + + public void endPrefixMapping(String prefix) throws SAXException { + wrappee.endPrefixMapping(prefix); + } + + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + wrappee.startElement(uri, localName, qName, new AttributesImpl(attributes)); + } + + public void endElement(String uri, String localName, String qName) throws SAXException { + wrappee.endElement(uri, localName, qName); + } + + public void characters(char ch[], int start, int length) throws SAXException { + wrappee.characters(copy(ch), start, length); + } + + public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { + } + + public void processingInstruction(String target, String data) throws SAXException { + wrappee.processingInstruction(target, data); + } + + public void skippedEntity(String name) throws SAXException { + wrappee.skippedEntity(name); + } + } + + private static class CopyingLexicalHandler implements LexicalHandler { + + private final LexicalHandler wrappee; + + private CopyingLexicalHandler(LexicalHandler wrappee) { + this.wrappee = wrappee; + } + + public void startDTD(String name, String publicId, String systemId) throws SAXException { + wrappee.startDTD("element", publicId, systemId); + } + + public void endDTD() throws SAXException { + wrappee.endDTD(); + } + + public void startEntity(String name) throws SAXException { + wrappee.startEntity(name); + } + + public void endEntity(String name) throws SAXException { + wrappee.endEntity(name); + } + + public void startCDATA() throws SAXException { + wrappee.startCDATA(); + } + + public void endCDATA() throws SAXException { + wrappee.endCDATA(); + } + + public void comment(char ch[], int start, int length) throws SAXException { + wrappee.comment(copy(ch), start, length); + } + } + + private static char[] copy(char[] ch) { + char[] copy = new char[ch.length]; + System.arraycopy(ch, 0, copy, 0, ch.length); + return copy; + } + +} diff --git a/org.springframework.core/src/test/java/org/springframework/util/xml/StaxEventXmlReaderTest.java b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxEventXmlReaderTest.java new file mode 100644 index 00000000000..ecc9d171e9c --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxEventXmlReaderTest.java @@ -0,0 +1,61 @@ +/* + * 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.io.InputStream; +import java.io.StringReader; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; + +import org.easymock.MockControl; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.helpers.AttributesImpl; + +public class StaxEventXmlReaderTest extends AbstractStaxXmlReaderTestCase { + + public static final String CONTENT = ""; + + @Override + protected AbstractStaxXmlReader createStaxXmlReader(InputStream inputStream) throws XMLStreamException { + return new StaxEventXmlReader(inputFactory.createXMLEventReader(inputStream)); + } + + public void testPartial() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLEventReader eventReader = inputFactory.createXMLEventReader(new StringReader(CONTENT)); + eventReader.nextTag(); // skip to root + StaxEventXmlReader xmlReader = new StaxEventXmlReader(eventReader); + + MockControl mockControl = MockControl.createStrictControl(ContentHandler.class); + mockControl.setDefaultMatcher(new SaxArgumentMatcher()); + ContentHandler contentHandlerMock = (ContentHandler) mockControl.getMock(); + + contentHandlerMock.startDocument(); + contentHandlerMock.startElement("http://springframework.org/spring-ws", "child", "child", new AttributesImpl()); + contentHandlerMock.endElement("http://springframework.org/spring-ws", "child", "child"); + contentHandlerMock.endDocument(); + + xmlReader.setContentHandler(contentHandlerMock); + mockControl.replay(); + xmlReader.parse(new InputSource()); + mockControl.verify(); + } + +} + diff --git a/org.springframework.core/src/test/java/org/springframework/util/xml/StaxSourceTest.java b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxSourceTest.java new file mode 100644 index 00000000000..843384cc6dd --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxSourceTest.java @@ -0,0 +1,68 @@ +/* + * 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.io.StringReader; +import java.io.StringWriter; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; + +import static org.custommonkey.xmlunit.XMLAssert.*; +import org.junit.Before; +import org.junit.Test; + +public class StaxSourceTest { + + private static final String XML = ""; + + private Transformer transformer; + + private XMLInputFactory inputFactory; + + @Before + public void createsetUp() throws Exception { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformer = transformerFactory.newTransformer(); + inputFactory = XMLInputFactory.newInstance(); + } + + @Test + public void streamReaderSource() throws Exception { + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(new StringReader(XML)); + StaxSource source = new StaxSource(streamReader); + assertEquals("Invalid streamReader returned", streamReader, source.getXMLStreamReader()); + assertNull("EventReader returned", source.getXMLEventReader()); + StringWriter writer = new StringWriter(); + transformer.transform(source, new StreamResult(writer)); + assertXMLEqual("Invalid result", XML, writer.toString()); + } + + @Test + public void eventReaderSource() throws Exception { + XMLEventReader eventReader = inputFactory.createXMLEventReader(new StringReader(XML)); + StaxSource source = new StaxSource(eventReader); + assertEquals("Invalid eventReader returned", eventReader, source.getXMLEventReader()); + assertNull("StreamReader returned", source.getXMLStreamReader()); + StringWriter writer = new StringWriter(); + transformer.transform(source, new StreamResult(writer)); + assertXMLEqual("Invalid result", XML, writer.toString()); + } +} \ No newline at end of file diff --git a/org.springframework.core/src/test/java/org/springframework/util/xml/StaxStreamXmlReaderTest.java b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxStreamXmlReaderTest.java new file mode 100644 index 00000000000..b786a56ddc6 --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxStreamXmlReaderTest.java @@ -0,0 +1,70 @@ +/* + * 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.io.InputStream; +import java.io.StringReader; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.easymock.MockControl; +import static org.junit.Assert.assertEquals; +import org.junit.Test; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.helpers.AttributesImpl; + +public class StaxStreamXmlReaderTest extends AbstractStaxXmlReaderTestCase { + + public static final String CONTENT = ""; + + @Override + protected AbstractStaxXmlReader createStaxXmlReader(InputStream inputStream) throws XMLStreamException { + return new StaxStreamXmlReader(inputFactory.createXMLStreamReader(inputStream)); + } + + @Test + public void testPartial() throws Exception { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(new StringReader(CONTENT)); + streamReader.nextTag(); // skip to root + assertEquals("Invalid element", new QName("http://springframework.org/spring-ws", "root"), + streamReader.getName()); + streamReader.nextTag(); // skip to child + assertEquals("Invalid element", new QName("http://springframework.org/spring-ws", "child"), + streamReader.getName()); + StaxStreamXmlReader xmlReader = new StaxStreamXmlReader(streamReader); + + MockControl mockControl = MockControl.createStrictControl(ContentHandler.class); + mockControl.setDefaultMatcher(new SaxArgumentMatcher()); + ContentHandler contentHandlerMock = (ContentHandler) mockControl.getMock(); + + contentHandlerMock.startDocument(); + contentHandlerMock.startElement("http://springframework.org/spring-ws", "child", "child", new AttributesImpl()); + contentHandlerMock.endElement("http://springframework.org/spring-ws", "child", "child"); + contentHandlerMock.endDocument(); + + xmlReader.setContentHandler(contentHandlerMock); + mockControl.replay(); + xmlReader.parse(new InputSource()); + mockControl.verify(); + } + + +} diff --git a/org.springframework.core/src/test/resources/org/springframework/util/xml/testContentHandler.xml b/org.springframework.core/src/test/resources/org/springframework/util/xml/testContentHandler.xml new file mode 100644 index 00000000000..27129db12fd --- /dev/null +++ b/org.springframework.core/src/test/resources/org/springframework/util/xml/testContentHandler.xml @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/org.springframework.core/src/test/resources/org/springframework/util/xml/testLexicalHandler.xml b/org.springframework.core/src/test/resources/org/springframework/util/xml/testLexicalHandler.xml new file mode 100644 index 00000000000..93bdf415872 --- /dev/null +++ b/org.springframework.core/src/test/resources/org/springframework/util/xml/testLexicalHandler.xml @@ -0,0 +1,8 @@ + +]> + + + + &entity; + \ No newline at end of file