首页 » XML » 跨浏览器使用XPath

跨浏览器使用XPath

鉴于IE对XPath功能的支持有限,因此跨浏览器XPath只能保证达到IE支持的功能。换句话说,也就是要在其它使用DOM3级XPath对象的浏览器中,重新创建selectSingleNode()和selectNodes()方法。第一个函数是selectSingleNode(),它接收三个参数:上下文节点、XPath表达式和可选的命名空间对象。命名空间对象应该是下面这种字面量的形式。

{
    prefix1: "uri1",
    prefix2: "uri2",
    prefix3: "uri3"
}

以这种方式提供的命名空间信息,可以方便地转换为针对特定浏览器的命名空间解析格式。下面给出了selectSingleNode()函数的完整代码。

function selectSingleNode(context, expression, namespaces) {
    var doc = (context.nodeType != 9 ? context.ownerDocument : context);
    if (typeof doc.evaluate != "undefined") {
        var nsresolver = null;
        if (namespaces instanceof Object) {
            nsresolver = function(prefix) {
                return namespaces[prefix];
            };
        }
        var result = doc.evaluate(expression, context, nsresolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        return (result !== null ? result.singleNodeValue : null);
    } else if (typeof context.selectSingleNode != "undefined") {
        if (namespaces instanceof Object) {
            var ns = "";
            for (var prefix in namespaces) {
                if (namespaces.hasOwnProperty(prefix)) {
                    ns += "xmlns:" + prefix + "='" + namespaces[prefix] + "' ";
                }
            }
            doc.setProperty("SelectionNamespaces", ns);
        }
        return context.selectSingleNode(expression);
    } else {
        throw new Error("No XPath engine found.");
    }

}

这个函数首先确定XML文档,以便基于该文档对表达式求值。由于上下文节点可能是文档,所以必须要检测nodeType属性。此后,变量doc中会保存对XML文档的引用。然后,可以检测文档中是否存在evaluate()方法,即是否支持DOM3级XPath。如果支持,接下来就是检测传入的namespaces对象。在这里使用instanceof操作符而不是typeof,是因为后者对null也返回”object”。然后将nsresolver变量初始化为null,如果提供了命名空间信息的话,就将其改为一个函数。这个函数是一个闭包,它使用传入的namespace对象来返回命名空间的URI。然后,调用evaluate()方法,并对其结果进行检测,在确定是节点之后再返回该结果。

在这个函数针对IE的分支中,需要检查context节点中是否存在selectSingleNode()方法。与DOM分支一样,这里的第一步是有选择地构建命名空间信息。如果传入了namespaces对象,则迭代其属性并以适当格式创建一个字符串。注意,这里使用了hasOwnProperty()方法来确保对Object.prototype的任何修改都不会影响到当前函数。最后,调用原生的selectSingleNode方()法并返回结果。

如果前面两种方法都没有得到支持,这个函数就会抛出一个错误,标识找不到XPath处理引擎。下面使用selectSingleNode()函数的示例。

function createDocument() {
    if(typeof arguments.callee.activeXString != "string") {
        var versions = ["MSXML2.DOMDocument.6.0", "MSXML2.DOMDocument.3.0", "MSXML2.DOMDocument"];

        for(var i = 0, len = versions.length; i < len; i++) {
            try {
                var xmldom = new ActiveXObject(versions[i]);
                arguments.callee.activeXString = versions[i];
                return xmldom;
            } catch(ex) {
                //skip
            }
        }
    }

    return new ActiveXObject(arguments.callee.activeXString);
}

function serializeXml(xmldom) {

    if(typeof XMLSerializer != "undefined") {
        return(new XMLSerializer()).serializeToString(xmldom);

    } else if(typeof xmldom.xml != "undefined") {
        return xmldom.xml;
    } else {
        throw new Error("Could not serialize XML DOM.");
    }
}

function parseXml(xml) {
    var xmldom = null;

    if(typeof DOMParser != "undefined") {
        xmldom = (new DOMParser()).parseFromString(xml, "text/xml");
        var errors = xmldom.getElementsByTagName("parsererror");
        if(errors.length) {
            throw new Error("XML parsing error:" + errors[0].textContent);
        }
    } else if(typeof ActiveXObject != "undefined") {
        xmldom = createDocument();
        xmldom.loadXML(xml);
        if(xmldom.parseError != 0) {
            throw new Error("XML parsing error: " + xmldom.parseError.reason);
        }
    } else {
        throw new Error("No XML parser available.");
    }

    return xmldom;
}

function selectSingleNode(context, expression, namespaces) {
    var doc = (context.nodeType != 9 ? context.ownerDocument : context);
    if(typeof doc.evaluate != "undefined") {
        var nsresolver = null;
        if(namespaces instanceof Object) {
            nsresolver = function(prefix) {
                return namespaces[prefix];
            };
        }
        var result = doc.evaluate(expression, context, nsresolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        return(result !== null ? result.singleNodeValue : null);
    } else if(typeof context.selectSingleNode != "undefined") {
        if(namespaces instanceof Object) {
            var ns = "";
            for(var prefix in namespaces) {
                if(namespaces.hasOwnProperty(prefix)) {
                    ns += "xmlns:" + prefix + "='" + namespaces[prefix] + "' ";
                }
            }
            doc.setProperty("SelectionNamespaces", ns);
        }
        return context.selectSingleNode(expression);
    } else {
        throw new Error("No XPath engine found.");
    }

}

var xmldom = parseXml("<?xml version=\"1.0\"?><w3cmm:books xmlns:w3cmm=\"https://www.w3cmm.com/\"><w3cmm:book><w3cmm:title>Professional JavaScript for Web Developers</w3cmm:title><w3cmm:author>Nicholas C. Zakas</w3cmm:author></w3cmm:book><w3cmm:book><w3cmm:title>Professional Ajax</w3cmm:title><w3cmm:author>Nicholas C. Zakas</w3cmm:author><w3cmm:author>Jeremy McPeak</w3cmm:author><w3cmm:author>Joe Fawcett</w3cmm:author></w3cmm:book></w3cmm:books>");
var result = selectSingleNode(xmldom.documentElement, "w3cmm:book/w3cmm:author", {
    w3cmm: "https://www.w3cmm.com/"
});
alert(serializeXml(result));

类似的,也可以创建一个跨浏览器的selectNodes()函数。这个函数接收与selectSingleNode()相同的三个参数,而且大部分逻辑都相似。

function selectNodes(context, expression, namespaces) {
    var doc = (context.nodeType != 9 ? context.ownerDocument : context);

    if(typeof doc.evaluate != "undefined") {
        var nsresolver = null;
        if(namespaces instanceof Object) {
            nsresolver = function(prefix) {
                return namespaces[prefix];
            };
        }

        var result = doc.evaluate(expression, context, nsresolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var nodes = new Array();

        if(result !== null) {
            for(var i = 0, len = result.snapshotLength; i < len; i++) {
                nodes.push(result.snapshotItem(i));
            }
        }

        return nodes;
    } else if(typeof context.selectNodes != "undefined") {

        //创建命名空间字符串
        if(namespaces instanceof Object) {
            var ns = "";
            for(var prefix in namespaces) {
                if(namespaces.hasOwnProperty(prefix)) {
                    ns += "xmlns:" + prefix + "='" + namespaces[prefix] + "' ";
                }
            }
            doc.setProperty("SelectionNamespaces", ns);
        }
        var result = context.selectNodes(expression);
        var nodes = new Array();

        for(var i = 0, len = result.length; i < len; i++) {
            nodes.push(result[i]);
        }

        return nodes;
    } else {
        throw new Error("No XPath engine found.");
    }
}

很明显,其中有很多逻辑都与selectSingleNode()方法相同。在函数针对DOM的部分,使用了有序快照结果类型,然后将结果保存在了一个数组中。为了与IE的实现看齐,这个函数应该在没找到匹配项的情况下也返回一个数组,因而最终都要返回数组nodes。在函数针对IE的分支中,调用了selectNodes()方法并将结果复制到了一个数组中。因为IE返回的是一个NodeList,所以最好将节点都复制到一个数组中,这样就可以确保在不同浏览器下,函数都能返回相同的数据类型。使用这个函数的示例如下:

var xmldom = parseXml("<?xml version=\"1.0\"?><w3cmm:books xmlns:w3cmm=\"https://www.w3cmm.com/\"><w3cmm:book><w3cmm:title>Professional JavaScript for Web Developers</w3cmm:title><w3cmm:author>Nicholas C. Zakas</w3cmm:author></w3cmm:book><w3cmm:book><w3cmm:title>Professional Ajax</w3cmm:title><w3cmm:author>Nicholas C. Zakas</w3cmm:author><w3cmm:author>Jeremy McPeak</w3cmm:author><w3cmm:author>Joe Fawcett</w3cmm:author></w3cmm:book></w3cmm:books>");

var result = selectNodes(xmldom.documentElement, "w3cmm:book/w3cmm:author", {
    w3cmm: "https://www.w3cmm.com/"
});
alert(result.length);

为了求的最佳的浏览器兼容性,我们建议在JavaScript中使用XPath时,只考虑使用这两个方法。

此文章发表在 XML. 将 固定链接 加入收藏.