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