org.opencms.util.ant.CmsXmlUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.util.ant.CmsXmlUtils.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.util.ant;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;

/**
 * Provides some basic XML handling utilities.<p>
 * 
 * @since 6.0.0 
 */
public final class CmsXmlUtils {

    /**
     * Prevents instances of this class from being generated.<p> 
     */
    private CmsXmlUtils() {

        // noop
    }

    /**
     * Translates a simple lookup path to the simplified Xpath format used for 
     * the internal bookmarks.<p>
     * 
     * Examples:<br> 
     * <code>title</code> becomes <code>title[1]</code><br>
     * <code>title[1]</code> is left untouched<br>
     * <code>title/subtitle</code> becomes <code>title[1]/subtitle[1]</code><br>
     * <code>title/subtitle[1]</code> becomes <code>title[1]/subtitle[1]</code><p>
     * 
     * Note: If the name already has the format <code>title[1]</code> then provided index parameter 
     * is ignored.<p> 
     * 
     * @param path the path to get the simplified Xpath for
     * @param index the index to append (if required)
     * 
     * @return the simplified Xpath for the given name
     */
    public static String createXpath(String path, int index) {

        if (path.indexOf('/') > -1) {
            // this is a complex path over more then 1 node
            StringBuffer result = new StringBuffer(path.length() + 32);

            // split the path into sub elements
            List<String> elements = CmsStringUtil.splitAsList(path, '/');
            int end = elements.size() - 1;
            for (int i = 0; i <= end; i++) {
                // append [i] to path element if required 
                result.append(createXpathElementCheck(elements.get(i), (i == end) ? index : 1));
                if (i < end) {
                    // append path delimiter if not final path element
                    result.append('/');
                }
            }
            return result.toString();
        }

        // this path has only 1 node, append [index] if required
        return createXpathElementCheck(path, index);
    }

    /**
     * Appends the provided index parameter in square brackets to the given name,
     * like <code>path[index]</code>.<p>
     * 
     * This method is used if it's clear that some path does not have 
     * a square bracket already appended.<p>
     * 
     * @param path the path append the index to
     * @param index the index to append
     * 
     * @return the simplified Xpath for the given name
     */
    public static String createXpathElement(String path, int index) {

        StringBuffer result = new StringBuffer(path.length() + 5);
        result.append(path);
        result.append('[');
        result.append(index);
        result.append(']');
        return result.toString();
    }

    /**
     * Ensures that a provided simplified Xpath has the format <code>title[1]</code>.<p>
     * 
     * This method is used if it's uncertain if some path does have 
     * a square bracket already appended or not.<p>
     * 
     * Note: If the name already has the format <code>title[1]</code>, then provided index parameter 
     * is ignored.<p> 
     * 
     * @param path the path to get the simplified Xpath for
     * @param index the index to append (if required)
     * 
     * @return the simplified Xpath for the given name
     */
    public static String createXpathElementCheck(String path, int index) {

        if (path.charAt(path.length() - 1) == ']') {
            // path is already in the form "title[1]"
            // ignore provided index and return the path "as is"
            return path;
        }

        // append index in square brackets
        return createXpathElement(path, index);
    }

    /**
     * Returns the first Xpath element from the provided path, 
     * without the index value.<p>
     * 
     * Examples:<br> 
     * <code>title</code> is left untouched<br>
     * <code>title[1]</code> becomes <code>title</code><br>
     * <code>title/subtitle</code> becomes <code>title</code><br>
     * <code>title[1]/subtitle[1]</code> becomes <code>title</code><p>
     * 
     * @param path the path to get the first Xpath element from
     * 
     * @return the first Xpath element from the provided path
     */
    public static String getFirstXpathElement(String path) {

        int pos = path.indexOf('/');
        if (pos >= 0) {
            path = path.substring(0, pos);
        }

        return CmsXmlUtils.removeXpathIndex(path);
    }

    /**
     * Returns the last Xpath element from the provided path, 
     * without the index value.<p>
     * 
     * Examples:<br> 
     * <code>title</code> is left untouched<br>
     * <code>title[1]</code> becomes <code>title</code><br>
     * <code>title/subtitle</code> becomes <code>subtitle</code><br>
     * <code>title[1]/subtitle[1]</code> becomes <code>subtitle</code><p>
     * 
     * @param path the path to get the last Xpath element from
     * 
     * @return the last Xpath element from the provided path
     */
    public static String getLastXpathElement(String path) {

        int pos = path.lastIndexOf('/');
        if (pos >= 0) {
            path = path.substring(pos + 1);
        }

        return CmsXmlUtils.removeXpathIndex(path);
    }

    /**
     * Returns the last Xpath index from the given path.<p>
     * 
     * Examples:<br> 
     * <code>title</code> returns the empty String<p>
     * <code>title[1]</code> returns <code>[1]</code><p>
     * <code>title/subtitle</code> returns them empty String<p>
     * <code>title[1]/subtitle[1]</code> returns <code>[1]</code><p>
     * 
     * @param path the path to extract the Xpath index from
     * 
     * @return  the last Xpath index from the given path
     */
    public static String getXpathIndex(String path) {

        int pos1 = path.lastIndexOf('/');
        int pos2 = path.lastIndexOf('[');
        if ((pos2 < 0) || (pos1 > pos2)) {
            return "";
        }

        return path.substring(pos2);
    }

    /**
     * Returns the last Xpath index from the given path as integer.<p>
     * 
     * Examples:<br> 
     * <code>title</code> returns 1<p>
     * <code>title[1]</code> returns 1<p>
     * <code>title/subtitle</code> returns 1<p>
     * <code>title[1]/subtitle[2]</code> returns 2<p>
     * 
     * @param path the path to extract the Xpath index from
     * 
     * @return the last Xpath index from the given path as integer
     */
    public static int getXpathIndexInt(String path) {

        int pos1 = path.lastIndexOf('/');
        int pos2 = path.lastIndexOf('[');
        if ((pos2 < 0) || (pos1 > pos2)) {
            return 1;
        }

        String idxStr = path.substring(pos2 + 1, path.lastIndexOf(']'));
        try {
            return Integer.parseInt(idxStr);
        } catch (NumberFormatException e) {
            // NOOP
        }
        return 1;
    }

    /**
     * Returns <code>true</code> if the given path is a Xpath with 
     * at last 2 elements.<p>
     * 
     * Examples:<br> 
     * <code>title</code> returns <code>false</code><br>
     * <code>title[1]</code> returns <code>false</code><br>
     * <code>title/subtitle</code> returns <code>true</code><br>
     * <code>title[1]/subtitle[1]</code> returns <code>true</code><p>
     * 
     * @param path the path to check
     * @return true if the given path is a Xpath with at last 2 elements
     */
    public static boolean isDeepXpath(String path) {

        return path.indexOf('/') > 0;
    }

    /**
     * Marshals (writes) an XML document into an output stream using XML pretty-print formatting.<p>
     * 
     * @param document the XML document to marshal
     * @param out the output stream to write to
     * @param encoding the encoding to use
     * @return the output stream with the xml content
     * @throws Exception if something goes wrong
     */
    public static OutputStream marshal(Document document, OutputStream out, String encoding) throws Exception {

        OutputFormat format = OutputFormat.createPrettyPrint();
        format.setEncoding(encoding);

        XMLWriter writer = new XMLWriter(out, format);
        writer.setEscapeText(false);

        writer.write(document);
        writer.close();

        return out;
    }

    /**
     * Marshals (writes) an XML document to a String using XML pretty-print formatting.<p>
     * 
     * @param document the XML document to marshal
     * @param encoding the encoding to use
     * @return the marshalled XML document
     * @throws Exception if something goes wrong
     */
    public static String marshal(Document document, String encoding) throws Exception {

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        marshal(document, out, encoding);
        return out.toString(encoding);
    }

    /**
     * Marshals (writes) an XML node into an output stream using XML pretty-print formatting.<p>
     * 
     * @param node the XML node to marshal
     * @param encoding the encoding to use
     * 
     * @return the string with the xml content
     * 
     * @throws Exception if something goes wrong
     */
    public static String marshal(Node node, String encoding) throws Exception {

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        OutputFormat format = OutputFormat.createPrettyPrint();
        format.setEncoding(encoding);
        format.setSuppressDeclaration(true);

        XMLWriter writer = new XMLWriter(out, format);
        writer.setEscapeText(false);

        writer.write(node);
        writer.close();
        return new String(out.toByteArray());
    }

    /**
     * Removes the first Xpath element from the path.<p>
     * 
     * If the provided path does not contain a "/" character, 
     * it is returned unchanged.<p>
     * 
     * <p>Examples:<br> 
     * <code>title</code> is left untouched<br>
     * <code>title[1]</code> is left untouched<br>
     * <code>title/subtitle</code> becomes <code>subtitle</code><br>
     * <code>title[1]/subtitle[1]</code> becomes <code>subtitle[1]</code><p>
     * 
     * @param path the Xpath to remove the first element from
     * 
     * @return the path with the first element removed
     */
    public static String removeFirstXpathElement(String path) {

        int pos = path.indexOf('/');
        if (pos < 0) {
            return path;
        }

        return path.substring(pos + 1);
    }

    /**
     * Removes the last complex Xpath element from the path.<p>
     * 
     * The same as {@link #removeLastXpathElement(String)} both it works with more complex xpaths.
     * 
     * <p>Example:<br> 
     * <code>system/backup[@date='23/10/2003']/resource[path='/a/b/c']</code> becomes <code>system/backup[@date='23/10/2003']</code><p>
     * 
     * @param path the Xpath to remove the last element from
     * 
     * @return the path with the last element removed
     */
    public static String removeLastComplexXpathElement(String path) {

        int pos = path.lastIndexOf('/');
        if (pos < 0) {
            return path;
        }
        // count ' chars
        int p = pos;
        int count = -1;
        while (p > 0) {
            count++;
            p = path.indexOf("\'", p + 1);
        }
        String parentPath = path.substring(0, pos);
        if (count % 2 == 0) {
            // if substring is complete 
            return parentPath;
        }
        // if not complete
        p = parentPath.lastIndexOf("'");
        if (p >= 0) {
            // complete it if possible
            return removeLastComplexXpathElement(parentPath.substring(0, p));
        }
        return parentPath;
    }

    /**
     * Removes the last Xpath element from the path.<p>
     * 
     * If the provided path does not contain a "/" character, 
     * it is returned unchanged.<p>
     * 
     * <p>Examples:<br> 
     * <code>title</code> is left untouched<br>
     * <code>title[1]</code> is left untouched<br>
     * <code>title/subtitle</code> becomes <code>title</code><br>
     * <code>title[1]/subtitle[1]</code> becomes <code>title[1]</code><p>
     * 
     * @param path the Xpath to remove the last element from
     * 
     * @return the path with the last element removed
     */
    public static String removeLastXpathElement(String path) {

        int pos = path.lastIndexOf('/');
        if (pos < 0) {
            return path;
        }

        return path.substring(0, pos);
    }

    /**
     * Removes all Xpath index information from the given input path.<p>
     * 
     * Examples:<br> 
     * <code>title</code> is left untouched<br>
     * <code>title[1]</code> becomes <code>title</code><br>
     * <code>title/subtitle</code> is left untouched<br>
     * <code>title[1]/subtitle[1]</code> becomes <code>title/subtitle</code><p>
     * 
     * @param path the path to remove the Xpath index information from
     * 
     * @return the simplified Xpath for the given name
     */
    public static String removeXpath(String path) {

        if (path.indexOf('/') > -1) {
            // this is a complex path over more then 1 node
            StringBuffer result = new StringBuffer(path.length() + 32);

            // split the path into sub-elements
            List<String> elements = CmsStringUtil.splitAsList(path, '/');
            int end = elements.size() - 1;
            for (int i = 0; i <= end; i++) {
                // remove [i] from path element if required 
                result.append(removeXpathIndex(elements.get(i)));
                if (i < end) {
                    // append path delimiter if not final path element
                    result.append('/');
                }
            }
            return result.toString();
        }

        // this path has only 1 node, remove last index if required
        return removeXpathIndex(path);
    }

    /**
     * Removes the last Xpath index from the given path.<p>
     * 
     * Examples:<br> 
     * <code>title</code> is left untouched<br>
     * <code>title[1]</code> becomes <code>title</code><br>
     * <code>title/subtitle</code> is left untouched<br>
     * <code>title[1]/subtitle[1]</code> becomes <code>title[1]/subtitle</code><p>
     * 
     * @param path the path to remove the Xpath index from
     * 
     * @return the path with the last Xpath index removed
     */
    public static String removeXpathIndex(String path) {

        int pos1 = path.lastIndexOf('/');
        int pos2 = path.lastIndexOf('[');
        if ((pos2 < 0) || (pos1 > pos2)) {
            return path;
        }

        return path.substring(0, pos2);
    }

    /**
     * Simplifies an Xpath by removing a leading and a trailing slash from the given path.<p> 
     * 
     * Examples:<br> 
     * <code>title/</code> becomes <code>title</code><br>
     * <code>/title[1]/</code> becomes <code>title[1]</code><br>
     * <code>/title/subtitle/</code> becomes <code>title/subtitle</code><br>
     * <code>/title/subtitle[1]/</code> becomes <code>title/subtitle[1]</code><p>
     * 
     * @param path the path to process
     * @return the input with a leading and a trailing slash removed
     */
    public static String simplifyXpath(String path) {

        StringBuffer result = new StringBuffer(path);
        if (result.charAt(0) == '/') {
            result.deleteCharAt(0);
        }
        int pos = result.length() - 1;
        if (result.charAt(pos) == '/') {
            result.deleteCharAt(pos);
        }
        return result.toString();
    }

    /**
     * Helper to unmarshal (read) xml contents from a byte array into a document.<p>
     * 
     * Using this method ensures that the OpenCms XML entity resolver is used.<p>
     * 
     * @param xmlData the XML data in a byte array
     * @param resolver the XML entity resolver to use
     * 
     * @return the base object initialized with the unmarshalled XML document
     * 
     * @throws Exception if something goes wrong
     * 
     * @see CmsXmlUtils#unmarshalHelper(InputSource, EntityResolver)
     */
    public static Document unmarshalHelper(byte[] xmlData, EntityResolver resolver) throws Exception {

        return CmsXmlUtils.unmarshalHelper(new InputSource(new ByteArrayInputStream(xmlData)), resolver);
    }

    /**
     * Helper to unmarshal (read) xml contents from a byte array into a document.<p>
     * 
     * Using this method ensures that the OpenCms XML entity resolver is used.<p>
     * 
     * @param xmlData the XML data in a byte array
     * @param resolver the XML entity resolver to use
     * @param validate if the reader should try to validate the xml code
     * 
     * @return the base object initialized with the unmarshalled XML document
     * 
     * @throws Exception if something goes wrong
     * 
     * @see CmsXmlUtils#unmarshalHelper(InputSource, EntityResolver)
     */
    public static Document unmarshalHelper(byte[] xmlData, EntityResolver resolver, boolean validate)
            throws Exception {

        return CmsXmlUtils.unmarshalHelper(new InputSource(new ByteArrayInputStream(xmlData)), resolver, validate);
    }

    /**
     * Helper to unmarshal (read) xml contents from an input source into a document.<p>
     * 
     * Using this method ensures that the OpenCms XML entity resolver is used.<p>
     * 
     * Important: The encoding provided will NOT be used during unmarshalling,
     * the XML parser will do this on the base of the information in the source String.
     * The encoding is used for initializing the created instance of the document,
     * which means it will be used when marshalling the document again later.<p>
     *  
     * @param source the XML input source to use
     * @param resolver the XML entity resolver to use
     * 
     * @return the unmarshalled XML document
     * 
     * @throws Exception if something goes wrong
     */
    public static Document unmarshalHelper(InputSource source, EntityResolver resolver) throws Exception {

        return unmarshalHelper(source, resolver, false);
    }

    /**
     * Helper to unmarshal (read) xml contents from an input source into a document.<p>
     * 
     * Using this method ensures that the OpenCms XML entity resolver is used.<p>
     * 
     * Important: The encoding provided will NOT be used during unmarshalling,
     * the XML parser will do this on the base of the information in the source String.
     * The encoding is used for initializing the created instance of the document,
     * which means it will be used when marshalling the document again later.<p>
     *  
     * @param source the XML input source to use
     * @param resolver the XML entity resolver to use
     * @param validate if the reader should try to validate the xml code
     * 
     * @return the unmarshalled XML document
     * 
     * @throws Exception if something goes wrong
     */
    public static Document unmarshalHelper(InputSource source, EntityResolver resolver, boolean validate)
            throws Exception {

        SAXReader reader = new SAXReader();
        if (resolver != null) {
            reader.setEntityResolver(resolver);
        }
        reader.setMergeAdjacentText(true);
        reader.setStripWhitespaceText(true);
        if (!validate) {
            reader.setValidation(false);
            reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        }
        return reader.read(source);
    }

    /**
     * Helper to unmarshal (read) xml contents from a String into a document.<p>
     * 
     * Using this method ensures that the OpenCms XML entitiy resolver is used.<p>
     * 
     * @param xmlData the xml data in a String 
     * @param resolver the XML entity resolver to use
     * @return the base object initialized with the unmarshalled XML document
     * @throws Exception if something goes wrong
     * @see CmsXmlUtils#unmarshalHelper(InputSource, EntityResolver)
     */
    public static Document unmarshalHelper(String xmlData, EntityResolver resolver) throws Exception {

        return CmsXmlUtils.unmarshalHelper(new InputSource(new StringReader(xmlData)), resolver);
    }
}