четверг, 25 августа 2011 г.

groovy Node xpath based on jaxen

This is an implementation of the xpath for groovy.util.Node.

Prerequisites:
Java 6
Groovy 1.7.5
Jaxen 1.1.3

Idea and example:
def x=new groovy.util.XmlParser().parseText('''
    
        
            test1
            test2
            test3
            test4
        
    
''');
//print out our xml
println( groovy.xml.XmlUtil.serialize(x) );

def nsCtx=['sab':'http://sab/'];

println( "/sab:foo/bar/sab:baz[@t='xx']/@f == "+GXPath.valueOf( x, "/sab:foo/bar/sab:baz[@t='xx']/@f", nsCtx  ) );
println( "/*/bar/baz[@t='xx']/@f           == "+GXPath.valueOf( x, "/*/bar/baz[@t='xx']/@f",           nsCtx  ) );
println( "sum(//*[local-name()='baz']/@f)  == "+GXPath.valueOf( x, "sum(//*[local-name()='baz']/@f)",  nsCtx  ) );
println( "concat(/sab:foo/@f,'-t')         == "+GXPath.valueOf( x, "concat(/sab:foo/@f,'-t')",         nsCtx  ) );
println( "//*[local-name()='foo']/@f       == "+GXPath.valueOf( x, "//*[local-name()='foo']/@f",       nsCtx  ) );
println( "//*[local-name()='baz']          == "+GXPath.getNode( x, "//*[local-name()='baz']",          nsCtx  ) );

Result:
/sab:foo/bar/sab:baz[@t='xx']/@f == 1
/*/bar/baz[@t='xx']/@f           == 2
sum(//*[local-name()='baz']/@f)  == 3
concat(/sab:foo/@f,'-t')         == root-t
//*[local-name()='foo']/@f       == root
//*[local-name()='baz']          == {http://sab/}baz[attributes={t=xx, f=1}; value=[test0]]

The groovy class to do that:
/* XPath for groovy node */

import org.jaxen.XPath;
import org.jaxen.XPathSyntaxException;
import org.jaxen.JaxenException;
import org.jaxen.BaseXPath;
import org.jaxen.JaxenException;
import org.jaxen.DefaultNavigator;
import org.jaxen.FunctionCallException;
import org.jaxen.NamedAccessNavigator;
import org.jaxen.Navigator;
import org.jaxen.XPath;
import java.util.HashMap;
import org.jaxen.util.SingleObjectIterator;
import org.jaxen.JaxenConstants;

public class GXPath extends DefaultNavigator implements NamedAccessNavigator {
    /*-------- HELPER METHODS --------*/
    public static org.jaxen.XPath parse(String path, java.util.HashMap nsContext=null){
        org.jaxen.XPath xpath=instance.parseXPath(path);
        if(nsContext!=null) {
            for(i in nsContext){
                xpath.addNamespace(i.getKey(), i.getValue()); 
            }
        }
        return xpath;
    }
    
    public static String valueOf(groovy.util.Node node, String path, java.util.HashMap nsContext=null){
        org.jaxen.XPath xpath=GXPath.parse(path,nsContext);
        return xpath.stringValueOf(node);
    }
    
    public static Object getNode(groovy.util.Node node, String path, java.util.HashMap nsContext=null){
        org.jaxen.XPath xpath=GXPath.parse(path,nsContext);
        return xpath.selectSingleNode(node);
    }
    
    public static List getNodes(groovy.util.Node node, String path, java.util.HashMap nsContext=null){
        org.jaxen.XPath xpath=GXPath.parse(path,nsContext);
        return xpath.selectNodes(node);
    }
    
    
    /*-------- NAVIGATOR IMPLEMENTATION --------*/
    private static GXPath instance = new GXPath();

    public static Navigator getInstance() {
        return instance;
    }
    
    //i guess it should return empty itarator to work faster
    //i can't understand why it's used for node comparison
    public Iterator getFollowingSiblingAxisIterator(Object contextNode) throws org.jaxen.UnsupportedAxisException {
        return JaxenConstants.EMPTY_ITERATOR;
    }
    
    java.util.Iterator getAttributeAxisIterator(Object obj, String localName, String namespacePrefix, String namespaceURI){
        if ( obj instanceof groovy.util.Node ) {
            def name=localName;
            if(namespaceURI!=null&&namespaceURI.length()>0)name=new groovy.xml.QName(namespaceURI,localName,'x');
            def ret=obj.attribute(name);
            if(ret!=null){
                return new SingleObjectIterator( ret );
            }
        }
        return JaxenConstants.EMPTY_ITERATOR;
    }

    public Iterator getChildAxisIterator(Object obj, String localName, String namespacePrefix, String namespaceURI){
        Iterator result = null;
        def name=localName;
        if(namespaceURI!=null&&namespaceURI.length()>0)name=new groovy.xml.QName(namespaceURI,localName,'x');
        if ( obj instanceof GDoc ) {
            if( name.equals( obj.getRoot().name() ) ){
                result = new SingleObjectIterator( obj.getRoot() );
            }
        }else if ( obj instanceof groovy.util.Node ) {
            result = obj[name]?.iterator();
        }
        if (result != null) return result;
        return JaxenConstants.EMPTY_ITERATOR;
    }

    public Iterator getChildAxisIterator(Object obj) {
        Iterator result = null;
        if ( obj instanceof GDoc ) {
            return  new SingleObjectIterator( obj.getRoot() );
        }else if ( obj instanceof groovy.util.Node ) {
            result = obj.iterator();
        }
        if (result != null) return result;
        return JaxenConstants.EMPTY_ITERATOR;
    }

    public Iterator getParentAxisIterator(Object contextNode) {
        Iterator result = null;
        if ( contextNode instanceof groovy.util.Node ) {
            groovy.util.Node node = (groovy.util.Node) contextNode;
            groovy.util.Node parent = node.parent();
            if(parent!=null)return new SingleObjectIterator( parent );
        }
        return JaxenConstants.EMPTY_ITERATOR;
    }

    
    public Object getDocumentNode(Object obj) {
        if ( obj instanceof groovy.util.Node ) {
            groovy.util.Node node = (groovy.util.Node) obj;
            while(node.parent()!=null){
                node=node.parent();
            }
            return new GDoc(node);
        }
        return null;
    }
    
    public Object getParentNode(Object contextNode) {
        if ( contextNode instanceof groovy.util.Node ) {
            groovy.util.Node node = (groovy.util.Node) contextNode;
            return node.parent();
        }
        return null;
    }

    public boolean isElement(Object obj) {
        return obj instanceof groovy.util.Node;
    }

    public boolean isComment(Object obj) {
        return false; //not supported by groovy node
    }

    public boolean isText(Object obj){
        return ( obj instanceof String );
    }

    public boolean isAttribute(Object obj) {
        return obj instanceof java.util.Map.Entry;
    }

    public boolean isProcessingInstruction(Object obj) {
        return false;
    }

    public boolean isDocument(Object obj) {
        if(obj instanceof groovy.util.Node){
            if(obj.parent()==null)return true;
        }
        return false;
    }

    public boolean isNamespace(Object obj) {
        return obj instanceof groovy.xml.Namespace;
    }

    public String getElementName(Object obj){
        groovy.util.Node elem = (groovy.util.Node) obj;
        Object name = elem.name();
        if(name instanceof String)return name;
        return name.getLocalPart();
    }

    public String getElementNamespaceUri(Object obj) {
        String uri=null;
        groovy.util.Node elem = (groovy.util.Node) obj;
        Object name = elem.name();
        if(name instanceof groovy.xml.QName){
            uri = name.getNamespaceURI();
        }
        if ( uri != null && uri.length() == 0 ) return null;
        return uri;
    }

    public String getElementQName(Object obj) {
        groovy.util.Node elem = (groovy.util.Node) obj;
        return elem.name().getQualifiedName();
    }

    public String getAttributeName(Object obj) {
        java.util.Map.Entry attr=(java.util.Map.Entry)obj;
        if(attr.getKey() instanceof String)return attr.getKey();
        if(attr.getKey() instanceof groovy.xml.QName )return attr.getKey().getLocalPart();
        return ""; //?exception
    }

    public String getAttributeNamespaceUri(Object obj) {
        java.util.Map.Entry attr=(java.util.Map.Entry)obj;
        if(attr.getKey() instanceof groovy.xml.QName )return attr.getKey().getNamespaceURI();
        return "";
    }

    public String getAttributeQName(Object obj) {
        java.util.Map.Entry attr=(java.util.Map.Entry)obj;
        if(attr.getKey() instanceof String)return attr.getKey();
        if(attr.getKey() instanceof groovy.xml.QName )return attr.getQualifiedName();
        return ""; //?exception
    }

    public Iterator getAttributeAxisIterator(Object contextNode) {
        if ( contextNode instanceof groovy.util.Node ) {
            groovy.util.Node node = (groovy.util.Node) contextNode;
            return node.attributes().entrySet().iterator();
        }
        return JaxenConstants.EMPTY_ITERATOR;
    }

    public XPath parseXPath (String xpath) throws org.jaxen.saxpath.SAXPathException {
        return new BaseXPath(xpath,instance);
    }

    public String getTextStringValue(Object obj) {
        if ( obj instanceof String ) {
            return obj;
        }
        return null;
    }

    public String getElementStringValue(Object obj) {
        if ( obj instanceof groovy.util.Node ) {
            groovy.util.Node node = (groovy.util.Node) obj;
            return node.text();
        }
        return null;
    }

    public String getAttributeStringValue(Object obj) {
        if ( obj instanceof java.util.Map.Entry ) {
            return obj.getValue();
        }
        return null;
    }

    public String getNamespaceStringValue(Object obj) {
        groovy.xml.Namespace ns = (groovy.xml.Namespace) obj;
        return ns.getUri();
    }

    public String getNamespacePrefix(Object obj) {
        groovy.xml.Namespace ns = (groovy.xml.Namespace) obj;
        return ns.getPrefix();
    }

    public String getCommentStringValue(Object obj) {
        return null;
    }
    
    public Iterator getNamespaceAxisIterator(Object contextNode) {
        if ( contextNode instanceof groovy.util.Node ) {
            groovy.util.Node node = (groovy.util.Node) contextNode;
            Map ns=new HashMap();
            for( i in node.depthFirst() ){
                if( i.getKey() instanceof groovy.xml.QName && ns.containsKey( i.getKey().getPrefix() ) ){
                    ns.put(i.getKey().getPrefix(), new groovy.xml.Namespace(i.getKey().getNamespaceURI(), i.getKey().getPrefix()) );
                }
            }
            return ns.values().iterator();
        }
        return JaxenConstants.EMPTY_ITERATOR;
    }

    public String translateNamespacePrefixToUri(String prefix, Object obj) {
        if ( obj instanceof groovy.util.Node && prefix!=null) {
            groovy.util.Node node = (groovy.util.Node) obj;
            for( i in node.depthFirst() ){
                if( i.getKey() instanceof groovy.xml.QName && prefix.equals(i.getKey().getPrefix()) ){
                    return i.getKey().getNamespaceURI();
                }
            }
        }
        return null;
    }
    
    
    public Object getDocument(String uri) throws FunctionCallException {
        try {
            return groovy.util.XmlParser.parse( uri );
        } catch (Exception e) {
            throw new FunctionCallException("Failed to parse document for URI: " + uri, e);
        }
    }
    
    public class GDoc{
        private groovy.util.Node root;
        public GDoc(groovy.util.Node n){
            root=n;
        }
        public groovy.util.Node getRoot(){
            return root;
        }
    }
    
}


Problems:

In Jaxen 1.1.3 there is a strange code that sorts the result nodes in own strange way...
So, when you try to get single node with GXPath, you will receive the last one instead of first one.