Package org.jboss.fresh.xml

Source Code of org.jboss.fresh.xml.XMLHelper

package org.jboss.fresh.xml;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.net.URL;
import java.net.URLConnection;
import java.net.MalformedURLException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;
import javax.xml.XMLConstants;

import org.apache.log4j.Logger;
import org.apache.xerces.parsers.DOMParser;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XML11Serializer;
import org.apache.xpath.CachedXPathAPI;
import org.apache.xpath.XPathAPI;
import org.apache.xpath.axes.NodeSequence;
import org.apache.xpath.objects.XBoolean;
import org.apache.xpath.objects.XNodeSet;
import org.apache.xpath.objects.XNull;
import org.apache.xpath.objects.XNumber;
import org.apache.xpath.objects.XObject;
import org.w3c.dom.*;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

/**
* <p>Supporting class for working with XMLs. Contains some usefull functions, that are usually just a
* few lines of code with some logging added, but your code will be more clear if you use
* this class.</p>
*
* @author boky
* @version $Revision: 3885 $
* @modified $Author: boky $
*/
public final class XMLHelper {
  private static Logger log = Logger.getLogger(XMLHelper.class);
  private static Logger logXPath = Logger.getLogger(XMLHelper.class.getName() + ".xpath");
  private static final Map<Class<? extends Element>, MethodCache> DOM3_SUPPORT = new HashMap<Class<? extends Element>, MethodCache>();
  public static final Pattern ENTITY_PATTERN = Pattern.compile("&(?:[^#])[a-zA-Z]+?[a-zA-Z0-9]*?;");
  public static final Map<String, String> ENTITIES;

  static class MethodCache {
    Method lookupNamespaceURI;
    Method lookupPrefix;
    Class<? extends Element> clazz;
    boolean ok = false;
    boolean lookupDone = false;

    public MethodCache() {
      super();
    }

    public MethodCache(Method lookupNamespaceURI, Method lookupPrefix, Class<? extends Element> clazz, boolean ok, boolean lookupDone) {
      this.lookupNamespaceURI = lookupNamespaceURI;
      this.lookupPrefix = lookupPrefix;
      this.clazz = clazz;
      this.ok = ok;
      this.lookupDone = lookupDone;
    }
  }

  static {
    Map<String, String> entities = new HashMap<String, String>();
    entities.put("&nbsp;", "&#160;");
    entities.put("&iexcl;", "&#161;");
    entities.put("&cent;", "&#162;");
    entities.put("&pound;", "&#163;");
    entities.put("&curren;", "&#164;");
    entities.put("&yen;", "&#165;");
    entities.put("&brvbar;", "&#166;");
    entities.put("&sect;", "&#167;");
    entities.put("&uml;", "&#168;");
    entities.put("&copy;", "&#169;");
    entities.put("&ordf;", "&#170;");
    entities.put("&laquo;", "&#171;");
    entities.put("&not;", "&#172;");
    entities.put("&shy;", "&#173;");
    entities.put("&reg;", "&#174;");
    entities.put("&macr;", "&#175;");
    entities.put("&deg;", "&#176;");
    entities.put("&plusmn;", "&#177;");
    entities.put("&sup2;", "&#178;");
    entities.put("&sup3;", "&#179;");
    entities.put("&acute;", "&#180;");
    entities.put("&micro;", "&#181;");
    entities.put("&para;", "&#182;");
    entities.put("&middot;", "&#183;");
    entities.put("&cedil;", "&#184;");
    entities.put("&sup1;", "&#185;");
    entities.put("&ordm;", "&#186;");
    entities.put("&raquo;", "&#187;");
    entities.put("&frac14;", "&#188;");
    entities.put("&frac12;", "&#189;");
    entities.put("&frac34;", "&#190;");
    entities.put("&iquest;", "&#191;");
    entities.put("&Agrave;", "&#192;");
    entities.put("&Aacute;", "&#193;");
    entities.put("&Acirc;", "&#194;");
    entities.put("&Atilde;", "&#195;");
    entities.put("&Auml;", "&#196;");
    entities.put("&Aring;", "&#197;");
    entities.put("&AElig;", "&#198;");
    entities.put("&Ccedil;", "&#199;");
    entities.put("&Egrave;", "&#200;");
    entities.put("&Eacute;", "&#201;");
    entities.put("&Ecirc;", "&#202;");
    entities.put("&Euml;", "&#203;");
    entities.put("&Igrave;", "&#204;");
    entities.put("&Iacute;", "&#205;");
    entities.put("&Icirc;", "&#206;");
    entities.put("&Iuml;", "&#207;");
    entities.put("&ETH;", "&#208;");
    entities.put("&Ntilde;", "&#209;");
    entities.put("&Ograve;", "&#210;");
    entities.put("&Oacute;", "&#211;");
    entities.put("&Ocirc;", "&#212;");
    entities.put("&Otilde;", "&#213;");
    entities.put("&Ouml;", "&#214;");
    entities.put("&times;", "&#215;");
    entities.put("&Oslash;", "&#216;");
    entities.put("&Ugrave;", "&#217;");
    entities.put("&Uacute;", "&#218;");
    entities.put("&Ucirc;", "&#219;");
    entities.put("&Uuml;", "&#220;");
    entities.put("&Yacute;", "&#221;");
    entities.put("&THORN;", "&#222;");
    entities.put("&szlig;", "&#223;");
    entities.put("&agrave;", "&#224;");
    entities.put("&aacute;", "&#225;");
    entities.put("&acirc;", "&#226;");
    entities.put("&atilde;", "&#227;");
    entities.put("&auml;", "&#228;");
    entities.put("&aring;", "&#229;");
    entities.put("&aelig;", "&#230;");
    entities.put("&ccedil;", "&#231;");
    entities.put("&egrave;", "&#232;");
    entities.put("&eacute;", "&#233;");
    entities.put("&ecirc;", "&#234;");
    entities.put("&euml;", "&#235;");
    entities.put("&igrave;", "&#236;");
    entities.put("&iacute;", "&#237;");
    entities.put("&icirc;", "&#238;");
    entities.put("&iuml;", "&#239;");
    entities.put("&eth;", "&#240;");
    entities.put("&ntilde;", "&#241;");
    entities.put("&ograve;", "&#242;");
    entities.put("&oacute;", "&#243;");
    entities.put("&ocirc;", "&#244;");
    entities.put("&otilde;", "&#245;");
    entities.put("&ouml;", "&#246;");
    entities.put("&divide;", "&#247;");
    entities.put("&oslash;", "&#248;");
    entities.put("&ugrave;", "&#249;");
    entities.put("&uacute;", "&#250;");
    entities.put("&ucirc;", "&#251;");
    entities.put("&uuml;", "&#252;");
    entities.put("&yacute;", "&#253;");
    entities.put("&thorn;", "&#254;");
    entities.put("&yuml;", "&#255;");
    entities.put("&fnof;", "&#402;");
    entities.put("&Alpha;", "&#913;");
    entities.put("&Beta;", "&#914;");
    entities.put("&Gamma;", "&#915;");
    entities.put("&Delta;", "&#916;");
    entities.put("&Epsilon;", "&#917;");
    entities.put("&Zeta;", "&#918;");
    entities.put("&Eta;", "&#919;");
    entities.put("&Theta;", "&#920;");
    entities.put("&Iota;", "&#921;");
    entities.put("&Kappa;", "&#922;");
    entities.put("&Lambda;", "&#923;");
    entities.put("&Mu;", "&#924;");
    entities.put("&Nu;", "&#925;");
    entities.put("&Xi;", "&#926;");
    entities.put("&Omicron;", "&#927;");
    entities.put("&Pi;", "&#928;");
    entities.put("&Rho;", "&#929;");
    entities.put("&Sigma;", "&#931;");
    entities.put("&Tau;", "&#932;");
    entities.put("&Upsilon;", "&#933;");
    entities.put("&Phi;", "&#934;");
    entities.put("&Chi;", "&#935;");
    entities.put("&Psi;", "&#936;");
    entities.put("&Omega;", "&#937;");
    entities.put("&alpha;", "&#945;");
    entities.put("&beta;", "&#946;");
    entities.put("&gamma;", "&#947;");
    entities.put("&delta;", "&#948;");
    entities.put("&epsilon;", "&#949;");
    entities.put("&zeta;", "&#950;");
    entities.put("&eta;", "&#951;");
    entities.put("&theta;", "&#952;");
    entities.put("&iota;", "&#953;");
    entities.put("&kappa;", "&#954;");
    entities.put("&lambda;", "&#955;");
    entities.put("&mu;", "&#956;");
    entities.put("&nu;", "&#957;");
    entities.put("&xi;", "&#958;");
    entities.put("&omicron;", "&#959;");
    entities.put("&pi;", "&#960;");
    entities.put("&rho;", "&#961;");
    entities.put("&sigmaf;", "&#962;");
    entities.put("&sigma;", "&#963;");
    entities.put("&tau;", "&#964;");
    entities.put("&upsilon;", "&#965;");
    entities.put("&phi;", "&#966;");
    entities.put("&chi;", "&#967;");
    entities.put("&psi;", "&#968;");
    entities.put("&omega;", "&#969;");
    entities.put("&thetasym;", "&#977;");
    entities.put("&upsih;", "&#978;");
    entities.put("&piv;", "&#982;");
    entities.put("&bull;", "&#8226;");
    entities.put("&hellip;", "&#8230;");
    entities.put("&prime;", "&#8242;");
    entities.put("&Prime;", "&#8243;");
    entities.put("&oline;", "&#8254;");
    entities.put("&frasl;", "&#8260;");
    entities.put("&weierp;", "&#8472;");
    entities.put("&image;", "&#8465;");
    entities.put("&real;", "&#8476;");
    entities.put("&trade;", "&#8482;");
    entities.put("&alefsym;", "&#8501;");
    entities.put("&larr;", "&#8592;");
    entities.put("&uarr;", "&#8593;");
    entities.put("&rarr;", "&#8594;");
    entities.put("&darr;", "&#8595;");
    entities.put("&harr;", "&#8596;");
    entities.put("&crarr;", "&#8629;");
    entities.put("&lArr;", "&#8656;");
    entities.put("&uArr;", "&#8657;");
    entities.put("&rArr;", "&#8658;");
    entities.put("&dArr;", "&#8659;");
    entities.put("&hArr;", "&#8660;");
    entities.put("&forall;", "&#8704;");
    entities.put("&part;", "&#8706;");
    entities.put("&exist;", "&#8707;");
    entities.put("&empty;", "&#8709;");
    entities.put("&nabla;", "&#8711;");
    entities.put("&isin;", "&#8712;");
    entities.put("&notin;", "&#8713;");
    entities.put("&ni;", "&#8715;");
    entities.put("&prod;", "&#8719;");
    entities.put("&sum;", "&#8721;");
    entities.put("&minus;", "&#8722;");
    entities.put("&lowast;", "&#8727;");
    entities.put("&radic;", "&#8730;");
    entities.put("&prop;", "&#8733;");
    entities.put("&infin;", "&#8734;");
    entities.put("&ang;", "&#8736;");
    entities.put("&and;", "&#8743;");
    entities.put("&or;", "&#8744;");
    entities.put("&cap;", "&#8745;");
    entities.put("&cup;", "&#8746;");
    entities.put("&int;", "&#8747;");
    entities.put("&there4;", "&#8756;");
    entities.put("&sim;", "&#8764;");
    entities.put("&cong;", "&#8773;");
    entities.put("&asymp;", "&#8776;");
    entities.put("&ne;", "&#8800;");
    entities.put("&equiv;", "&#8801;");
    entities.put("&le;", "&#8804;");
    entities.put("&ge;", "&#8805;");
    entities.put("&sub;", "&#8834;");
    entities.put("&sup;", "&#8835;");
    entities.put("&nsub;", "&#8836;");
    entities.put("&sube;", "&#8838;");
    entities.put("&supe;", "&#8839;");
    entities.put("&oplus;", "&#8853;");
    entities.put("&otimes;", "&#8855;");
    entities.put("&perp;", "&#8869;");
    entities.put("&sdot;", "&#8901;");
    entities.put("&lceil;", "&#8968;");
    entities.put("&rceil;", "&#8969;");
    entities.put("&lfloor;", "&#8970;");
    entities.put("&rfloor;", "&#8971;");
    entities.put("&lang;", "&#9001;");
    entities.put("&rang;", "&#9002;");
    entities.put("&loz;", "&#9674;");
    entities.put("&spades;", "&#9824;");
    entities.put("&clubs;", "&#9827;");
    entities.put("&hearts;", "&#9829;");
    entities.put("&diams;", "&#9830;");
    entities.put("&quot;", "&#34;");
    entities.put("&amp;", "&#38;");
    entities.put("&apos;", "&#39;");
    entities.put("&lt;", "&#60;");
    entities.put("&gt;", "&#62;");
    entities.put("&OElig;", "&#338;");
    entities.put("&oelig;", "&#339;");
    entities.put("&Scaron;", "&#352;");
    entities.put("&scaron;", "&#353;");
    entities.put("&Yuml;", "&#376;");
    entities.put("&circ;", "&#710;");
    entities.put("&tilde;", "&#732;");
    entities.put("&ensp;", "&#8194;");
    entities.put("&emsp;", "&#8195;");
    entities.put("&thinsp;", "&#8201;");
    entities.put("&zwnj;", "&#8204;");
    entities.put("&zwj;", "&#8205;");
    entities.put("&lrm;", "&#8206;");
    entities.put("&rlm;", "&#8207;");
    entities.put("&ndash;", "&#8211;");
    entities.put("&mdash;", "&#8212;");
    entities.put("&lsquo;", "&#8216;");
    entities.put("&rsquo;", "&#8217;");
    entities.put("&sbquo;", "&#8218;");
    entities.put("&ldquo;", "&#8220;");
    entities.put("&rdquo;", "&#8221;");
    entities.put("&bdquo;", "&#8222;");
    entities.put("&dagger;", "&#8224;");
    entities.put("&Dagger;", "&#8225;");
    entities.put("&permil;", "&#8240;");
    entities.put("&lsaquo;", "&#8249;");
    entities.put("&rsaquo;", "&#8250;");
    entities.put("&euro;", "&#8364;");
    ENTITIES = Collections.unmodifiableMap(entities);
  }

  public static Object executeXpathQuery(final Node contextNode, final String str) throws javax.xml.transform.TransformerException {
    return executeXpathQuery(null, contextNode, str, null);
  }

  public static Object executeXpathQuery(final Node contextNode, final String str, final Node namespaceNode) throws javax.xml.transform.TransformerException {
    return executeXpathQuery(null, contextNode, str, namespaceNode);
  }

  public static Object executeXpathQuery(final CachedXPathAPI cachedXPath, final Node contextNode, final String str) throws javax.xml.transform.TransformerException {
    return executeXpathQuery(cachedXPath, contextNode, str, null);
  }

  public static class LocalPrefixResolver implements org.apache.xml.utils.PrefixResolver {
    final Node prefixNode;
    private static final String XMLNS_NS = "http://www.w3.org/2000/xmlns/";

    LocalPrefixResolver(final Node prefixNode) {
      this.prefixNode = prefixNode;
    }

    public String getNamespaceForPrefix(final String prefix) {
      return getNamespaceForPrefix(prefix, prefixNode);
    }

    public String getNamespaceForPrefix(String prefix, Node context) {
      if (prefix == null) {
        prefix = "";
      }

      if (context == null) {
        context = prefixNode;
      }

      while(!(context instanceof Element || context == null)) {
        context = context.getParentNode();
      }
     
      return lookupNamespaceURI((Element) context, prefix);
      // return getNamespaceForPrefix(prefix, (Element) context, (Element) context.getOwnerDocument().getFirstChild());
    }

    static String getNamespaceForPrefix(String prefix, final Element ctx, final Element root) {
      String result = null;
      if ("".equals(prefix)) {
        result = ctx.getAttribute("xmlns");
      } else {
        try {
          result = ctx.getAttributeNS(XMLNS_NS, prefix);
        } catch (NullPointerException e) {
          log.error("NullPointer!\r\nctx=" + ctx + "\r\nprefix=" + prefix + "\r\nns=" + XMLNS_NS, e);
          throw e;
        }
        if (result == null) {
          result = ctx.getAttribute("xmlns:" + prefix);
        }
      }

      if (result == null) {
        if (ctx == root) {
          log.warn("Could not resolve prefix '" + prefix + "' to namespace!");
          return null;
        }
        return getNamespaceForPrefix(prefix, (Element) ctx.getParentNode(), root);
      }

      return result;

    }


    public String getBaseIdentifier() {
      return null;
    }

    public boolean handlesNullPrefixes() {
      return true;
    }
  }

  /**
   * Executes the given <code>query</code> on the <code>contextNode</code>
   * and returns the result. The resulting {@link XObject} is then converted
   * to a corresponding java type using the following rules: <ul>
   * <li><code>null</code> stays <code>null</code></li>
   * <li>{@link org.apache.xpath.objects.XBoolean} becomes {@link Boolean}</li>
   * <li>{@link org.apache.xpath.objects.XNodeSet} becomes {@link NodeList}</li>
   * <li>{@link org.apache.xpath.objects.XNumber} becomes a {@link java.math.BigDecimal}</li>
   * <li>Anything else is casted to a {@link String} using {@link XObject#str}.</li>
   * </ul>
   *
   * @param contextNode The context {@link Node} for the query.
   * @param query     The XPath query.
   * @return The resulting objects.
   * @throws javax.xml.transform.TransformerException
   *          Thrown if there was an error in the
   *          query.
   */
  public static Object executeXpathQuery(final CachedXPathAPI cachedXPath,
                            final Node contextNode,
                            final String query,
                            final Node namespaceNode) throws javax.xml.transform.TransformerException {
    if (contextNode == null) {
      logXPath.info("contextNode is null. Returning null.");
      return null;
    }
    if (query == null || "".equals(query)) {
      logXPath.info("query is null. Returning null.");
      return null;
    }
    try {
      if (log.isDebugEnabled()) {
        log.debug("xpath query " + query + " on context " + getXML(contextNode) + " with namespaces from " + (namespaceNode == null ? "<none>" : getXML(namespaceNode)));
      }


      XObject x;
      if (cachedXPath != null) {
        if (namespaceNode != null) {
          x = cachedXPath.eval(contextNode, query, new LocalPrefixResolver(namespaceNode));
        } else {
          x = cachedXPath.eval(contextNode, query);
        }
      } else {
        if (namespaceNode != null) {
          x = XPathAPI.eval(contextNode, query, new LocalPrefixResolver(namespaceNode));
        } else {
          x = XPathAPI.eval(contextNode, query);
        }
      }
      if (x == null || x instanceof XNull) {
        return null;
      } else if (XObject.CLASS_BOOLEAN == x.getType() || x instanceof XBoolean) {
        return Boolean.valueOf(x.bool());
      } else if (XObject.CLASS_NODESET == x.getType() || x instanceof XNodeSet || x instanceof NodeSequence) {
        return x.nodelist();
      } else if (XObject.CLASS_NUMBER == x.getType() || x instanceof XNumber) {
        return new java.math.BigDecimal(x.str());
      } else if (XObject.CLASS_UNRESOLVEDVARIABLE == x.getType()) {
        return new java.math.BigDecimal(x.str());
      } else if (XObject.CLASS_RTREEFRAG == x.getType()) {
        return x.xstr().toString();
      } else {
        return x.str();
      }
    } catch (javax.xml.transform.TransformerException te) {
      if ("org.apache.xpath.domapi.XPathStylesheetDOM3Exception".equals(te.getClass().getName())) {
        logXPath.warn(
          "context: <" + contextNode.getNodeName() + ">"
            + "\r\nnamespace: <" + namespaceNode.getNodeName() + ">"
            + "\r\nquery: " + query
            + "\r\nroot:\r\n" + getXML(contextNode.getOwnerDocument()), te);
      } else {
        logXPath.warn('<' + contextNode.getNodeName() + ">: " + query, te);
      }
      throw te;
    }
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectSingleNode} just that does
   * some usefull logging.
   *
   * @param contextNode Context note where the search starts
   * @param str      XPath query
   * @return Returns the {@link Node} if found, null if not found
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static Node selectSingleNode(final Node contextNode,
                          final String str) throws javax.xml.transform.TransformerException {
    return selectSingleNode(contextNode, str, null);
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectSingleNode} just that does
   * some usefull logging.
   *
   * @param contextNode  Context note where the search starts
   * @param str        XPath query
   * @param namespaceNode {@link Node} with namespaces
   * @return Returns the {@link Node} if found, null if not found
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static Node selectSingleNode(final Node contextNode, final String str,
                          final Node namespaceNode) throws javax.xml.transform.TransformerException {
    return selectSingleNode(null, contextNode, str, namespaceNode);
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectSingleNode} just that does
   * some usefull logging.
   *
   * @param cachedXPath An instante of {@link CachedXPathAPI} useful to speed-up very slow
   *                    XPath lookups. {@link CachedXPathAPI} is valid only as long as the XML document
   *                    does not change.
   * @param contextNode Context note where the search starts
   * @param str      XPath query
   * @return Returns the {@link Node} if found, null if not found
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static Node selectSingleNode(final CachedXPathAPI cachedXPath,
                          final Node contextNode,
                          final String str) throws javax.xml.transform.TransformerException {
    return selectSingleNode(cachedXPath, contextNode, str, null);
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectSingleNode} just that does
   * some usefull logging.
   *
   * @param cachedXPath  An instante of {@link CachedXPathAPI} useful to speed-up very slow
   *                      XPath lookups. {@link CachedXPathAPI} is valid only as long as the XML document
   *                      does not change.
   * @param contextNode  Context note where the search starts
   * @param str        XPath query
   * @param namespaceNode {@link Node} with namespaces
   * @return Returns the {@link Node} if found, null if not found
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static Node selectSingleNode(final CachedXPathAPI cachedXPath,
                          final Node contextNode,
                          final String str,
                          final Node namespaceNode) throws javax.xml.transform.TransformerException {
    final Object result = executeXpathQuery(cachedXPath, contextNode, str, namespaceNode);
    if (result == null || result instanceof NodeList && ((NodeList) result).getLength() == 0) {
      return null;
    } else if (result instanceof NodeList) {
      return ((NodeList) result).item(0);
    } else if (result instanceof Node) {
      return (Node) result;
    } else {
      logXPath.warn('<' + contextNode.getNodeName() + ">, " +
        (namespaceNode == null ? "no namespace" : "namespace <" + namespaceNode.getNodeName() + '>') +
        ": unexpected result of '" + str + "'. " +
        "Expected Node, got " + result.getClass().getName() + '.');
      return null;
    }
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectNodeList} that does
   * some usefull logging.
   * XPath lookups. {@link CachedXPathAPI} is valid only as long as the XML document
   * does not change.
   *
   * @param contextNode Context {@link Node} where the search starts
   * @param str      XPath query
   * @return Returns the {@link NodeList}, null if <code>contextNode</code> or <code>str</code> is null
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static NodeList selectNodeList(final Node contextNode,
                            final String str) throws javax.xml.transform.TransformerException {
    return selectNodeList(null, contextNode, str, null);
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectNodeList} that does
   * some usefull logging.
   * XPath lookups. {@link CachedXPathAPI} is valid only as long as the XML document
   * does not change.
   *
   * @param contextNode  Context {@link Node} where the search starts
   * @param str        XPath query
   * @param namespaceNode {@link Node} with namespaces
   * @return Returns the {@link NodeList}, null if <code>contextNode</code> or <code>str</code> is null
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static NodeList selectNodeList(final Node contextNode,
                            final String str,
                            final Node namespaceNode) throws javax.xml.transform.TransformerException {
    return selectNodeList(null, contextNode, str, namespaceNode);
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectNodeList} that does
   * some usefull logging.
   *
   * @param cachedXPath An instante of {@link CachedXPathAPI} useful to speed-up very slow
   *                    XPath lookups. {@link CachedXPathAPI} is valid only as long as the XML document
   *                    does not change.
   * @param contextNode Context {@link Node} where the search starts
   * @param str      XPath query
   * @return Returns the {@link NodeList}, null if <code>contextNode</code> or <code>str</code> is null
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static NodeList selectNodeList(final CachedXPathAPI cachedXPath,
                            final Node contextNode,
                            final String str) throws javax.xml.transform.TransformerException {
    return selectNodeList(cachedXPath, contextNode, str, null);
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectNodeList} that does
   * some usefull logging.
   *
   * @param cachedXPath  An instante of {@link CachedXPathAPI} useful to speed-up very slow
   *                      XPath lookups. {@link CachedXPathAPI} is valid only as long as the XML document
   *                      does not change.
   * @param contextNode  Context {@link Node} where the search starts
   * @param str        XPath query
   * @param namespaceNode {@link Node} with namespaces
   * @return Returns the {@link NodeList}, null if <code>contextNode</code> or <code>str</code> is null
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static NodeList selectNodeList(final CachedXPathAPI cachedXPath,
                            final Node contextNode,
                            final String str,
                            final Node namespaceNode) throws javax.xml.transform.TransformerException {
    final Object result = executeXpathQuery(cachedXPath, contextNode, str, namespaceNode);
    if (result == null) {
      return null;
    } else if (result instanceof NodeList) {
      return (NodeList) result;
    } else if (result instanceof Node) {
      return new ArrayNodeList(new Node[]{(Node) result});
    } else {
      logXPath.warn(new StringBuffer()
        .append('<').append(contextNode.getNodeName()).append(">, ")
        .append(namespaceNode == null ? "no namespace" : "namespace <" + namespaceNode.getNodeName() + '>')
        .append(": unexpected result of '").append(str).append("'. ")
        .append("Expected Node, got ").append(result.getClass().getName())
        .append('.')
        .toString());
      return null;
    }
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectNodeIterator} that does
   * some usefull logging.
   *
   * @param contextNode Context {@link Node} where the search starts
   * @param str      XPath query
   * @return Returns the {@link NodeIterator}, null if <code>contextNode</code> or <code>str</code> is null
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static NodeIterator selectNodeIterator(final Node contextNode,
                                 final String str) throws javax.xml.transform.TransformerException {
    return selectNodeIterator(null, contextNode, str, null);
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectNodeIterator} that does
   * some usefull logging.
   *
   * @param contextNode  Context {@link Node} where the search starts
   * @param str        XPath query
   * @param namespaceNode {@link Node} with namespaces
   * @return Returns the {@link NodeIterator}, null if <code>contextNode</code> or <code>str</code> is null
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static NodeIterator selectNodeIterator(final Node contextNode,
                                 final String str,
                                 final Node namespaceNode) throws javax.xml.transform.TransformerException {
    return selectNodeIterator(null, contextNode, str, namespaceNode);
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectNodeIterator} that does
   * some usefull logging.
   *
   * @param cachedXPath An instante of {@link CachedXPathAPI} useful to speed-up very slow
   *                    XPath lookups. {@link CachedXPathAPI} is valid only as long as the XML document
   *                    does not change.
   * @param contextNode Context {@link Node} where the search starts
   * @param str      XPath query
   * @return Returns the {@link NodeIterator}, null if <code>contextNode</code> or <code>str</code> is null
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static NodeIterator selectNodeIterator(final CachedXPathAPI cachedXPath,
                                 final Node contextNode,
                                 final String str) throws javax.xml.transform.TransformerException {
    return selectNodeIterator(cachedXPath, contextNode, str, null);
  }

  /**
   * Wrapper for the {@link org.apache.xpath.XPathAPI#selectNodeIterator} that does
   * some usefull logging.
   *
   * @param cachedXPath  An instante of {@link CachedXPathAPI} useful to speed-up very slow
   *                      XPath lookups. {@link CachedXPathAPI} is valid only as long as the XML document
   *                      does not change.
   * @param contextNode  Context {@link Node} where the search starts
   * @param str        XPath query
   * @param namespaceNode {@link Node} with namespaces
   * @return Returns the {@link NodeIterator}, null if <code>contextNode</code> or <code>str</code> is null
   * @throws javax.xml.transform.TransformerException
   *          Thrown if the query is invalid or something
   */
  public static NodeIterator selectNodeIterator(final CachedXPathAPI cachedXPath,
                                 final Node contextNode,
                                 final String str,
                                 final Node namespaceNode) throws javax.xml.transform.TransformerException {
    if (contextNode == null) {
      logXPath.info("contextNode is null. Returning null.");
      return null;
    }
    if (str == null || "".equals(str)) {
      logXPath.info("str is null. Returning null.");
      return null;
    }
    try {
      NodeIterator x;
      if (cachedXPath != null) {
        if (namespaceNode != null) {
          x = cachedXPath.selectNodeIterator(contextNode, str, namespaceNode);
        } else {
          x = cachedXPath.selectNodeIterator(contextNode, str);
        }
      } else {
        if (namespaceNode != null) {
          x = XPathAPI.selectNodeIterator(contextNode, str, namespaceNode);
        } else {
          x = XPathAPI.selectNodeIterator(contextNode, str);
        }
      }
      return x;
    } catch (javax.xml.transform.TransformerException te) {
      logXPath.warn('<' + contextNode.getNodeName() + ">: " + str, te);
      throw te;
    }
  }

  /**
   * <p>Returns an element with the given ID. Transverses through the whole tree, starting
   * with <code>root</code>. The first node with the attribute ID which is equal
   * to <code>id</code> is returned. If there's no match, the function will return
   * <code>null</code>.</p>
   *
   * @param root Root specifying where to start search.
   * @param id  ID to look for. If the ID is <code>null</code>, the method returns <code>null</code>.
   * @return The {@link org.w3c.dom.Node} found, or <code>null</code> if not found.
   */
  public static Node getElementById(Node root, String id) {
    if (id == null) {
      log.warn("Passed id==<null> into getElementById(). Returning null");
      return null;
    }
    String id_attr = ((Element) root).getAttribute("id");
    log.debug("Comparing <" + root.getLocalName() + " id='" + id_attr + "'> with param id='" + id + "'.");
    if (id.equalsIgnoreCase(id_attr)) {
      log.info("Node found. Returning it.");
      return root;
    } else {
      NodeList children = root.getChildNodes();
      if (children != null) {
        org.w3c.dom.Node res;
        res = null;
        for (int i = 0; i < children.getLength(); i++) {
          if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
            res = getElementById(children.item(i), id);
          }
          if (res != null) {
            return res;
          }
        }
      }
      return null;
    }
  }

  public static TransformerFactory newTransformerFactory() {
    return new org.apache.xalan.processor.TransformerFactoryImpl();
  }

  /**
   * Same as calling <code>{@link #getXML}(node, false)</code>
   *
   * @param node <code>Node</code> to transform (please note:
   *             only {@link Document}s, {@link Element}s and {@link DocumentFragment}s
   *             can be transformed; other nodes will throw a {@link RuntimeException}.
   * @return The resulting XML.
   */
  public static String getXML(Node node) {
    return getXML(node, false);
  }

  /**
   * Same as calling <code>{@link #getXML}(element, false)</code>
   *
   * @param element <code>Element</code> to transform (please note:
   *                only {@link Document}s, {@link Element}s and {@link DocumentFragment}s
   *                can be transformed; other nodes will throw a {@link RuntimeException}.
   * @return The resulting XML.
   */
  public static String getXML(Element element) {
    return getXML((Node) element, false);
  }

  /**
   * Same as calling <code>{@link #getXML}(doc, false)</code>
   *
   * @param doc <code>Document</code> to transform (please note:
   *            only {@link Document}s, {@link Element}s and {@link DocumentFragment}s
   *            can be transformed; other nodes will throw a {@link RuntimeException}.
   * @return The resulting XML.
   */
  public static String getXML(Document doc) {
    return getXML((Node) doc, false);
  }

  /**
   * Same as calling <code>{@link #getXML}(df, false)</code>
   *
   * @param df <code>DocumentFragment</code> to transform (please note:
   *           only {@link Document}s, {@link Element}s and {@link DocumentFragment}s
   *           can be transformed; other nodes will throw a {@link RuntimeException}.
   * @return The resulting XML.
   */
  public static String getXML(DocumentFragment df) {
    return getXML((Node) df, false);
  }

  /**
   * Same as calling <code>{@link #getXML}((Node) element, indent)</code>
   *
   * @param element <code>Element</code> to transform (please note:
   *                only {@link Document}s, {@link Element}s and {@link DocumentFragment}s
   *                can be transformed; other nodes will throw a {@link RuntimeException}.
   * @return The resulting XML.
   */
  public static String getXML(Element element, boolean indent) {
    return getXML((Node) element, indent);
  }

  /**
   * Same as calling <code>{@link #getXML}((Node) doc, indent)</code>
   *
   * @param doc <code>Document</code> to transform (please note:
   *            only {@link Document}s, {@link Element}s and {@link DocumentFragment}s
   *            can be transformed; other nodes will throw a {@link RuntimeException}.
   * @return The resulting XML.
   */
  public static String getXML(Document doc, boolean indent) {
    return getXML((Node) doc, indent);
  }

  /**
   * Same as calling <code>{@link #getXML}((Node) df, indent)</code>
   *
   * @param df <code>DocumentFragment</code> to transform (please note:
   *           only {@link Document}s, {@link Element}s and {@link DocumentFragment}s
   *           can be transformed; other nodes will throw a {@link RuntimeException}.
   * @return The resulting XML.
   */
  public static String getXML(DocumentFragment df, boolean indent) {
    return getXML((Node) df, indent);
  }

  /**
   * <p>This method will transform given {@link Node} into a String.</p>
   * <p>If DOM level 3 support is available, the DOM3 serializer will be used (which
   * supports namespaces), otherwise, the standard serializer will be used. Standard serializer
   * has no defined namespace support and each parser implementation handles it differently.
   * For example, Xalan wil happily strip all namespaces from your XML. So be altert when
   * serializing XMLs with namespaces.</p>
   * <p>If you have no namespaces in your document, this should not concearn you.</p>
   *
   * @param node  <code>Node</code> to transform
   * @param indent Set to true to have indented XML output
   * @return The resulting XML.
   */
  public static String getXML(Node node, boolean indent) {
    if (node == null) {
      throw new NullPointerException("Trying to serialize <null>, eh? That cannot be done, sorry.");
    }
    if (!(node instanceof Element || node instanceof Document || node instanceof DocumentFragment)) {
      throw new InvalidNodeRuntimeException("getXML() is only capable of transforming nodes of type " +
        Element.class.getName() + ", " +
        Document.class.getName() + " and " +
        DocumentFragment.class.getName() +
        ", but you passed in " + node.getClass().getName() + '!', node
      );
    }
    OutputFormat format;
    StringWriter stringOut;
    XML11Serializer serial;
    if (node.getOwnerDocument() == null) {
      format = new OutputFormat(org.apache.xml.serialize.Method.XML, null, true);
    } else {
      format = new OutputFormat(node.getOwnerDocument(), null, true);
    }
    format.setOmitXMLDeclaration(true);
    if (indent) {
      format.setIndent(2);
      format.setLineSeparator("\r\n");
    } else {
      format.setIndent(0);
      format.setLineSeparator("");
    }
    stringOut = new StringWriter();
    serial = new XML11Serializer(stringOut, format);
    serial.setNamespaces(true);

    // serial.asDOMSerializer();

    try {
      if (node instanceof Element) {
        serial.serialize((Element) node);
      } else if (node instanceof Document) {
        serial.serialize((Document) node);
      } else {
        serial.serialize((DocumentFragment) node);
      }
      return stringOut.toString();
    } catch (java.io.IOException ioe) {
      throw new IOErrorRuntimeException("Strange error. IOException occurred where it cannot!", ioe);
    }
  }

  /**
   * <p>Creates a new, empty {@link Document}.</p>
   *
   * @return New DOM Document.
   */
  public static Document getDocument() {
    return new org.apache.xerces.dom.DocumentImpl();
  }


  /**
   * <p>Parses the given string into a {@link Document}.</p>
   *
   * @param xml String to parse
   * @return The <code>Document</code>, parsed from String.
   * @throws SAXException Thrown if a parsing error occurs.
   * @throws IOException  Thrown in rare ocasions when the String cannot be read.
   */
  public static Document getDocument(CharSequence xml) throws SAXException, IOException {
    return getDocument(xml.toString());
  }


  /**
   * <p>Parses the given string into a {@link Document}.</p>
   *
   * @param xml String to parse
   * @return The <code>Document</code>, parsed from String.
   * @throws SAXException Thrown if a parsing error occurs.
   * @throws IOException  Thrown in rare ocasions when the String cannot be read.
   */
  public static Document getDocument(String xml) throws SAXException, IOException {
    DOMParser parser = new DOMParser();
    InputSource input = new InputSource(new StringReader(xml));
    parser.parse(input);
    return parser.getDocument();
  }

  /**
   * Reads in a {@link File} and creates a new {@link Document}.
   *
   * @param xml File to read. Encoding is determined automatically from the
   *            <code>&lt;?xml?&gt;</code> processing instruction.
   * @return The <code>Document</code>.
   * @throws SAXException Thrown if a parsing error occurs.
   * @throws IOException  Thrown if the file cannot be read.
   */
  public static Document getDocument(File xml) throws SAXException, IOException {
    DOMParser parser = new DOMParser();
    InputSource input = new InputSource(new FileInputStream(xml));
    parser.parse(input);
    return parser.getDocument();
  }

  /**
   * <p>Reads characters from {@link InputStream} and creates a new {@link Document}.</p>
   *
   * @param xml File to read. Encoding is determined automatically from the
   *            <code>&lt;?xml?&gt;</code> processing instruction.
   * @return The <code>Document</code>.
   * @throws SAXException Thrown if a parsing error occurs.
   * @throws IOException  Thrown if the file cannot be read.
   */
  public static Document getDocument(InputStream xml) throws SAXException, IOException {
    DOMParser parser = new DOMParser();
    InputSource input = new InputSource(xml);
    parser.parse(input);
    return parser.getDocument();
  }

  /**
   * <p>Reads characters from {@link InputStream} and creates a new {@link Document}.</p>
   *
   * @param xml File to read. Encoding is determined automatically from the
   *            <code>&lt;?xml?&gt;</code> processing instruction.
   * @return The <code>Document</code>.
   * @throws SAXException Thrown if a parsing error occurs.
   * @throws IOException  Thrown if the file cannot be read.
   */
  public static Document getDocument(Reader xml) throws SAXException, IOException {
    DOMParser parser = new DOMParser();
    InputSource input = new InputSource(xml);
    parser.parse(input);
    return parser.getDocument();
  }

  private static String recursiveLookupPrefix(Element element, final String namespace) {
    while (true) {
      final NamedNodeMap attributes = element.getAttributes();
      for (int i = 0; i < attributes.getLength(); i++) {
        final Attr attr = (Attr) attributes.item(i);
        if ("xmlns".equals(attr.getPrefix()) || attr.getName().startsWith("xmlns")) {
          if (attr.getValue().equals(namespace)) {
            String result;
            result = attr.getLocalName();
            if (result == null) {
              result = attr.getName();
            }
            if ("xmlns".equals(result)) {
              return "";
            }
            if (result.startsWith("xmlns:")) {
              result = result.substring(6);
            }
            return result;
          }
        }
      }
      Node parent = element.getParentNode();
      if (parent == null || parent == element || !(parent instanceof Element)) {
        log.info(new StringBuffer().append("Namespace '")
          .append(namespace)
          .append("' not found. Returning null from recursiveLookupPrefix()")
          .toString());
        return null;
      } else {
        element = (Element) parent;
      }
    }
  }

  protected static MethodCache getMethodCache(final Class<? extends Element> eClass) {
    MethodCache mc = DOM3_SUPPORT.get(eClass);
    if(mc == null) {
      mc = new MethodCache();

      try {
        mc.lookupPrefix = eClass.getMethod("lookupPrefix", new Class[]{String.class});
      } catch (NoSuchMethodException e) {
        log.info("DOM 3 support not available - lookupPrefix method not found on " + eClass.getName());
      } catch (SecurityException e) {
        log.warn("DOM 3 support not available - lookupPrefix method not accessable on " + eClass.getName(), e);
      }

      try {
        mc.lookupNamespaceURI = eClass.getMethod("lookupNamespaceURI", new Class[]{String.class});
      } catch (NoSuchMethodException e) {
        log.info("DOM 3 support not available - lookupNamespaceURI method not found on " + eClass.getName());
      } catch (SecurityException e) {
        log.warn("DOM 3 support not available - lookupNamespaceURI method not accessable on " + eClass.getName(), e);
      }

      mc.lookupDone = true;
      mc.ok = true;

      synchronized(DOM3_SUPPORT) {
        DOM3_SUPPORT.put(eClass, mc);
      }
    }

    return mc;
  }

  /**
   * <p>Finds a prefix (i.e. <code>xsd</code>) for a given namespace (i.e. <code>http://www.w3.org/2001/XMLSchema</code>).</p>
   * <p>If you have a DOM level 3 parser, native method will be used, otherwise, internal
   * method is used. Lookup starts on the current element and works its way up through the tree and
   * returns the first found prefix. If nothing matches, <code>null</code> is returned.</p>
   *
   * @param element  Element on which lookup should start.
   * @param namespace Namespace to look for.
   * @return The prefix for the given namespace, without the colon (<code>:</code>) mark.
   * @see #lookupNamespaceURI
   */
  public static String lookupPrefix(Element element, String namespace) {
    if (element == null) {
      return null;
    }
    final Class<? extends Element> aClass = element.getClass();
    final MethodCache mc = getMethodCache(aClass);

    if (mc.lookupPrefix != null && mc.ok) {
      // DOM 3 Level support! Yey!

      try {
        Object result = mc.lookupPrefix.invoke(element, new Object[]{namespace});

        // if found, return, otherwise try fallback mechanism
        // we have seen problems with this PIMG branch filter
        if (result != null) {
          // takes too much time log.debug(namespace + " = xmlns:" + result);
          // it's a string, I know it.
          return (String) result;
        }
      } catch (IllegalAccessException e) {
        log.error("Error with DOM3 support!", e);
      } catch (IllegalArgumentException e) {
        log.error("Error with DOM3 support!", e);
      } catch (InvocationTargetException e) {
        if(e.getCause() instanceof UnsupportedOperationException) {
          mc.ok = false;
        } else {
          log.warn("Error with DOM3 support!", e);
        }
      }
    }
    String result = recursiveLookupPrefix(element, namespace);
    return result;
  }

  private static String recursiveLookupNamespaceURI(Element element, final String prefix) {
    final String name = new StringBuffer("xmlns:").append(prefix).toString();
    while (true) {
      final NamedNodeMap attributes = element.getAttributes();
      for (int i = 0; i < attributes.getLength(); i++) {
        final Attr attr = (Attr) attributes.item(i);
        if ("xmlns".equals(attr.getPrefix()) || attr.getName().startsWith("xmlns")) {
          if (name.equals(attr.getName()) || prefix.equals(attr.getLocalName())) {
            return attr.getValue();
          }
        }
      }
      Node parent = element.getParentNode();
      if (parent == null || parent == element || !(parent instanceof Element)) {
        log.info(new StringBuffer().append("Prefix '")
          .append(prefix)
          .append("' not found. Returning null from recursiveLookupNamespaceURI()")
          .toString());
        return null;
      } else {
        element = (Element) parent;
      }
    }
  }

  /**
   * <p>Finds a namespace (i.e. <code>http://www.w3.org/2001/XMLSchema</code>) for a given prefix (i.e. <code>xsd</code>).</p>
   * <p>If you have a DOM level 3 parser, native method will be used, otherwise, internal
   * method is used. Lookup starts on the current element and works its way up through the tree and
   * returns the first found prefix. If nothing matches, <code>null</code> is returned.</p>
   *
   * @param element Element on which lookup should start.
   * @param prefix  Prefix to look for.
   * @return The namespace for the given prefix or <code>null</code> if not found..
   * @see #lookupPrefix
   */
  public static String lookupNamespaceURI(Element element, String prefix) {
    if (element == null) {
      return null;
    }
    final Class<? extends Element> aClass = element.getClass();
    final MethodCache mc = getMethodCache(aClass);

    if (mc.lookupNamespaceURI != null && mc.ok) {
      // DOM 3 Level support! Yey!

      try {
        Object result = mc.lookupNamespaceURI.invoke(element, new Object[]{prefix});

        // it's a string, I know it.

        return (String) result;
      } catch (IllegalAccessException e) {
        log.error("Error with DOM3 support!", e);
      } catch (IllegalArgumentException e) {
        log.error("Error with DOM3 support!", e);
      } catch (InvocationTargetException e) {
        if(e.getCause() instanceof UnsupportedOperationException) {
          mc.ok = false;
        } else {
          log.warn("Error with DOM3 support!", e);
        }
      }
    }

    String result = recursiveLookupNamespaceURI(element, prefix);
    return result;
  }

  /**
   * Returns an iterator over all elements which are children of the parameter.
   *
   * @param el The parent {@link Element}.
   * @return {@link NodeListIterator} over all the subelements.
   */
  public static Iterator getElementChildrenIterator(Element el) {
    return new NodeListIterator(el.getChildNodes(), Node.ELEMENT_NODE);
  }

  /**
   * <p>Returns a first child element with the specified name. If not found,
   * <code>null</code> is returned.</p>
   *
   * @param el  The parent {@link Element}
   * @param name Child {@link Element} name.
   * @return The {@link Element} if found, <code>null</code> if otherwise.
   */
  public static Element getChildElement(Element el, String name) {
    NodeList nl = el.getChildNodes();
    for (int k = 0; k < nl.getLength(); k++) {
      Node nd = nl.item(k);
      if (nd.getNodeType() == Node.ELEMENT_NODE) {
        // now check the name

        if (((Element) nd).getTagName().equals(name)) {
          return (Element) nd;
        }
      }
    }
    return null;
  }

  /**
   * This method will just remove all the children from the given
   * {@link Element}. The actual code is <code><pre>
   * NodeList nl = el.getChildNodes();
   * for (int i = 0; i < nl.getLength(); i++) {
   *    Node nd = nl.item(i);
   *    el.removeChild(nd);
   * }
   * </pre></code> and it saves you some typing.
   *
   * @param el {@link Element} to flush.
   */
  public static void flushElement(Element el) {
    NodeList nl = el.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
      Node nd = nl.item(i);
      el.removeChild(nd);
    }
  }

  /**
   * Retrieves the contents of the given {@link Element}
   * as a XML string.
   *
   * @param el Element to process
   * @return The contents of the element (without the element
   *         itself) as a XML fragment.
   */
  public static String getInnerXML(Element el) {
    DocumentFragment df = el.getOwnerDocument().createDocumentFragment();
    NodeList nl = el.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
      df.appendChild(nl.item(i).cloneNode(true));
    }
    return getXML(df, false);
  }

  /**
   * Returns all the text contained in the element <code>el</code>
   * by adding it to a given {@link StringBuffer} and recursevly
   * calling itself for each encoutered {@link Element}
   *
   * @param el Element to retrieve text from
   * @param sb StringBuffer that will hold the resulting text.
   * @return Returns a concatination of all <code>el</code>'s text.
   */
  private static StringBuffer getInnerText(Element el, StringBuffer sb) {
    NodeList nl = el.getChildNodes();
    for (int i = 0; i < nl.getLength(); i++) {
      Node nd = nl.item(i);
      if (nd.getNodeType() == Node.TEXT_NODE || nd.getNodeType() == Node.CDATA_SECTION_NODE) {
        sb.append(nd.getNodeValue());
      } else if (nd.getNodeType() == Node.ELEMENT_NODE) {
        getInnerText((Element) nd, sb);
      }
    }
    return sb;
  }

  /**
   * Returns all the text contained in the element <code>el</code>.
   *
   * @param e Element to retrieve text from
   * @return Returns a concatination of all <code>el</code>'s text.
   * @see #getText
   * @see #getInnerXML
   */
  public static String getInnerText(Element e) {
    StringBuffer sb = new StringBuffer();
    getInnerText(e, sb);
    return sb.toString();
  }

  public static void setInnerText(Element el, String text) {
    flushElement(el);
    el.appendChild(el.getOwnerDocument().createTextNode(text));
  }

  /**
   * Returns the text contained in the element <code>el</code>.
   * <strong>Grand children are not processed.</strong> If you wish
   * to retrieve the whole text or a whole XML under a specific
   * {@link Element}, look at methods {@link #getInnerText} and
   * {@link #getInnerXML}. <em>Please note, that the value of each
   * node is given on it's own line.</em>
   *
   * @param el Element which holds the text.
   * @return Returns a concatination of all <code>el</code>'s children
   *         of type {@link Node#TEXT_NODE} values by calling {@link Node#getNodeValue}.
   */
  public static String getText(Element el) {
    StringBuffer buf = new StringBuffer();
    NodeList nl = el.getChildNodes();
    for (int k = 0; k < nl.getLength(); k++) {
      Node nd = nl.item(k);
      if (nd.getNodeType() == Node.TEXT_NODE) {
        // now check the name

        if (k > 0) {
          buf.append('\n');
        }
        buf.append(nd.getNodeValue());
      }
    }
    return buf.toString();
  }

  /**
   * Returns a string value of the {@link Element} <code>name</code> which is
   * contained in the {@link Element} <code>el</code>. If the <code>name</code>
   * element is not found, an empty string is returned.
   *
   * @param el  Parent {@link Element}.
   * @param name Child {@link Element} name
   * @return The same as calling <code>{@link #getText}({@link #getChildElement}(el, name))</code>
   *         except that <codde>null</code> check is conducted.
   */
  public static String getChildElementText(Element el, String name) {
    Element child = getChildElement(el, name);
    if (child != null) {
      return getText(child);
    }
    return "";
  }

  /**
   * @deprecated Don't use.
   */
  public static String listToString(List l) {
    if (l == null) {
      return "<NULL>";
    }
    StringBuffer sb = new StringBuffer("{");
    for (int i = 0; i < l.size(); i++) {
      if (i != 0) {
        sb.append(',');
      }
      sb.append(l.get(i).toString());
    }
    return sb.append('}').toString();
  }

  /**
   * <p>Very similar to {@link #mapFromXML}. It serializes a {@link List}
   * into XML. Serialization is almost the same, with a small exception
   * that no <code>key</code> and <code>value</code> elements are created.
   * The value of a specific element in a list is simply dumped as a
   * {@link String} into the <code>element</code> {@link Element}.</p>
   * <p>If the <code> {@link List} l</code> is <code>null</code>, the
   * method simply returns without doing anything.</p>
   *
   * @param l A {@link List} scheduled for serialization into XML.
   * @param e {@link Element} that will hold the serialized list.
   *          created <code>element</code>s.
   * @see #mapToXML
   */
  public static void listToXML(List l, Element e) {
    listToXML(l, e, "");
  }

  /**
   * <p>Very similar to {@link #mapToXML}. It serializes a {@link List}
   * into XML. Serialization is almost the same, with a small exception
   * that no <code>key</code> and <code>value</code> elements are created.
   * The value of a specific element in a list is simply dumped as a
   * {@link String} into the <code>element</code> {@link Element}.</p>
   * <p>If the <code> {@link List} l</code> is <code>null</code>, the
   * method simply returns without doing anything.</p>
   *
   * @param l      A {@link List} scheduled for serialization into XML.
   * @param e      {@link Element} that will hold the serialized list.
   * @param namespace The Namespace that will be assigned to
   *                  created <code>element</code>s.
   * @see #mapToXML
   */
  public static void listToXML(List l, Element e, String namespace) {
    if (l == null) {
      return;
    }
    Document doc = e.getOwnerDocument();
    for (int i = 0; i < l.size(); i++) {
      Element element = doc.createElementNS(namespace, "element");
      Object o = l.get(i);
      if (o instanceof List) {
        listToXML((List) o, element, namespace);
      } else if (o instanceof Map) {
        mapToXML((Map) o, element, namespace);
      } else if (o != null) {
        element.appendChild(doc.createTextNode(o.toString()));
      }
      e.appendChild(element);
    }
  }

  /**
   * @deprecated Don't use.
   */
  public static String mapToString(Map m) {
    if (m == null) {
      return "<NULL>";
    }
    StringBuffer sb = new StringBuffer("{");
    Iterator it = m.keySet().iterator();
    boolean firstRun = true;
    while (it.hasNext()) {
      if (firstRun) {
        firstRun = false;
      } else {
        sb.append(',');
      }
      Object key = it.next();
      if (m.get(key) != null) {
        sb.append(key).append('=').append(m.get(key).toString());
      } else {
        sb.append(key).append('=').append("");
      }
    }
    return sb.append('}').toString();
  }

  /**
   * This method will take an element and try to construct a {@link Map}
   * from it. To preserve the order of the elements,  {@link LinkedHashMap}
   * is returned. The given elemen is expected to have a child
   * list of elements named <code>element</code> (anything else - including the
   * whitespace - is ignored) with each having two sub-elements: <code>key</code>
   * and <code>value</code>. (As you might have guessed, everything
   * else is here also ignored.)
   *
   * @param e Element containing the root of your list
   * @return The XML converted into a {@link Map}.
   */
  public static LinkedHashMap<String, String> mapFromXML(final Element e) {
    if (e == null) {
      log.warn("mapFromXML got <null> element. Returning <null>.");
      return null;
    }
    LinkedHashMap<String, String> m = new LinkedHashMap<String, String>();
    NodeList childNodes = e.getChildNodes();
    for (int i = 0; i < childNodes.getLength(); i++) {
      Node element = childNodes.item(i);
      if (element.getNodeType() == Node.ELEMENT_NODE && ("element".equals(element.getNodeName()) || "element".equals(element.getLocalName()))) {
        final Element el = (Element) element;

        String key = el.getAttribute("key");
        String value = el.getAttribute("value");

        if (key != null) {
          key = key.trim();
          if ("".equals(key)) {
            key = null;
          }
        }

        if (value != null && "".equals(value.trim())) {
          value = null;
        }

        if (key != null && value != null) {
          // nothing to do
        } else if (key != null) {
          value = XMLHelper.getText(el);
        } else {
          NodeList nl = element.getChildNodes();
          for (int j = 0; j < nl.getLength(); j++) {
            Node n = nl.item(j);
            if (n.getNodeType() == Node.ELEMENT_NODE && ("key".equals(n.getNodeName()) || "key".equals(n.getLocalName()))) {
              key = XMLHelper.getText((Element) n);
              if (value != null) {
                break;
              }
            } else if (n.getNodeType() == Node.ELEMENT_NODE && ("value".equals(n.getNodeName()) || "value".equals(n.getLocalName()))) {
              value = XMLHelper.getText((Element) n);
              if (key != null) {
                break;
              }
            }
          }
        }

        m.put(key, value);
      }
    }
    return m;
  }

  /**
   * This nifty method will convert your map to an XML tree under the
   * given {@link Element} <code>e</code>. Each entry in the map will
   * get a corresponding {@link Element} named <code>element</code> with
   * two sub-elements: <code>key</code> (containing the {@link String}
   * representation of the key) and <code>value</code> (same here - the
   * <code>toString()</code> value of the <code>value</code>). If the
   * <code>value</code> is another {@link Map} or a {@link List},
   * {@link #mapToXML} (or {@link #listToXML}) is called. <em>This method
   * does not check for circular references, so it is quite possible to
   * get an {@link StackOverflowError} if you are not careful
   * what you are putting in your map.</em>
   *
   * @param m {@link Map} that you want serialized
   * @param e {@link Element} that will hold the serialized map. It's
   *          nice if it's empty (has no children), but is not mandatory.
   */
  public static void mapToXML(final Map m, final Element e) {
    mapToXML(m, e, "");
  }

  /**
   * This nifty method will convert your map to an XML tree under the
   * given {@link Element} <code>e</code>. Each entry in the map will
   * get a corresponding {@link Element} named <code>element</code> with
   * two sub-elements: <code>key</code> (containing the {@link String}
   * representation of the key) and <code>value</code> (same here - the
   * <code>toString()</code> value of the <code>value</code>). If the
   * <code>value</code> is another {@link Map} or a {@link List},
   * {@link #mapToXML} (or {@link #listToXML}) is called. <em>This method
   * does not check for circular references, so it is quite possible to
   * get an {@link StackOverflowError} if you are not careful
   * what you are putting in your map.</em>
   *
   * @param m      {@link Map} that you want serialized
   * @param e      {@link Element} that will hold the serialized map. It's
   *                  nice if it's empty (has no children), but is not mandatory.
   * @param namespace If specified (not an empty string or not <code>null</code>),
   *                  all the created elements (<code>element</code>, <code>key</code> and
   *                  <code>value</code>) will be in this namespace.
   */
  public static void mapToXML(final Map m, final Element e, final String namespace) {
    if (m == null) {
      return;
    }
    Document doc = e.getOwnerDocument();
    Iterator it = m.entrySet().iterator();
    while (it.hasNext()) {
      Map.Entry ent = (Map.Entry) it.next();
      Object name = ent.getKey();
      Element element = doc.createElementNS(namespace, "element");
      e.appendChild(element);
      Element key = doc.createElementNS(namespace, "key");
      key.appendChild(doc.createTextNode(name.toString()));
      element.appendChild(key);
      Element value = doc.createElementNS(namespace, "value");
      element.appendChild(value);
      Object o = ent.getValue();
      if (o instanceof List) {
        listToXML((List) o, value, namespace);
      } else if (o instanceof Map) {
        mapToXML((Map) o, value, namespace);
      } else if (o != null) {
        value.appendChild(doc.createTextNode(o.toString()));
      }
    }
  }

  /**
   * This little thingie replaces all HTML entities (i.e. &amp;nbsp; with &amp;#160;) with escape codes.
   *
   * @param str The incoming XHTML
   * @return Ready-to-use XML :-)
   */
  public static StringBuffer htmlEntityToEscapeCodes(CharSequence str) {
    final Matcher m = ENTITY_PATTERN.matcher(str);
    StringBuffer result = new StringBuffer();
    while (m.find()) {
      final String key = m.group();
      Object replacement = ENTITIES.get(key);
      if (replacement == null) {
        log.warn("Unknown entity '" + key + "'. Keeping as is.");
        replacement = key;
      }
      m.appendReplacement(result, replacement.toString());
    }
    m.appendTail(result);

    return result;
  }


  /**
   * This method will take a valid XML String as an input and strip it of all tags.
   * That way you will only get the text contained in the XML document without
   * any tags.
   *
   * @param xml XML to perform this transformation on
   * @return The sole text of the XML.
   * @throws SAXException Thrown if in case you have not specified a valid XML.
   * @throws IOException  Thrown in case data cannot be read from the string
   *                      (how weird is that?)
   */
  public static String stripAllTags(String xml) throws SAXException, IOException {
    final StringBuffer result = new StringBuffer();
    try {
      SAXParserFactory factory = SAXParserFactory.newInstance();
      SAXParser parser = factory.newSAXParser();
      if (!xml.startsWith("<?xml")) {
        xml = "<?xml version=\"1.0\"?>" + xml;
      }
      parser.parse(new InputSource(new StringReader(xml)), new DefaultHandler() {
        public void characters(char[] ch, int start, int length) {
          result.append(ch, start, length);
        }
      });
    } catch (ParserConfigurationException ex) {
      throw new ParserConfigurationRuntimeException("Could not parse XML!", xml, ex);
    }
    return result.toString();
  }

  /**
   * The most basic validator with all the defaults.
   * @param source Source of validation.
   * @throws SAXException The usual.
   * @throws IOException The usual.
   * @see #validate(javax.xml.transform.Source, org.xml.sax.ErrorHandler, String, javax.xml.transform.Source[])
   */
  public static void validate(final Source source) throws SAXException, IOException {
    validate(source, null);
  }

  /**
   * The most basic validator with all the defaults except for custom error handler.
   * @param source Source of validation.
   * @param handler Error reporter.
   * @throws SAXException The usual.
   * @throws IOException The usual.
   * @see #validate(javax.xml.transform.Source, org.xml.sax.ErrorHandler, String, javax.xml.transform.Source[])
   */
  public static void validate(
    final Source source,
    final ErrorHandler handler
  ) throws SAXException, IOException {
    validate(source, handler, null, null);
  }

  /**
   * Schema validator
   * @param source Source of validation
   * @param handler Your error reporter. May be <tt>null</tt>. See also {@link org.jboss.fresh.xml.XMLHelper.ForgivingErrorHandler}.
   * @param schemaType Type of schema to validate. May be <tt>null</tt> to read schema embeded in document. See {@link javax.xml.XMLConstants}.
   * @param schemas List of schemas. May be <tt>null</tt> to use the schema embedded with the document.
   * @throws SAXException The usual.
   * @throws IOException The usual.
   */
  public static void validate(
    final Source source,
    final ErrorHandler handler,
    final String schemaType,
    final Source[] schemas
  ) throws SAXException, IOException {

    /*
    The javax.xml.constants class defines several constants to identify schema languages:

       * XMLConstants.W3C_XML_SCHEMA_NS_URI: http://www.w3.org/2001/XMLSchema
       * XMLConstants.RELAXNG_NS_URI: http://relaxng.org/ns/structure/1.0
       * XMLConstants.XML_DTD_NS_URI: http://www.w3.org/TR/REC-xml

     */
    final SchemaFactory factory = SchemaFactory.newInstance(schemaType == null || schemas == null ? XMLConstants.W3C_XML_SCHEMA_NS_URI : schemaType);
    final Schema schema = schemas == null ? factory.newSchema() : factory.newSchema(schemas);
    final Validator validator = schema.newValidator();

    if (handler != null) {
      validator.setErrorHandler(handler);
    }
    validator.validate(source);
  }

  /**
   * This error handler reports every warning, error and fatal to {@link System#err}. It only throws error in case
   * of FATALs. You can also use {@link org.jboss.fresh.xml.XMLHelper.ForgivingErrorHandler#errorsFound} method to check
   * if there were any errors or not.
   */
  public static class ForgivingErrorHandler implements ErrorHandler {
    boolean errorsFound = false;

    public void warning(SAXParseException ex) {
      errorsFound = true;
      System.err.println("WARNING at row " + ex.getLineNumber() + ", col " + ex.getColumnNumber() + ": " + ex.getMessage());
      ex.printStackTrace();
    }

    public void error(SAXParseException ex) {
      errorsFound = true;
      System.err.println("ERROR   at row " + ex.getLineNumber() + ", col " + ex.getColumnNumber() + ": " + ex.getMessage());
      ex.printStackTrace();
    }

    public void fatalError(SAXParseException ex) throws SAXException {
      errorsFound = true;
      System.err.println("FATAL   at row " + ex.getLineNumber() + ", col " + ex.getColumnNumber() + ": " + ex.getMessage());
      ex.printStackTrace();
      throw ex;
    }

    public boolean errorsFound() {
      return errorsFound;
    }

  }

  private static void printHelp() {
    System.out.println("So far, you can only do this: ");
    System.out.println("\t" + XMLHelper.class.getName() + " validate <xml file name or url>");
  }

  public static void main(String[] args) {
    if (args.length == 0) {
      printHelp();
      return;
    }
    if ("validate".equalsIgnoreCase(args[0]) && args.length >= 2) {

      final ForgivingErrorHandler eh = new ForgivingErrorHandler();
      Source s;
      InputStream is = null;

      try {
        File f = new File(args[1]);
        if (f.exists()) {
          is = new FileInputStream(f);
          s = new StreamSource(is);
          validate(s, eh);
          System.out.println(eh.errorsFound ? "Completed with errors" : "Document validates OK!");
          return;
        }

        try {
          URL u = new URL(args[1]);
          URLConnection c = u.openConnection();
          is = c.getInputStream();
          s = new StreamSource(is);
          validate(s, eh);
          System.out.println(eh.errorsFound ? "Completed with errors" : "Document validates OK!");
          return;
        } catch (MalformedURLException e) {
          e.printStackTrace();
        }
      } catch (SAXException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        if (is != null) {
          //noinspection OverlyBroadCatchBlock
          try {
            is.close();
          } catch (Exception e) {
            //noinspection ObjectToString
          }
        }
      }

    }
    printHelp();

  }

}
TOP

Related Classes of org.jboss.fresh.xml.XMLHelper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.