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(" ", " ");
entities.put("¡", "¡");
entities.put("¢", "¢");
entities.put("£", "£");
entities.put("¤", "¤");
entities.put("¥", "¥");
entities.put("¦", "¦");
entities.put("§", "§");
entities.put("¨", "¨");
entities.put("©", "©");
entities.put("ª", "ª");
entities.put("«", "«");
entities.put("¬", "¬");
entities.put("­", "­");
entities.put("®", "®");
entities.put("¯", "¯");
entities.put("°", "°");
entities.put("±", "±");
entities.put("²", "²");
entities.put("³", "³");
entities.put("´", "´");
entities.put("µ", "µ");
entities.put("¶", "¶");
entities.put("·", "·");
entities.put("¸", "¸");
entities.put("¹", "¹");
entities.put("º", "º");
entities.put("»", "»");
entities.put("¼", "¼");
entities.put("½", "½");
entities.put("¾", "¾");
entities.put("¿", "¿");
entities.put("À", "À");
entities.put("Á", "Á");
entities.put("Â", "Â");
entities.put("Ã", "Ã");
entities.put("Ä", "Ä");
entities.put("Å", "Å");
entities.put("Æ", "Æ");
entities.put("Ç", "Ç");
entities.put("È", "È");
entities.put("É", "É");
entities.put("Ê", "Ê");
entities.put("Ë", "Ë");
entities.put("Ì", "Ì");
entities.put("Í", "Í");
entities.put("Î", "Î");
entities.put("Ï", "Ï");
entities.put("Ð", "Ð");
entities.put("Ñ", "Ñ");
entities.put("Ò", "Ò");
entities.put("Ó", "Ó");
entities.put("Ô", "Ô");
entities.put("Õ", "Õ");
entities.put("Ö", "Ö");
entities.put("×", "×");
entities.put("Ø", "Ø");
entities.put("Ù", "Ù");
entities.put("Ú", "Ú");
entities.put("Û", "Û");
entities.put("Ü", "Ü");
entities.put("Ý", "Ý");
entities.put("Þ", "Þ");
entities.put("ß", "ß");
entities.put("à", "à");
entities.put("á", "á");
entities.put("â", "â");
entities.put("ã", "ã");
entities.put("ä", "ä");
entities.put("å", "å");
entities.put("æ", "æ");
entities.put("ç", "ç");
entities.put("è", "è");
entities.put("é", "é");
entities.put("ê", "ê");
entities.put("ë", "ë");
entities.put("ì", "ì");
entities.put("í", "í");
entities.put("î", "î");
entities.put("ï", "ï");
entities.put("ð", "ð");
entities.put("ñ", "ñ");
entities.put("ò", "ò");
entities.put("ó", "ó");
entities.put("ô", "ô");
entities.put("õ", "õ");
entities.put("ö", "ö");
entities.put("÷", "÷");
entities.put("ø", "ø");
entities.put("ù", "ù");
entities.put("ú", "ú");
entities.put("û", "û");
entities.put("ü", "ü");
entities.put("ý", "ý");
entities.put("þ", "þ");
entities.put("ÿ", "ÿ");
entities.put("ƒ", "ƒ");
entities.put("Α", "Α");
entities.put("Β", "Β");
entities.put("Γ", "Γ");
entities.put("Δ", "Δ");
entities.put("Ε", "Ε");
entities.put("Ζ", "Ζ");
entities.put("Η", "Η");
entities.put("Θ", "Θ");
entities.put("Ι", "Ι");
entities.put("Κ", "Κ");
entities.put("Λ", "Λ");
entities.put("Μ", "Μ");
entities.put("Ν", "Ν");
entities.put("Ξ", "Ξ");
entities.put("Ο", "Ο");
entities.put("Π", "Π");
entities.put("Ρ", "Ρ");
entities.put("Σ", "Σ");
entities.put("Τ", "Τ");
entities.put("Υ", "Υ");
entities.put("Φ", "Φ");
entities.put("Χ", "Χ");
entities.put("Ψ", "Ψ");
entities.put("Ω", "Ω");
entities.put("α", "α");
entities.put("β", "β");
entities.put("γ", "γ");
entities.put("δ", "δ");
entities.put("ε", "ε");
entities.put("ζ", "ζ");
entities.put("η", "η");
entities.put("θ", "θ");
entities.put("ι", "ι");
entities.put("κ", "κ");
entities.put("λ", "λ");
entities.put("μ", "μ");
entities.put("ν", "ν");
entities.put("ξ", "ξ");
entities.put("ο", "ο");
entities.put("π", "π");
entities.put("ρ", "ρ");
entities.put("ς", "ς");
entities.put("σ", "σ");
entities.put("τ", "τ");
entities.put("υ", "υ");
entities.put("φ", "φ");
entities.put("χ", "χ");
entities.put("ψ", "ψ");
entities.put("ω", "ω");
entities.put("ϑ", "ϑ");
entities.put("ϒ", "ϒ");
entities.put("ϖ", "ϖ");
entities.put("•", "•");
entities.put("…", "…");
entities.put("′", "′");
entities.put("″", "″");
entities.put("‾", "‾");
entities.put("⁄", "⁄");
entities.put("℘", "℘");
entities.put("ℑ", "ℑ");
entities.put("ℜ", "ℜ");
entities.put("™", "™");
entities.put("ℵ", "ℵ");
entities.put("←", "←");
entities.put("↑", "↑");
entities.put("→", "→");
entities.put("↓", "↓");
entities.put("↔", "↔");
entities.put("↵", "↵");
entities.put("⇐", "⇐");
entities.put("⇑", "⇑");
entities.put("⇒", "⇒");
entities.put("⇓", "⇓");
entities.put("⇔", "⇔");
entities.put("∀", "∀");
entities.put("∂", "∂");
entities.put("∃", "∃");
entities.put("∅", "∅");
entities.put("∇", "∇");
entities.put("∈", "∈");
entities.put("∉", "∉");
entities.put("∋", "∋");
entities.put("∏", "∏");
entities.put("∑", "∑");
entities.put("−", "−");
entities.put("∗", "∗");
entities.put("√", "√");
entities.put("∝", "∝");
entities.put("∞", "∞");
entities.put("∠", "∠");
entities.put("∧", "∧");
entities.put("∨", "∨");
entities.put("∩", "∩");
entities.put("∪", "∪");
entities.put("∫", "∫");
entities.put("∴", "∴");
entities.put("∼", "∼");
entities.put("≅", "≅");
entities.put("≈", "≈");
entities.put("≠", "≠");
entities.put("≡", "≡");
entities.put("≤", "≤");
entities.put("≥", "≥");
entities.put("⊂", "⊂");
entities.put("⊃", "⊃");
entities.put("⊄", "⊄");
entities.put("⊆", "⊆");
entities.put("⊇", "⊇");
entities.put("⊕", "⊕");
entities.put("⊗", "⊗");
entities.put("⊥", "⊥");
entities.put("⋅", "⋅");
entities.put("⌈", "⌈");
entities.put("⌉", "⌉");
entities.put("⌊", "⌊");
entities.put("⌋", "⌋");
entities.put("⟨", "〈");
entities.put("⟩", "〉");
entities.put("◊", "◊");
entities.put("♠", "♠");
entities.put("♣", "♣");
entities.put("♥", "♥");
entities.put("♦", "♦");
entities.put(""", """);
entities.put("&", "&");
entities.put("'", "'");
entities.put("<", "<");
entities.put(">", ">");
entities.put("Œ", "Œ");
entities.put("œ", "œ");
entities.put("Š", "Š");
entities.put("š", "š");
entities.put("Ÿ", "Ÿ");
entities.put("ˆ", "ˆ");
entities.put("˜", "˜");
entities.put(" ", " ");
entities.put(" ", " ");
entities.put(" ", " ");
entities.put("‌", "‌");
entities.put("‍", "‍");
entities.put("‎", "‎");
entities.put("‏", "‏");
entities.put("–", "–");
entities.put("—", "—");
entities.put("‘", "‘");
entities.put("’", "’");
entities.put("‚", "‚");
entities.put("“", "“");
entities.put("”", "”");
entities.put("„", "„");
entities.put("†", "†");
entities.put("‡", "‡");
entities.put("‰", "‰");
entities.put("‹", "‹");
entities.put("›", "›");
entities.put("€", "€");
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><?xml?></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><?xml?></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><?xml?></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. &nbsp; with &#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();
}
}