diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/DomContentHandler.java b/org.springframework.core/src/main/java/org/springframework/util/xml/DomContentHandler.java new file mode 100644 index 00000000000..7c3610e7ab2 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/util/xml/DomContentHandler.java @@ -0,0 +1,137 @@ +/* + * 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.List; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; + +import org.springframework.util.Assert; + +/** + * SAX ContentHandler that transforms callback calls to DOM Nodes. + * + * @author Arjen Poutsma + * @see org.w3c.dom.Node + * @since 3.0 + */ +class DomContentHandler implements ContentHandler { + + private final Document document; + + private final List elements = new ArrayList(); + + private final Node node; + + /** + * Creates a new instance of the DomContentHandler with the given node. + * + * @param node the node to publish events to + */ + DomContentHandler(Node node) { + Assert.notNull(node, "node must not be null"); + this.node = node; + if (node instanceof Document) { + document = (Document) node; + } + else { + document = node.getOwnerDocument(); + } + Assert.notNull(document, "document must not be null"); + } + + private Node getParent() { + if (!elements.isEmpty()) { + return elements.get(elements.size() - 1); + } + else { + return node; + } + } + + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + Node parent = getParent(); + Element element = document.createElementNS(uri, qName); + for (int i = 0; i < attributes.getLength(); i++) { + String attrUri = attributes.getURI(i); + String attrQname = attributes.getQName(i); + String value = attributes.getValue(i); + if (!attrQname.startsWith("xmlns")) { + element.setAttributeNS(attrUri, attrQname, value); + } + } + element = (Element) parent.appendChild(element); + elements.add(element); + } + + public void endElement(String uri, String localName, String qName) throws SAXException { + elements.remove(elements.size() - 1); + } + + public void characters(char ch[], int start, int length) throws SAXException { + String data = new String(ch, start, length); + Node parent = getParent(); + Node lastChild = parent.getLastChild(); + if (lastChild != null && lastChild.getNodeType() == Node.TEXT_NODE) { + ((Text) lastChild).appendData(data); + } + else { + Text text = document.createTextNode(data); + parent.appendChild(text); + } + } + + public void processingInstruction(String target, String data) throws SAXException { + Node parent = getParent(); + ProcessingInstruction pi = document.createProcessingInstruction(target, data); + parent.appendChild(pi); + } + + /* + * Unsupported + */ + + public void setDocumentLocator(Locator locator) { + } + + public void startDocument() throws SAXException { + } + + public void endDocument() throws SAXException { + } + + public void startPrefixMapping(String prefix, String uri) throws SAXException { + } + + public void endPrefixMapping(String prefix) throws SAXException { + } + + public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { + } + + public void skippedEntity(String name) throws SAXException { + } +} diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/DomUtils.java b/org.springframework.core/src/main/java/org/springframework/util/xml/DomUtils.java index f4a255b53a3..6fdae8221ee 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/xml/DomUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/xml/DomUtils.java @@ -27,28 +27,28 @@ import org.w3c.dom.Element; import org.w3c.dom.EntityReference; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import org.xml.sax.ContentHandler; import org.springframework.util.Assert; /** - * Convenience methods for working with the DOM API, - * in particular for working with DOM Nodes and DOM Elements. + * Convenience methods for working with the DOM API, in particular for working with DOM Nodes and DOM Elements. * * @author Juergen Hoeller * @author Rob Harrop * @author Costin Leau - * @since 1.2 * @see org.w3c.dom.Node * @see org.w3c.dom.Element + * @since 1.2 */ public abstract class DomUtils { /** - * Retrieve all child elements of the given DOM element that match any of - * the given element names. Only look at the direct child level of the - * given element; do not go into further depth (in contrast to the - * DOM API's getElementsByTagName method). - * @param ele the DOM element to analyze + * Retrieve all child elements of the given DOM element that match any of the given element names. Only look at the + * direct child level of the given element; do not go into further depth (in contrast to the DOM API's + * getElementsByTagName method). + * + * @param ele the DOM element to analyze * @param childEleNames the child element names to look for * @return a List of child org.w3c.dom.Element instances * @see org.w3c.dom.Element @@ -70,27 +70,26 @@ public abstract class DomUtils { } /** - * Retrieve all child elements of the given DOM element that match - * the given element name. Only look at the direct child level of the - * given element; do not go into further depth (in contrast to the - * DOM API's getElementsByTagName method). - * @param ele the DOM element to analyze + * Retrieve all child elements of the given DOM element that match the given element name. Only look at the direct + * child level of the given element; do not go into further depth (in contrast to the DOM API's + * getElementsByTagName method). + * + * @param ele the DOM element to analyze * @param childEleName the child element name to look for * @return a List of child org.w3c.dom.Element instances * @see org.w3c.dom.Element * @see org.w3c.dom.Element#getElementsByTagName */ public static List getChildElementsByTagName(Element ele, String childEleName) { - return getChildElementsByTagName(ele, new String[] {childEleName}); + return getChildElementsByTagName(ele, new String[]{childEleName}); } /** - * Utility method that returns the first child element - * identified by its name. - * @param ele the DOM element to analyze + * Utility method that returns the first child element identified by its name. + * + * @param ele the DOM element to analyze * @param childEleName the child element name to look for - * @return the org.w3c.dom.Element instance, - * or null if none found + * @return the org.w3c.dom.Element instance, or null if none found */ public static Element getChildElementByTagName(Element ele, String childEleName) { Assert.notNull(ele, "Element must not be null"); @@ -106,12 +105,11 @@ public abstract class DomUtils { } /** - * Utility method that returns the first child element value - * identified by its name. - * @param ele the DOM element to analyze + * Utility method that returns the first child element value identified by its name. + * + * @param ele the DOM element to analyze * @param childEleName the child element name to look for - * @return the extracted text value, - * or null if no child element found + * @return the extracted text value, or null if no child element found */ public static String getChildElementValueByTagName(Element ele, String childEleName) { Element child = getChildElementByTagName(ele, childEleName); @@ -119,9 +117,9 @@ public abstract class DomUtils { } /** - * Extract the text value from the given DOM element, ignoring XML comments. - *

Appends all CharacterData nodes and EntityReference nodes - * into a single String value, excluding Comment nodes. + * Extract the text value from the given DOM element, ignoring XML comments.

Appends all CharacterData nodes and + * EntityReference nodes into a single String value, excluding Comment nodes. + * * @see CharacterData * @see EntityReference * @see Comment @@ -140,9 +138,8 @@ public abstract class DomUtils { } /** - * Namespace-aware equals comparison. Returns true if either - * {@link Node#getLocalName} or {@link Node#getNodeName} equals desiredName, - * otherwise returns false. + * Namespace-aware equals comparison. Returns true if either {@link Node#getLocalName} or {@link + * Node#getNodeName} equals desiredName, otherwise returns false. */ public static boolean nodeNameEquals(Node node, String desiredName) { Assert.notNull(node, "Node must not be null"); @@ -151,15 +148,21 @@ public abstract class DomUtils { } /** - * Matches the given node's name and local name against the given desired name. + * Returns a SAX ContentHandler that transforms callback calls to DOM Nodes. + * + * @param node the node to publish events to + * @return the content handler */ + public static ContentHandler createContentHandler(Node node) { + return new DomContentHandler(node); + } + + /** Matches the given node's name and local name against the given desired name. */ private static boolean nodeNameMatch(Node node, String desiredName) { return (desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName())); } - /** - * Matches the given node's name and local name against the given desired names. - */ + /** Matches the given node's name and local name against the given desired names. */ private static boolean nodeNameMatch(Node node, Collection desiredNames) { return (desiredNames.contains(node.getNodeName()) || desiredNames.contains(node.getLocalName())); } diff --git a/org.springframework.core/src/test/java/org/springframework/util/xml/DomContentHandlerTest.java b/org.springframework.core/src/test/java/org/springframework/util/xml/DomContentHandlerTest.java new file mode 100644 index 00000000000..40ee7e6a68c --- /dev/null +++ b/org.springframework.core/src/test/java/org/springframework/util/xml/DomContentHandlerTest.java @@ -0,0 +1,95 @@ +/* + * 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 javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; +import org.junit.Before; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +public class DomContentHandlerTest { + + private static final String XML_1 = + "" + "" + "" + + "content" + + ""; + + private static final String XML_2_EXPECTED = + "" + "" + "" + + ""; + + private static final String XML_2_SNIPPET = + "" + ""; + + private Document expected; + + private DomContentHandler handler; + + private Document result; + + private XMLReader xmlReader; + + private DocumentBuilder documentBuilder; + + @Before + public void setUp() throws Exception { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + documentBuilder = documentBuilderFactory.newDocumentBuilder(); + result = documentBuilder.newDocument(); + xmlReader = XMLReaderFactory.createXMLReader(); + } + + @Test + public void contentHandlerDocumentNamespacePrefixes() throws Exception { + xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); + handler = new DomContentHandler(result); + expected = documentBuilder.parse(new InputSource(new StringReader(XML_1))); + xmlReader.setContentHandler(handler); + xmlReader.parse(new InputSource(new StringReader(XML_1))); + assertXMLEqual("Invalid result", expected, result); + } + + @Test + public void contentHandlerDocumentNoNamespacePrefixes() throws Exception { + handler = new DomContentHandler(result); + expected = documentBuilder.parse(new InputSource(new StringReader(XML_1))); + xmlReader.setContentHandler(handler); + xmlReader.parse(new InputSource(new StringReader(XML_1))); + assertXMLEqual("Invalid result", expected, result); + } + + @Test + public void contentHandlerElement() throws Exception { + Element rootElement = result.createElementNS("namespace", "root"); + result.appendChild(rootElement); + handler = new DomContentHandler(rootElement); + expected = documentBuilder.parse(new InputSource(new StringReader(XML_2_EXPECTED))); + xmlReader.setContentHandler(handler); + xmlReader.parse(new InputSource(new StringReader(XML_2_SNIPPET))); + assertXMLEqual("Invalid result", expected, result); + + } +} \ No newline at end of file