All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.xolite.dom.DomUtils Maven / Gradle / Ivy

Go to download

This project provides a lightweight framework to serialize/deserialize (or marshall/unmarshall) java objects into XML. The implementation is based on standard SAX (Simple Api for Xml) but it follows a original approach based on the strong data encapsulation paradigm of Object Oriented (OO) programming.

The newest version!
/*-------------------------------------------------------------------------
 Copyright 2006 Olivier Berlanger

 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 net.sf.xolite.dom;


import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import net.sf.xolite.XMLSerializable;
import net.sf.xolite.XMLSerializeException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
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;


/**
 * Utilities for DOM.
 */
public abstract class DomUtils {


    private static Log log = LogFactory.getLog(DomUtils.class);


    /** Flag for comparison: ignore case for string values. */
    public static final int IGNORE_CASE = 1;
    /** Flag for comparison: trim string values. */
    public static final int TRIM_STRING_VALUES = 2;
    /** Flag for comparison: normalize whitespaces in string values . */
    public static final int NORMALIZE_STRING_VALUES = 4 + TRIM_STRING_VALUES;
    /** Flag for comparison: ignore order of elements in sequences. */
    public static final int IGNORE_SEQUENCE_ORDER = 8;
    /** Flag for comparison: empty elements (like <el/> or <el></el>) are equivalent to no-existent elements. */
    public static final int EMPTY_EQUALS_NULL = 16;


    private DomUtils() {
    }


    /**
     * Get the list of direct child of an element matching the given namespace+tag.
     * 
     * @param parent
     *            parent node when the child are searched.
     * @param namespaceURI
     *            requested element namespace URI.
     * @param localName
     *            requested element local name (tag).
     * @return List a list with all the direct children matching the given namespace+localName.
     */
    public static List getDirectChildElementsByTagNameNS(Element parent, String namespaceURI, String localName) {
        List matchedChilds = null;
        NodeList allChilds = parent.getChildNodes();
        int len = (allChilds == null) ? 0 : allChilds.getLength();
        Node nod;
        for (int i = 0; i < len; i++) {
            nod = allChilds.item(i);
            if (matchElement(nod, namespaceURI, localName)) {
                if (matchedChilds == null) matchedChilds = new ArrayList();
                matchedChilds.add(nod);
            }
        }
        return matchedChilds;
    }


    /**
     * Get the direct child of an element matching the given namespace+tag+index.
     * 
     * @param parent
     *            parent node when the child are searched.
     * @param namespaceURI
     *            requested element namespace URI.
     * @param localName
     *            requested element local name (tag).
     * @param index
     *            the child element index (among the elements matching the namespace+localName). Note: Unlike XPath standard,
     *            this index is zero-based.
     * @return List a list with all the direct childs matching the given namespace+localName.
     */
    public static Element getDirectChild(Element parent, String namespaceURI, String localName, int index) {
        Element matchedElement = null;
        int matchedCount = 0;
        NodeList allChilds = parent.getChildNodes();
        int len = (allChilds == null) ? 0 : allChilds.getLength();
        Node nod;
        for (int i = 0; (i < len) && (matchedElement == null); i++) {
            nod = allChilds.item(i);
            if (matchElement(nod, namespaceURI, localName)) {
                if ((index >= 0) && (matchedCount < index)) matchedCount++;
                else matchedElement = (Element) nod;
            }
        }
        return matchedElement;
    }


    /**
     * Get the prefix already defined in the DOM at the node location for the given namespace URI. Unlike the DOM implementation
     * this method returns "" to denote that the default prefix is used (with a xmlns="...." declaration). This method returns
     * null when no prefix is found in the given context for the URI.
     * 
     * @param startNode
     *            the context node where we search for prefix.
     * @param uri
     *            the namespace URI to search.
     * @return the prefix already used to define the URI or null if the URI is not yet defined in this context.
     */
    public static String getDefinedPerfix(Node startNode, String uri) {
        if (uri == null) return null;
        Node nod = startNode;
        while (nod != null) {
            if (nod.getNodeType() == Node.ELEMENT_NODE) {
                NamedNodeMap nnm = nod.getAttributes();
                int len = nnm.getLength();
                for (int i = 0; i < len; i++) {
                    Node attr = nnm.item(i);
                    String attrName = attr.getNodeName();
                    if (attrName.startsWith("xmlns")) {
                        if (attrName.length() == 5) {
                            if (uri.equals(attr.getNodeValue())) return "";
                        } else if (attrName.charAt(6) == ':') {
                            if (uri.equals(attr.getNodeValue())) return attrName.substring(6);
                        }
                    }
                }

            }
            nod = nod.getParentNode();
        }
        return null;
    }


    /**
     * Get the list of prefixs already defined in the DOM at the node location for the given namespace URI. Unlike the DOM
     * implementation this method returns "" to denote that the default prefix is used (with a xmlns="...." declaration). This
     * method returns an empty list when no prefix is found in the given context for the URI.
     * 
     * @param startNode
     *            the context node where we search for prefix.
     * @param uri
     *            the namespace URI to search.
     * @return the list of all prefix already used to define the URI or empty list if the URI is not yet defined in this
     *         context.
     */
    public static List getDefinedPerfixes(Node startNode, String uri) {
        List result = new ArrayList();
        if (uri != null) {
            Node nod = startNode;
            while (nod != null) {
                if (nod.getNodeType() == Node.ELEMENT_NODE) {
                    NamedNodeMap nnm = nod.getAttributes();
                    int len = nnm.getLength();
                    for (int i = 0; i < len; i++) {
                        Node attr = nnm.item(i);
                        String attrName = attr.getNodeName();
                        if (attrName.startsWith("xmlns")) {
                            if (attrName.length() == 5) {
                                if (uri.equals(attr.getNodeValue())) {
                                    if (!result.contains("")) result.add("");
                                }
                            } else if (attrName.charAt(6) == ':') {
                                if (uri.equals(attr.getNodeValue())) {
                                    String prefix = attrName.substring(6);
                                    if (!result.contains(prefix)) result.add(prefix);
                                }
                            }
                        }
                    }

                }
                nod = nod.getParentNode();
            }
        }
        return result;
    }


    /**
     * Get the URI already defined in the DOM and associated to the given prefix.
     * 
     * @param startNode
     *            the context node where we search for prefix.
     * @param prefix
     *            the prefix to search.
     * @return the URI already defined in the DOM or null if no URI is mapped to the prefix in this context.
     */
    public static String getDefinedUri(Node startNode, String prefix) {
        if (prefix == null) throw new IllegalArgumentException("Prefix cannot be null");
        String attrName = (prefix.equals("")) ? "xmlns" : "xmlns:" + prefix;
        Node nod = startNode;
        while (nod != null) {
            if (prefix.equals(nod.getPrefix())) return nod.getNamespaceURI();
            if (nod.getNodeType() == Node.ELEMENT_NODE) {
                String uri = ((Element) nod).getAttribute(attrName);
                if ((uri != null) && !uri.equals("")) return uri;
            }
            nod = nod.getParentNode();
        }
        return null;
    }


    /**
     * Check if the given node is an element matching the given namespace+tag.
     * 
     * @param nod
     *            the node
     * @param uri
     *            namespace for the element
     * @param tag
     *            local name for the element
     * @return boolean true iff the given element match the given namespace+tag.
     */
    public static boolean matchElement(Node nod, String uri, String tag) {
        if (nod.getNodeType() != Node.ELEMENT_NODE) return false;
        if (!tag.equals(nod.getLocalName())) return false;
        String elUri = nod.getNamespaceURI();
        if ((uri == null) || uri.equals("")) return (elUri == null) || elUri.equals("");
        return uri.equals(elUri);
    }


    /**
     * Get the text value of an element or attribute node. The value of all the text nodes of an element are concatenated.
     * 
     * @param nod
     *            The element or attribute node.
     * @return String it's text value.
     */
    public static String getTextValue(Node nod) {
        String text = null;
        if (nod != null) {
            if (nod.getNodeType() == Node.ELEMENT_NODE) {
                NodeList lst = nod.getChildNodes();
                int len = lst.getLength();
                Node child;
                for (int i = 0; i < len; i++) {
                    child = lst.item(i);
                    if (child.getNodeType() == Node.TEXT_NODE) {
                        if (text == null) text = ((Text) child).getData();
                        else text += ((Text) child).getData();
                    }
                }
            } else if (nod.getNodeType() == Node.ATTRIBUTE_NODE) {
                Attr attrNode = (Attr) nod;
                text = attrNode.getValue();
            } else {
                throw new IllegalArgumentException("GetTextValue only works for element and attribute nodes, not for: " + nod);
            }
        }
        return text;
    }


    /**
     * Set the text value of an element or attribute node. For elements, the value of the first the text node is replaced and
     * any other text node are removed. If the element did not contain text node previously, a new text node is added with given
     * text value.
     * 
     * @param nod
     *            The element or attribute node.
     * @param text
     *            it's new text value.
     */
    public static void setTextValue(Node nod, String text) {
        if (nod == null) throw new NullPointerException("Cannot set text value on a null node");
        if (nod.getNodeType() == Node.ELEMENT_NODE) {
            NodeList lst = nod.getChildNodes();
            int len = lst.getLength();
            Node child;
            boolean textSet = false;
            for (int i = 0; i < len; i++) {
                child = lst.item(i);
                if (child.getNodeType() == Node.TEXT_NODE) {
                    if (textSet) {
                        nod.removeChild(child);
                    } else {
                        ((Text) child).setData(text);
                        textSet = true;
                    }
                }
            }
            if (!textSet) {
                // append a new text node if the element did not contain any text nodes.
                Text newText = nod.getOwnerDocument().createTextNode(text);
                nod.appendChild(newText);
            }
        } else if (nod.getNodeType() == Node.ELEMENT_NODE) {
            Attr attrNode = (Attr) nod;
            attrNode.setValue(text);
        } else {
            throw new IllegalArgumentException("GetTextValue only works for element and attribute nodes, not for: " + nod);
        }
    }


    /**
     * Check that result of the given XMLSerializable serialisation is the same XML as the given inputStream content. 
* Both are transformed to DOM documents and then compared, this way ignorable whitespaces, prefixes, attribute order, ... * are ignored. * * @param src * a XML serializable object * @param is * a stream containing the expected serialisation result. * @return true if the serialisation match the expected result. */ public static boolean checkObjectSerialisation(XMLSerializable src, InputStream is) { if (log.isDebugEnabled()) log.debug("------------ Check serialisation of " + src); boolean result = checkObjectSerialisation(src, is, TRIM_STRING_VALUES); if (log.isDebugEnabled()) log.debug(" --> " + result); return result; } public static boolean checkObjectSerialisation(XMLSerializable src, InputStream is, int flags) { try { DomXMLSerializer serializer = new DomXMLSerializer(); Document doc1 = serializer.serializeToDOM(src); Document doc2 = streamToDom(is); return documentsAreEquals(doc1, doc2, flags); } catch (XMLSerializeException e) { throw new RuntimeException("Failed to serialize object " + src); } } public static boolean documentsAreEquals(Document doc1, Document doc2) { if (log.isDebugEnabled()) log.debug("------------ Compare DOM documents"); boolean result = documentsAreEquals(doc1, doc2, TRIM_STRING_VALUES); if (log.isDebugEnabled()) log.debug(" --> " + result); return result; } public static boolean documentsAreEquals(Document doc1, Document doc2, int flags) { if (doc1 == null) return (doc2 == null); if (doc2 == null) return false; return elementsAreEquals(doc1.getDocumentElement(), doc2.getDocumentElement(), flags); } public static boolean elementsAreEquals(Element elem1, Element elem2, int flags) { // compare namespace + tag if (elem1 == null) { if (elem2 == null) return true; if (log.isDebugEnabled()) log.debug("Compare elements = [" + null + "] to <" + elem2.getLocalName() + ">"); return false; } if (elem2 == null) { if (log.isDebugEnabled()) log.debug("Compare elements = <" + elem1.getLocalName() + "> to [" + null + "]"); return false; } if (log.isDebugEnabled()) log.debug("Compare elements = <" + elem1.getLocalName() + "> to <" + elem2.getLocalName() + ">"); if (!stringAreEquals(elem1.getNamespaceURI(), elem2.getNamespaceURI(), 0)) { if (log.isDebugEnabled()) log.debug("Namespaces are diffrent: " + elem1.getNamespaceURI() + " != " + elem2.getNamespaceURI()); return false; } if (!elem1.getLocalName().equals(elem2.getLocalName())) { if (log.isDebugEnabled()) log.debug("Local names are diffrent: " + elem1.getLocalName() + " != " + elem2.getLocalName()); return false; } // compare attributes NamedNodeMap attrs1 = elem1.getAttributes(); NamedNodeMap attrs2 = elem2.getAttributes(); if (!attributeMapsAreEquals(attrs1, attrs2, flags)) return false; if (!attributeMapsAreEquals(attrs2, attrs1, flags)) return false; // compare children List children1 = getChildElementNodes(elem1, flags); List children2 = getChildElementNodes(elem2, flags); int nbrChild = children1.size(); if (nbrChild != children2.size()) { if (log.isDebugEnabled()) log.debug("Child count of element <" + elem1.getLocalName() + "> differs: " + nbrChild + " != " + children2.size()); return false; } if (nbrChild == 0) { if (!stringAreEquals(getTextValue(elem1), getTextValue(elem2), flags)) return false; } else { if ((flags & IGNORE_SEQUENCE_ORDER) == 0) { // sub elements must be in the same sequence order for (int i = 0; i < nbrChild; i++) { if (!elementsAreEquals(children1.get(i), children2.get(i), flags)) return false; } } else { // sub-elements can be in different orders boolean found; Element child1; for (int i = 0; i < nbrChild; i++) { child1 = children1.get(i); found = false; for (int j = 0; j < (nbrChild - i); j++) { if (elementsAreEquals(child1, children2.get(j), flags)) { found = true; children2.remove(j); break; } } if (!found) return false; } } } return true; } /** * An empty element means an element without attributes and without text or with white text. */ public static boolean isEmpty(Element el) { NamedNodeMap attrs = el.getAttributes(); if (attrs.getLength() > 0) return false; List subElements = getChildElementNodes(el, 0); if (subElements.size() > 0) return false; String text = getTextValue(el); return isEmpty(text); } // ------------------ DOM to string ----------------------------------------------- /** * Serialize a DOM document to a String with multiple lines and 4 indentation spaces. Note: Don't support mixed content. */ public static String domToString(Document doc) { return domToString(doc, 4, true); } /** * Serialize a DOM document to a String. Note: Don't support mixed content. */ public static String domToString(Document doc, int nbrIndentationSpace, boolean multiLine) { StringWriter wr = new StringWriter(); try { wr.write(""); if (multiLine) wr.write("\n"); serializeElement(doc.getDocumentElement(), wr, 0, nbrIndentationSpace, multiLine); wr.close(); } catch (IOException ioe) { throw new IllegalStateException("domToString failed: " + ioe); } return wr.toString(); } /** * Serialize a DOM document to a String with multiple lines and 4 indentation spaces. Note: Don't support mixed content. */ public static String elementToString(Element elem) { return elementToString(elem, 4, true); } /** * Serialize a DOM element (and all it's sub-elements) to a String. Note: Don't support mixed content. */ public static String elementToString(Element elem, int nbrIndentationSpace, boolean multiLine) { StringWriter wr = new StringWriter(); try { serializeElement(elem, wr, 0, nbrIndentationSpace, multiLine); wr.close(); } catch (IOException ioe) { throw new IllegalStateException("elementToString failed: " + ioe); } return wr.toString(); } public static Document stringToDom(String xml) { InputStream is; String encoding = "???"; try { encoding = getEncoding(xml); is = new ByteArrayInputStream(xml.getBytes(encoding)); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("Unsupported encoding defined in XML header: " + encoding, e); } return streamToDom(is); } /** * Get the encoding declared in the given XML or "UTF-8" by default.
*/ public static String getEncoding(String xml) { String encoding = "UTF-8"; if (xml != null) { if (xml.startsWith(""); if (headerEnd > 10) { String header = xml.substring(0, headerEnd); int len = header.length(); int encodingStart = header.indexOf("encoding"); if (encodingStart > 0) { int i = encodingStart + "encoding".length(); while ((header.charAt(i) != '\'') && (header.charAt(i) != '"') && (i < len)) { i++; } if (i < len) { char quote = header.charAt(i); int start = i + 1; int end = 0; for (i = start; i < len; i++) { if (header.charAt(i) == quote) { end = i; break; } } if (end > 0) encoding = header.substring(start, end); } } } } } return encoding; } public static Document resourceToDom(Class resolver, String resourcePath) { InputStream is = resolver.getResourceAsStream(resourcePath); if (is == null) throw new IllegalArgumentException("Ressource not found '" + resourcePath + "' relative to " + resolver); return streamToDom(is); } public static Document streamToDom(InputStream is) { Document doc = null; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); if (is == null) throw new NullPointerException("Input stream is null"); doc = builder.parse(is); } catch (Exception e) { throw new RuntimeException("parsing to Dom failed", e); } return doc; } // ------------------ Implementation ----------------------------------------------- /** * Get al the direct-child elements as a list. */ private static List getChildElementNodes(Element parent, int flags) { List children = new ArrayList(); NodeList nodes = parent.getChildNodes(); Node nod; int len = nodes.getLength(); boolean keepEmptyElements = (flags & EMPTY_EQUALS_NULL) == 0; for (int i = 0; i < len; i++) { nod = nodes.item(i); if (nod instanceof Element) { if (keepEmptyElements || !isEmpty((Element) nod)) { children.add((Element) nod); } } } return children; } private static void serializeElement(Element el, Writer wr, int indentation, int nbrIndentationSpace, boolean multiLine) throws IOException { int len; int indentCount = indentation * nbrIndentationSpace; for (int j = 0; j < indentCount; j++) wr.write(' '); wr.write("<"); wr.write(el.getNodeName()); // write attributes NamedNodeMap allAttributes = el.getAttributes(); int attrCount = allAttributes.getLength(); Attr attribute; for (int i = 0; i < attrCount; i++) { attribute = (Attr) allAttributes.item(i); wr.write(" "); wr.write(attribute.getName()); wr.write("=\""); writeEscaped(attribute.getValue(), wr); wr.write("\""); } // write sub-elements or text if (isComposite(el)) { NodeList childs = el.getChildNodes(); len = childs.getLength(); wr.write(">"); if (multiLine) wr.write("\n"); int newIndentation = indentation + 1; Node child; for (int i = 0; i < len; i++) { child = childs.item(i); if (child instanceof Element) { serializeElement((Element) child, wr, newIndentation, nbrIndentationSpace, multiLine); } } for (int j = 0; j < indentCount; j++) wr.write(' '); wr.write(""); if (multiLine) wr.write("\n"); } else { String text = getTextValue(el); if ((text == null) || (text.trim().equals(""))) { wr.write("/>"); if (multiLine) wr.write("\n"); } else { wr.write(">"); writeEscaped(text, wr); wr.write(""); if (multiLine) wr.write("\n"); } } } private static boolean isComposite(Element el) { NodeList lst = el.getChildNodes(); int len = lst.getLength(); for (int i = 0; i < len; i++) { if (lst.item(i).getNodeType() == Node.ELEMENT_NODE) return true; } return false; } private static void writeEscaped(String value, Writer wr) throws IOException { int len = (value == null) ? 0 : value.length(); char ch; for (int i = 0; i < len; i++) { ch = value.charAt(i); switch (ch) { case '&': wr.write("&"); break; case '<': wr.write("<"); break; case '>': wr.write(">"); break; case '"': wr.write("""); break; case '\'': wr.write("'"); break; default: wr.write(ch); } } } private static boolean attributeMapsAreEquals(NamedNodeMap attrMap1, NamedNodeMap attrMap2, int flags) { if (attrMap1 == null) return (attrMap2 == null); if (attrMap2 == null) return false; int len = attrMap1.getLength(); Attr attr1; Attr attr2; String attrName; for (int i = 0; i < len; i++) { attr1 = (Attr) attrMap1.item(i); attrName = attr1.getName(); if ((!attrName.equals("xmlns")) && (!attrName.startsWith("xmlns:"))) { attr2 = (Attr) attrMap2.getNamedItem(attrName); if (attr2 == null) { log.debug("attribute " + attrName + " not found in other element"); return false; } if (!stringAreEquals(attr1.getValue(), attr2.getValue(), flags)) { log.debug("attribute " + attrName + "='" + attr1.getValue() + "' has value '" + attr2.getValue() + "' in other element"); return false; } } } return true; } private static boolean stringAreEquals(String str1, String str2, int flags) { if (isEmpty(str1)) return isEmpty(str2); if (isEmpty(str2)) return false; if ((flags & TRIM_STRING_VALUES) != 0) { str1 = str1.trim(); str2 = str2.trim(); } if ((flags & NORMALIZE_STRING_VALUES) != 0) { str1 = str1.replaceAll("\\s+", " "); str2 = str2.replaceAll("\\s+", " "); } boolean equals = ((flags & IGNORE_CASE) != 0) ? str1.equalsIgnoreCase(str2) : str1.equals(str2); return equals; } private static boolean isEmpty(String str) { if (str == null) return true; return str.trim().equals(""); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy