package org.jboss.windup.util.xml;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.lang3.StringUtils;
import org.jboss.windup.util.Logging;
import org.jboss.windup.util.exception.MarshallingException;
import org.jboss.windup.util.exception.XPathException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Contains utility methods for interacting with XML files.
*/
public class XmlUtil
{
private static final Logger LOG = Logging.get(XmlUtil.class);
protected static final Map<String, String> objs;
static
{
objs = new HashMap<>();
objs.put("xsi", "http://www.w3.org/2001/XMLSchema-instance");
}
/**
* Converts the given {@link NodeList} to a {@link String}.
*/
public static String nodeListToString(NodeList nodeList)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nodeList.getLength(); i++)
{
Node node = nodeList.item(i);
sb.append(nodeToString(node));
}
return sb.toString();
}
/**
* Converts the given {@link Node} to a {@link String}.
*/
public static String nodeToString(Node node)
{
StringWriter sw = new StringWriter();
if (node instanceof Attr)
{
Attr attr = (Attr) node;
return attr.getValue();
}
try
{
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.transform(new DOMSource(node), new StreamResult(sw));
}
catch (TransformerException te)
{
LOG.warning("Transformer Exception: " + te.getMessage());
}
return sw.toString();
}
/**
* Returns the names and locations defined in this {@link Document}.
*/
public static Map<String, String> getSchemaLocations(Document doc)
{
Set<String> namespaces = new HashSet<>();
namespaces.addAll(LocationAwareXmlReader.getNamespaces(doc));
String schemaLocation;
try
{
schemaLocation = XmlUtil.xpathExtract(doc, "//@xsi:schemaLocation", objs);
}
catch (MarshallingException e)
{
throw new RuntimeException("Exception extracting xpath.", e);
}
Map<String, String> result = new HashMap<>();
if (StringUtils.isNotBlank(schemaLocation))
{
schemaLocation = StringUtils.trim(schemaLocation);
String[] locations = schemaLocation.split("\\s+");
for (int i = 0, j = locations.length - 1; i < j; i++)
{
if (namespaces.contains(locations[i]))
{
result.put(locations[i], locations[i + 1]);
}
}
}
for (String r : result.keySet())
{
namespaces.remove(r);
}
// if there are namespaces without namespace locations, load here.
if (namespaces.size() > 0)
{
for (String namespace : namespaces)
{
result.put(namespace, null);
}
}
return result;
}
/**
* Runs the given xpath and returns a boolean result.
*/
public static boolean xpathExists(Node document, String xpathExpression, Map<String, String> namespaceMapping) throws XPathException,
MarshallingException
{
Boolean result = (Boolean) executeXPath(document, xpathExpression, namespaceMapping, XPathConstants.BOOLEAN);
return result != null && result;
}
/**
* Runs the given xpath and returns a {@link String} result.
*/
public static String xpathExtract(Node document, String xpathExpression, Map<String, String> namespaceMapping) throws XPathException,
MarshallingException
{
return (String) executeXPath(document, xpathExpression, namespaceMapping, XPathConstants.STRING);
}
/**
* Runs the given xpath and returns a {@link NodeList} result.
*/
public static NodeList xpathNodeList(Node document, String xpathExpression, Map<String, String> namespaceMapping) throws XPathException,
MarshallingException
{
return (NodeList) executeXPath(document, xpathExpression, namespaceMapping, XPathConstants.NODESET);
}
/**
* Runs the given xpath and returns a {@link NodeList} result.
*/
public static NodeList xpathNodeList(Node document, XPathExpression xpathExpression) throws XPathException, MarshallingException
{
return (NodeList) executeXPath(document, xpathExpression, XPathConstants.NODESET);
}
/**
* Executes the given xpath and returns the result with the type specified.
*/
public static Object executeXPath(Node document, String xpathExpression, Map<String, String> namespaceMapping, QName result)
throws XPathException, MarshallingException
{
NamespaceMapContext mapContext = new NamespaceMapContext(namespaceMapping);
try
{
XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
xpath.setNamespaceContext(mapContext);
XPathExpression expr = xpath.compile(xpathExpression);
return executeXPath(document, expr, result);
}
catch (XPathExpressionException e)
{
throw new XPathException("Xpath(" + xpathExpression + ") cannot be compiled", e);
}
catch (Exception e)
{
throw new MarshallingException("Exception unmarshalling XML.", e);
}
}
/**
* Executes the given {@link XPathExpression} and returns the result with the type specified.
*/
public static Object executeXPath(Node document, XPathExpression expr, QName result) throws XPathException, MarshallingException
{
try
{
return expr.evaluate(document, result);
}
catch (Exception e)
{
throw new MarshallingException("Exception unmarshalling XML.", e);
}
}
}