diff options
Diffstat (limited to 'src/main/java/at/gv/util/DOMUtils.java')
-rw-r--r-- | src/main/java/at/gv/util/DOMUtils.java | 1029 |
1 files changed, 1029 insertions, 0 deletions
diff --git a/src/main/java/at/gv/util/DOMUtils.java b/src/main/java/at/gv/util/DOMUtils.java new file mode 100644 index 0000000..62eb0c0 --- /dev/null +++ b/src/main/java/at/gv/util/DOMUtils.java @@ -0,0 +1,1029 @@ +/* + * Copyright 2011 Federal Chancellery Austria and + * Graz University of Technology + * + * 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 at.gv.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.apache.xerces.parsers.DOMParser; +import org.apache.xerces.parsers.SAXParser; +import org.apache.xerces.parsers.XMLGrammarPreparser; +import org.apache.xerces.util.SymbolTable; +import org.apache.xerces.util.XMLGrammarPoolImpl; +import org.apache.xerces.xni.grammars.XMLGrammarDescription; +import org.apache.xerces.xni.grammars.XMLGrammarPool; +import org.apache.xerces.xni.parser.XMLInputSource; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Various utility functions for handling XML DOM trees. + * + * The parsing methods in this class make use of some features internal to the + * Xerces DOM parser, mainly for performance reasons. As soon as JAXP (currently + * at version 1.2) is better at schema handling, it should be used as the parser + * interface. + * + * @author Patrick Peck + */ +public class DOMUtils { + + public static Document newDocument() { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + try { + return factory.newDocumentBuilder().newDocument(); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + return null; + } + } + + /** Feature URI for namespace aware parsing. */ + private static final String NAMESPACES_FEATURE = "http://xml.org/sax/features/namespaces"; + /** Feature URI for validating parsing. */ + private static final String VALIDATION_FEATURE = "http://xml.org/sax/features/validation"; + /** Feature URI for schema validating parsing. */ + private static final String SCHEMA_VALIDATION_FEATURE = "http://apache.org/xml/features/validation/schema"; + /** Feature URI for normalization of element/attribute values. */ + private static final String NORMALIZED_VALUE_FEATURE = "http://apache.org/xml/features/validation/schema/normalized-value"; + /** Feature URI for parsing ignorable whitespace. */ + private static final String INCLUDE_IGNORABLE_WHITESPACE_FEATURE = "http://apache.org/xml/features/dom/include-ignorable-whitespace"; + /** Feature URI for creating EntityReference nodes in the DOM tree. */ + private static final String CREATE_ENTITY_REF_NODES_FEATURE = "http://apache.org/xml/features/dom/create-entity-ref-nodes"; + /** Property URI for providing external schema locations. */ + private static final String EXTERNAL_SCHEMA_LOCATION_PROPERTY = "http://apache.org/xml/properties/schema/external-schemaLocation"; + /** + * Property URI for providing the external schema location for elements + * without a namespace. + */ + private static final String EXTERNAL_NO_NAMESPACE_SCHEMA_LOCATION_PROPERTY = "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation"; + /** Property URI for the Xerces grammar pool. */ + private static final String GRAMMAR_POOL = org.apache.xerces.impl.Constants.XERCES_PROPERTY_PREFIX + + org.apache.xerces.impl.Constants.XMLGRAMMAR_POOL_PROPERTY; + /** A prime number for initializing the symbol table. */ + private static final int BIG_PRIME = 2039; + /** Symbol table for the grammar pool. */ + private static SymbolTable symbolTable = new SymbolTable(BIG_PRIME); + /** Xerces schema grammar pool. */ + private static XMLGrammarPool grammarPool = new XMLGrammarPoolImpl(); + /** + * Set holding the NamespaceURIs of the grammarPool, to prevent multiple + * entries of same grammars to the pool + */ + private static Set grammarNamespaces; + + static { + grammarPool.lockPool(); + grammarNamespaces = new HashSet(); + } + + /** + * Preparse a schema and add it to the schema pool. The method only adds the + * schema to the pool if a schema having the same <code>systemId</code> + * (namespace URI) is not already present in the pool. + * + * @param inputStream + * An <code>InputStream</code> providing the contents of the schema. + * @param systemId + * The systemId (namespace URI) to use for the schema. + * @throws IOException + * An error occurred reading the schema. + */ + public static void addSchemaToPool(InputStream inputStream, String systemId) + throws IOException { + XMLGrammarPreparser preparser; + + if (!grammarNamespaces.contains(systemId)) { + + grammarNamespaces.add(systemId); + + // unlock the pool so that we can add another grammar + grammarPool.unlockPool(); + + // prepare the preparser + preparser = new XMLGrammarPreparser(symbolTable); + preparser.registerPreparser(XMLGrammarDescription.XML_SCHEMA, null); + preparser.setProperty(GRAMMAR_POOL, grammarPool); + preparser.setFeature(NAMESPACES_FEATURE, true); + preparser.setFeature(VALIDATION_FEATURE, true); + + // add the grammar to the pool + preparser.preparseGrammar(XMLGrammarDescription.XML_SCHEMA, + new XMLInputSource(null, systemId, null, inputStream, null)); + + // lock the pool again so that schemas are not added automatically + grammarPool.lockPool(); + } + } + + /** + * Parse an XML document from an <code>InputStream</code>. + * + * @param inputStream + * The <code>InputStream</code> containing the XML document. + * @param validating + * If <code>true</code>, parse validating. + * @param externalSchemaLocations + * A <code>String</code> containing namespace URI to schema location + * pairs, the same way it is accepted by the <code>xsi: + * schemaLocation</code> attribute. + * @param externalNoNamespaceSchemaLocation + * The schema location of the schema for elements without a + * namespace, the same way it is accepted by the + * <code>xsi:noNamespaceSchemaLocation</code> attribute. + * @param entityResolver + * An <code>EntityResolver</code> to resolve external entities + * (schemas and DTDs). If <code>null</code>, it will not be set. + * @param errorHandler + * An <code>ErrorHandler</code> to decide what to do with parsing + * errors. If <code>null</code>, it will not be set. + * @return The parsed XML document as a DOM tree. + * @throws SAXException + * An error occurred parsing the document. + * @throws IOException + * An error occurred reading the document. + * @throws ParserConfigurationException + * An error occurred configuring the XML parser. + */ + public static Document parseDocument(InputStream inputStream, + boolean validating, String externalSchemaLocations, + String externalNoNamespaceSchemaLocation, EntityResolver entityResolver, + ErrorHandler errorHandler) throws SAXException, IOException, + ParserConfigurationException { + + DOMParser parser; + + // if Debug is enabled make a copy of inputStream to enable debug output in + // case of SAXException + byte buffer[] = null; + /* + * ByteArrayInputStream baStream = null; int len = inputStream.available(); + * buffer = new byte[len]; inputStream.read(buffer); baStream = new + * ByteArrayInputStream(buffer); + */ + + // create the DOM parser + if (symbolTable != null) { + parser = new DOMParser(symbolTable, grammarPool); + } else { + parser = new DOMParser(); + } + + // set parser features and properties + try { + parser.setFeature(NAMESPACES_FEATURE, true); + parser.setFeature(VALIDATION_FEATURE, validating); + parser.setFeature(SCHEMA_VALIDATION_FEATURE, validating); + parser.setFeature(NORMALIZED_VALUE_FEATURE, false); + parser.setFeature(INCLUDE_IGNORABLE_WHITESPACE_FEATURE, true); + parser.setFeature(CREATE_ENTITY_REF_NODES_FEATURE, false); + + if (validating) { + if (externalSchemaLocations != null) { + parser.setProperty(EXTERNAL_SCHEMA_LOCATION_PROPERTY, + externalSchemaLocations); + } + if (externalNoNamespaceSchemaLocation != null) { + parser.setProperty(EXTERNAL_NO_NAMESPACE_SCHEMA_LOCATION_PROPERTY, + externalNoNamespaceSchemaLocation); + } + } + + // set entity resolver and error handler + if (entityResolver != null) { + parser.setEntityResolver(entityResolver); + } + if (errorHandler != null) { + parser.setErrorHandler(errorHandler); + } + + // parse the document and return it + // if debug is enabled: use copy of strem (baStream) else use orig stream + /* + * if(null != baStream) parser.parse(new InputSource(baStream)); else + */ + parser.parse(new InputSource(inputStream)); + } catch (SAXException e) { + throw (e); + } + + return parser.getDocument(); + } + + /** + * Parse an XML document from an <code>InputStream</code>. + * + * It uses a <code>MOAEntityResolver</code> as the <code>EntityResolver</code> + * and a <code>MOAErrorHandler</code> as the <code>ErrorHandler</code>. + * + * @param inputStream + * The <code>InputStream</code> containing the XML document. + * @param validating + * If <code>true</code>, parse validating. + * @param externalSchemaLocations + * A <code>String</code> containing namespace URI to schema location + * pairs, the same way it is accepted by the <code>xsi: + * schemaLocation</code> attribute. + * @param externalNoNamespaceSchemaLocation + * The schema location of the schema for elements without a + * namespace, the same way it is accepted by the + * <code>xsi:noNamespaceSchemaLocation</code> attribute. + * @return The parsed XML document as a DOM tree. + * @throws SAXException + * An error occurred parsing the document. + * @throws IOException + * An error occurred reading the document. + * @throws ParserConfigurationException + * An error occurred configuring the XML parser. + */ + public static Document parseDocument(InputStream inputStream, + boolean validating, String externalSchemaLocations, + String externalNoNamespaceSchemaLocation) throws SAXException, + IOException, ParserConfigurationException { + + return parseDocument(inputStream, validating, externalSchemaLocations, + externalNoNamespaceSchemaLocation, null, null); + } + + /** + * Parse an XML document from a <code>String</code>. + * + * It uses a <code>MOAEntityResolver</code> as the <code>EntityResolver</code> + * and a <code>MOAErrorHandler</code> as the <code>ErrorHandler</code>. + * + * @param xmlString + * The <code>String</code> containing the XML document. + * @param encoding + * The encoding of the XML document. + * @param validating + * If <code>true</code>, parse validating. + * @param externalSchemaLocations + * A <code>String</code> containing namespace URI to schema location + * pairs, the same way it is accepted by the <code>xsi: + * schemaLocation</code> attribute. + * @param externalNoNamespaceSchemaLocation + * The schema location of the schema for elements without a + * namespace, the same way it is accepted by the + * <code>xsi:noNamespaceSchemaLocation</code> attribute. + * @return The parsed XML document as a DOM tree. + * @throws SAXException + * An error occurred parsing the document. + * @throws IOException + * An error occurred reading the document. + * @throws ParserConfigurationException + * An error occurred configuring the XML parser. + */ + public static Document parseDocument(String xmlString, String encoding, + boolean validating, String externalSchemaLocations, + String externalNoNamespaceSchemaLocation) throws SAXException, + IOException, ParserConfigurationException { + + InputStream in = new ByteArrayInputStream(xmlString.getBytes(encoding)); + return parseDocument(in, validating, externalSchemaLocations, + externalNoNamespaceSchemaLocation); + } + + /** + * Parse an UTF-8 encoded XML document from a <code>String</code>. + * + * @param xmlString + * The <code>String</code> containing the XML document. + * @param validating + * If <code>true</code>, parse validating. + * @param externalSchemaLocations + * A <code>String</code> containing namespace URI to schema location + * pairs, the same way it is accepted by the <code>xsi: + * schemaLocation</code> attribute. + * @param externalNoNamespaceSchemaLocation + * The schema location of the schema for elements without a + * namespace, the same way it is accepted by the + * <code>xsi:noNamespaceSchemaLocation</code> attribute. + * @return The parsed XML document as a DOM tree. + * @throws SAXException + * An error occurred parsing the document. + * @throws IOException + * An error occurred reading the document. + * @throws ParserConfigurationException + * An error occurred configuring the XML parser. + */ + public static Document parseDocument(String xmlString, boolean validating, + String externalSchemaLocations, String externalNoNamespaceSchemaLocation) + throws SAXException, IOException, ParserConfigurationException { + + return parseDocument(xmlString, "UTF-8", validating, + externalSchemaLocations, externalNoNamespaceSchemaLocation); + } + + /** + * A convenience method to parse an XML document validating. + * + * @param inputStream + * The <code>InputStream</code> containing the XML document. + * @return The root element of the parsed XML document. + * @throws SAXException + * An error occurred parsing the document. + * @throws IOException + * An error occurred reading the document. + * @throws ParserConfigurationException + * An error occurred configuring the XML parser. + */ + public static Element parseXmlValidating(InputStream inputStream) + throws ParserConfigurationException, SAXException, IOException { + return DOMUtils.parseDocument(inputStream, true, + Constants.ALL_SCHEMA_LOCATIONS, null).getDocumentElement(); + } + + /** + * A convenience method to parse an XML document non validating. + * + * @param inputStream + * The <code>InputStream</code> containing the XML document. + * @return The root element of the parsed XML document. + * @throws SAXException + * An error occurred parsing the document. + * @throws IOException + * An error occurred reading the document. + * @throws ParserConfigurationException + * An error occurred configuring the XML parser. + */ + public static Element parseXmlNonValidating(InputStream inputStream) + throws ParserConfigurationException, SAXException, IOException { + return DOMUtils.parseDocument(inputStream, false, + Constants.ALL_SCHEMA_LOCATIONS, null).getDocumentElement(); + } + + /** + * Schema validate a given DOM element. + * + * @param element + * The element to validate. + * @param externalSchemaLocations + * A <code>String</code> containing namespace URI to schema location + * pairs, the same way it is accepted by the <code>xsi: + * schemaLocation</code> attribute. + * @param externalNoNamespaceSchemaLocation + * The schema location of the schema for elements without a + * namespace, the same way it is accepted by the + * <code>xsi:noNamespaceSchemaLocation</code> attribute. + * @return <code>true</code>, if the <code>element</code> validates against + * the schemas declared in it. + * @throws SAXException + * An error occurred parsing the document. + * @throws IOException + * An error occurred reading the document from its serialized + * representation. + * @throws ParserConfigurationException + * An error occurred configuring the XML + * @throws TransformerException + * An error occurred serializing the element. + */ + public static boolean validateElement(Element element, + String externalSchemaLocations, String externalNoNamespaceSchemaLocation) + throws ParserConfigurationException, IOException, SAXException, + TransformerException { + + byte[] docBytes; + SAXParser parser; + + // create the SAX parser + if (symbolTable != null) { + parser = new SAXParser(symbolTable, grammarPool); + } else { + parser = new SAXParser(); + } + + // serialize the document + docBytes = serializeNode(element, "UTF-8"); + + // set up parser features and attributes + parser.setFeature(NAMESPACES_FEATURE, true); + parser.setFeature(VALIDATION_FEATURE, true); + parser.setFeature(SCHEMA_VALIDATION_FEATURE, true); + if (externalSchemaLocations != null) { + parser.setProperty(EXTERNAL_SCHEMA_LOCATION_PROPERTY, + externalSchemaLocations); + } + if (externalNoNamespaceSchemaLocation != null) { + parser.setProperty(EXTERNAL_NO_NAMESPACE_SCHEMA_LOCATION_PROPERTY, + "externalNoNamespaceSchemaLocation"); + } + + // set up entity resolver and error handler + // parser.setEntityResolver(new MOAEntityResolver()); + // parser.setErrorHandler(new MOAErrorHandler()); + + // parse validating + parser.parse(new InputSource(new ByteArrayInputStream(docBytes))); + return true; + } + + /** + * Serialize the given DOM node. + * + * The node will be serialized using the UTF-8 encoding. + * + * @param node + * The node to serialize. + * @return String The <code>String</code> representation of the given DOM + * node. + * @throws TransformerException + * An error occurred transforming the node to a <code>String</code>. + * @throws IOException + * An IO error occurred writing the node to a byte array. + */ + public static String serializeNode(Node node) throws TransformerException, + IOException { + return new String(serializeNode(node, "UTF-8", false), "UTF-8"); + } + + /** + * Serialize the given DOM node. + * + * The node will be serialized using the UTF-8 encoding. + * + * @param node + * The node to serialize. + * @param omitXmlDeclaration + * The boolean value for omitting the XML Declaration. + * @return String The <code>String</code> representation of the given DOM + * node. + * @throws TransformerException + * An error occurred transforming the node to a <code>String</code>. + * @throws IOException + * An IO error occurred writing the node to a byte array. + */ + public static String serializeNode(Node node, boolean omitXmlDeclaration) + throws TransformerException, IOException { + return new String(serializeNode(node, "UTF-8", omitXmlDeclaration), "UTF-8"); + } + + /** + * Serialize the given DOM node. + * + * The node will be serialized using the UTF-8 encoding. + * + * @param node + * The node to serialize. + * @param omitXmlDeclaration + * The boolean value for omitting the XML Declaration. + * @param lineSeperator + * Sets the line seperator String of the parser + * @return String The <code>String</code> representation of the given DOM + * node. + * @throws TransformerException + * An error occurred transforming the node to a <code>String</code>. + * @throws IOException + * An IO error occurred writing the node to a byte array. + */ + public static String serializeNode(Node node, boolean omitXmlDeclaration, + String lineSeperator) throws TransformerException, IOException { + return new String(serializeNode(node, "UTF-8", omitXmlDeclaration, + lineSeperator), "UTF-8"); + } + + /** + * Serialize the given DOM node to a byte array. + * + * @param node + * The node to serialize. + * @param xmlEncoding + * The XML encoding to use. + * @return The serialized node, as a byte array. Using a compatible encoding + * this can easily be converted into a <code>String</code>. + * @throws TransformerException + * An error occurred transforming the node to a byte array. + * @throws IOException + * An IO error occurred writing the node to a byte array. + */ + public static byte[] serializeNode(Node node, String xmlEncoding) + throws TransformerException, IOException { + return serializeNode(node, xmlEncoding, false); + } + + /** + * Serialize the given DOM node to a byte array. + * + * @param node + * The node to serialize. + * @param xmlEncoding + * The XML encoding to use. + * @param omitDeclaration + * The boolean value for omitting the XML Declaration. + * @return The serialized node, as a byte array. Using a compatible encoding + * this can easily be converted into a <code>String</code>. + * @throws TransformerException + * An error occurred transforming the node to a byte array. + * @throws IOException + * An IO error occurred writing the node to a byte array. + */ + public static byte[] serializeNode(Node node, String xmlEncoding, + boolean omitDeclaration) throws TransformerException, IOException { + return serializeNode(node, xmlEncoding, omitDeclaration, null); + } + + /** + * Serialize the given DOM node to a byte array. + * + * @param node + * The node to serialize. + * @param xmlEncoding + * The XML encoding to use. + * @param omitDeclaration + * The boolean value for omitting the XML Declaration. + * @param lineSeperator + * Sets the line seperator String of the parser + * @return The serialized node, as a byte array. Using a compatible encoding + * this can easily be converted into a <code>String</code>. + * @throws TransformerException + * An error occurred transforming the node to a byte array. + * @throws IOException + * An IO error occurred writing the node to a byte array. + */ + public static byte[] serializeNode(Node node, String xmlEncoding, + boolean omitDeclaration, String lineSeperator) + throws TransformerException, IOException { + + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(16384); + + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, xmlEncoding); + String omit = omitDeclaration ? "yes" : "no"; + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, omit); + if (null != lineSeperator) { + transformer.setOutputProperty( + "{http://xml.apache.org/xalan}line-separator", lineSeperator);// does + // not + // work + // for + // xalan + // <= + // 2.5.1 + } + transformer.transform(new DOMSource(node), new StreamResult(bos)); + + bos.flush(); + bos.close(); + + return bos.toByteArray(); + } + + /** + * Return the text that a node contains. + * + * This routine: + * <ul> + * <li>Ignores comments and processing instructions.</li> + * <li>Concatenates TEXT nodes, CDATA nodes, and the results recursively + * processing EntityRef nodes.</li> + * <li>Ignores any element nodes in the sublist. (Other possible options are + * to recurse into element sublists or throw an exception.)</li> + * </ul> + * + * @param node + * A DOM node from which to extract text. + * @return A String representing its contents. + */ + public static String getText(Node node) { + if (!node.hasChildNodes()) { + return ""; + } + + StringBuffer result = new StringBuffer(); + NodeList list = node.getChildNodes(); + + for (int i = 0; i < list.getLength(); i++) { + Node subnode = list.item(i); + if (subnode.getNodeType() == Node.TEXT_NODE) { + result.append(subnode.getNodeValue()); + } else if (subnode.getNodeType() == Node.CDATA_SECTION_NODE) { + result.append(subnode.getNodeValue()); + } else if (subnode.getNodeType() == Node.ENTITY_REFERENCE_NODE) { + // Recurse into the subtree for text + // (and ignore comments) + result.append(getText(subnode)); + } + } + return result.toString(); + } + + /** + * Build the namespace prefix to namespace URL mapping in effect for a given + * node. + * + * @param node + * The context node for which build the map. + * @return The namespace prefix to namespace URL mapping ( a + * <code>String</code> value to <code>String</code> value mapping). + */ + public static Map getNamespaceDeclarations(Node node) { + Map nsDecls = new HashMap(); + int i; + + do { + if (node.hasAttributes()) { + NamedNodeMap attrs = node.getAttributes(); + + for (i = 0; i < attrs.getLength(); i++) { + Attr attr = (Attr) attrs.item(i); + + // add prefix mapping if none exists + if ("xmlns".equals(attr.getPrefix()) + || "xmlns".equals(attr.getName())) { + + String nsPrefix = attr.getPrefix() != null ? attr.getLocalName() + : ""; + + if (nsDecls.get(nsPrefix) == null) { + nsDecls.put(nsPrefix, attr.getValue()); + } + } + } + } + } while ((node = node.getParentNode()) != null); + + return nsDecls; + } + + /** + * Add all namespace declarations declared in the parent(s) of a given element + * and used in the subtree of the given element to the given element. + * + * @param context + * The element to which to add the namespaces. + */ + public static void localizeNamespaceDeclarations(Element context) { + Node parent = context.getParentNode(); + + if (parent != null) { + Map namespaces = getNamespaceDeclarations(context.getParentNode()); + Set nsUris = collectNamespaceURIs(context); + Iterator iter; + + for (iter = namespaces.entrySet().iterator(); iter.hasNext();) { + Map.Entry e = (Map.Entry) iter.next(); + + if (nsUris.contains(e.getValue())) { + String prefix = (String) e.getKey(); + String nsUri = (String) e.getValue(); + String nsAttrName = "".equals(prefix) ? "xmlns" : "xmlns:" + prefix; + + context.setAttributeNS(Constants.XMLNS_NS_URI, nsAttrName, nsUri); + } + } + } + } + + /** + * Collect all the namespace URIs used in the subtree of a given element. + * + * @param context + * The element that should be searched for namespace URIs. + * @return All namespace URIs used in the subtree of <code>context</code>, + * including the ones used in <code>context</code> itself. + */ + public static Set collectNamespaceURIs(Element context) { + Set result = new HashSet(); + + collectNamespaceURIsImpl(context, result); + return result; + } + + /** + * A recursive method to do the work of <code>collectNamespaceURIs</code>. + * + * @param context + * The context element to evaluate. + * @param result + * The result, passed as a parameter to avoid unnecessary + * instantiations of <code>Set</code>. + */ + private static void collectNamespaceURIsImpl(Element context, Set result) { + NamedNodeMap attrs = context.getAttributes(); + NodeList childNodes = context.getChildNodes(); + String nsUri; + int i; + + // add the namespace of the context element + nsUri = context.getNamespaceURI(); + if (nsUri != null && nsUri != Constants.XMLNS_NS_URI) { + result.add(nsUri); + } + + // add all namespace URIs from attributes + for (i = 0; i < attrs.getLength(); i++) { + nsUri = attrs.item(i).getNamespaceURI(); + if (nsUri != null && nsUri != Constants.XMLNS_NS_URI) { + result.add(nsUri); + } + } + + // add all namespaces from subelements + for (i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + + if (node.getNodeType() == Node.ELEMENT_NODE) { + collectNamespaceURIsImpl((Element) node, result); + } + } + } + + /** + * Check, that each attribute node in the given <code>NodeList</code> has its + * parent in the <code>NodeList</code> as well. + * + * @param nodes + * The <code>NodeList</code> to check. + * @return <code>true</code>, if each attribute node in <code>nodes</code> has + * its parent in <code>nodes</code> as well. + */ + public static boolean checkAttributeParentsInNodeList(NodeList nodes) { + Set nodeSet = new HashSet(); + int i; + + // put the nodes into the nodeSet + for (i = 0; i < nodes.getLength(); i++) { + nodeSet.add(nodes.item(i)); + } + + // check that each attribute node's parent is in the node list + for (i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + + if (n.getNodeType() == Node.ATTRIBUTE_NODE) { + Attr attr = (Attr) n; + Element owner = attr.getOwnerElement(); + + if (owner == null) { + if (!isNamespaceDeclaration(attr)) { + return false; + } + } + + if (!nodeSet.contains(owner) && !isNamespaceDeclaration(attr)) { + return false; + } + } + } + + return true; + } + + /** + * Convert an unstructured <code>NodeList</code> into a + * <code>DocumentFragment</code>. + * + * @param nodeList + * Contains the node list to be converted into a DOM + * DocumentFragment. + * @return the resulting DocumentFragment. The DocumentFragment will be backed + * by a new DOM Document, i.e. all noded of the node list will be + * cloned. + * @throws ParserConfigurationException + * An error occurred creating the DocumentFragment. + * @precondition The nodes in the node list appear in document order + * @precondition for each Attr node in the node list, the owning Element is in + * the node list as well. + * @precondition each Element or Attr node in the node list is namespace + * aware. + */ + public static DocumentFragment nodeList2DocumentFragment(NodeList nodeList) + throws ParserConfigurationException { + + DocumentBuilder builder = DocumentBuilderFactory.newInstance() + .newDocumentBuilder(); + Document doc = builder.newDocument(); + DocumentFragment result = doc.createDocumentFragment(); + + if (null == nodeList || nodeList.getLength() == 0) { + return result; + } + + int currPos = 0; + currPos = nodeList2DocumentFragment(nodeList, currPos, result, null, null) + 1; + + while (currPos < nodeList.getLength()) { + currPos = nodeList2DocumentFragment(nodeList, currPos, result, null, null) + 1; + } + return result; + } + + /** + * Helper method for the <code>nodeList2DocumentFragment</code>. + * + * @param nodeList + * The <code>NodeList</code> to convert. + * @param currPos + * The current position in the <code>nodeList</code>. + * @param result + * The resulting <code>DocumentFragment</code>. + * @param currOrgElem + * The current original element. + * @param currClonedElem + * The current cloned element. + * @return The current position. + */ + private static int nodeList2DocumentFragment(NodeList nodeList, int currPos, + DocumentFragment result, Element currOrgElem, Element currClonedElem) { + + while (currPos < nodeList.getLength()) { + Node currentNode = nodeList.item(currPos); + switch (currentNode.getNodeType()) { + case Node.COMMENT_NODE: + case Node.PROCESSING_INSTRUCTION_NODE: + case Node.TEXT_NODE: { + // Append current node either to resulting DocumentFragment or to + // current cloned Element + if (null == currClonedElem) { + result.appendChild(result.getOwnerDocument().importNode(currentNode, + false)); + } else { + // Stop processing if current Node is not a descendant of + // current Element + if (!isAncestor(currOrgElem, currentNode)) { + return --currPos; + } + + currClonedElem.appendChild(result.getOwnerDocument().importNode( + currentNode, false)); + } + break; + } + + case Node.ELEMENT_NODE: { + Element nextCurrOrgElem = (Element) currentNode; + Element nextCurrClonedElem = result.getOwnerDocument().createElementNS( + nextCurrOrgElem.getNamespaceURI(), nextCurrOrgElem.getNodeName()); + + // Append current Node either to resulting DocumentFragment or to + // current cloned Element + if (null == currClonedElem) { + result.appendChild(nextCurrClonedElem); + currOrgElem = nextCurrOrgElem; + currClonedElem = nextCurrClonedElem; + } else { + // Stop processing if current Node is not a descendant of + // current Element + if (!isAncestor(currOrgElem, currentNode)) { + return --currPos; + } + + currClonedElem.appendChild(nextCurrClonedElem); + } + + // Process current Node (of type Element) recursively + currPos = nodeList2DocumentFragment(nodeList, ++currPos, result, + nextCurrOrgElem, nextCurrClonedElem); + + break; + } + + case Node.ATTRIBUTE_NODE: { + Attr currAttr = (Attr) currentNode; + + // GK 20030411: Hack to overcome problems with IAIK IXSIL + if (currAttr.getOwnerElement() == null) + break; + if (currClonedElem == null) + break; + + // currClonedElem must be the owner Element of currAttr if + // preconditions are met + currClonedElem.setAttributeNS(currAttr.getNamespaceURI(), + currAttr.getNodeName(), currAttr.getValue()); + break; + } + + default: { + // All other nodes will be ignored + } + } + + currPos++; + } + + return currPos; + } + + /** + * Check, if the given attribute is a namespace declaration. + * + * @param attr + * The attribute to check. + * @return <code>true</code>, if the attribute is a namespace declaration, + * <code>false</code> otherwise. + */ + private static boolean isNamespaceDeclaration(Attr attr) { + return Constants.XMLNS_NS_URI.equals(attr.getNamespaceURI()); + } + + /** + * Check, if a given DOM element is an ancestor of a given node. + * + * @param candAnc + * The DOM element to check for being the ancestor. + * @param cand + * The node to check for being the child. + * @return <code>true</code>, if <code>candAnc</code> is an (indirect) + * ancestor of <code>cand</code>; <code>false</code> otherwise. + */ + public static boolean isAncestor(Element candAnc, Node cand) { + Node currPar = cand.getParentNode(); + + while (currPar != null) { + if (candAnc == currPar) + return true; + currPar = currPar.getParentNode(); + } + return false; + } + + /** + * Selects the (first) element from a node list and returns it. + * + * @param nl + * The NodeList to get the element from. + * @return The (first) element included in the node list or <code>null</code> + * if the node list is <code>null</code> or empty or no element is + * included in the list. + */ + public static Element getElementFromNodeList(NodeList nl) { + if ((nl == null) || (nl.getLength() == 0)) { + return null; + } + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + return (Element) node; + } + } + return null; + } + + /** + * Returns all child elements of the given element. + * + * @param parent + * The element to get the child elements from. + * + * @return A list including all child elements of the given element. Maybe + * empty if the parent element has no child elements. + */ + public static List getChildElements(Element parent) { + Vector v = new Vector(); + NodeList nl = parent.getChildNodes(); + int length = nl.getLength(); + for (int i = 0; i < length; i++) { + Node node = nl.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + v.add((Element) node); + } + } + return v; + } + +} |