diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java new file mode 100644 index 0000000000..3f23918706 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java @@ -0,0 +1,189 @@ +/* + * 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.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.stream.Location; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.Namespace; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.XMLEventConsumer; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + +import org.springframework.util.StringUtils; + +/** + * SAX ContentHandler that transforms callback calls to XMLEvents and writes them to a + * XMLEventConsumer. + * + * @author Arjen Poutsma + * @see XMLEvent + * @see XMLEventConsumer + * @since 3.0 + */ +class StaxEventContentHandler extends AbstractStaxContentHandler { + + private final XMLEventFactory eventFactory; + + private final XMLEventConsumer eventConsumer; + + private Locator locator; + + /** + * Constructs a new instance of the StaxEventContentHandler that writes to the given + * XMLEventConsumer. A default XMLEventFactory will be created. + * + * @param consumer the consumer to write events to + */ + StaxEventContentHandler(XMLEventConsumer consumer) { + eventFactory = XMLEventFactory.newInstance(); + eventConsumer = consumer; + } + + /** + * Constructs a new instance of the StaxEventContentHandler that uses the given event factory to create + * events and writes to the given XMLEventConsumer. + * + * @param consumer the consumer to write events to + * @param factory the factory used to create events + */ + StaxEventContentHandler(XMLEventConsumer consumer, XMLEventFactory factory) { + eventFactory = factory; + eventConsumer = consumer; + } + + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } + + @Override + protected void startDocumentInternal() throws XMLStreamException { + consumeEvent(eventFactory.createStartDocument()); + } + + @Override + protected void endDocumentInternal() throws XMLStreamException { + consumeEvent(eventFactory.createEndDocument()); + } + + @Override + protected void startElementInternal(QName name, Attributes atts, SimpleNamespaceContext namespaceContext) + throws XMLStreamException { + List attributes = getAttributes(atts); + List namespaces = createNamespaces(namespaceContext); + consumeEvent(eventFactory.createStartElement(name, attributes.iterator(), namespaces.iterator())); + } + + @Override + protected void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) throws XMLStreamException { + List namespaces = createNamespaces(namespaceContext); + consumeEvent(eventFactory.createEndElement(name, namespaces.iterator())); + } + + @Override + protected void charactersInternal(char[] ch, int start, int length) throws XMLStreamException { + consumeEvent(eventFactory.createCharacters(new String(ch, start, length))); + } + + @Override + protected void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException { + consumeEvent(eventFactory.createIgnorableSpace(new String(ch, start, length))); + } + + @Override + protected void processingInstructionInternal(String target, String data) throws XMLStreamException { + consumeEvent(eventFactory.createProcessingInstruction(target, data)); + } + + private void consumeEvent(XMLEvent event) throws XMLStreamException { + if (locator != null) { + eventFactory.setLocation(new SaxLocation(locator)); + } + eventConsumer.add(event); + } + + /** Creates and returns a list of NameSpace objects from the NamespaceContext. */ + private List createNamespaces(SimpleNamespaceContext namespaceContext) { + List namespaces = new ArrayList(); + String defaultNamespaceUri = namespaceContext.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX); + if (StringUtils.hasLength(defaultNamespaceUri)) { + namespaces.add(eventFactory.createNamespace(defaultNamespaceUri)); + } + for (Iterator iterator = namespaceContext.getBoundPrefixes(); iterator.hasNext();) { + String prefix = (String) iterator.next(); + String namespaceUri = namespaceContext.getNamespaceURI(prefix); + namespaces.add(eventFactory.createNamespace(prefix, namespaceUri)); + } + return namespaces; + } + + private List getAttributes(Attributes attributes) { + List list = new ArrayList(); + for (int i = 0; i < attributes.getLength(); i++) { + QName name = toQName(attributes.getURI(i), attributes.getQName(i)); + if (!("xmlns".equals(name.getLocalPart()) || "xmlns".equals(name.getPrefix()))) { + list.add(eventFactory.createAttribute(name, attributes.getValue(i))); + } + } + return list; + } + + // + // No operation + // + + @Override + protected void skippedEntityInternal(String name) throws XMLStreamException { + } + + private static class SaxLocation implements Location { + + private Locator locator; + + private SaxLocation(Locator locator) { + this.locator = locator; + } + + public int getLineNumber() { + return locator.getLineNumber(); + } + + public int getColumnNumber() { + return locator.getColumnNumber(); + } + + public int getCharacterOffset() { + return -1; + } + + public String getPublicId() { + return locator.getPublicId(); + } + + public String getSystemId() { + return locator.getSystemId(); + } + } +} \ No newline at end of file diff --git a/org.springframework.core/src/test/java/org/springframework/util/xml/StaxEventContentHandlerTests.java b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxEventContentHandlerTests.java new file mode 100644 index 0000000000..da1657b891 --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxEventContentHandlerTests.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 StaxEventContentHandlerTests extends AbstractStaxContentHandlerTestCase { + + @Override + protected AbstractStaxContentHandler createStaxContentHandler(Writer writer) throws XMLStreamException { + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + return new StaxEventContentHandler(outputFactory.createXMLEventWriter(writer)); + } +} \ No newline at end of file