org.pentaho.di.core.xml.XMLHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.di.core.xml.XMLHandler.java

Source

/*! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com
 *
 *******************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ******************************************************************************/

package org.pentaho.di.core.xml;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.vfs.FileObject;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.Encoder;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.KettleAttributeInterface;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleXMLException;
import org.pentaho.di.core.row.ValueMeta;
import org.pentaho.di.core.vfs.KettleVFS;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;

/**
 * This class contains a number of (static final) methods to facilitate the retrieval of information from XML Node(s).
 *
 * @author Matt
 * @since 04-04-2003
 *
 */
public class XMLHandler {
    private static XMLHandlerCache cache = XMLHandlerCache.getInstance();
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(ValueMeta.DEFAULT_DATE_FORMAT_MASK);

    /**
     * The header string to specify encoding in UTF-8 for XML files
     *
     * @return The XML header.
     */
    public static final String getXMLHeader() {
        return getXMLHeader(Const.XML_ENCODING);
    }

    /**
     * The header string to specify encoding in an XML file
     *
     * @param encoding
     *          The desired encoding to use in the XML file
     * @return The XML header.
     */
    public static final String getXMLHeader(String encoding) {
        return "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>" + Const.CR;
    }

    /**
     * Get the value of a tag in a node
     *
     * @param n
     *          The node to look in
     * @param tag
     *          The tag to look for
     * @return The value of the tag or null if nothing was found.
     */
    public static final String getTagValue(Node n, KettleAttributeInterface code) {
        return getTagValue(n, code.getXmlCode());
    }

    /**
     * Get the value of a tag in a node
     *
     * @param n
     *          The node to look in
     * @param tag
     *          The tag to look for
     * @return The value of the tag or null if nothing was found.
     */
    public static final String getTagValue(Node n, String tag) {
        NodeList children;
        Node childnode;

        if (n == null) {
            return null;
        }

        children = n.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            childnode = children.item(i);
            if (childnode.getNodeName().equalsIgnoreCase(tag)) {
                if (childnode.getFirstChild() != null) {
                    return childnode.getFirstChild().getNodeValue();
                }
            }
        }
        return null;
    }

    /**
     * Get the value of a tag in a node
     *
     * @param n
     *          The node to look in
     * @param tag
     *          The tag to look for
     * @return The value of the tag or null if nothing was found.
     */
    public static final String getTagValueWithAttribute(Node n, String tag, String attribute) {
        NodeList children;
        Node childnode;

        if (n == null) {
            return null;
        }

        children = n.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            childnode = children.item(i);
            if (childnode.getNodeName().equalsIgnoreCase(tag)
                    && childnode.getAttributes().getNamedItem(attribute) != null) {
                if (childnode.getFirstChild() != null) {
                    return childnode.getFirstChild().getNodeValue();
                }
            }
        }
        return null;
    }

    /**
     * Search a node for a certain tag, in that subnode search for a certain subtag. Return the value of that subtag.
     *
     * @param n
     *          The node to look in
     * @param tag
     *          The tag to look for
     * @param subtag
     *          The subtag to look for
     * @return The string of the subtag or null if nothing was found.
     */
    public static final String getTagValue(Node n, String tag, String subtag) {
        NodeList children, tags;
        Node childnode, tagnode;

        if (n == null) {
            return null;
        }

        children = n.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            childnode = children.item(i);
            if (childnode.getNodeName().equalsIgnoreCase(tag)) {
                // <file>
                tags = childnode.getChildNodes();
                for (int j = 0; j < tags.getLength(); j++) {
                    tagnode = tags.item(j);
                    if (tagnode.getNodeName().equalsIgnoreCase(subtag)) {
                        if (tagnode.getFirstChild() != null) {
                            return tagnode.getFirstChild().getNodeValue();
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * Count nodes with a certain tag
     *
     * @param n
     *          The node to look in
     * @param tag
     *          The tags to count
     * @return The number of nodes found with a certain tag
     */
    public static final int countNodes(Node n, String tag) {
        NodeList children;
        Node childnode;

        int count = 0;

        if (n == null) {
            return 0;
        }

        children = n.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            childnode = children.item(i);
            if (childnode.getNodeName().equalsIgnoreCase(tag)) {
                // <file>
                count++;
            }
        }
        return count;
    }

    /**
     * Get nodes with a certain tag one level down
     *
     * @param n
     *          The node to look in
     * @param tag
     *          The tags to count
     * @return The list of nodes found with the specified tag
     */
    public static final List<Node> getNodes(Node n, String tag) {
        NodeList children;
        Node childnode;

        List<Node> nodes = new ArrayList<Node>();

        if (n == null) {
            return nodes;
        }

        children = n.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            childnode = children.item(i);
            if (childnode.getNodeName().equalsIgnoreCase(tag)) {
                // <file>
                nodes.add(childnode);
            }
        }
        return nodes;
    }

    /**
     * Get node child with a certain subtag set to a certain value
     *
     * @param n
     *          The node to search in
     * @param tag
     *          The tag to look for
     * @param subtag
     *          The subtag to look for
     * @param subtagvalue
     *          The value the subtag should have
     * @param nr
     *          The nr of occurance of the value
     * @return The node found or null if we couldn't find anything.
     */
    public static final Node getNodeWithTagValue(Node n, String tag, String subtag, String subtagvalue, int nr) {
        NodeList children;
        Node childnode, tagnode;
        String value;

        int count = 0;

        children = n.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            childnode = children.item(i);
            if (childnode.getNodeName().equalsIgnoreCase(tag)) {
                // <hop>
                tagnode = getSubNode(childnode, subtag);
                value = getNodeValue(tagnode);
                if (value.equalsIgnoreCase(subtagvalue)) {
                    if (count == nr) {
                        return childnode;
                    }
                    count++;
                }
            }
        }
        return null;
    }

    /**
     * Get node child with a certain subtag set to a certain value
     *
     * @param n
     *          The node to search in
     * @param tag
     *          The tag to look for
     * @param subtag
     *          The subtag to look for
     * @param subtagvalue
     *          The value the subtag should have
     * @param copyNr
     *          The nr of occurance of the value
     * @return The node found or null if we couldn't find anything.
     */
    public static final Node getNodeWithAttributeValue(Node n, String tag, String attributeName,
            String attributeValue) {
        NodeList children;
        Node childnode;

        children = n.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            childnode = children.item(i);
            if (childnode.getNodeName().equalsIgnoreCase(tag)) {
                // <hop>
                Node attribute = childnode.getAttributes().getNamedItem(attributeName);

                if (attribute != null && attributeValue.equals(attribute.getTextContent())) {
                    return childnode;
                }
            }
        }
        return null;
    }

    /**
     * Search for a subnode in the node with a certain tag.
     *
     * @param n
     *          The node to look in
     * @param tag
     *          The tag to look for
     * @return The subnode if the tag was found, or null if nothing was found.
     */
    public static final Node getSubNode(Node n, String tag) {
        int i;
        NodeList children;
        Node childnode;

        if (n == null) {
            return null;
        }

        // Get the childres one by one out of the node,
        // compare the tags and return the first found.
        //
        children = n.getChildNodes();
        for (i = 0; i < children.getLength(); i++) {
            childnode = children.item(i);
            if (childnode.getNodeName().equalsIgnoreCase(tag)) {
                return childnode;
            }
        }
        return null;
    }

    /**
     * Search a node for a child of child
     *
     * @param n
     *          The node to look in
     * @param tag
     *          The tag to look for in the node
     * @param subtag
     *          The tag to look for in the children of the node
     * @return The sub-node found or null if nothing was found.
     */
    public static final Node getSubNode(Node n, String tag, String subtag) {
        Node t = getSubNode(n, tag);
        if (t != null) {
            return getSubNode(t, subtag);
        }
        return null;
    }

    /**
     * Get a subnode in a node by nr.<br>
     * This method uses caching and assumes you loop over subnodes in sequential order (nr is increasing by 1 each call)
     *
     * @param n
     *          The node to look in
     * @param tag
     *          The tag to count
     * @param nr
     *          The position in the node
     * @return The subnode found or null in case the position was invalid.
     */
    public static final Node getSubNodeByNr(Node n, String tag, int nr) {
        return getSubNodeByNr(n, tag, nr, true);
    }

    /**
     * Get a subnode in a node by nr.<br>
     * It optially allows you to use caching.<br>
     * Caching assumes that you loop over subnodes in sequential order (nr is increasing by 1 each call)
     *
     * @param n
     *          The node to look in
     * @param tag
     *          The tag to count
     * @param nr
     *          The position in the node
     * @param useCache
     *          set this to false if you don't want to use caching. For example in cases where you want to loop over
     *          subnodes of a certain tag in reverse or random order.
     * @return The subnode found or null in case the position was invalid.
     */
    public static final Node getSubNodeByNr(Node n, String tag, int nr, boolean useCache) {
        NodeList children;
        Node childnode;

        if (n == null) {
            return null;
        }

        int count = 0;
        // Find the child-nodes of this Node n:
        children = n.getChildNodes();

        int lastChildNr = -1;
        XMLHandlerCacheEntry entry = null;

        if (useCache) {
            entry = new XMLHandlerCacheEntry(n, tag);
            lastChildNr = cache.getLastChildNr(entry);
        }
        if (lastChildNr < 0) {
            lastChildNr = 0;
        } else {
            count = nr; // we assume we found the previous nr-1 at the lastChildNr
            lastChildNr++; // we left off at the previouso one, so continue with the next.
        }

        for (int i = lastChildNr; i < children.getLength(); i++) // Try all children
        {
            childnode = children.item(i);
            if (childnode.getNodeName().equalsIgnoreCase(tag)) // We found the right tag
            {
                if (count == nr) {
                    if (useCache) {
                        cache.storeCache(entry, i);
                    }
                    return childnode;
                }
                count++;
            }
        }
        return null;
    }

    /**
     * Find the value entry in a node
     *
     * @param n
     *          The node
     * @return The value entry as a string
     */
    public static final String getNodeValue(Node n) {
        if (n == null) {
            return null;
        }

        // Find the child-nodes of this Node n:
        NodeList children = n.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            // Try all children
            Node childnode = children.item(i);
            String retval = childnode.getNodeValue();
            if (retval != null) { // We found the right value
                return retval;
            }
        }
        return null;
    }

    public static final String getTagAttribute(Node node, String attribute) {
        if (node == null) {
            return null;
        }

        String retval = null;

        NamedNodeMap nnm = node.getAttributes();
        if (nnm != null) {
            Node attr = nnm.getNamedItem(attribute);
            if (attr != null) {
                retval = attr.getNodeValue();
            }
        }
        return retval;
    }

    /**
     * Load a file into an XML document
     *
     * @param filename
     *          The filename to load into a document
     * @return the Document if all went well, null if an error occurred!
     */
    public static final Document loadXMLFile(String filename) throws KettleXMLException {
        try {
            return loadXMLFile(KettleVFS.getFileObject(filename));
        } catch (Exception e) {
            throw new KettleXMLException(e);
        }
    }

    /**
     * Load a file into an XML document
     *
     * @param filename
     *          The filename to load into a document
     * @return the Document if all went well, null if an error occured!
     */
    public static final Document loadXMLFile(FileObject fileObject) throws KettleXMLException {
        return loadXMLFile(fileObject, null, false, false);
    }

    /**
     * Load a file into an XML document
     *
     * @param filename
     *          The filename to load into a document
     * @param systemId
     *          Provide a base for resolving relative URIs.
     * @param ignoreEntities
     *          Ignores external entities and returns an empty dummy.
     * @param namespaceAware
     *          support XML namespaces.
     * @return the Document if all went well, null if an error occured!
     */
    public static final Document loadXMLFile(FileObject fileObject, String systemID, boolean ignoreEntities,
            boolean namespaceAware) throws KettleXMLException {
        try {
            return loadXMLFile(KettleVFS.getInputStream(fileObject), systemID, ignoreEntities, namespaceAware);
        } catch (IOException e) {
            throw new KettleXMLException("Unable to read file [" + fileObject.toString() + "]", e);
        }
    }

    /**
     * Read in an XML file from the passed input stream and return an XML document
     *
     * @param inputStream
     *          The filename input stream to read the document from
     * @return the Document if all went well, null if an error occurred!
     */
    public static final Document loadXMLFile(InputStream inputStream) throws KettleXMLException {
        return loadXMLFile(inputStream, null, false, false);
    }

    /**
     * Load a file into an XML document
     *
     * @param inputStream
     *          The stream to load a document from
     * @param systemId
     *          Provide a base for resolving relative URIs.
     * @param ignoreEntities
     *          Ignores external entities and returns an empty dummy.
     * @param namespaceAware
     *          support XML namespaces.
     * @return the Document if all went well, null if an error occured!
     */
    public static final Document loadXMLFile(InputStream inputStream, String systemID, boolean ignoreEntities,
            boolean namespaceAware) throws KettleXMLException {
        DocumentBuilderFactory dbf;
        DocumentBuilder db;
        Document doc;

        try {
            // Check and open XML document
            //
            dbf = DocumentBuilderFactory.newInstance();
            dbf.setIgnoringComments(true);
            dbf.setNamespaceAware(namespaceAware);
            db = dbf.newDocumentBuilder();

            // even dbf.setValidating(false) will the parser NOT prevent from checking the existance of the DTD
            // thus we need to give the BaseURI (systemID) below to have a chance to get it
            // or return empty dummy documents for all external entities (sources)
            //
            if (ignoreEntities) {
                db.setEntityResolver(new DTDIgnoringEntityResolver());
            }

            try {
                if (Const.isEmpty(systemID)) {
                    // Normal parsing
                    //
                    doc = db.parse(inputStream);
                } else {
                    // Do extra verifications
                    //
                    String systemIDwithEndingSlash = systemID.trim();

                    // make sure we have an ending slash, otherwise the last part will be ignored
                    //
                    if (!systemIDwithEndingSlash.endsWith("/") && !systemIDwithEndingSlash.endsWith("\\")) {
                        systemIDwithEndingSlash = systemIDwithEndingSlash.concat("/");
                    }
                    doc = db.parse(inputStream, systemIDwithEndingSlash);
                }
            } catch (FileNotFoundException ef) {
                throw new KettleXMLException(ef);
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }
            }

            return doc;
        } catch (Exception e) {
            throw new KettleXMLException("Error reading information from input stream", e);
        }
    }

    public static final Document loadXMLFile(File resource) throws KettleXMLException {
        try {
            return loadXMLFile(resource.toURI().toURL());
        } catch (MalformedURLException e) {
            throw new KettleXMLException(e);
        }
    }

    /**
     * Load a file into an XML document
     *
     * @param file
     *          The file to load into a document
     * @return the Document if all went well, null if an error occured!
     */
    public static final Document loadXMLFile(URL resource) throws KettleXMLException {
        DocumentBuilderFactory dbf;
        DocumentBuilder db;
        Document doc;

        try {
            // Check and open XML document
            dbf = DocumentBuilderFactory.newInstance();
            db = dbf.newDocumentBuilder();
            InputStream inputStream = resource.openStream();
            try {
                doc = db.parse(inputStream);
            } catch (IOException ef) {
                throw new KettleXMLException(ef);
            } finally {
                inputStream.close();
            }

            return doc;
        } catch (Exception e) {
            throw new KettleXMLException("Error reading information from resource", e);
        }
    }

    /**
     * Calls loadXMLString with deferNodeExpansion = TRUE
     *
     * @param string
     * @return
     * @throws KettleXMLException
     */
    public static final Document loadXMLString(String string) throws KettleXMLException {

        return loadXMLString(string, Boolean.FALSE, Boolean.TRUE);

    }

    /**
     * Loads the XML document in parameter xml and returns the 'tag' entry.
     *
     * @param xml
     *          the XML to load
     * @param tag
     *          the node to return
     * @return the requested node
     * @throws KettleXMLException
     *           in case there is a problem reading the XML
     */
    public static final Node loadXMLString(String xml, String tag) throws KettleXMLException {
        Document doc = loadXMLString(xml);
        return getSubNode(doc, tag);
    }

    /**
     * Load a String into an XML document
     *
     * @param string
     *          The XML text to load into a document
     * @param Boolean
     *          true to defer node expansion, false to not defer.
     * @return the Document if all went well, null if an error occurred!
     */
    public static final Document loadXMLString(String string, Boolean namespaceAware, Boolean deferNodeExpansion)
            throws KettleXMLException {
        DocumentBuilderFactory dbf;
        DocumentBuilder db;
        Document doc;

        try {
            // Check and open XML document
            dbf = DocumentBuilderFactory.newInstance();
            dbf.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", deferNodeExpansion);
            dbf.setNamespaceAware(namespaceAware); // parameterize this as well
            db = dbf.newDocumentBuilder();
            StringReader stringReader = new java.io.StringReader(string);
            InputSource inputSource = new InputSource(stringReader);
            try {
                doc = db.parse(inputSource);
            } catch (IOException ef) {
                throw new KettleXMLException("Error parsing XML", ef);
            } finally {
                stringReader.close();
            }

            return doc;
        } catch (Exception e) {
            throw new KettleXMLException("Error reading information from XML string : " + Const.CR + string, e);
        }
    }

    public static final String getString() {
        return XMLHandler.class.getName();
    }

    /**
     * Build an XML string for a certain tag String value
     *
     * @param tag
     *          The XML tag
     * @param val
     *          The String value of the tag
     * @param cr
     *          true if a carriage return is desired after the ending tag.
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, String val, boolean cr, String... attributes) {
        StringBuffer value;
        Encoder encoder = ESAPI.encoder();
        value = new StringBuffer("<");
        value.append(tag);

        for (int i = 0; i < attributes.length; i += 2) {
            value.append(" ").append(encoder.encodeForXMLAttribute(attributes[i])).append("=\"")
                    .append(attributes[i + 1]).append("\" ");
        }

        if (val != null && val.length() > 0) {
            value.append('>');
            value.append(encoder.encodeForXML(val));

            value.append("</");
            value.append(tag);
            value.append('>');
        } else {
            value.append("/>");
        }

        if (cr) {
            value.append(Const.CR);
        }

        return value.toString();
    }

    /**
     * Take the characters from string val and append them to the value stringbuffer In case a character is not allowed in
     * XML, we convert it to an XML code
     *
     * @param value
     *          the stringbuffer to append to
     * @param string
     *          the string to "encode"
     */
    public static void appendReplacedChars(StringBuffer value, String string) {
        Encoder encoder = ESAPI.encoder();
        value.append(encoder.encodeForXML(string));
    }

    /**
     * Build an XML string (including a carriage return) for a certain tag String value
     *
     * @param tag
     *          The XML tag
     * @param val
     *          The String value of the tag
     * @return The XML String for the tag.
     */
    public static final String addTagValue(KettleAttributeInterface tag, String val) {
        return addTagValue(tag.getXmlCode(), val);
    }

    /**
     * Build an XML string (including a carriage return) for a certain tag String value
     *
     * @param tag
     *          The XML tag
     * @param val
     *          The String value of the tag
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, String val) {
        return addTagValue(tag, val, true);
    }

    /**
     * Build an XML string (including a carriage return) for a certain tag boolean value
     *
     * @param tag
     *          The XML tag
     * @param bool
     *          The boolean value of the tag
     * @return The XML String for the tag.
     */
    public static final String addTagValue(KettleAttributeInterface tag, boolean bool) {
        return addTagValue(tag.getXmlCode(), bool);
    }

    /**
     * Build an XML string (including a carriage return) for a certain tag boolean value
     *
     * @param tag
     *          The XML tag
     * @param bool
     *          The boolean value of the tag
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, boolean bool) {
        return addTagValue(tag, bool, true);
    }

    /**
     * Build an XML string for a certain tag boolean value
     *
     * @param tag
     *          The XML tag
     * @param bool
     *          The boolean value of the tag
     * @param cr
     *          true if a carriage return is desired after the ending tag.
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, boolean bool, boolean cr) {
        return addTagValue(tag, bool ? "Y" : "N", cr);
    }

    /**
     * Build an XML string for a certain tag long integer value
     *
     * @param tag
     *          The XML tag
     * @param l
     *          The long integer value of the tag
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, long l) {
        return addTagValue(tag, l, true);
    }

    /**
     * Build an XML string for a certain tag long integer value
     *
     * @param tag
     *          The XML tag
     * @param l
     *          The long integer value of the tag
     * @param cr
     *          true if a carriage return is desired after the ending tag.
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, long l, boolean cr) {
        // Tom modified this for performance
        // return addTagValue(tag, ""+l, cr);
        return addTagValue(tag, String.valueOf(l), cr);
    }

    /**
     * Build an XML string (with carriage return) for a certain tag integer value
     *
     * @param tag
     *          The XML tag
     * @param i
     *          The integer value of the tag
     * @return The XML String for the tag.
     */
    public static final String addTagValue(KettleAttributeInterface tag, int i) {
        return addTagValue(tag.getXmlCode(), i);
    }

    /**
     * Build an XML string (with carriage return) for a certain tag integer value
     *
     * @param tag
     *          The XML tag
     * @param i
     *          The integer value of the tag
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, int i) {
        return addTagValue(tag, i, true);
    }

    /**
     * Build an XML string for a certain tag integer value
     *
     * @param tag
     *          The XML tag
     * @param i
     *          The integer value of the tag
     * @param cr
     *          true if a carriage return is desired after the ending tag.
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, int i, boolean cr) {
        return addTagValue(tag, "" + i, cr);
    }

    /**
     * Build an XML string (with carriage return) for a certain tag double value
     *
     * @param tag
     *          The XML tag
     * @param d
     *          The double value of the tag
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, double d) {
        return addTagValue(tag, d, true);
    }

    /**
     * Build an XML string for a certain tag double value
     *
     * @param tag
     *          The XML tag
     * @param d
     *          The double value of the tag
     * @param cr
     *          true if a carriage return is desired after the ending tag.
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, double d, boolean cr) {
        return addTagValue(tag, "" + d, cr);
    }

    /**
     * Build an XML string (with carriage return) for a certain tag Date value
     *
     * @param tag
     *          The XML tag
     * @param date
     *          The Date value of the tag
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, Date date) {
        return addTagValue(tag, date, true);
    }

    /**
     * Build an XML string for a certain tag Date value
     *
     * @param tag
     *          The XML tag
     * @param date
     *          The Date value of the tag
     * @param cr
     *          true if a carriage return is desired after the ending tag.
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, Date date, boolean cr) {
        return addTagValue(tag, date2string(date), cr);
    }

    /**
     * Build an XML string (including a carriage return) for a certain tag BigDecimal value
     *
     * @param tag
     *          The XML tag
     * @param val
     *          The BigDecimal value of the tag
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, BigDecimal val) {
        return addTagValue(tag, val, true);
    }

    /**
     * Build an XML string (including a carriage return) for a certain tag BigDecimal value
     *
     * @param tag
     *          The XML tag
     * @param val
     *          The BigDecimal value of the tag
     *
     * @return The XML String for the tag.
     */
    public static final String addTagValue(String tag, BigDecimal val, boolean cr) {
        return addTagValue(tag, val != null ? val.toString() : (String) null, true);
    }

    /**
     * Build an XML string (including a carriage return) for a certain tag binary (byte[]) value
     *
     * @param tag
     *          The XML tag
     * @param val
     *          The binary value of the tag
     * @return The XML String for the tag.
     * @throws IOException
     *           in case there is an Base64 or GZip encoding problem
     */
    public static final String addTagValue(String tag, byte[] val) throws IOException {
        return addTagValue(tag, val, true);
    }

    /**
     * Build an XML string (including a carriage return) for a certain tag binary (byte[]) value
     *
     * @param tag
     *          The XML tag
     * @param val
     *          The binary value of the tag
     * @return The XML String for the tag.
     * @throws IOException
     *           in case there is an Base64 or GZip encoding problem
     */
    public static final String addTagValue(String tag, byte[] val, boolean cr) throws IOException {
        String string;
        if (val == null) {
            string = null;
        } else {
            string = encodeBinaryData(val);
        }

        return addTagValue(tag, string, true);
    }

    public static String encodeBinaryData(byte[] val) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        GZIPOutputStream gzos = new GZIPOutputStream(baos);
        BufferedOutputStream bos = new BufferedOutputStream(gzos);
        bos.write(val);
        bos.flush();
        bos.close();

        return new String(Base64.encodeBase64(baos.toByteArray()));
    }

    /**
     * Get all the attributes in a certain node (on the root level)
     *
     * @param node
     *          The node to examine
     * @return an array of strings containing the names of the attributes.
     */
    public static String[] getNodeAttributes(Node node) {
        NamedNodeMap nnm = node.getAttributes();
        if (nnm != null) {
            String[] attributes = new String[nnm.getLength()];
            for (int i = 0; i < nnm.getLength(); i++) {
                Node attr = nnm.item(i);
                attributes[i] = attr.getNodeName();
            }
            return attributes;
        }
        return null;

    }

    public static String[] getNodeElements(Node node) {
        ArrayList<String> elements = new ArrayList<String>(); // List of String

        NodeList nodeList = node.getChildNodes();
        if (nodeList == null) {
            return null;
        }

        for (int i = 0; i < nodeList.getLength(); i++) {
            String nodeName = nodeList.item(i).getNodeName();
            if (elements.indexOf(nodeName) < 0) {
                elements.add(nodeName);
            }
        }

        if (elements.isEmpty()) {
            return null;
        }

        return elements.toArray(new String[elements.size()]);
    }

    public static Date stringToDate(String dateString) {
        if (Const.isEmpty(dateString)) {
            return null;
        }

        try {
            synchronized (simpleDateFormat) {
                return simpleDateFormat.parse(dateString);
            }
        } catch (ParseException e) {
            return null;
        }
    }

    public static String date2string(Date date) {
        if (date == null) {
            return null;
        }
        synchronized (simpleDateFormat) {
            return simpleDateFormat.format(date);
        }
    }

    /**
     * Convert a XML encoded binary string back to binary format
     *
     * @param string
     *          the (Byte64/GZip) encoded string
     * @return the decoded binary (byte[]) object
     * @throws KettleException
     *           In case there is a decoding error
     */
    public static byte[] stringToBinary(String string) throws KettleException {
        try {
            byte[] bytes;
            if (string == null) {
                bytes = new byte[] {};
            } else {
                bytes = Base64.decodeBase64(string.getBytes());
            }
            if (bytes.length > 0) {
                ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
                GZIPInputStream gzip = new GZIPInputStream(bais);
                BufferedInputStream bi = new BufferedInputStream(gzip);
                byte[] result = new byte[] {};

                byte[] extra = new byte[1000000];
                int nrExtra = bi.read(extra);
                while (nrExtra >= 0) {
                    // add it to bytes...
                    //
                    int newSize = result.length + nrExtra;
                    byte[] tmp = new byte[newSize];
                    for (int i = 0; i < result.length; i++) {
                        tmp[i] = result[i];
                    }
                    for (int i = 0; i < nrExtra; i++) {
                        tmp[result.length + i] = extra[i];
                    }

                    // change the result
                    result = tmp;
                    nrExtra = bi.read(extra);
                }
                bytes = result;
                gzip.close();
            }

            return bytes;
        } catch (Exception e) {
            throw new KettleException("Error converting string to binary", e);
        }
    }

    public static String buildCDATA(String string) {
        StringBuffer cdata = new StringBuffer("<![CDATA[");
        cdata.append(Const.NVL(string, "")).append("]]>");
        return cdata.toString();
    }

    public static final String openTag(String tag) {
        return "<" + tag + ">";
    }

    public static final String closeTag(String tag) {
        return "</" + tag + ">";
    }

    public static String formatNode(Node node) throws KettleXMLException {
        StringWriter sw = new StringWriter();
        try {
            Transformer t = TransformerFactory.newInstance().newTransformer();
            t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            t.transform(new DOMSource(node), new StreamResult(sw));
        } catch (Exception e) {
            throw new KettleXMLException("Unable to format Node as XML", e);
        }
        return sw.toString();
    }

}

/**
 * Handle external references and return an empty dummy document.
 *
 * @author jb
 * @since 2007-12-21
 *
 */
class DTDIgnoringEntityResolver implements EntityResolver {
    public DTDIgnoringEntityResolver() {
        // nothing
    }

    @Override
    public InputSource resolveEntity(java.lang.String publicID, java.lang.String systemID) throws IOException {
        System.out.println("Public-ID: " + publicID.toString());
        System.out.println("System-ID: " + systemID.toString());
        return new InputSource(new ByteArrayInputStream("<?xml version='1.0' encoding='UTF-8'?>".getBytes()));
    }

}