/** * Copyright 2015 Santhosh Kumar Tekuri * * The JLibs authors license this file to you 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 jlibs.examples.xml.sax.dog.engines; import jlibs.core.graph.Convertor; import jlibs.core.graph.Navigator2; import jlibs.core.graph.PredicateConvertor; import jlibs.core.graph.Sequence; import jlibs.core.graph.sequences.AbstractSequence; import jlibs.core.lang.StringUtil; import jlibs.examples.xml.sax.dog.TestCase; import jlibs.examples.xml.sax.dog.XPathEngine; import jlibs.examples.xml.sax.dog.XPathInfo; import jlibs.xml.Namespaces; import jlibs.xml.dom.DOMUtil; import jlibs.xml.sax.dog.NodeItem; import jlibs.xml.sax.dog.NodeType; import net.sf.saxon.Configuration; import net.sf.saxon.lib.StandardErrorListener; import net.sf.saxon.om.AxisInfo; import net.sf.saxon.om.NodeInfo; import net.sf.saxon.tree.NamespaceNode; import net.sf.saxon.tree.iter.AxisIterator; import net.sf.saxon.xpath.XPathEvaluator; import net.sf.saxon.xpath.XPathFactoryImpl; import org.w3c.dom.*; import org.xml.sax.InputSource; import javax.xml.namespace.NamespaceContext; import javax.xml.transform.sax.SAXSource; import javax.xml.xpath.XPathConstants; import java.util.ArrayList; import java.util.BitSet; import java.util.List; /** * @author Santhosh Kumar T */ public class SaxonEngine extends XPathEngine{ @Override public String getName(){ return "SAXON"; } @Override public List<Object> evaluate(TestCase testCase, String file) throws Exception{ XPathFactoryImpl xpf = new XPathFactoryImpl(); XPathEvaluator xpe = (XPathEvaluator)xpf.newXPath(); xpe.getConfiguration().setVersionWarning(false); ((StandardErrorListener)xpe.getConfiguration().getErrorListener()).setRecoveryPolicy(Configuration.RECOVER_SILENTLY); xpe.getStaticContext().setBackwardsCompatibilityMode(true); xpe.setXPathVariableResolver(testCase.variableResolver); xpe.setXPathFunctionResolver(testCase.functionResolver); xpe.setNamespaceContext(testCase.nsContext); NodeInfo doc = xpe.getConfiguration().buildDocument(new SAXSource(new InputSource(file))); List<Object> results = new ArrayList<Object>(testCase.xpaths.size()); for(XPathInfo xpathInfo: testCase.xpaths){ if(xpathInfo.forEach==null) results.add(xpe.evaluate(xpathInfo.xpath, doc, xpathInfo.resultType)); else{ List<Object> list = new ArrayList<Object>(); List nodeList = (List)xpe.evaluate(xpathInfo.forEach, doc, XPathConstants.NODESET); for(Object context: nodeList) list.add(xpe.evaluate(xpathInfo.xpath, context, xpathInfo.resultType)); results.add(list); } } return results; } @Override @SuppressWarnings({"unchecked"}) public List<?> translate(Object result, NamespaceContext nsContext){ List nodeList = (List)result; int i = 0; for(Object item: nodeList){ if(item instanceof List) nodeList.set(i, translate(item, nsContext)); else{ NodeInfo node = (NodeInfo)item; int type = node.getNodeKind(); String value = ""; if(type!=NodeType.DOCUMENT && type!=NodeType.ELEMENT) value = node.getStringValue(); String localName = node.getLocalPart(); String namespaceURI = node.getURI(); String qualifiedName = node.getDisplayName(); String location = SaxonNavigator.INSTANCE.getXPath(node, nsContext); NodeItem nodeItem = new NodeItem(type, location, value, localName, namespaceURI, qualifiedName); nodeItem.xml = node; nodeList.set(i, nodeItem); } i++; } return nodeList; } @Override public boolean equals(Object node1, Node node2){ if(node1==node2) return true; NodeInfo n1 = (NodeInfo)node1; Node n2 = node2; while(true){ if(!shallowEquals(n1, n2)) return false; if(n1==null){ assert n2==null; return true; } NodeInfo n1Child = new NodeInfoSequence(n1, AxisInfo.CHILD).findNext(); Node n2Child = null; if(n2.getNodeType()==Node.DOCUMENT_NODE || n2.getNodeType()==Node.ELEMENT_NODE) n2Child = n2.getFirstChild(); // the jdk's dom impl returns non-null child for attribute etc if(!shallowEquals(n1Child, n2Child)) return false; if(n1Child==null){ assert n2Child==null; if(n1==node1 && n2==node2) return true; while(true){ NodeInfo n1Sibling = new NodeInfoSequence(n1, AxisInfo.FOLLOWING_SIBLING).findNext(); Node n2Sibling = n2.getNextSibling(); if(!shallowEquals(n1Sibling, n2Sibling)) return false; if(n1Sibling==null){ assert n2Sibling==null; NodeInfo n1Parent = new NodeInfoSequence(n1, AxisInfo.ANCESTOR).findNext(); Node n2Parent = n2.getParentNode(); if(n1Parent==null && n2Parent==null) return true; if(n1Parent==node1 && n2Parent==node2) return true; assert n1Parent!=null && n2Parent!=null; n1 = n1Parent; n2 = n2Parent; }else{ assert n2Sibling!=null; n1 = n1Sibling; n2 = n2Sibling; break; } } }else{ n1 = n1Child; n2 = n2Child; } } } public static boolean isNamespaceDeclaration(NodeInfo attr){ return Namespaces.URI_XMLNS.equals(attr.getURI()) || attr instanceof NamespaceNode; } private boolean shallowEquals(NodeInfo n1, Node n2){ if(n1==n2) return true; if(n1==null || n2==null) return false; int type1 = n1.getNodeKind(); if(type1==Node.CDATA_SECTION_NODE) type1 = Node.TEXT_NODE; else if(type1==NodeType.NAMESPACE) type1 = Node.ATTRIBUTE_NODE; int type2 = n2.getNodeType(); if(type2==Node.CDATA_SECTION_NODE) type2 = Node.TEXT_NODE; if(type1!=type2) return false; switch(type1){ case Node.PROCESSING_INSTRUCTION_NODE: ProcessingInstruction pi2 = (ProcessingInstruction)n2; String target1 = n1.getDisplayName(); String target2 = pi2.getTarget(); if(!target1.equals(target2)) return false; String data1 = n1.getStringValue(); String data2 = pi2.getData(); if(!data1.equals(data2)) return false; break; case Node.COMMENT_NODE: Comment comment2 = (Comment)n2; data1 = n1.getStringValue(); data2 = comment2.getData(); if(!data1.equals(data2)) return false; break; case Node.ELEMENT_NODE: Element element2 = (Element)n2; String namespaceURI1 = n1.getURI(); if(namespaceURI1==null) namespaceURI1 = ""; String namespaceURI2 = element2.getNamespaceURI(); if(namespaceURI2==null) namespaceURI2 = ""; if(!namespaceURI1.equals(namespaceURI2)) return false; String localName1 = n1.getLocalPart(); String localName2 = element2.getLocalName(); if(!localName1.equals(localName2)) return false; NodeInfoSequence attrs1 = new NodeInfoSequence(n1, AxisInfo.ATTRIBUTE); NamedNodeMap attrs2 = element2.getAttributes(); BitSet bitSet = new BitSet(); NodeInfo attr1; while((attr1=attrs1.findNext())!=null){ if(isNamespaceDeclaration(attr1)) continue; namespaceURI1 = attr1.getURI(); if(namespaceURI1==null) namespaceURI1 = ""; localName1 = attr1.getLocalPart(); String value1 = attr1.getStringValue(); int found = -1; for(int i=0; i<attrs2.getLength(); i++){ Attr attr2 = (Attr)attrs2.item(i); namespaceURI2 = attr2.getNamespaceURI(); if(namespaceURI2==null) namespaceURI2 = ""; localName2 = attr2.getLocalName(); if(namespaceURI1.equals(namespaceURI2) && localName1.equals(localName2)){ String value2 = attr2.getNodeValue(); if(!value1.equals(value2)) return false; found = i; break; } } if(found==-1) return false; else bitSet.set(found); } for(int i=0; i<attrs2.getLength(); i++){ if(!bitSet.get(i)){ Attr attr2 = (Attr)attrs2.item(i); if(!DOMUtil.isNamespaceDeclaration(attr2)) return false; } } break; case Node.ATTRIBUTE_NODE: Attr attr2 = (Attr)n2; namespaceURI1 = isNamespaceDeclaration(n1) ? Namespaces.URI_XMLNS : n1.getURI(); if(namespaceURI1==null) namespaceURI1 = ""; namespaceURI2 = attr2.getNamespaceURI(); if(namespaceURI2==null) namespaceURI2 = ""; if(!namespaceURI1.equals(namespaceURI2)) return false; localName1 = n1.getLocalPart(); localName2 = attr2.getLocalName(); if(!localName1.equals(localName2)) return false; String value1 = n1.getStringValue(); String value2 = attr2.getNodeValue(); if(!value1.equals(value2)) return false; break; case Node.TEXT_NODE: value1 = n1.getStringValue(); value2 = n2.getNodeValue(); if(!value1.equals(value2)) return false; } return true; } static class SaxonNavigator extends Navigator2<NodeInfo>{ static SaxonNavigator INSTANCE = new SaxonNavigator(); @Override public NodeInfo parent(NodeInfo node){ return node.getParent(); } @Override public Sequence<? extends NodeInfo> children(NodeInfo node){ return new NodeInfoSequence(node, AxisInfo.CHILD); } private class XPathConvertor extends PredicateConvertor<NodeInfo>{ public XPathConvertor(NamespaceContext nsContext){ super(INSTANCE, new SaxonXPathNameConvertor(nsContext)); } @Override public String convert(NodeInfo source){ switch(source.getNodeKind()){ case NodeType.ATTRIBUTE: case NodeType.NAMESPACE: return delegate.convert(source); } return super.convert(source); } } public String getXPath(NodeInfo node, NamespaceContext nsContext){ if(node.getNodeKind()==NodeType.DOCUMENT) return "/"; else return getPath(node, new XPathConvertor(nsContext), "/"); } } static class NodeInfoSequence extends AbstractSequence<NodeInfo>{ private NodeInfo nodeInfo; private byte axis; private AxisIterator iterator; NodeInfoSequence(NodeInfo nodeInfo, byte axis){ this.nodeInfo = nodeInfo; this.axis = axis; iterator = nodeInfo.iterateAxis(axis); } NodeInfoSequence(AxisIterator iterator){ this.iterator = iterator; } @Override protected NodeInfo findNext(){ return (NodeInfo)iterator.next(); } @Override public Sequence<NodeInfo> copy(){ return new NodeInfoSequence(nodeInfo, axis); } } static class SaxonXPathNameConvertor implements Convertor<NodeInfo, String>{ private NamespaceContext nsContext; public SaxonXPathNameConvertor(NamespaceContext nsContext){ this.nsContext = nsContext; } @Override public String convert(NodeInfo source){ switch(source.getNodeKind()){ case Node.DOCUMENT_NODE: return ""; case Node.TEXT_NODE: case Node.CDATA_SECTION_NODE: return "text()"; case Node.COMMENT_NODE: return "comment()"; case Node.PROCESSING_INSTRUCTION_NODE: return "processing-instruction('"+source.getDisplayName() +"')"; case Node.ELEMENT_NODE: String prefix = nsContext.getPrefix(source.getURI()); String name = source.getLocalPart(); return StringUtil.isEmpty(prefix) ? name : prefix+':'+name; case Node.ATTRIBUTE_NODE: if(Namespaces.URI_XMLNS.equals(source.getURI())) return "namespace::"+source.getLocalPart(); prefix = nsContext.getPrefix(source.getURI()); name = source.getLocalPart(); return '@'+ (StringUtil.isEmpty(prefix) ? name : prefix+':'+name); case NodeType.NAMESPACE: return "namespace::"+source.getLocalPart(); default: return null; } } } }