/* * © Copyright IBM Corp. 2012 * * 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 com.ibm.commons.xml; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.math.BigDecimal; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import com.ibm.commons.util.EmptyIterator; import com.ibm.commons.util.StringUtil; import com.ibm.commons.util.io.FastBufferedOutputStream; import com.ibm.commons.xml.drivers.AbstractJAXPDriver; import com.ibm.commons.xml.drivers.JAXPDriverSun; import com.ibm.commons.xml.drivers.JAXPDriverXerces; import com.ibm.commons.xml.drivers.XMLParserDriver; import com.ibm.commons.xml.drivers.XercesDriver; import com.ibm.commons.xml.drivers.XercesSunDriver; import com.ibm.commons.xml.util.XMIConverter; import com.ibm.commons.xml.xpath.NodeListImpl; import com.ibm.commons.xml.xpath.XPathException; import com.ibm.commons.xml.xpath.XPathExpression; import com.ibm.commons.xml.xpath.XPathExpressionFactory; import com.ibm.commons.xml.xpath.xml.XmlXPathExpressionFactory; /** * W3C DOM Utilities.<br/><br/> * * A set of utility methods that allow clients to perform various DOM * manipulations. * * @ibm-api */ public class DOMUtil { // ====================================================================== // DOM common functions // ====================================================================== private static XMLParserDriver s_parserDriver; private static XmlXPathExpressionFactory s_xpathFactory; static { //s_parserDriver = new XercesDriver(); loadDriver(); s_xpathFactory = new XmlXPathExpressionFactory(s_parserDriver); } private static void loadDriver() { DocumentBuilder builder = null; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); builder = factory.newDocumentBuilder(); } catch(Throwable ex) { } // If Xerces if available in the path, using it if(builder.getClass().getName().startsWith("org.apache.xerces")) { //$NON-NLS-1$ s_parserDriver = new XercesDriver(); return; } // If the Sun version of Xerces is available, then use the full Sun Xerces driver if(builder.getClass().getName().startsWith("com.sun.org.apache.xerces")) { //$NON-NLS-1$ s_parserDriver = new XercesSunDriver(); return; } // Else use JAXP // If the Xerces XPath API is available, then use it try { Class.forName("org.apache.xpath.XPathAPI"); //$NON-NLS-1$ s_parserDriver = new JAXPDriverXerces(); } catch(Throwable ex) { } // If the Sun Server XPath API is available, then use it try { Class.forName("com.sun.org.apache.xpath.internal.XPath"); //$NON-NLS-1$ s_parserDriver = new JAXPDriverSun(); } catch(Throwable ex) { } // Else forget about XPath evaluation // Just use a JAXP driver s_parserDriver = new AbstractJAXPDriver() { public Object createXPath(String xpath) throws XPathException { throw noXpathAvail(); } public XResult evaluateXPath(Node node, Object xpath, NamespaceContext nsContext) throws XPathException { throw noXpathAvail(); } public XResult evaluateXPath(NodeList nodeList, Object xpath, NamespaceContext nsContext) throws XPathException { throw noXpathAvail(); } }; } private static XPathException noXpathAvail() { return new XPathException(null,"XPath engine is not available");//$NLS-DOMUtil.XPathengineisnotavailable-1$ } /** * Get the internal XML parser driver used. * This object is not meant to be used directly. Use DOMUtil high level methods instead. * @return the XML parser driver */ public static XMLParserDriver getParserDriver() { return s_parserDriver; } /** * Get the internal XPath factory used. * This object is not meant to be used directly. Use DOMUtil high level methods instead. * @return the XPath factory */ public static XPathExpressionFactory getXPathExpressionFactory() { return s_xpathFactory; } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(String namespaceURI, String qualifiedName, DocumentType doctype) throws XMLException { return s_parserDriver.createDocument(namespaceURI,qualifiedName,doctype); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(String namespaceURI, String qualifiedName) throws XMLException { return createDocument(namespaceURI,qualifiedName,null); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(DocumentType docType) throws XMLException { return createDocument(null,null,docType); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument() throws XMLException { return createDocument(null,null,null); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(InputStream is, boolean ignoreBlanks) throws XMLException { return s_parserDriver.parse(is,ignoreBlanks,true,false); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(InputStream is, boolean ignoreBlanks, boolean resolveEntities) throws XMLException { return s_parserDriver.parse(is,ignoreBlanks, resolveEntities, false); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(InputStream is, boolean ignoreBlanks, boolean resolveEntities, boolean validate) throws XMLException { return s_parserDriver.parse(is,ignoreBlanks, resolveEntities, validate); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(InputStream is) throws XMLException { return createDocument(is,true); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(Reader is, boolean ignoreBlanks) throws XMLException { return s_parserDriver.parse(is,ignoreBlanks,true,false); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(Reader is, boolean ignoreBlanks, boolean resolveEntities) throws XMLException { return s_parserDriver.parse(is,ignoreBlanks,resolveEntities,false); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(Reader is, boolean ignoreBlanks, boolean resolveEntities, boolean validate) throws XMLException { return s_parserDriver.parse(is,ignoreBlanks,resolveEntities,validate); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(Reader is) throws XMLException { return createDocument(is,true); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(String xmlText, boolean ignoreBlanks) throws XMLException { if(StringUtil.isEmpty(xmlText)) { return createDocument(); } return createDocument(new StringReader(xmlText),ignoreBlanks); } /** * Create a new W3C Document. * @return the newly created document */ public static Document createDocument(String xmlText) throws XMLException { return createDocument(xmlText,true); } /** * Create a new W3C DocumentType. * @return the newly created document */ public static DocumentType createDocumentType(String qualifiedName, String publicId, String systemId) throws XMLException { return s_parserDriver.createDocumentType(qualifiedName, publicId, systemId); } /** * Returns the root element of a DOM. * This is for compatibility only. * @param document the source document * @return the DOM root element * @deprecated use document.getDocumentElement() instead */ public static Element rootElement( Document document ) { return document.getDocumentElement(); } /** * Returns whether a DOM has a root. * @param document the source document * @return true if a root element exists, false otherwise */ public static boolean hasRootElement( Document document ) { if(document!=null) { Element tmp = document.getDocumentElement(); if ( tmp == null ) return false; return true; } return false; } /** * Check if an element has some children. * This is for compatibility only. * @param element the element to check * @return true if the element has at least one child * @deprecated use element.hasChildNode() instead */ public static boolean hasChildren( Element element ) { if(element!=null) { return element.hasChildNodes(); } return false; } // ====================================================================== // Some useful DOM utilities // ====================================================================== /** * Remove all the children from a node. * This method is surprisingly absent from the Node interface. * @param node the node to remove the children */ public static void removeChildren(Node node) { if(node!=null) { while(node.hasChildNodes()) { node.removeChild(node.getFirstChild()); } } } /** * Remove the content of an existing document. * @param doc the document to clear * @return the empty */ public static Document emptyDocument(Document doc) { Element rootNode = doc.getDocumentElement(); if(rootNode!=null) { doc.removeChild(rootNode); } return doc; } // ====================================================================== // Text routines // ====================================================================== /** * Get the text associated with a node. * If case of an Element, then it concatenate all the Text parts into one big. * @return the text associated to the node */ public static String getTextValue(Node node) { if(node!=null) { if(node.getNodeType()==Node.ELEMENT_NODE) { if(node.hasChildNodes()) { NodeList l = node.getChildNodes(); int len = l.getLength(); if(len==1) { Node child = l.item(0); if( child.getNodeType()==Node.TEXT_NODE || child.getNodeType()==Node.CDATA_SECTION_NODE ) { return child.getNodeValue(); } return null; } else { StringBuilder b = new StringBuilder(128); for( int i=0; i<len; i++ ) { Node child = l.item(i); if( child.getNodeType()==Node.TEXT_NODE || child.getNodeType()==Node.CDATA_SECTION_NODE ) { String s = child.getNodeValue(); if(s!=null) { b.append(s); } } } return b.toString(); } } } else if (node.getNodeType()==Node.TEXT_NODE || node.getNodeType()==Node.CDATA_SECTION_NODE || node.getNodeType()==Node.ATTRIBUTE_NODE) { return node.getNodeValue(); } } return null; } /** * Set the text associated with a node. * If case of an Element, it finds the first occurence of text and change its content. If * none is available, then it add a new one at the end of the content. For the other nodes, * it then calls setNodeValue() * @return the text associated to the node */ public static void setTextValue(Node node, String value) { if(node!=null) { if(node.getNodeType()==Node.ELEMENT_NODE) { for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() == Node.TEXT_NODE) { ((Text)child).setNodeValue(value); return; } if (child.getNodeType() == Node.CDATA_SECTION_NODE) { ((Text)child).setNodeValue(value); return; } child = child.getNextSibling(); if (child == null) { break; } } if(node.hasChildNodes()) { removeChildren(node); } Text textNode = node.getOwnerDocument().createTextNode(value); node.appendChild(textNode); } else { node.setNodeValue(value); } } } public static void setTextValue(Node node, String value, boolean cdata) { if(node!=null) { if(node.getNodeType()==Node.ELEMENT_NODE) { for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() == Node.TEXT_NODE && !cdata) { ((Text)child).setNodeValue(value); return; } if (child.getNodeType() == Node.CDATA_SECTION_NODE && cdata) { ((Text)child).setNodeValue(value); return; } } if(node.hasChildNodes()) { removeChildren(node); } Node textNode = cdata ? node.getOwnerDocument().createCDATASection(value) : node.getOwnerDocument().createTextNode(value); node.appendChild(textNode); } else { node.setNodeValue(value); } } } // ====================================================================== // Some Java iterator // ====================================================================== /** * Get the list of the children Element. * Only the elements are extracted here. * @return an iterator on the children elements */ public static Iterator getChildren(Node node) { if(node!=null) { return new NodeListIterator(node.getChildNodes(),NodeListIterator.ELEMENT_FILTER); } return EmptyIterator.getInstance(); } // ====================================================================== // XSL Tranformer // ====================================================================== // /** // * Applies a XSL transformation (defined in .xsp file, which name is 'xslName') to // * the given source document. // * The result of XSL transformation is returned. // * @param the source document // * @param xslName the XSL name to apply // * @return the result document // * @throws XMLException if an error occurred // */ // public static org.jdom.Document xslTransform( org.jdom.Document source, String xslName ) throws XMLException { // return JDOM.xslTransform(source,xslName); // } // // // ====================================================================== // // XML load/save // // ====================================================================== // // /** // * Reads a JDOM object from a given XML resource and write it into the given source document. // * A resource is a file within the application directory (ex: c:\FlowBuilder20), and is relative // * to that directory. This works when running in an expanded directory as well as when running // * from a .war file, even non expanded.<br> // * Note that the file separator is a slash '/',regardless the platform where it is running. // * @param source the document to fill with the data // * @param resourceName the resource name to load // * @throws XMLException if an error occurred // */ // public static void loadResource( org.jdom.Document source, String resourceName ) throws XMLException { // try { // com.ibm.workplace.designer.Application.get().getVFS().getFile(resourceName).readXML(new ExistingDocumentFactory(source)); // } catch( VFSException e ) { // throw new XMLException( e, "Error while loading XML resource '{0}'", resourceName ); //$NLS-DOMUtil.JDom.Loading.Exception-1$ // } // } // // /** // * Writes the given JDOM document into a file. // * A resource is a file within the application directory (ex: c:\FlowBuilder20), and is relative // * to that directory. This works when running in an expanded directory as well as when running // * from a .war file, even non expanded. In case of a war file, the value is written to a temporary // * directory. It is lost when the application is redeployed!<br> // * Note that the file separator is a slash '/',regardless the platform where it is running. // * @param source the document to save // * @param resourceName the resource name to write // * @throws XMLException if an error occurred // */ // public static void saveResource( org.jdom.Document source, String resourceName ) throws XMLException { // try { // com.ibm.workplace.designer.Application.get().getVFS().getFile(resourceName).writeXML(source,false); // } catch( VFSException e ) { // throw new XMLException( e, "Error while saving XML resource '{0}'", resourceName ); //$NLS-DOMUtil.JDom.Saving.Exception-1$ // } // } // // /** // * Writes the given JDOM document in a file , with a compact format (no indent, no // * empty lines) if the <code>compact</code> parameter is true. // * A resource is a file within the application directory (ex: c:\FlowBuilder20), and is relative // * to that directory. This works when running in an expanded directory as well as when running // * from a .war file, even non expanded. In case of a war file, the value is written to a temporary // * directory. It is lost when the application is redeployed!<br> // * Note that the file separator is a slash '/',regardless the platform where it is running. // * @param source the document to save // * @param resourceName the resource name to write // * @param compact if true, the document does'nt contains any formatting text (blanks, new lines..) // * @throws XMLException if an error occurred // */ // public static void saveResource( org.jdom.Document source, String resourceName, boolean compact ) throws XMLException { // try { // com.ibm.workplace.designer.Application.get().getVFS().getFile(resourceName).writeXML(source,compact); // } catch( VFSException e ) { // throw new XMLException( e, "Error while saving XML resource '{0}'", resourceName ); //$NLS-DOMUtil.JDom.Saving.Exception-1$ // } // } // // /** // * Reads a JDOM object from a given XML file, and write it into the given source Document. // * The file name is using the underlying OS conventions. Note that, for accessing a file // * located in your FlowBuilder directory, it is better to use <code>loadResource()</code>, // * which also works when the application is deployed as a war file. // * @param source the document to fill with the data // * @param fileName the file name to load // * @throws XMLException if an error occurred // */ // public static void loadFile( org.jdom.Document source, String fileName ) throws XMLException { // Document d = JDOM.readFileOrURL(fileName,new ExistingDocumentFactory(source)); // } // // /** // * Writes the given document into the given file. // * The file name is using the underlying OS conventions. Note that, for accessing a file // * located in your FlowBuilder directory, it is better to use <code>saveResource()</code>, // * which also works when the application is deployed as a war file. // * @param source the document to save // * @param fileName the file name to load // * @throws XMLException if an error occurred // */ // public static void saveFile( org.jdom.Document source, String fileName ) throws XMLException { // JDOM.save(new File(fileName),source,false); // } // // /** // * Writes the given document into the given file, with a compact format (no indent, no // * empty lines) if the <code>compact</code> parameter is true. // * Writes the given document into the given file. // * The file name is using the underlying OS conventions. Note that, for accessing a file // * located in your FlowBuilder directory, it is better to use <code>saveResource()</code>, // * which also works when the application is deployed as a war file. // * @param source the document to save // * @param fileName the file name to load // * @param compact if true, the document does'nt contains any formatting text (blanks, new lines..) // * @throws XMLException if an error occurred // */ // public static void saveFile( org.jdom.Document source, String fileName, boolean compact ) throws XMLException { // JDOM.save(new File(fileName),source,compact); // } // // // ====================================================================== // Serialization // ====================================================================== //jtw-add /** * Serialize a node to a file. * @param file the java.io.File * @param node the source node * @param format the desired output format */ public static void serialize( File file, Node node, Format format) throws XMLException { try { OutputStream os = new FastBufferedOutputStream(new FileOutputStream(file)); try { serialize(os, node, format); } finally { os.close(); } } catch(IOException e) { //TODO - after resource bundle clarification throw new XMLException(e, "Error while saving XML to file '{0}'", file.getPath()); //$NLS-DOMUtil.JDOMUtil.XmlSavingToFile.Exception-1$ throw new XMLException(e,"JDOMUtil.XmlSavingToFile.Exception"+file.getPath()); // $NON-NLS-1$ } } /** * Serialize a node to a stream. * @param os the output stream * @param node the source node * @param format the desired output format */ public static void serialize( OutputStream os, Node node, Format format ) throws XMLException { s_parserDriver.serialize(os,node,format); } /** * Serialize a node to a stream. * The serialization is done using UTF-8 encoding. * @param os the output stream * @param node the source node * @param compact if true, the document does'nt contains any formatting text (blanks, new lines..) * @param xmldecl if true, include the XML declaration statement */ public static void serialize( OutputStream os, Node node, boolean compact, boolean xmldecl ) throws XMLException { Format fmt = new Format( compact ? 0 : 2, xmldecl, "UTF-8" // $NON-NLS-1$ ); serialize(os,node,fmt); } /** * Serialize a node to a writer. * @param w the writer * @param node the source node * @param format the desired output format */ public static void serialize( Writer w, Node node, Format format ) throws XMLException { s_parserDriver.serialize(w,node,format); } /** * Serialize a node to a writer. * The serialization is done using UTF-8 encoding. * @param w the writer * @param node the source node * @param compact if true, the document does'nt contains any formatting text (blanks, new lines..) * @param xmldecl if true, include the XML declaration statement */ public static void serialize( Writer w, Node node, boolean compact, boolean xmldecl ) throws XMLException { Format fmt = new Format( compact ? 0 : 2, xmldecl, "UTF-8" // $NON-NLS-1$ ); s_parserDriver.serialize(w,node,fmt); } // ====================================================================== // String conversion // ====================================================================== /** * Method to convert a DOM document into a string. * <p> If <code>xmlDecl</code> is false, the resulting string * won't contain an XML declaration. (<code><?xml version="1.0"?></code>) * <p> If <code>compact</code> is true, the resulting string will be compacted * (no indent, no empty lines). * @param node the source XML node * @param compact if true, the document does'nt contains any formatting text (blanks, new lines..) * @param xmldecl if true, include the XML declaration statement * @return the document as a string * @throws XMLException if an error occurred */ public static String getXMLString(Node node, Format format) throws XMLException { StringWriter w = new StringWriter(); serialize(w,node,format); return w.toString(); } /** * Method to convert a DOM document into a string. * <p> If <code>xmlDecl</code> is false, the resulting string * won't contain an XML declaration. (<code><?xml version="1.0"?></code>) * <p> If <code>compact</code> is true, the resulting string will be compacted * (no indent, no empty lines). * @param node the source XML node * @param compact if true, the document does'nt contains any formatting text (blanks, new lines..) * @param xmldecl if true, include the XML declaration statement * @return the document as a string * @throws XMLException if an error occurred */ public static String getXMLString(Node node, boolean compact, boolean xmldecl) throws XMLException { StringWriter w = new StringWriter(); serialize(w,node,compact,xmldecl); return w.toString(); } /** * Method to convert a DOM document into a string with no XML declaration line. * <p> If <code>compact</code> is true, the resulting string will be compacted * (no indent, no empty lines). * @param node the source XML node * @param compact if true, the document does'nt contains any formatting text (blanks, new lines..) * @return the document as a string * @throws XMLException if an error occurred */ public static String getXMLString(Node node, boolean compact) throws XMLException { return getXMLString(node,compact,false); } /** * Method to convert a DOM document into a string with no XML declaration line. * The resulting string is not compacted (indent, blank lines) * @param node the source XML node * @return the document as a string * @throws XMLException if an error occurred */ public static String getXMLString(Node node) throws XMLException { return getXMLString(node,false,false); } /** * Reads the given string to update the document content. * The string is parsed to an XML DOM, which is then assigned to the document * @param doc the XML node to update * @param s the XML string to parse * @throws XMLException if an error occurred */ public static void setXMLString(Document target, String s) throws XMLException { Document source = createDocument(s); setDocument(source, target); } /** * Clear all of the content from the specified document * @param doc */ public static void clearDocument(Document doc) { if (doc == null) { return; } while (doc.hasChildNodes()) { doc.removeChild(doc.getFirstChild()); } } /** * Set the content of the target document from the sourec document. * @param source * @param target */ public static void setDocument(Document source, Document target) { if (source == null || target == null) { return; } clearDocument(target); Node node = target.importNode(source.getDocumentElement(), true); target.appendChild(node); } // ====================================================================== // XPath access // ====================================================================== /** * Push an XPath context to an existing document. * An XML document contains a base XPath that is used when evaluating XPath against a * node. This base XPath is composed by the aggregating all the XPath contexts * pushed to the document. * @param doc the source document * @param xPathContext the XPath context to push to the document */ public static void pushXPathContext(Document doc, String xPathContext) throws XMLException { s_parserDriver.pushXPathContext(doc, xPathContext); } /** * Remove the latest XPath context that has been set to the document * An XML document contains a base XPath that is used when evaluating XPath against a * node. This base XPath is composed by the aggregating all the XPath contexts * pushed to the document. * @param doc the source document */ public static void popXPathContext(Document doc) throws XMLException { s_parserDriver.popXPathContext(doc); } /** * Get the current XPath context result that is associated to a document. * The result is either a Node or a NodeList. * @param doc the source document */ public static Object getContextNodes(Document doc) { XPathContext ctx = s_parserDriver.getXPathContext(doc); return ctx!=null ? ctx.getContextNodes() : null; } /** * Get the current XPath contextthat is associated to a document. * @param doc the source document */ public static XPathContext getXPathContext(Document doc) { return s_parserDriver.getXPathContext(doc); } /** * Set the filter index on the current XPath context that is associated with the * specified document. * @param doc * @param index * @throws XMLException */ public static void setFilterIndex(Document doc, int index) throws XMLException { XPathContext ctx = s_parserDriver.getXPathContext(doc); if(ctx==null) { throw new XMLException(null,"No current context defined for the document"); // $NLS-DOMUtil.Nocurrentcontextdefinedforthedocu-1$ } ctx.setFilterIndex(index); } /** * Set the namespaces map use during the XPath evaluation on the document. * @param doc the source document * @param selectionNS the NamespaceContext used during XPath evaluation on the document, * in order to resolve namespaces. */ public static void setSelectionNamespaces(Document doc, NamespaceContext selectionNS) { // Delegate to the driver s_parserDriver.setNamespaceContext(doc,selectionNS); } /** * Get the selection NamespaceContext use during the XPath evaluation on the document. * @param doc the source document */ public static NamespaceContext getSelectionNamespaces(Document doc) { // Delegate to the driver return s_parserDriver.getNamespaceContext(doc); } /** * Debugging function for NamespaceContext. * @param namespaceContext the namespace context to dump. * @return a string representing the namespace context */ public static String getNamespaceContextAsString(NamespaceContext namespaceContext) { StringBuffer b = new StringBuffer(); boolean first = true; for( Iterator it=namespaceContext.getPrefixes(); it.hasNext(); ) { String s = it.next().toString(); if(!first) { b.append("\n"); // $NON-NLS-1$ } else { first = false; } b.append(s); b.append("="); b.append(namespaceContext.getNamespaceURI(s)); } return b.toString(); } /** * Create an XPath expression object. * This object can be safetly reused for subsequent evaluations. * WARN: This object does not take care of the Document XPath context. To take * advantage of this context, use one of the string based functions. * @param xpathExpr the xpath expression * @param useCache indicate if the xpath should be cached * @return the XPath expression * @throws XMLException */ public static XPathExpression createXPath(String xpathExpr, boolean useCache) throws XMLException { return s_xpathFactory.createExpression(null, xpathExpr, useCache); } /** * Create an XPath expression object. * This object can be safetly reused for subsequent evaluations. * WARN: This object does not take care of the Document XPath context. To take * advantage of this context, use one of the string based functions. * @param xpathExpr the xpath expression * @return the XPath expression * @throws XMLException */ public static XPathExpression createXPath(String xpathExpr) throws XMLException { return s_xpathFactory.createExpression(null, xpathExpr, true); } /** * Evaluate an XPath expression. * This object can be safetly reused for subsequent evaluations. * @param node the node source of the XPath * @param xpathExpr the xpath expression * @param nsContext the namespace context to use by the engine * @param useCache indicate if the xpath should be cached * @return the result of the XPath evaluation * @throws XMLException */ public static XResult evaluateXPath(Node node, String xpathExpr, NamespaceContext nsContext, boolean useCache) throws XMLException { XPathExpression expr = createXPath(xpathExpr,useCache); return expr.eval(node,nsContext); } /** * Evaluate an XPath expression. * This object can be safetly reused for subsequent evaluations. * @param node the node source of the XPath * @param xpathExpr the xpath expression * @param nsContext the namespace context to use by the engine * @return the result of the XPath evaluation * @throws XMLException */ public static XResult evaluateXPath(Node node, String xpathExpr, NamespaceContext nsContext) throws XMLException { return evaluateXPath(node, xpathExpr, nsContext, true); } /** * Evaluate an XPath expression. * This object can be safetly reused for subsequent evaluations. * @param node the node source of the XPath * @param xpathExpr the xpath expression * @return the result of the XPath evaluation * @throws XMLException */ public static XResult evaluateXPath(Node node, String xpathExpr) throws XMLException { return evaluateXPath(node, xpathExpr, null, true); } /** * Evaluate an XPath expression. * This object can be safetly reused for subsequent evaluations. * @param node the node source of the XPath * @param xpathExpr the xpath expression * @param useCache indicate if the xpath should be cached * @return the result of the XPath evaluation * @throws XMLException */ public static XResult evaluateXPath(Node node, String xpathExpr, boolean useCache) throws XMLException { return evaluateXPath(node, xpathExpr, null, useCache); } /** * Create a node based on an XPath expression object. * @param node the node to start from * @param xpathExpr the xpath expression * @param useCache indicate if the xpath should be cached * @return the XPath expression * @throws XMLException */ public static Object createNodes(Node node, String xpathExpr, boolean useCache) throws XMLException { XPathExpression xp = createXPath(xpathExpr); // If we start from the document, we should take care of the XPath context if(!xp.isFromRoot() && (node instanceof Document)) { Document doc = (Document)node; XPathContext ctx = s_parserDriver.getXPathContext(doc); if(ctx!=null) { // Ensure that the intermediate context are created ctx.createNodes(); // And evaluate from this context return xp.createNodes(ctx.getUniqueContextNode(),null); } } // Else, start from the current node return xp.createNodes(node,null); } /** * Create a node based on an XPath expression object. * @param node the node to start from * @param xpathExpr the xpath expression * @param useCache indicate if the xpath should be cached * @return the XPath expression * @throws XMLException */ public static Object createNodes(Node node, String xpathExpr) throws XMLException { return createNodes(node,xpathExpr,true); } /** * Create an element with the specified name and add it as a child of the specified parent. * @param document * @param parent * @param name * @return */ public static Element createElement(Document document, Element parent, String name) { Element element = document.createElement(name); parent.appendChild(element); return element; } /** * Create an element with the specified name and add it as a child of the specified parent. * @param document * @param name * @return */ public static Element createElement(Document document, String name) { Element element = document.createElement(name); document.appendChild(element); return element; } /////////////////////////////////////////////////////////////////////////// // Value extraction /////////////////////////////////////////////////////////////////////////// /** * Evaluate the given XPath expression on the given document, and returns an array with * all elements and attributes that match this XPath. * @param node the source node * @param xpath the XPath to evaluate * @return the array of XML nodes that match the XPath * @throws XMLException if an error occurred */ public static Object[] nodes(Node node, String xpath) throws XMLException { XResult r = evaluateXPath(node,xpath); return r.getNodes(); } /** * Evaluate the given XPath expression on the given document, and returns the * first element or attribute that match this XPath. * @param node the source node * @param xpath the XPath to evaluate * @return the resulting object * @throws XMLException if an error occurred */ public static Object node(Node node, String xpath) throws XMLException { XResult r = evaluateXPath(node,xpath); return r.getSingleNode(); } /** * Evaluate the given XPath expression on the given document, and returns an array with * all elements and attributes that match this XPath. * @param node the source node * @param xpath the XPath to evaluate * @param selectionNS the NamespacContext used to resolve namespaces during XPath evaluation * @return the array of XML nodes that match the XPath * @throws XMLException if an error occurred */ public static Object[] nodes(Node node, String xpath, NamespaceContext selectionNS) throws XMLException { XResult r = evaluateXPath(node,xpath,selectionNS); return r.getNodes(); } /** * Evaluate the given XPath expression on the given document, and returns the * first element or attribute that match this XPath. * @param node the source node * @param xpath the XPath to evaluate * @param selectionNS the NamespacContext used to resolve namespaces during XPath evaluation * @return the resulting object * @throws XMLException if an error occurred */ public static Object node(Node node, String xpath, NamespaceContext selectionNS) throws XMLException { XResult r = evaluateXPath(node,xpath,selectionNS); return r.getSingleNode(); } /////////////////////////////////////////////////////////////////////////// // Value extraction /////////////////////////////////////////////////////////////////////////// /** * Get a string value from the first XPath matching element. * @param node the source node * @param xpath the XPath to evaluate * @return the value as a string * @throws XMLException if an error occurred */ public static String value(Node node, String xpath) throws XMLException { XResult r = evaluateXPath(node,xpath); return r.getStringValue(); } /** * Get a string value from the first XPath matching element. * @param node the source node * @param xpath the XPath to evaluate * @param selectionNS the NamespacContext used to resolve namespaces during XPath evaluation * @return the value as a string * @throws XMLException if an error occurred */ public static String value(Node node, String xpath, NamespaceContext selectionNS) throws XMLException { XResult r = evaluateXPath(node,xpath,selectionNS); return r.getStringValue(); } /** * Get the string values from the XPath matching elements. * @param node the source node * @param xpath the XPath to evaluate * @return the values as a string array * @throws XMLException if an error occurred */ public static String[] values(Node node, String xpath) throws XMLException { XResult r = evaluateXPath(node,xpath); return r.getValues(); } /** * Get the string values from the XPath matching elements. * @param node the source node * @param xpath the XPath to evaluate * @param selectionNS the NamespacContext used to resolve namespaces during XPath evaluation * @return the values as a string array * @throws XMLException if an error occurred */ public static String[] values(Node node, String xpath, NamespaceContext selectionNS) throws XMLException { XResult r = evaluateXPath(node,xpath,selectionNS); return r.getValues(); } /** * Set the value of the first XPath matching element. * That method first evaluates the XPath and, if it returns an existing nodes, then * it updates it value. If not, then it tries to add a new node corresponding to that * XPath. This operation is possible only if the XPath is simple, thus in a form * like <code>node[/node...]</code>. * @param node the source node * @param path the XPath to use * @param value the value to set * @throws XMLException if an error occurred */ public static void setValue(Node node, String path, String value) throws XMLException { setValue(node,path,value,null); } /** * Set the value of the first XPath matching element. * That method first evaluates the XPath and, if it returns an existing nodes, then * it updates it value. If not, then it tries to add a new node corresponding to that * XPath. This operation is possible only if the XPath is simple, thus in a form * like <code>node[/node...]</code>. * @param node the source node * @param path the XPath to use * @param value the value to set * @param selectionNS the NamespacContext used to resolve namespaces during XPath evaluation * @throws XMLException if an error occurred */ public static void setValue(Node node, String path, String value, NamespaceContext selectionNS) throws XMLException { XPathExpression xp = createXPath(path); // If we start from the document, we should take care of the XPath context if(!xp.isFromRoot() && (node instanceof Document)) { Document doc = (Document)node; XPathContext ctx = s_parserDriver.getXPathContext(doc); if(ctx!=null) { // Ensure that the intermediate context are created ctx.createNodes(); // And evaluate from this context xp.setValue(ctx.getUniqueContextNode(),value,selectionNS,true); return; } } // Else, start from the current node xp.setValue(node,value,selectionNS,true); } // ====================================================================== // Element accessor // ====================================================================== /** * Find a element by name. * @return the element found, or null if it is not available */ public static Element getFirstElementByName( Element parent, String name ){ NodeList l = parent.getChildNodes(); int count = l.getLength(); for( int i=0; i<count; i++ ) { Node n = l.item(i); if(n.getNodeType()==Node.ELEMENT_NODE) { Element e = (Element)n; String localName = e.getLocalName(); if (localName == null) { if( name.equals(e.getNodeName()) ) { return e; } } } } return null; } /** * Find a element by name. * @return the element found, or null if it is not available */ public static Element getFirstElementByNameNS( Element parent, String namespaceURI, String name ){ NodeList l = parent.getChildNodes(); int count = l.getLength(); for( int i=0; i<count; i++ ) { Node n = l.item(i); if(n.getNodeType()==Node.ELEMENT_NODE) { Element e = (Element)n; String localName = e.getLocalName(); if (localName == null) { if( name.equals(e.getNodeName()) ) { return e; } } if (namespaceURI!=null && name.equals(localName)) { String elemNsURI = e.getNamespaceURI(); if (namespaceURI.equals(elemNsURI)) { return e; } } } } return null; } /** * Find a element by name. * @return the element found, or null if it is not available */ public static NodeList getElementsByName( Element parent, String name ){ NodeListImpl result = new NodeListImpl(); NodeList l = parent.getChildNodes(); int count = l.getLength(); for( int i=0; i<count; i++ ) { Node n = l.item(i); if(n.getNodeType()==Node.ELEMENT_NODE) { Element e = (Element)n; String localName = e.getLocalName(); if (localName == null) { if( name.equals(e.getNodeName()) ) { result.add(e); } } } } return result; } /** * Find a element by name. * @return the element found, or null if it is not available */ public static NodeList getElementsByNameNS( Element parent, String namespaceURI, String name ){ NodeListImpl result = new NodeListImpl(); NodeList l = parent.getChildNodes(); int count = l.getLength(); for( int i=0; i<count; i++ ) { Node n = l.item(i); if(n.getNodeType()==Node.ELEMENT_NODE) { Element e = (Element)n; String localName = e.getLocalName(); if (localName == null) { if( name.equals(e.getNodeName()) ) { result.add(e); } } if (namespaceURI!=null && name.equals(localName)) { String elemNsURI = e.getNamespaceURI(); if (namespaceURI.equals(elemNsURI)) { result.add(e); } } } } return result; } /** * Inserts the node newChild after the existing child node refChild. * Inserts the node newChild after the existing child node refChild. If refChild is null, * insert newChild at the end of the list of children. * If newChild is a DocumentFragment object, all of its children are inserted, * in the same order, after refChild. If the newChild is already in the tree, * it is first removed. * @return The node being inserted */ public static Node insertAfter(Node parent, Node newChild, Node refChild) { if(refChild!=null) { Node next = refChild.getNextSibling(); return parent.insertBefore(newChild, next); } return parent.insertBefore(newChild, refChild); } /** * Move an element prior to his previous sibling. * If the element doesn't have a previous sibling, then nothing is moved * @param node the node to move up * @return true if the element was moved */ public static boolean moveNodeUp(Node node) { Node parent = node.getParentNode(); Node previous = node.getPreviousSibling(); if(previous!=null) { parent.removeChild(node); parent.insertBefore(node, previous); return true; } return false; } /** * Move an element prior to his previous sibling. * If the element doesn't have a previous sibling, then nothing is moved * @param node the node to move down * @return true if the element was moved */ public static boolean moveNodeDown(Node node) { Node parent = node.getParentNode(); Node next = node.getNextSibling(); if(next!=null) { parent.removeChild(node); insertAfter(parent, node, next); return true; } return false; } // ====================================================================== // Attributes accessors // ====================================================================== /** * Determines if an Attribute is a namespace attribute. */ public static boolean isNamespaceAttribute( Attr attribute ){ boolean rtnVal = false; String prefix = attribute.getPrefix(); if ( StringUtil.equals( prefix, "xmlns") == true) { // $NON-NLS-1$ rtnVal = true; } return rtnVal; } /** * Gets the DOM attribute of <code>element</code> with the given name. * It returns the first attribute that matches the name, regardless the case. * This is particularly useful when dealing with an HTML DOM, as HTML is not * case sensitive. * @param element the source element * @param attributeName the attribute name * @return the XML attribute if any, null otherwise */ public static Attr getAttributeIgnoreCase(Element element, String attributeName) { NamedNodeMap nm = element.getAttributes(); for(int i=0; i<nm.getLength(); i++ ) { Attr a = (Attr)nm.item(i); if(a.getName().equalsIgnoreCase(attributeName)) { return a; } } return null; } /** * Removes the DOM attribute from <code>element</code> with the given name. * It removes the first attribute that matches the name, regardless the case. * This is particularly useful when dealing with an HTML DOM, as HTML is not * case sensitive. * @param element the source element * @param attributeName the attribute name */ public static void removeAttributeIgnoreCase(Element element, String attributeName) { Attr a = getAttributeIgnoreCase(element,attributeName); if(a!=null) { element.removeAttributeNode(a); } } /** * Gets the value of DOM attribute of <code>element</code> with the given name * It returns the first attribute value that matches the name, regardless the case. * This is particularly useful when dealing with an HTML DOM, as HTML is not * case sensitive. * @param element the source element * @param attributeName the attribute name * @return the XML attribute if any, null otherwise */ public static String getAttributeValueIgnoreCase(Element element, String attributeName) { Attr a = getAttributeIgnoreCase(element,attributeName); if(a!=null) { a.getValue(); } return null; } /** * Return the owner document for the specified node. * * @param node * @return */ public static Document getOwnerDocument(Node node) { if (node instanceof Document) { return (Document)node; } return node.getOwnerDocument(); } /** * Traverse a node, and parent nodes, for a namespace URI. If the namespace * URI is located, the corresponding name is returned. If the namespace * uri is not located (i.e., does not exist), it is created as an attribute * node of the document root node. * @param parentNode - node from which to begin the search * @param preferredPrefix - Prefix to use if the namespace attribute must be created. * @param nsUri - the namesapce URI to search for * @return the located/created namespace prefix */ public static String getNamespacePrefix(Node parentNode, String preferredPrefix, String nsUri) { //traverse the list of namespaces Node currentNode = parentNode; do{ NamedNodeMap nodeMap = currentNode.getAttributes(); for ( int i = 0 ; nodeMap != null && i < nodeMap.getLength(); i++ ){ Node item = nodeMap.item( i ); String itemName = item.getNodeName(); if ( itemName.startsWith( "xmlns" ) ){ // $NON-NLS-1$ String uri = getTextValue( item ); if ( nsUri.equals( uri ) ){ return itemName.substring( itemName.indexOf(":") + 1); } } } }while( (currentNode = currentNode.getParentNode()) != null ); // If the namespace does not exist, then create it Element root = parentNode.getOwnerDocument().getDocumentElement(); if(root!=null) { for( int i=1; ; i++) { String p = preferredPrefix; if( i>1 ) { p += i; } if( root.getAttributeNode("xmlns:"+p)==null) { // $NON-NLS-1$ root.setAttribute("xmlns:"+p,nsUri); // $NON-NLS-1$ return p; } } } return ""; } // ====================================================================== // clone method // ====================================================================== /** * Clone a Document. This method returns a deep copy of the input * Document. * @param inDoc the input document * @return the cloned Document */ public static Document clone( Document inDoc ) { Document rtnVal = (Document)inDoc.cloneNode(true); return ( rtnVal ); } // ====================================================================== // jdom compatibility methods // ====================================================================== /** * Returns the textual content of the named child element, or null if * there's no such child. * See: org.jdom.Element 'getChildText()' * @param element to search for children * @return java.lang.String */ public static String getChildText(Element element, String nodeName) { NodeList ndlist = element.getChildNodes(); Node nd; String rtnVal = null; for(int i=0; i<ndlist.getLength(); i++ ) { nd = (Node)ndlist.item(i); if ( nd.getNodeType() == Node.ELEMENT_NODE && nd.getNodeName().equals( nodeName )){ rtnVal = getText(nd); break; } } return rtnVal; } //========================================================================= // utiltiy methods //========================================================================= /** * Returns the textual content directly held under this element as a * string. The call does not recurse into child elements. If no * textual value exists for the element, an empty string is returned. * @param n * @return */ public static String getText(Node n) { String rtnVal = ""; NodeList nlist = n.getChildNodes(); Node currentNode; for ( int i = 0 ; i < nlist.getLength() ; i++ ){ currentNode = nlist.item(i); if ( currentNode.getNodeType() == Node.TEXT_NODE ){ rtnVal = currentNode.getNodeValue(); } } return rtnVal; } /** * This returns the attribute value for the attribute of the input element * with the given name and within no namespace. Returns null if there is * no such attribute, and the empty string if the attribute value is empty. * @param e the Node * @param key the attribute name * @return */ public static String getAttributeValue( Element e, String key ){ String value = null; NamedNodeMap map = e.getAttributes(); Node keyNode = map.getNamedItem(key); if (keyNode != null) { value = keyNode.getNodeValue(); } return value; } /** * Add a child element to a parent element. The child element will have the * indicated name and default (null) namespace. The parent element * is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addNode(Element parent, String elementName){ addNode( parent, elementName, null); } /** * Add a child element to a parent element. The child element will have the * indicated name and namespace. The parent element * is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addNode(Element parent, String elementName, String namespaceURI){ Document doc = parent.getOwnerDocument(); Element elt = doc.createElementNS(namespaceURI, elementName); parent.appendChild(elt); } /** * Add a child element to a parent element. The child element will have the * indicated name, BigDecimal value, and a defult (null) namespace. The parent * element is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addBigDecimal(Element parent, String elementName, BigDecimal value) { addString(parent, elementName, XMIConverter.toString(value), null); } /** * Add a child element to a parent element. The child element will have the * indicated name, BigDecimal value, and a defult (null) namespace. The parent * element is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addBigDecimal(Element parent, String elementName, BigDecimal value, String namespace) { addString(parent, elementName, XMIConverter.toString(value), namespace); } /** * Add a child element to a parent element. The child element will have the * indicated name, boolean value, and namespace. The parent elenent is assumed * to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addBoolean(Element parent, String elementName, boolean value) { addString(parent, elementName, XMIConverter.toString(value), null); } /** * Add a child element to a parent element. The child element will have the * indicated name, boolean value, and namespace. The parent elenent is assumed to be attached * to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addBoolean(Element parent, String elementName, boolean value, String namespace) { addString(parent, elementName, XMIConverter.toString(value), namespace); } /** * Add a child element to a parent element. The child element will have the * indicated name, java.sql.Date value, and the default (null) namespace. The * parent element is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addDate(Element parent, String elementName, Date value) { addString(parent, elementName, XMIConverter.toString(value), null); } /** * Add a child element to a parent element. The child element will have the * indicated name, java.sql.Date value, and namespace. The parent element is * assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addDate(Element parent, String elementName, Date value, String namespace) { addString(parent, elementName, XMIConverter.toString(value), namespace); } /** * Add a child element to a parent element. The child element will have the * indicated name, double value, and default (null) namespace. The parent * element is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addDouble(Element parent, String elementName, double value) { addString(parent, elementName, XMIConverter.toString(value), null); } /** * Add a child element to a parent element. The child element will have the * indicated name, double value, and namespace. The parent element is assumed * to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addDouble(Element parent, String elementName, double value, String namespace) { addString(parent, elementName, XMIConverter.toString(value), namespace); } /** * Add a child element to a parent element. The child element will have the * indicated name, int value, and the default (null) namespace. The parent * element is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addInteger(Element parent, String elementName, int value) { addString(parent, elementName, XMIConverter.toString(value), null); } /** * Add a child element to a parent element. The child element will have the * indicated name, int value, and namespace. The parent element is assumed to be * attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addInteger(Element parent, String elementName, int value, String namespace) { addString(parent, elementName, XMIConverter.toString(value), namespace); } /** * Add a child element to a parent element. The child element will have the * indicated name, long value, and the default (null) namespace. The parent * element is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addLong(Element parent, String elementName, long value) { addString(parent, elementName, XMIConverter.toString(value), null); } /** * Add a child element to a parent element. The child element will have the * indicated name, long value, and namespace. The parent element is assumed * to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addLong(Element parent, String elementName, long value, String namespace) { addString(parent, elementName, XMIConverter.toString(value), namespace); } /** * Add a child element to a parent element. The child element will have the * indicated name, String value, and default (null) namespace. The parent element * is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addString(Element parent, String elementName, String value){ addString( parent, elementName, value, null); } /** * Add a child element to a parent element. The child element will have the * indicated name, String value, and namespace. The parent element * is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addString(Element parent, String elementName, String value, String namespaceURI){ Document doc = parent.getOwnerDocument(); Element elt = doc.createElementNS(namespaceURI, elementName); elt.appendChild(doc.createTextNode(value)); parent.appendChild(elt); } /** * Add a child element to a parent element. The child element will have the * indicated name, java.sqlTime value, and default (null) namespace. The * parent element is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addTime(Element parent, String elementName, Time value){ addString( parent, elementName, XMIConverter.toString(value), null); } /** * Add a child element to a parent element. The child element will have the * indicated name, java.sqlTime value, and namespaceURI. The parent element * is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addTime(Element parent, String elementName, Time value, String namespaceURI){ addString( parent, elementName, XMIConverter.toString(value), namespaceURI); } /** * Add a child element to a parent element. The child element will have the * indicated name, java.sql.Timestamp value, and default (null) namespace. * The parent element is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addTimestamp(Element parent, String elementName, Timestamp value){ addString( parent, elementName, XMIConverter.toString(value), null); } /** * Add a child element to a parent element. The child element will have the * indicated name, java.sql.Timestamp value, and namespaceURI. The parent * element is assumed to be attached to a Document. * @param parent the parent element * @param elementName the Element name * @param value the Element value */ public static void addTimestamp(Element parent, String elementName, Timestamp value, String namespaceURI){ addString( parent, elementName, XMIConverter.toString(value), namespaceURI); } /** * Create a java.util.List contain the child nodes of the specified * element. * @param elt * @return */ public static List <Node> getChildrenAsList(Element elt) { List <Node> rtnVal = new ArrayList <Node> (); NodeList nlist = elt.getChildNodes(); for (int i=0 ; i<nlist.getLength() ; i++) { rtnVal.add(i, (Node)nlist.item(i)); } return rtnVal; } /** * Return the source reference id for the specified node. * @param node * @return */ public static String getSourceReferenceId(Node node) { if (node instanceof Element) { Element element = (Element)node; String id = element.getAttribute("id"); //$NON-NLS-1$ if (StringUtil.isEmpty(id)) { return createSourceReferenceId(element); } return id; } if (node instanceof Attr) { Attr attr = (Attr)node; Element element = attr.getOwnerElement(); String sourceId = getSourceReferenceId(element); return sourceId + "/@" + attr.getName(); //$NON-NLS-1$ } if (node.getNodeType() == Node.CDATA_SECTION_NODE || node.getNodeType() == Node.TEXT_NODE) { // See these on the xpath for a text or cdata: // http://www.w3.org/TR/xpath#section-Text-Nodes // http://www.w3.org/TR/xpath#node-tests // Note if you have an element containing text, a cdata, more text // then all of these will correspond to the same xpath expression // ending in text(), because xpath always treats adjacent text and // cdatas as if they were merged. Node parent = node.getParentNode(); String parentPath = getSourceReferenceId(parent); if( parentPath.lastIndexOf('/') != parentPath.length() - 1){ // make it end in '/' parentPath += '/'; } int textsCount = 0; boolean lastWasText = false; // the current node's index in the list of it's parent's child texts int nodeTextListIndex = -1; // for all siblings // find if more than one texts (each text includes merged cdatas) // calculate the current node's 1-base index (cannot be 0) NodeList children = parent.getChildNodes(); int len = children.getLength(); boolean siblingIsText; for (int i = 0; i < len; i++) { Node sibling = children.item(i); siblingIsText = Node.TEXT_NODE == sibling.getNodeType() || Node.CDATA_SECTION_NODE == sibling.getNodeType(); if( siblingIsText ){ if( ! lastWasText ){ textsCount++; if( textsCount > 1 && -1 != nodeTextListIndex ){ break; } } if( node == sibling ){ nodeTextListIndex = textsCount; if( textsCount > 1 && -1 != nodeTextListIndex ){ break; } } } lastWasText = siblingIsText; } String path; if( textsCount == 1 ){ // text() resolves to the only text child path = parentPath + "text()"; // $NON-NLS-1$ }else{ // text() resolves to the set of text children path = parentPath + "text()["+nodeTextListIndex+"]"; // $NON-NLS-1$ } return path; } if( node instanceof Document ){ return "/"; } return null; } private static String createSourceReferenceId(Element elt) { StringBuffer buffer = new StringBuffer(); while (StringUtil.isEmpty(elt.getAttribute("id"))) { //$NON-NLS-1$ insertElementReferenceId(buffer, elt); if (elt.getParentNode() == elt.getOwnerDocument()) { break; } elt = (Element)elt.getParentNode(); } buffer.insert(0, elt.getAttribute("id")); //$NON-NLS-1$ return buffer.toString(); } private static void insertElementReferenceId(StringBuffer buffer, Element elt) { int predicate = 1; Node sibling = elt; while ((sibling = sibling.getPreviousSibling()) != null) { if (elt.getNodeName().equals(sibling.getNodeName())) { predicate++; } } buffer.insert(0, "]"); //$NON-NLS-1$ buffer.insert(0, predicate); buffer.insert(0, "["); //$NON-NLS-1$ buffer.insert(0, elt.getNodeName()); buffer.insert(0, "/"); //$NON-NLS-1$ } /** * Behaves like getElementsByTagNameNS() but only return nodes that are the children * of a specified element. * * @param element - element to find the children of, may not be null. * @param uri - only return children matching this namespace uri. * @param tag - only return children matching this tag name. * @return NodeList - matching elements that are children of element. */ public static NodeList getChildElementsByTagNameNS(Element element, String uri, String tag) { NodeList found = element.getElementsByTagNameNS(uri, tag); NodeListImpl children = new NodeListImpl(); for (int i = 0; i < found.getLength(); i++) { Node node = found.item(i); Node parentNode = node.getParentNode(); if (parentNode.equals(element)) { children.add(node); } } return children; } /** * Behaves like getElementsByTagName() but only return nodes that are the children * of a specified element. * * @param element - element to find the children of, may not be null. * @param tag - only return children matching this tag name. * @return NodeList - matching elements that are children of element. */ public static NodeList getChildElementsByTagName(Element element, String tag) { NodeList found = element.getElementsByTagName(tag); NodeListImpl children = new NodeListImpl(); for (int i = 0; i < found.getLength(); i++) { Node node = found.item(i); Node parentNode = node.getParentNode(); if (parentNode.equals(element)) { children.add(node); } } return children; } /** * compare two nodes. Needed to write my own because of problems in the implementation * of the WST Range.compareBoundaryPoints(); * * @param node1 * @param offset1 * @param node2 * @param offset2 * @return less than zero if node1 < nodes2, 0 if node1 == node2, greater than zero if node1 > node2 */ public static int compareLocations(Node node1, int offset1, Node node2, int offset2) { if (node1 == node2) { return offset1 - offset2; } // need to find a common parent of the two edit parts, // then compare the respective ancestors of that // commons part. The ancestor that is first in the list // is considers less than the other. List parents1 = getParents(node1); List parents2 = getParents(node2); // insert the parts into the parents list parents1.add(0, node1); parents2.add(0, node2); // add effective nodes to the list if (node1.hasChildNodes()) { parents1.add(0, node1.getChildNodes().item(offset1)); } if (node2.hasChildNodes()) { parents2.add(0, node2.getChildNodes().item(offset2)); } Node commonParent = null; int cpIndex1 = 1; // index of common parent in parents1 int cpIndex2 = 0; // index of common parent in parents2 // find a common parent by searching parents2 for items // in parent1. // skip the first element, so we don't get // a part on the mark as the common parent. for (Iterator iter = parents1.listIterator(cpIndex1); iter.hasNext();) { Node node = (Node) iter.next(); int index = parents2.indexOf(node); if (index > 0) { // don't allow the part at the mark to be a parent commonParent = node; cpIndex2 = index; break; } cpIndex1++; } // this can happen when the document is in flux. if (commonParent == null) { return -1; } // get the direct childs of the common parent and // compare their position in the flow. NodeList cpChildren = commonParent.getChildNodes(); Node cpChild1 = (Node) parents1.get(cpIndex1 - 1); // child of common parent representing node1 Node cpChild2 = (Node) parents2.get(cpIndex2 - 1); // child of common parent representing node2 if (cpChild1 == cpChild2) { return offset1 - offset2; } else if (cpChild1 == null) { // cpChild1 is the last child return 1; } else if (cpChild2 == null) { // cpChild2 is the last child return -1; } int indexCpChild1 = getOffsetInParent(cpChild1); for (int i = (indexCpChild1 + 1); i < cpChildren.getLength(); i++) { if (cpChildren.item(i) == cpChild2) { return -1; // node1 comes before node2 (less than) } } // node2 not found in the above loop so it must have // come before node1 in the list. This means node1 is // greater than node2. return 1; } /** * * @param node * @return a list of parent nodes */ private static List getParents(Node node) { List list = new ArrayList(); node = node.getParentNode(); while (node != null) { list.add(node); node = node.getParentNode(); } return list; } /** * Convert the parent, offset into the child node. * Node without child are just returned. * * @param node * @param offset * @return Node */ public static Node getNode(Node node, int offset) { if (node.getNodeType() != Node.TEXT_NODE) { return node.getChildNodes().item(offset); } return node; } /** * Helper method to get the offset of a given node with respect to its * parent. * * @param node - * may not be null * @return int - zero-based offset of node in its container or -1 if the * node as no container. */ public static int getOffsetInParent(Node node) { Node parent = node.getParentNode(); if (parent != null) { NodeList children = parent.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = (Node) children.item(i); if (child == node) { return i; } } throw new IllegalStateException(); // how could the child not be // found? } return -1; } /** * Find all element of a given name that is a child or subchild to n levels of a given parent element. * If an a child element has the given tag name, none of its child nodes that may also have that tag * name will be added to the NodeList. So essentially only the first child elelent with the given name * will be returned from each branch. * For example a panel within a panel, only the parent panel would be returned. * @return the elements found, or null if none are found or if there is an error. */ public static NodeList getAllChildElementsByName( Element parent, String name ){ NodeListImpl result = new NodeListImpl(); NodeList l = parent.getChildNodes(); int count = l.getLength(); for( int i=0; i<count; i++ ) { Node n = l.item(i); if(n.getNodeType()==Node.ELEMENT_NODE) { Element e = (Element)n; String nodeName = e.getNodeName(); if( name.equals(nodeName) ) { result.add(e); } else{ NodeList nl = getAllChildElementsByName((Element)n, name); if(nl.getLength()>0){ result = concatNodeLists(result, nl); } } } } return result; } /** * This method will return a nodelist containing all the nodes from 2 given nodeLists. * @param nl1 * @param nl2 * @return */ public static NodeListImpl concatNodeLists(NodeList nl1, NodeList nl2){ NodeListImpl results = new NodeListImpl(); for(int i=0; i<nl1.getLength(); i++){ results.add(nl1.item(i)); } for(int i=0; i<nl2.getLength(); i++){ results.add(nl2.item(i)); } return results; } /** * Find the first element that is a child or subchild under a given Element parent. So essentially * returning the first occurance of a given node anywhere in the parent elements child subtree. * * @return the element found, or null if it is not available */ public static Element getFirstElementAtAnyDepthByNodeName( Element parent, String name ){ NodeList l = parent.getChildNodes(); int count = l.getLength(); for( int i=0; i<count; i++ ) { Node n = l.item(i); if(n.getNodeType()==Node.ELEMENT_NODE) { Element e = (Element)n; String nodeName = e.getNodeName(); if( name.equals(nodeName) ) { return e; } else{ Element returnElement = getFirstElementAtAnyDepthByNodeName(e,name); if(null != returnElement){ return returnElement; } } } } return null; } /** * Find all children and subChildren of the given parent to n sub levels. This will include text nodes! * * @return NodeList of all child elements of the parent */ public static NodeList getChildNodesToNLevels( Node parent){ NodeList l = parent.getChildNodes(); NodeListImpl returnList = new NodeListImpl(); int count = l.getLength(); for( int i=0; i<count; i++ ) { Node n = l.item(i); returnList.add(n); NodeList children = getChildNodesToNLevels(n); if(null != children){ returnList = concatNodeLists(returnList, children); } } return returnList; } /** * Search through all child nodes and sub child nodes of the given parent node, and return true, if the given child node is ever found. * * @return NodeList of all child elements of the parent */ public static boolean findChildAtAnyLevel( Node parent, Node child){ NodeList l = parent.getChildNodes(); boolean returnBoolean = false; int count = l.getLength(); for( int i=0; i<count; i++ ) { Node n = l.item(i); if(n == child){ returnBoolean = true; } else{ returnBoolean = returnBoolean || findChildAtAnyLevel(n, child); } if(returnBoolean){ return true; } } return returnBoolean; } }