From 3e88fbd27001e270fb4dc94721057c24645639bd Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Wed, 7 Jan 2009 11:24:01 +0000 Subject: [PATCH] Added StaxStreamContentHandler git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@520 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../util/xml/AbstractStaxContentHandler.java | 167 ++++++++++++++++++ .../util/xml/StaxStreamContentHandler.java | 109 ++++++++++++ .../AbstractStaxContentHandlerTestCase.java | 66 +++++++ .../xml/StaxStreamContentHandlerTest.java | 30 ++++ 4 files changed, 372 insertions(+) create mode 100644 org.springframework.core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java create mode 100644 org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java create mode 100644 org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxContentHandlerTestCase.java create mode 100644 org.springframework.core/src/test/java/org/springframework/util/xml/StaxStreamContentHandlerTest.java diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java b/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java new file mode 100644 index 00000000000..4831c68ae82 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java @@ -0,0 +1,167 @@ +/* + * 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.XMLStreamException; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +/** + * Abstract base class for SAX ContentHandler implementations that use StAX as a basis. All methods + * delegate to internal template methods, capable of throwing a XMLStreamException. Additionally, an + * namespace context is used to keep track of declared namespaces. + * + * @author Arjen Poutsma + * @since 3.0 + */ +abstract class AbstractStaxContentHandler implements ContentHandler { + + private SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext(); + + public final void startDocument() throws SAXException { + namespaceContext.clear(); + try { + startDocumentInternal(); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle startDocument: " + ex.getMessage(), ex); + } + } + + protected abstract void startDocumentInternal() throws XMLStreamException; + + public final void endDocument() throws SAXException { + namespaceContext.clear(); + try { + endDocumentInternal(); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle startDocument: " + ex.getMessage(), ex); + } + } + + protected abstract void endDocumentInternal() throws XMLStreamException; + + /** + * Binds the given prefix to the given namespaces. + * + * @see SimpleNamespaceContext#bindNamespaceUri(String,String) + */ + public final void startPrefixMapping(String prefix, String uri) { + namespaceContext.bindNamespaceUri(prefix, uri); + } + + /** + * Removes the binding for the given prefix. + * + * @see SimpleNamespaceContext#removeBinding(String) + */ + public final void endPrefixMapping(String prefix) { + namespaceContext.removeBinding(prefix); + } + + public final void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + try { + startElementInternal(toQName(uri, qName), atts, namespaceContext); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle startElement: " + ex.getMessage(), ex); + } + } + + protected abstract void startElementInternal(QName name, Attributes atts, SimpleNamespaceContext namespaceContext) + throws XMLStreamException; + + public final void endElement(String uri, String localName, String qName) throws SAXException { + try { + endElementInternal(toQName(uri, qName), namespaceContext); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle endElement: " + ex.getMessage(), ex); + } + } + + protected abstract void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) + throws XMLStreamException; + + public final void characters(char ch[], int start, int length) throws SAXException { + try { + charactersInternal(ch, start, length); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle characters: " + ex.getMessage(), ex); + } + } + + protected abstract void charactersInternal(char[] ch, int start, int length) throws XMLStreamException; + + public final void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { + try { + ignorableWhitespaceInternal(ch, start, length); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle ignorableWhitespace:" + ex.getMessage(), ex); + } + } + + protected abstract void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException; + + public final void processingInstruction(String target, String data) throws SAXException { + try { + processingInstructionInternal(target, data); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle processingInstruction: " + ex.getMessage(), ex); + } + } + + protected abstract void processingInstructionInternal(String target, String data) throws XMLStreamException; + + public final void skippedEntity(String name) throws SAXException { + try { + skippedEntityInternal(name); + } + catch (XMLStreamException ex) { + throw new SAXException("Could not handle skippedEntity: " + ex.getMessage(), ex); + } + } + + /** + * Convert a namespace URI and DOM or SAX qualified name to a QName. The qualified name can have the form + * prefix:localname or localName. + * + * @param namespaceUri the namespace URI + * @param qualifiedName the qualified name + * @return a QName + */ + protected QName toQName(String namespaceUri, String qualifiedName) { + int idx = qualifiedName.indexOf(':'); + if (idx == -1) { + return new QName(namespaceUri, qualifiedName); + } + else { + String prefix = qualifiedName.substring(0, idx); + String localPart = qualifiedName.substring(idx + 1); + return new QName(namespaceUri, localPart, prefix); + } + } + + protected abstract void skippedEntityInternal(String name) throws XMLStreamException; +} diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java new file mode 100644 index 00000000000..c1dc23ea516 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java @@ -0,0 +1,109 @@ +/* + * 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.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.springframework.util.StringUtils; + +/** + * SAX ContentHandler that writes to a XMLStreamWriter. + * + * @author Arjen Poutsma + * @see XMLStreamWriter + * @since 3.0 + */ +class StaxStreamContentHandler extends AbstractStaxContentHandler { + + private final XMLStreamWriter streamWriter; + + /** + * Constructs a new instance of the StaxStreamContentHandler that writes to the given + * XMLStreamWriter. + * + * @param streamWriter the stream writer to write to + */ + StaxStreamContentHandler(XMLStreamWriter streamWriter) { + this.streamWriter = streamWriter; + } + + public void setDocumentLocator(Locator locator) { + } + + @Override + protected void charactersInternal(char[] ch, int start, int length) throws XMLStreamException { + streamWriter.writeCharacters(ch, start, length); + } + + @Override + protected void endDocumentInternal() throws XMLStreamException { + streamWriter.writeEndDocument(); + } + + @Override + protected void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) throws XMLStreamException { + streamWriter.writeEndElement(); + } + + @Override + protected void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException { + streamWriter.writeCharacters(ch, start, length); + } + + @Override + protected void processingInstructionInternal(String target, String data) throws XMLStreamException { + streamWriter.writeProcessingInstruction(target, data); + } + + @Override + protected void skippedEntityInternal(String name) { + } + + @Override + protected void startDocumentInternal() throws XMLStreamException { + streamWriter.writeStartDocument(); + } + + @Override + protected void startElementInternal(QName name, Attributes attributes, SimpleNamespaceContext namespaceContext) + throws XMLStreamException { + streamWriter.writeStartElement(name.getPrefix(), name.getLocalPart(), name.getNamespaceURI()); + String defaultNamespaceUri = namespaceContext.getNamespaceURI(""); + if (StringUtils.hasLength(defaultNamespaceUri)) { + streamWriter.writeNamespace("", defaultNamespaceUri); + streamWriter.setDefaultNamespace(defaultNamespaceUri); + } + for (Iterator iterator = namespaceContext.getBoundPrefixes(); iterator.hasNext();) { + String prefix = (String) iterator.next(); + streamWriter.writeNamespace(prefix, namespaceContext.getNamespaceURI(prefix)); + streamWriter.setPrefix(prefix, namespaceContext.getNamespaceURI(prefix)); + } + for (int i = 0; i < attributes.getLength(); i++) { + QName attrName = toQName(attributes.getURI(i), attributes.getQName(i)); + if (!("xmlns".equals(attrName.getLocalPart()) || "xmlns".equals(attrName.getPrefix()))) { + streamWriter.writeAttribute(attrName.getPrefix(), attrName.getNamespaceURI(), attrName.getLocalPart(), + attributes.getValue(i)); + } + } + } +} diff --git a/org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxContentHandlerTestCase.java b/org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxContentHandlerTestCase.java new file mode 100644 index 00000000000..9cab8c0d3fd --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxContentHandlerTestCase.java @@ -0,0 +1,66 @@ +/* + * 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 java.io.Writer; +import javax.xml.stream.XMLStreamException; + +import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; +import org.junit.Before; +import org.junit.Test; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +public abstract class AbstractStaxContentHandlerTestCase { + + private static final String XML_CONTENT_HANDLER = + "content" + ; + + private XMLReader xmlReader; + + @Before + public void createXMLReader() throws Exception { + xmlReader = XMLReaderFactory.createXMLReader(); + } + + @Test + public void contentHandler() throws Exception { + StringWriter stringWriter = new StringWriter(); + AbstractStaxContentHandler handler = createStaxContentHandler(stringWriter); + xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", false); + xmlReader.setContentHandler(handler); + xmlReader.parse(new InputSource(new StringReader(XML_CONTENT_HANDLER))); + assertXMLEqual("Invalid result", XML_CONTENT_HANDLER, stringWriter.toString()); + } + + @Test + public void contentHandlerNamespacePrefixes() throws Exception { + StringWriter stringWriter = new StringWriter(); + AbstractStaxContentHandler handler = createStaxContentHandler(stringWriter); + xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); + xmlReader.setContentHandler(handler); + xmlReader.parse(new InputSource(new StringReader(XML_CONTENT_HANDLER))); + assertXMLEqual("Invalid result", XML_CONTENT_HANDLER, stringWriter.toString()); + } + + protected abstract AbstractStaxContentHandler createStaxContentHandler(Writer writer) throws XMLStreamException; + +} diff --git a/org.springframework.core/src/test/java/org/springframework/util/xml/StaxStreamContentHandlerTest.java b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxStreamContentHandlerTest.java new file mode 100644 index 00000000000..bb9c4430547 --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxStreamContentHandlerTest.java @@ -0,0 +1,30 @@ +/* + * 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.Writer; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; + +public class StaxStreamContentHandlerTest extends AbstractStaxContentHandlerTestCase { + + @Override + protected AbstractStaxContentHandler createStaxContentHandler(Writer writer) throws XMLStreamException { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + return new StaxStreamContentHandler(outputFactory.createXMLStreamWriter(writer)); + } +} \ No newline at end of file