com.twinsoft.convertigo.engine.util.XMLUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.twinsoft.convertigo.engine.util.XMLUtils.java

Source

/*
 * Copyright (c) 2001-2011 Convertigo SA.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 *
 * $URL$
 * $Author$
 * $Revision$
 * $Date$
 */

package com.twinsoft.convertigo.engine.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.log4j.Logger;
import org.apache.xml.resolver.Catalog;
import org.apache.xml.resolver.tools.CatalogResolver;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.DOMException;
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;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.twinsoft.convertigo.beans.common.XMLizable;
import com.twinsoft.convertigo.engine.Engine;
import com.twinsoft.convertigo.engine.EngineException;
import com.twinsoft.convertigo.engine.EnginePropertiesManager;
import com.twinsoft.convertigo.engine.EnginePropertiesManager.PropertyName;
import com.twinsoft.convertigo.engine.enums.JsonOutput.JsonRoot;
import com.twinsoft.util.StringEx;

public class XMLUtils {
    private static ThreadLocal<DocumentBuilderFactory> defaultDocumentBuilderFactory = new ThreadLocal<DocumentBuilderFactory>() {
        @Override
        protected DocumentBuilderFactory initialValue() {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            try {
                String s = EnginePropertiesManager.getProperty(PropertyName.DOCUMENT_NAMESPACE_AWARE);
                if (s.equalsIgnoreCase("true"))
                    documentBuilderFactory.setNamespaceAware(true);
            } catch (IllegalStateException e) {
                documentBuilderFactory.setNamespaceAware(true);
            }
            return documentBuilderFactory;
        }
    };

    private static ThreadLocal<DocumentBuilder> defaultDocumentBuilder = new ThreadLocal<DocumentBuilder>() {
        @Override
        protected DocumentBuilder initialValue() {
            try {
                return defaultDocumentBuilderFactory.get().newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                e.printStackTrace();
                return null;
            }
        }
    };

    public static DocumentBuilder getDefaultDocumentBuilder() {
        return defaultDocumentBuilder.get();
    }

    private static ThreadLocal<TransformerFactory> defaultTransformerFactory = new ThreadLocal<TransformerFactory>() {
        @Override
        protected TransformerFactory initialValue() {
            return TransformerFactory.newInstance();
        }
    };

    public static Transformer getNewTransformer() throws TransformerConfigurationException {
        return defaultTransformerFactory.get().newTransformer();
    }

    public static Transformer getNewTransformer(Source source) throws TransformerConfigurationException {
        return defaultTransformerFactory.get().newTransformer(source);
    }

    private static ThreadLocal<SAXParser> defaultSAXParser = new ThreadLocal<SAXParser>() {
        @Override
        protected SAXParser initialValue() {
            try {
                return SAXParserFactory.newInstance().newSAXParser();
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    };

    public static void saxParse(File file, DefaultHandler defaultHandler) throws SAXException, IOException {
        defaultSAXParser.get().parse(file, defaultHandler);
    }

    public static void saxParse(InputStream inputStream, DefaultHandler defaultHandler)
            throws SAXException, IOException {
        defaultSAXParser.get().parse(inputStream, defaultHandler);
    }

    public static String simplePrettyPrintDOM(String sDocument) {
        StringEx sxDocument = new StringEx(sDocument);
        sxDocument.replaceAll(">", ">\n");
        return sxDocument.toString();
    }

    public static String prettyPrintDOM(String sDocument)
            throws ParserConfigurationException, SAXException, IOException {
        Document document = getDefaultDocumentBuilder().parse(new InputSource(new StringReader(sDocument)));
        return XMLUtils.prettyPrintDOM(document);
    }

    public static String prettyPrintDOM(String sDocument, String relativeUriResolver)
            throws ParserConfigurationException, SAXException, IOException {
        InputSource inputSource = new InputSource(new StringReader(sDocument));
        inputSource.setSystemId(relativeUriResolver);
        Document document = getDefaultDocumentBuilder().parse(inputSource);
        return XMLUtils.prettyPrintDOM(document);
    }

    public static String prettyPrintDOM(Node node) {
        return XMLUtils.prettyPrintDOM(node.getOwnerDocument());
    }

    public static String prettyPrintDOMWithEncoding(Document doc) {
        return prettyPrintDOMWithEncoding(doc, "ISO-8859-1");
    }

    public static String prettyPrintDOMWithEncoding(Document doc, String defaultEncoding) {
        StringWriter writer = new StringWriter();
        prettyPrintDOMWithEncoding(doc, defaultEncoding, writer);
        return writer.getBuffer().toString();
    }

    public static String prettyPrintDOMWithEncoding(String sDocument, String encoding)
            throws ParserConfigurationException, SAXException, IOException {
        Document document = getDefaultDocumentBuilder().parse(new InputSource(new StringReader(sDocument)));
        return XMLUtils.prettyPrintDOMWithEncoding(document, encoding);
    }

    public static void prettyPrintDOM(Document doc, Writer writer) {
        prettyPrintDOMWithEncoding(doc, "UTF-8", writer);
    }

    public static void prettyPrintDOM(Document doc, String defaultEncoding, Writer writer) {
        prettyPrintDOMWithEncoding(doc, defaultEncoding, writer);
    }

    public static void prettyPrintDOMWithEncoding(Document doc, String defaultEncoding, Writer writer) {
        prettyPrintDOMWithEncoding(doc, defaultEncoding, new StreamResult(writer));
    }

    public static void prettyPrintDOMWithEncoding(Document doc, String defaultEncoding, OutputStream outputStream) {
        prettyPrintDOMWithEncoding(doc, defaultEncoding, new StreamResult(outputStream));
    }

    public static void prettyPrintDOMWithEncoding(Document doc, String defaultEncoding, Result result) {
        Node firstChild = doc.getFirstChild();
        boolean omitXMLDeclaration = false;
        String encoding = defaultEncoding; // default Encoding char set if non
        // is found in the PI

        if ((firstChild.getNodeType() == Document.PROCESSING_INSTRUCTION_NODE)
                && (firstChild.getNodeName().equals("xml"))) {
            omitXMLDeclaration = true;
            String piValue = firstChild.getNodeValue();
            // extract from PI the encoding Char Set
            int encodingOffset = piValue.indexOf("encoding=\"");
            if (encodingOffset != -1) {
                encoding = piValue.substring(encodingOffset + 10);
                // remove the last "
                encoding = encoding.substring(0, encoding.length() - 1);
            }
        }

        try {
            Transformer t = getNewTransformer();
            t.setOutputProperty(OutputKeys.ENCODING, encoding);
            t.setOutputProperty(OutputKeys.INDENT, "yes");
            t.setOutputProperty(OutputKeys.METHOD, "xml"); // xml, html, text
            t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, omitXMLDeclaration ? "yes" : "no");
            t.transform(new DOMSource(doc), result);
        } catch (Exception e) {
            Engine.logEngine.error("Unexpected exception while pretty print DOM", e);
        }
    }

    public static String prettyPrintDOM(Document doc) {
        return prettyPrintDOMWithEncoding(doc, "ISO-8859-1");
    }

    public static String prettyPrintElement(Element elt) {
        return prettyPrintElement(elt, true, true);
    }

    public static String prettyPrintElement(Element elt, boolean omitXMLDeclaration, boolean bIndent) {
        Node firstChild = elt;
        String encoding = "ISO-8859-1"; // default Encoding char set if non is
        // found in the PI

        if (omitXMLDeclaration && (firstChild.getNodeType() == Document.PROCESSING_INSTRUCTION_NODE)
                && (firstChild.getNodeName().equals("xml"))) {
            String piValue = firstChild.getNodeValue();
            // extract from PI the encoding Char Set
            int encodingOffset = piValue.indexOf("encoding=\"");
            if (encodingOffset != -1) {
                encoding = piValue.substring(encodingOffset + 10);
                // remove the last "
                encoding = encoding.substring(0, encoding.length() - 1);
            }
        }
        StringWriter strWtr = new StringWriter();
        try {
            Transformer t = getNewTransformer();
            t.setOutputProperty(OutputKeys.ENCODING, encoding);
            t.setOutputProperty(OutputKeys.INDENT, bIndent ? "yes" : "no");
            t.setOutputProperty(OutputKeys.METHOD, "xml"); // xml, html, text
            t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, omitXMLDeclaration ? "yes" : "no");
            t.transform(new DOMSource(elt), new StreamResult(strWtr));
            return strWtr.getBuffer().toString();
        } catch (Exception e) {
            System.err.println("XML.toString(Document): " + e);
            Engine.logEngine.error("Unexpected exception", e);
            return e.getMessage();
        }
    }

    /*
     * public static String prettyPrintElement(Element elt) { Node firstChild =
     * elt; boolean omitXMLDeclaration = false; if ( (firstChild.getNodeType()
     * == Document.PROCESSING_INSTRUCTION_NODE) &&
     * (firstChild.getNodeName().equals("xml"))) omitXMLDeclaration = true;
     * 
     * OutputFormat format = new OutputFormat("XML", "ISO-8859-1", true);
     * format.setLineWidth(0); format.setIndent(4);
     * 
     * format.setNonEscapingElements(new String[] {"SCRIPT", "script"});
     * 
     * format.setOmitDocumentType(true);
     * format.setOmitXMLDeclaration(omitXMLDeclaration); StringWriter sw = new
     * StringWriter(); XMLSerializer serializer = new XMLSerializer(sw, format);
     * 
     * try { serializer.serialize(elt); return sw.toString(); }
     * catch(IOException e) { Engine.logEngine.error("Unexpected exception", e);
     * return e.getMessage(); } }
     * 
     * public static String prettyPrintElement(Element elt, boolean
     * omitXMLDeclaration, int indent) { OutputFormat format = new
     * OutputFormat("XML", "ISO-8859-1", true); format.setLineWidth(0);
     * format.setIndent(indent);
     * 
     * format.setNonEscapingElements(new String[] {"SCRIPT", "script"});
     * 
     * format.setOmitDocumentType(true);
     * format.setOmitXMLDeclaration(omitXMLDeclaration); StringWriter sw = new
     * StringWriter(); XMLSerializer serializer = new XMLSerializer(sw, format);
     * 
     * try { serializer.serialize(elt); return sw.toString(); }
     * catch(IOException e) { Engine.logEngine.error("Unexpected exception", e);
     * return e.getMessage(); } }
     */
    public static Node writeObjectToXml(Document document, Object object) throws Exception {
        return writeObjectToXml(document, object, null);
    }

    public static Node writeObjectToXml(Document document, Object object, Object compiledValue) throws Exception {
        if (object == null)
            return null;

        if (object instanceof Enum) {
            object = ((Enum<?>) object).name();
        }

        // Simple objects
        if ((object instanceof Boolean) || (object instanceof Integer) || (object instanceof Double)
                || (object instanceof Float) || (object instanceof Character) || (object instanceof Long)
                || (object instanceof Short) || (object instanceof Byte)) {
            Element element = document.createElement(object.getClass().getName());
            element.setAttribute("value", object.toString());
            if (compiledValue != null)
                element.setAttribute("compiledValue", compiledValue.toString());
            return element;
        }
        // Strings
        else if (object instanceof String) {
            Element element = document.createElement(object.getClass().getName());
            element.setAttribute("value", object.toString());
            if (compiledValue != null) {
                element.setAttribute("compiledValueClass", compiledValue.getClass().getCanonicalName());
                element.setAttribute("compiledValue", compiledValue.toString());
            }
            return element;
        }
        // Arrays
        else if (object.getClass().getName().startsWith("[")) {
            String arrayClassName = object.getClass().getName();
            int i = arrayClassName.lastIndexOf('[');

            String itemClassName = arrayClassName.substring(i + 1);
            char c = itemClassName.charAt(itemClassName.length() - 1);
            switch (c) {
            case ';':
                itemClassName = itemClassName.substring(1, itemClassName.length() - 1);
                break;
            case 'B':
                itemClassName = "byte";
                break;
            case 'C':
                itemClassName = "char";
                break;
            case 'D':
                itemClassName = "double";
                break;
            case 'F':
                itemClassName = "float";
                break;
            case 'I':
                itemClassName = "int";
                break;
            case 'J':
                itemClassName = "long";
                break;
            case 'S':
                itemClassName = "short";
                break;
            case 'Z':
                itemClassName = "boolean";
                break;
            }

            Element element = document.createElement("array");
            element.setAttribute("classname", itemClassName);

            int len = Array.getLength(object);
            element.setAttribute("length", Integer.toString(len));

            Node subNode;
            for (int k = 0; k < len; k++) {
                subNode = writeObjectToXml(document, Array.get(object, k));
                if (subNode != null) {
                    element.appendChild(subNode);
                }
            }

            return element;
        }
        // XMLization
        else if (object instanceof XMLizable) {
            Element element = document.createElement("xmlizable");
            element.setAttribute("classname", object.getClass().getName());
            element.appendChild(((XMLizable) object).writeXml(document));
            return element;
        }
        // Default serialization
        else {
            Element element = document.createElement("serializable");
            element.setAttribute("classname", object.getClass().getName());

            String objectBytesEncoded = null;
            byte[] objectBytes = null;

            // We write the object to a bytes array
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(object);
            objectOutputStream.flush();
            outputStream.close();

            // Now, we retrieve the object bytes
            objectBytes = outputStream.toByteArray();
            objectBytesEncoded = org.apache.commons.codec.binary.Base64.encodeBase64String(objectBytes);

            CDATASection cDATASection = document.createCDATASection(new String(objectBytesEncoded));
            element.appendChild(cDATASection);

            return element;
        }
    }

    public static Object readObjectFromXml(Element node) throws Exception {
        String nodeName = node.getNodeName();
        String nodeValue = ((Element) node).getAttribute("value");

        if (nodeName.equals("java.lang.Boolean")) {
            return new Boolean(nodeValue);
        } else if (nodeName.equals("java.lang.Byte")) {
            return new Byte(nodeValue);
        } else if (nodeName.equals("java.lang.Character")) {
            return new Character(nodeValue.charAt(0));
        } else if (nodeName.equals("java.lang.Integer")) {
            return new Integer(nodeValue);
        } else if (nodeName.equals("java.lang.Double")) {
            return new Double(nodeValue);
        } else if (nodeName.equals("java.lang.Float")) {
            return new Float(nodeValue);
        } else if (nodeName.equals("java.lang.Long")) {
            return new Long(nodeValue);
        } else if (nodeName.equals("java.lang.Short")) {
            return new Short(nodeValue);
        } else if (nodeName.equals("java.lang.String")) {
            return nodeValue;
        } else if (nodeName.equals("array")) {
            String className = node.getAttribute("classname");
            String length = node.getAttribute("length");
            int len = (new Integer(length)).intValue();

            Object array;
            if (className.equals("byte")) {
                array = new byte[len];
            } else if (className.equals("boolean")) {
                array = new boolean[len];
            } else if (className.equals("char")) {
                array = new char[len];
            } else if (className.equals("double")) {
                array = new double[len];
            } else if (className.equals("float")) {
                array = new float[len];
            } else if (className.equals("int")) {
                array = new int[len];
            } else if (className.equals("long")) {
                array = new long[len];
            } else if (className.equals("short")) {
                array = new short[len];
            } else {
                array = Array.newInstance(Class.forName(className), len);
            }

            Node xmlNode = null;
            NodeList nl = node.getChildNodes();
            int len_nl = nl.getLength();
            int i = 0;
            for (int j = 0; j < len_nl; j++) {
                xmlNode = nl.item(j);
                if (xmlNode.getNodeType() == Node.ELEMENT_NODE) {
                    Object o = XMLUtils.readObjectFromXml((Element) xmlNode);
                    Array.set(array, i, o);
                    i++;
                }
            }

            return array;
        }
        // XMLization
        else if (nodeName.equals("xmlizable")) {
            String className = node.getAttribute("classname");

            Node xmlNode = findChildNode(node, Node.ELEMENT_NODE);
            Object xmlizable = Class.forName(className).newInstance();
            ((XMLizable) xmlizable).readXml(xmlNode);

            return xmlizable;
        }
        // Serialization
        else if (nodeName.equals("serializable")) {
            Node cdata = findChildNode(node, Node.CDATA_SECTION_NODE);
            String objectSerializationData = cdata.getNodeValue();
            Engine.logEngine.debug("Object serialization data:\n" + objectSerializationData);
            byte[] objectBytes = org.apache.commons.codec.binary.Base64.decodeBase64(objectSerializationData);

            // We read the object to a bytes array
            ByteArrayInputStream inputStream = new ByteArrayInputStream(objectBytes);
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            Object object = objectInputStream.readObject();
            inputStream.close();

            return object;
        }

        return null;
    }

    public static Node findChildNode(Node parentNode, int type) {
        Node foundNode = null;
        NodeList nl = parentNode.getChildNodes();
        int len_nl = nl.getLength();
        for (int j = 0; j < len_nl; j++) {
            foundNode = nl.item(j);
            if (foundNode.getNodeType() == type)
                return foundNode;
        }

        return null;
    }

    public static Element findNodeByAttributeValue(NodeList nodeList, String attributeName, String attributeValue) {
        int len = nodeList.getLength();
        String tmp;
        Element property;
        for (int i = 0; i < len; i++) {
            property = (Element) nodeList.item(i);
            tmp = property.getAttribute(attributeName);
            if (attributeValue.equals(tmp)) {
                return property;
            }
        }
        return null;
    }

    public static Object findPropertyValue(Element databaseObjectNode, String propertyName) throws Exception {
        NodeList properties = databaseObjectNode.getElementsByTagName("property");

        Element projectNameElement = XMLUtils.findNodeByAttributeValue(properties, "name", propertyName);

        Node xmlNode = null;
        NodeList nl = projectNameElement.getChildNodes();
        int len_nl = nl.getLength();
        for (int j = 0; j < len_nl; j++) {
            xmlNode = nl.item(j);
            if (xmlNode.getNodeType() == Node.ELEMENT_NODE) {
                return XMLUtils.readObjectFromXml((Element) xmlNode);
            }
        }

        throw new EngineException("No such property ('" + propertyName + "')");
    }

    public static String findXslHref(Node node) {
        int type = node.getNodeType();

        if (type == Node.PROCESSING_INSTRUCTION_NODE) {
            String nodeName = node.getNodeName();
            if (nodeName.equalsIgnoreCase("xml-stylesheet")) {
                String nodeValue = node.getNodeValue();
                try {
                    int i = nodeValue.indexOf("href=\"");
                    int j = nodeValue.indexOf("\"", i + 6);
                    String result = nodeValue.substring(i + 6, j);
                    return result;
                } catch (StringIndexOutOfBoundsException e) {
                    return null;
                }
            }
            return null;
        } else {
            NodeList children = node.getChildNodes();
            int len = children.getLength();
            for (int i = 0; i < len; i++) {
                String result = findXslHref(children.item(i));
                if (result != null)
                    return result;
            }
        }
        return null;
    }

    public static void copyDocument(Document from, Document to) {
        Node node = to.importNode(from.getDocumentElement(), true);
        to.getDocumentElement().appendChild(node);
    }

    public static Element findSingleElement(Element parent, String fullXPath) {
        Element elt = null;
        if (parent != null) {
            if (parent.hasChildNodes()) {
                if (fullXPath.startsWith("/"))
                    fullXPath = fullXPath.substring(1);
                int index = fullXPath.indexOf("/");
                String childName = ((index != -1) ? fullXPath.substring(0, index) : fullXPath);

                NodeList list = parent.getChildNodes();
                for (int i = 0; i < list.getLength(); i++) {
                    Node child = list.item(i);
                    if (child.getNodeType() == Node.ELEMENT_NODE) {
                        if (child.getNodeName().equalsIgnoreCase(childName)) {
                            if (index == -1) {
                                elt = (Element) child;
                                break;
                            } else {
                                fullXPath = fullXPath.substring(index + 1);
                                elt = findSingleElement((Element) child, fullXPath);
                            }
                        }
                    }
                }
            }
        }
        return elt;
    }

    public static NodeList findElements(Element parent, String fullXPath) {
        NodeList list = null;
        Element elt = findSingleElement(parent, fullXPath);
        if (elt != null)
            list = ((Element) elt.getParentNode()).getElementsByTagName(elt.getNodeName());
        return list;
    }

    public static String getXPath(Element elt) {
        String xpath = "";
        if (elt != null) {
            Document doc = elt.getOwnerDocument();
            Element root = doc.getDocumentElement();
            Element parent = elt;
            while (parent != root) {
                xpath = "/" + parent.getNodeName() + xpath;
                parent = (Element) parent.getParentNode();
            }
        }
        return xpath;
    }

    public static String calcXpath(Node node) {
        return calcXpath(node, null);
    }

    /**
     * Compute the xpath of a node relative to an anchor.
     * 
     * @param node
     *            node to find the xpath from.
     * @param anchor
     *            the relative point to fid from.
     * @return the computed xpath.
     */
    public static String calcXpath(Node node, Node anchor) {
        String xpath = "";
        Node current = null;

        if (node == null || node.equals(anchor))
            return "";

        // add attribute to xpath
        if (node instanceof Attr) {
            Attr attr = (Attr) node;
            node = attr.getOwnerElement();
            xpath = '@' + attr.getName() + '/';
        }

        while ((current = node.getParentNode()) != anchor) {
            Engine.logEngine.trace("Calc Xpath : current node : " + current.getNodeName());
            NodeList childs = current.getChildNodes();
            int index = 0;
            for (int i = 0; i < childs.getLength(); i++) {
                if (childs.item(i).getNodeType() != Node.ELEMENT_NODE
                        && !childs.item(i).getNodeName().equalsIgnoreCase("#text"))
                    continue;

                Engine.logEngine.trace("Calc Xpath : ==== > Child node : " + childs.item(i).getNodeName());

                // Bump the index if we have the same tag names..
                if (childs.item(i).getNodeName().equalsIgnoreCase(node.getNodeName())) {
                    // tag names are equal ==> bump the index.
                    index++;
                    // is our node the one that is listed ?
                    if (childs.item(i).equals(node))
                        // We found our node in the parent node list
                        break;
                }
            }
            // count the number of elements having the same tag
            int nbElements = 0;
            for (int i = 0; i < childs.getLength(); i++) {
                if (childs.item(i).getNodeName().equalsIgnoreCase(node.getNodeName())) {
                    nbElements++;
                }
            }

            String name = node.getNodeName();
            if (name.equalsIgnoreCase("#text"))
                name = "text()";
            name = xpathEscapeColon(name);

            if (nbElements > 1) {
                xpath = name + "[" + index + "]/" + xpath;
            } else {
                // only one element had the same tag ==> do not compute the [xx]
                // syntax..
                xpath = name + "/" + xpath;
            }
            node = current;
        }
        if (xpath.length() > 0)
            // remove the trailing '/'
            xpath = xpath.substring(0, xpath.length() - 1);

        return xpath;
    }

    public static String xpathEscapeColon(String nameToEscape) {
        if (nameToEscape.contains(":"))
            return "*[name()=\"" + nameToEscape + "\"]";
        return nameToEscape;
    }

    public static String xpathGenerateConcat(String queryString) {
        String returnString = "";
        String searchString = queryString;

        int quotePosition = (searchString.indexOf("'") != -1) ? searchString.indexOf("'")
                : searchString.indexOf("\"");
        if (quotePosition == -1) {
            returnString = "'" + searchString + "'";
        } else {
            returnString = "concat(";
            while (quotePosition != -1) {
                String subString = searchString.substring(0, quotePosition);
                returnString += "'" + subString + "', ";
                if (("'").equals(searchString.substring(quotePosition, quotePosition + 1))) {
                    returnString += "\"'\", ";
                } else {
                    //must be a double quote
                    returnString += "'\"', ";
                }
                searchString = searchString.substring(quotePosition + 1, searchString.length());
                quotePosition = (searchString.indexOf("'") != -1) ? searchString.indexOf("'")
                        : searchString.indexOf("\"");
            }
            returnString += "'" + searchString + "')";
        }
        return returnString;
    }

    public static Document loadXml(File file) throws ParserConfigurationException, SAXException, IOException {
        Document document = getDefaultDocumentBuilder().parse(file);
        return document;
    }

    public static Document loadXml(String filePath) throws ParserConfigurationException, SAXException, IOException {
        return loadXml(new File(filePath));
    }

    public static Document createDom() throws ParserConfigurationException {
        Document document = getDefaultDocumentBuilder().newDocument();
        return document;
    }

    public static Document createDom(String xmlEngine) throws ParserConfigurationException {
        return createDom();
    }

    public static Document parseDOM(String xmlEngine, String xml)
            throws ParserConfigurationException, SAXException, IOException {
        Document document = getDefaultDocumentBuilder().parse(new InputSource(new StringReader(xml)));
        return document;
    }

    public static void saveXml(Document dom, String filePath) throws IOException {
        saveXml(dom, filePath, false);
    }

    public static void saveXml(Document dom, String filePath, boolean omitXmlDeclaration) throws IOException {
        saveXml(dom, new File(filePath), omitXmlDeclaration);
    }

    public static void saveXml(Document dom, File file) throws IOException {
        saveXml(dom, file, false);
    }

    public static void saveXml(Document dom, File file, boolean omitXmlDeclaration) throws IOException {
        try {
            Transformer transformer = getNewTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration ? "yes" : "no");
            transformer.transform(new DOMSource(dom.getDocumentElement()),
                    new StreamResult(file.toURI().getPath()));
        } catch (Exception e) {
            throw new IOException("saveXml failed because : " + e.getMessage());
        }
    }

    public static String getNormalizedText(Node node) {
        String res = "";
        if (node.hasChildNodes()) {
            NodeList nl = node.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                if (nl.item(i).getNodeType() == Node.TEXT_NODE) {
                    res += nl.item(i).getNodeValue();
                } else {
                    // ignore <SCRIPT> nodes ...
                    if (!nl.item(i).getNodeName().equalsIgnoreCase("script"))
                        res += getNormalizedText(nl.item(i));
                }
            }
        }
        return res;
    }

    public static void removeChilds(Node node) {
        NodeList nl = node.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++)
            node.removeChild(nl.item(i));
    }

    public static void removeNode(Node node) {
        node.getParentNode().removeChild(node);
    }

    public static void removeNodeListContent(NodeList nodeList) {
        while (nodeList.getLength() > 0) {
            removeNode(nodeList.item(0));
        }
    }

    static public Document parseDOM(InputStream is) throws SAXException, IOException, EngineException {
        Document doc = getDefaultDocumentBuilder().parse(is);
        doc.normalizeDocument();
        return doc;

    }

    static public Document parseDOM(File file) throws SAXException, IOException, EngineException {
        InputStream is = null;
        try {
            return parseDOM(is = new FileInputStream(file));
        } finally {
            try {
                is.close();
            } catch (Exception e) {
            }
        }
    }

    static public Document parseDOM(String filename) throws SAXException, IOException, EngineException {
        return parseDOM(new File(filename));
    }

    static public Document parseDOMFromString(String sDom) throws SAXException, IOException {
        Document dom = getDefaultDocumentBuilder().parse(new InputSource(new StringReader(sDom)));
        return dom;
    }

    public static EntityResolver getEntityResolver() {
        return new EntityResolver() {
            public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
                if ("-//W3C//ENTITIES Latin 1 for XHTML//EN".equals(publicId))
                    return new InputSource(new FileInputStream(Engine.DTD_PATH + "/xhtml-lat1.ent"));
                if ("-//W3C//ENTITIES Special for XHTML//EN".equals(publicId))
                    return new InputSource(new FileInputStream(Engine.DTD_PATH + "/xhtml-special.ent"));
                if ("-//W3C//ENTITIES Symbols for XHTML//EN".equals(publicId))
                    return new InputSource(new FileInputStream(Engine.DTD_PATH + "/xhtml-symbol.ent"));
                if ("-//W3C//DTD XHTML 1.0 Strict//EN".equals(publicId))
                    return new InputSource(new FileInputStream(Engine.DTD_PATH + "/xhtml1-strict.dtd"));
                if ("-//W3C//DTD XHTML 1.0 Transitional//EN".equals(publicId))
                    return new InputSource(new FileInputStream(Engine.DTD_PATH + "/xhtml1-transitional.dtd"));
                return new InputSource(new FileInputStream(systemId));
            }
        };
    }

    public static CatalogResolver getCatalogResolver() {
        return new CatalogResolver() {

            @Override
            public Catalog getCatalog() {
                return super.getCatalog();
            }

            @Override
            public String getResolvedEntity(String publicId, String systemId) {
                return super.getResolvedEntity(publicId, systemId);
            }

            @Override
            public Source resolve(String href, String base) throws TransformerException {
                return super.resolve(href, base);
            }

            @Override
            public InputSource resolveEntity(String publicId, String systemId) {
                try {
                    if ("-//W3C//ENTITIES Latin 1 for XHTML//EN".equals(publicId))
                        return new InputSource(new FileInputStream(Engine.DTD_PATH + "/xhtml-lat1.ent"));
                    if ("-//W3C//ENTITIES Special for XHTML//EN".equals(publicId))
                        return new InputSource(new FileInputStream(Engine.DTD_PATH + "/xhtml-special.ent"));
                    if ("-//W3C//ENTITIES Symbols for XHTML//EN".equals(publicId))
                        return new InputSource(new FileInputStream(Engine.DTD_PATH + "/xhtml-symbol.ent"));
                    if ("-//W3C//DTD XHTML 1.0 Strict//EN".equals(publicId))
                        return new InputSource(new FileInputStream(Engine.DTD_PATH + "/xhtml1-strict.dtd"));
                    if ("-//W3C//DTD XHTML 1.0 Transitional//EN".equals(publicId))
                        return new InputSource(new FileInputStream(Engine.DTD_PATH + "/xhtml1-transitional.dtd"));
                } catch (FileNotFoundException e) {
                }
                return super.resolveEntity(publicId, systemId);
            }
        };
    }

    public static String getCDataText(String s) {
        String cdataText = "";
        try {
            if (!s.equals("")) {
                Document dom = createDom("java");
                Element root = dom.createElement("root");
                CDATASection cDATASection = dom.createCDATASection(s);
                root.appendChild(cDATASection);
                dom.appendChild(root);

                cdataText = prettyPrintElement(root, true, true);
                cdataText = cdataText.replaceAll("<root>", "");
                cdataText = cdataText.replaceAll("</root>", "");

                String cdataStart = "<![CDATA[";
                if (cdataText.startsWith(cdataStart)) {
                    int i = cdataText.substring(cdataStart.length()).indexOf(cdataStart);
                    if (i < 0) {
                        cdataText = cdataText.replaceAll("<!\\[CDATA\\[", "");
                        cdataText = cdataText.replaceAll("\\]\\]>", "");
                    }
                }
            }
        } catch (ParserConfigurationException e) {
        }
        return cdataText;
    }

    public static String getCDataXml(String s) {
        return StringEscapeUtils.escapeXml(getCDataText(s));
    }

    public static int MAX_XML_SIZE_FOR_LOG_INFO = 5;

    public static void logXml(Document document, Logger log, String message) {
        if (document != null && log.isInfoEnabled()) {
            String xml = XMLUtils.prettyPrintDOM(document);

            if (xml.length() > XMLUtils.MAX_XML_SIZE_FOR_LOG_INFO * 1000) {
                if (!log.isDebugEnabled()) {
                    log.info(message + "\n[XML size is > " + XMLUtils.MAX_XML_SIZE_FOR_LOG_INFO
                            + "KB, enable DEBUG log level for this logger to see it completly!]\n"
                            + "[Extract limited to the first " + XMLUtils.MAX_XML_SIZE_FOR_LOG_INFO + "KB]\n"
                            + xml.substring(0, XMLUtils.MAX_XML_SIZE_FOR_LOG_INFO * 1000)
                            + "... (see the complete message in DEBUG log level)");
                }
                log.debug(message + ":\n" + xml);
            } else {
                log.info(message + ":\n" + xml);
            }
        }
    }

    public static Node[] toNodeArray(NodeList nl) {
        Node[] res = new Node[nl.getLength()];
        for (int i = 0; i < res.length; i++) {
            res[i] = nl.item(i);
        }
        return res;
    }

    public static List<Node> toArrayList(NodeList nl) {
        List<Node> res = new ArrayList<Node>();
        for (int i = 0; i < nl.getLength(); i++) {
            res.add(nl.item(i));
        }
        return res;
    }

    public static NodeList toNodeList(List<Node> nl) {
        Document doc = getDefaultDocumentBuilder().newDocument();
        Element root = doc.createElement("root");
        for (Node n : nl) {
            root.appendChild(doc.adoptNode(n));
        }
        return root.getChildNodes();
    }

    public static void spreadNamespaces(Node node, String tns, boolean overwrite) {
        Document doc = node instanceof Document ? (Document) node : node.getOwnerDocument();
        boolean isParent = false;
        while (node != null) {
            Node next = null;
            if (!isParent && node.getNodeType() == Node.ELEMENT_NODE) {
                if (node.getNamespaceURI() == null) {
                    node = doc.renameNode(node, tns, node.getNodeName());
                } else {
                    if (overwrite) {
                        tns = node.getNamespaceURI();
                    }
                }
                NamedNodeMap nodeMap = node.getAttributes();
                int nodeMapLengthl = nodeMap.getLength();
                for (int i = 0; i < nodeMapLengthl; i++) {
                    Node attr = nodeMap.item(i);
                    if (attr.getNamespaceURI() == null) {
                        doc.renameNode(attr, tns, attr.getNodeName());
                    }
                }
            }
            isParent = (isParent || (next = node.getFirstChild()) == null)
                    && (next = node.getNextSibling()) == null;
            node = isParent ? node.getParentNode() : next;
            if (isParent && node != null) {
                if (overwrite) {
                    tns = node.getNamespaceURI();
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    public static <N extends Node> N setNamespace(N node, String tns) {
        return (N) node.getOwnerDocument().renameNode(node, tns, node.getNodeName());
    }

    public static NodeList asNodeList(String... strings) {
        Document doc = getDefaultDocumentBuilder().newDocument();
        Element root = doc.createElement("root");
        for (String string : strings) {
            root.appendChild(doc.createTextNode(string));
        }
        return root.getChildNodes();
    }

    /**
     * Check if a name is XML-compliant.
     * @param xmlName the XML name
     * @return true if the provided name is XML compliant or false otherwise
     */
    public static boolean checkName(String xmlName) {
        try {
            createDom("java").createElement(xmlName);
            return true;
        } catch (DOMException e) {
            return false;
        } catch (ParserConfigurationException e) {
            // Should never occur
            return false;
        }
    }

    public static Document copyDocumentWithoutNamespace(Document document) throws ParserConfigurationException {
        Document newDocument = XMLUtils.createDom("java");
        copyNodeWithoutNamespace(newDocument, newDocument, document);
        return newDocument;
    }

    private static void copyNodeWithoutNamespace(Document document, Node parentNode, Node sourceNode) {
        Node destinationNode;
        if (sourceNode instanceof Document) {
            destinationNode = parentNode;
        } else {
            if (sourceNode instanceof Element) {
                String localName = XMLUtils.getLocalName(sourceNode);
                destinationNode = document.createElement(localName);

                // Copy attributes
                NamedNodeMap attributes = sourceNode.getAttributes();
                for (int i = 0; i < attributes.getLength(); i++) {
                    Node sourceAttribute = attributes.item(i);

                    String prefix = XMLUtils.getPrefix(sourceAttribute);

                    if (!prefix.equalsIgnoreCase("xmlns")) {
                        ((Element) destinationNode).setAttribute(XMLUtils.getLocalName(sourceAttribute),
                                sourceAttribute.getNodeValue());
                    }
                }
            } else {
                destinationNode = document.importNode(sourceNode, false);
            }

            parentNode.appendChild(destinationNode);
        }

        NodeList childNodes = sourceNode.getChildNodes();
        int len = childNodes.getLength();
        for (int i = 0; i < len; i++) {
            XMLUtils.copyNodeWithoutNamespace(document, destinationNode, childNodes.item(i));
        }
    }

    public static String getPrefix(Node node) {
        String prefix = node.getPrefix();
        if (prefix == null) {
            prefix = node.getNodeName();

            // If the document is not namespace aware, we must split the attribute name
            // with ':' character.
            int i = prefix.indexOf(':');
            if (i != -1) {
                prefix = prefix.substring(0, i);
            }
        }

        return prefix;
    }

    public static String getLocalName(Node node) {
        String localName = node.getLocalName();
        if (localName == null) {
            localName = node.getNodeName();

            // If the document is not namespace aware, we must split the tag name
            // with ':' character.
            int i = localName.indexOf(':');
            if (i != -1) {
                localName = localName.substring(i + 1);
            }
        }

        return localName;
    }

    private static Object getValue(Element elt, boolean ignoreStepIds, boolean useType) throws JSONException {
        Object value = null;

        try {
            if (elt.hasAttribute("type")) {
                String type = elt.getAttribute("type");

                if (type.equals("object")) {
                    JSONObject jsonObject = new JSONObject();

                    NodeList nl = elt.getChildNodes();
                    for (int i = 0; i < nl.getLength(); i++) {
                        Node node = nl.item(i);

                        if (node instanceof Element) {
                            Element child = (Element) node;
                            String childName = child.hasAttribute("originalKeyName")
                                    ? child.getAttribute("originalKeyName")
                                    : child.getTagName();
                            Object childValue = getValue(child, ignoreStepIds, useType);

                            if (childValue != null) {
                                jsonObject.put(childName, childValue);
                            } else {
                                handleElement(child, jsonObject, ignoreStepIds, useType);
                            }
                        }
                    }
                    value = jsonObject;
                } else if (type.equals("array")) {
                    JSONArray array = new JSONArray();

                    NodeList nl = elt.getChildNodes();
                    for (int i = 0; i < nl.getLength(); i++) {
                        Node node = nl.item(i);

                        if (node instanceof Element) {
                            Element child = (Element) node;
                            Object childValue = getValue(child, ignoreStepIds, useType);

                            if (childValue != null) {
                                array.put(childValue);
                            } else {
                                JSONObject obj = new JSONObject();
                                array.put(obj);
                                handleElement(child, obj, ignoreStepIds, useType);
                            }
                        }
                    }

                    value = array;
                } else if (type.equals("string")) {
                    value = elt.getTextContent();
                } else if (type.equals("boolean")) {
                    value = Boolean.parseBoolean(elt.getTextContent());
                } else if (type.equals("null")) {
                    value = JSONObject.NULL;
                } else if (type.equals("integer")) {
                    value = Integer.parseInt(elt.getTextContent());
                } else if (type.equals("long")) {
                    value = Long.parseLong(elt.getTextContent());
                } else if (type.equals("double")) {
                    value = Double.parseDouble(elt.getTextContent());
                } else if (type.equals("float")) {
                    value = Float.parseFloat(elt.getTextContent());
                }

                if (value != null) {
                    elt.removeAttribute(type);
                }
            }
        } catch (Throwable t) {
            Engine.logEngine.debug("failed to convert the element " + elt.getTagName(), t);
        }

        return value;
    }

    public static void handleElement(Element elt, JSONObject obj, boolean ignoreStepIds, boolean useType)
            throws JSONException {
        String key = elt.getTagName();
        JSONObject value = new JSONObject();
        NodeList nl = elt.getChildNodes();

        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);

            if (node instanceof Element) {
                Element child = (Element) node;
                Object childValue = useType ? getValue(child, ignoreStepIds, useType) : null;

                if (childValue != null) {
                    value.accumulate(child.getTagName(), childValue);
                } else {
                    handleElement(child, value, ignoreStepIds, useType);
                }
            }
        }

        JSONObject attr = new JSONObject();
        NamedNodeMap nnm = elt.getAttributes();

        for (int i = 0; i < nnm.getLength(); i++) {
            Node node = nnm.item(i);
            if (ignoreStepIds && (node.getNodeName() != "step_id")) {
                attr.accumulate(node.getNodeName(), node.getNodeValue());
            }
        }

        if (value.length() == 0) {
            String content = elt.getTextContent();
            if (attr.length() == 0) {
                obj.accumulate(key, content);
            } else {
                value.accumulate("text", content);
            }
        }

        if (attr.length() != 0) {
            value.accumulate("attr", attr);
        }

        if (value.length() != 0) {
            obj.accumulate(key, value);
        }
    }

    public static String XmlToJson(Element elt, boolean ignoreStepIds) throws JSONException {
        return (XmlToJson(elt, ignoreStepIds, false));
    }

    public static String XmlToJson(Element elt, boolean ignoreStepIds, boolean useType) throws JSONException {
        return (XmlToJson(elt, ignoreStepIds, useType, null));
    }

    public static String XmlToJson(Element elt, boolean ignoreStepIds, boolean useType, JsonRoot jsonRoot)
            throws JSONException {
        JSONObject json = new JSONObject();
        handleElement(elt, json, ignoreStepIds, useType);
        String jsonString = json.toString(1);
        if (jsonRoot != null && !jsonRoot.equals(JsonRoot.docNode)) {
            JSONObject jso = new JSONObject(jsonString).getJSONObject(elt.getTagName());
            if (jsonRoot.equals(JsonRoot.docChildNodes)) {
                jso.remove("attr");
            }
            jsonString = jso.toString(1);
        }
        return jsonString;
    }

    public static void jsonToXml(Object object, Element element) throws JSONException {
        jsonToXml(object, null, element, true, true, false, "item");
    }

    public static void jsonToXml(Object object, String objectKey, Element parentElement, boolean includeDataType,
            boolean compactArray, String arrayChildrenTag) throws JSONException {
        jsonToXml(object, objectKey, parentElement, false, includeDataType, compactArray, arrayChildrenTag);
    }

    private static void jsonToXml(Object object, String objectKey, Element parentElement, boolean modifyElement,
            boolean includeDataType, boolean compactArray, String arrayChildrenTag) throws JSONException {
        Engine.logBeans.trace("Converting JSON to XML: object=" + object + "; objectKey=\"" + objectKey + "\"");

        Document doc = parentElement.getOwnerDocument();

        if ("_attachments".equals(parentElement.getNodeName()) && "item".equals(arrayChildrenTag)
                && object instanceof JSONObject) {
            // special case when retrieving attachments with Couch : attachment name is the object key
            ((JSONObject) object).put("name", objectKey);
            objectKey = "attachment";
        }

        // Normalize object key
        String originalObjectKey = objectKey;
        if (objectKey != null) {
            objectKey = StringUtils.normalize(objectKey);
        }

        // JSON object value case
        if (object instanceof JSONObject) {
            JSONObject json = (JSONObject) object;

            Element element = doc.createElement(objectKey == null ? "object" : objectKey);
            if (objectKey != null && !objectKey.equals(originalObjectKey)) {
                element.setAttribute("originalKeyName", originalObjectKey);
            }

            if (compactArray || modifyElement) {
                if (objectKey == null) {
                    element = parentElement;
                } else {
                    parentElement.appendChild(element);
                }
            } else {
                parentElement.appendChild(element);
            }

            if (includeDataType) {
                element.setAttribute("type", "object");
            }

            String[] keys = new String[json.length()];

            int index = 0;
            for (Iterator<String> i = GenericUtils.cast(json.keys()); i.hasNext();) {
                keys[index++] = i.next();
            }

            Arrays.sort(keys);

            for (String key : keys) {
                jsonToXml(json.get(key), key, element, false, includeDataType, compactArray, arrayChildrenTag);
            }
        }
        // Array value case
        else if (object instanceof JSONArray) {
            JSONArray array = (JSONArray) object;
            int len = array.length();

            Element arrayElement = parentElement;
            String arrayItemObjectKey = arrayChildrenTag;
            if (!(compactArray || modifyElement)) {
                arrayElement = doc.createElement(objectKey == null ? "array" : objectKey);
                if (objectKey != null && !objectKey.equals(originalObjectKey)) {
                    arrayElement.setAttribute("originalKeyName", originalObjectKey);
                }
                parentElement.appendChild(arrayElement);

                if (includeDataType) {
                    arrayElement.setAttribute("type", "array");
                    arrayElement.setAttribute("length", "" + len);
                }
            } else if (objectKey != null) {
                arrayItemObjectKey = objectKey;
            }

            for (int i = 0; i < len; i++) {
                Object itemArray = array.get(i);
                jsonToXml(itemArray, arrayItemObjectKey, arrayElement, false, includeDataType, compactArray,
                        arrayChildrenTag);
            }
        } else {
            Element element = doc.createElement(objectKey == null ? "value" : objectKey);
            if (objectKey != null && !objectKey.equals(originalObjectKey)) {
                element.setAttribute("originalKeyName", originalObjectKey);
            }

            parentElement.appendChild(element);

            if (JSONObject.NULL.equals(object)) {
                object = null;
            }

            if (object != null) {
                Text text = doc.createTextNode(object.toString());
                element.appendChild(text);
            }

            if (includeDataType) {
                String objectType = object == null ? "null" : object.getClass().getCanonicalName();
                if (objectType.startsWith("java.lang.")) {
                    objectType = objectType.substring(10);
                }
                element.setAttribute("type", objectType.toLowerCase());
            }
        }

    }

    public static Charset getEncoding(File file) {
        return getEncoding(file, null);
    }

    public static Charset getEncoding(File file, Charset charset) {
        InputStream is = null;
        try {
            byte[] buffer = new byte[128];
            int nb = (is = new FileInputStream(file)).read(buffer);
            String encoding = new String(buffer, 0, nb, "ASCII")
                    .replaceFirst("[\\d\\D]*encoding=\"(.*?)\"[\\d\\D]*", "$1");
            charset = Charset.forName(encoding);
        } catch (Exception e) {
            Engine.logEngine.debug("failed to detect xml encoding", e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
        }

        return charset == null ? Charset.defaultCharset() : charset;
    }

    public static Charset getEncoding(byte[] bytes, Charset charset) {
        try {
            String encoding = new String(bytes, 0, Math.min(bytes.length, 128), "ASCII")
                    .replaceFirst("[\\d\\D]*encoding=\"(.*?)\"[\\d\\D]*", "$1");
            charset = Charset.forName(encoding);
        } catch (Exception e) {
        }

        return charset == null ? Charset.defaultCharset() : charset;
    }

    public static String stripNonValidXMLCharacters(String text) {
        if (text == null || ("".equals(text))) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            int codePoint = text.codePointAt(i);
            if (codePoint > 0xFFFF) {
                i++;
            }
            if ((codePoint == 0x9) || (codePoint == 0xA) || (codePoint == 0xD)
                    || ((codePoint >= 0x20) && (codePoint <= 0xD7FF))
                    || ((codePoint >= 0xE000) && (codePoint <= 0xFFFD))
                    || ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF))) {
                sb.appendCodePoint(codePoint);
            }
        }
        return sb.toString();
    }
}