blob: c92e6bb0da759dc90f7d09f1ec4eb6e783f70379 [file] [log] [blame]
/* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.xmlbeans.impl.xpath.saxon;
import net.sf.saxon.Configuration;
import net.sf.saxon.dom.DOMNodeWrapper;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.om.SequenceTool;
import net.sf.saxon.sxpath.*;
import net.sf.saxon.tree.wrapper.VirtualNode;
import org.apache.xmlbeans.impl.store.PathDelegate;
import org.w3c.dom.Node;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings("WeakerAccess")
public class XBeansXPath
implements PathDelegate.SelectPathInterface {
private final Map<String, String> namespaceMap = new HashMap<String, String>();
private String path;
private String contextVar;
private String defaultNS;
/**
* Construct given an XPath expression string.
*
* @param path The XPath expression
* @param contextVar The name of the context variable
* @param namespaceMap a map of prefix/uri bindings for NS support
* @param defaultNS the uri for the default element NS, if any
*/
public XBeansXPath(String path, String contextVar,
Map<String, String> namespaceMap, String defaultNS) {
this.path = path;
this.contextVar = contextVar;
this.defaultNS = defaultNS;
this.namespaceMap.putAll(namespaceMap);
}
/**
* Select all nodes that are selectable by this XPath
* expression. If multiple nodes match, multiple nodes
* will be returned.
* <p/>
* <p/>
* <b>NOTE:</b> In most cases, nodes will be returned
* in document-order, as defined by the XML Canonicalization
* specification. The exception occurs when using XPath
* expressions involving the <code>union</code> operator
* (denoted with the pipe '|' character).
* </p>
* <p/>
* <p/>
* <b>NOTE:</b> Param node must be a DOM node which will be used
* during the xpath execution and iteration through the results.
* A call of node.dispose() must be done after reading all results.
* </p>
*
* @param node The node, nodeset or Context object for evaluation.
* This value can be null.
* @return The <code>List</code> of all items selected
* by this XPath expression.
*/
public List selectNodes(Object node) {
try {
Node contextNode = (Node) node;
Configuration config = new Configuration();
IndependentContext sc = new IndependentContext(config);
// Declare ns bindings
// also see https://saxonica.plan.io/issues/2130
// (XPath referencing attribute with namespace fails when using DOM)
if (defaultNS != null) {
sc.setDefaultElementNamespace(defaultNS);
}
namespaceMap.forEach(sc::declareNamespace);
NodeInfo contextItem = config.unravel(new DOMSource(contextNode));
XPathEvaluator xpe = new XPathEvaluator(config);
xpe.setStaticContext(sc);
XPathVariable thisVar = sc.declareVariable("", contextVar);
XPathExpression xpath = xpe.createExpression(path);
XPathDynamicContext dc = xpath.createDynamicContext(null);
dc.setContextItem(contextItem);
dc.setVariable(thisVar, contextItem);
List<Item> saxonNodes = xpath.evaluate(dc);
List<Object> retNodes = new ArrayList<>(saxonNodes.size());
for (Item o : saxonNodes) {
if (o instanceof DOMNodeWrapper) {
Node n = getUnderlyingNode((DOMNodeWrapper) o);
retNodes.add(n);
} else if (o instanceof NodeInfo) {
retNodes.add(o.getStringValue());
} else {
retNodes.add(SequenceTool.convertToJava(o));
}
}
return retNodes;
} catch (TransformerException e) {
throw new RuntimeException(e);
}
}
public List selectPath(Object node) {
return selectNodes(node);
}
/**
* According to the Saxon javadoc:
* <code>getUnderlyingNode</code> in <code>NodeWrapper</code> implements
* the method specified in the interface <code>VirtualNode</code>, and
* the specification of the latter says that it may return another
* <code>VirtualNode</code>, and you may have to drill down through
* several layers of wrapping.
* To be safe, this method is provided to drill down through multiple
* layers of wrapping.
*
* @param v The <code>VirtualNode</code>
* @return The underlying node
*/
private static Node getUnderlyingNode(VirtualNode v) {
Object o = v;
while (o instanceof VirtualNode) {
o = ((VirtualNode) o).getUnderlyingNode();
}
return (Node) o;
}
}