JavaScript中实现跨浏览器XPath

35

我正在寻找一款XPath库,可以在FF、IE、Opera和Safari中查询XML文档……但是我找不到合适的。你有见过吗?


Федор,看起来你没有阅读已发布的答案?你已经找到合适的实现了吗?干杯 - Dimitre Novatchev
12个回答

11

谷歌刚刚发布了“Wicked Good XPath”——Cybozu Lab著名的JavaScript-XPath的重写版。

链接:https://github.com/google/wicked-good-xpath

这个重写版相比原始实现,体积缩小了40%,速度提高了约30%。


这是2012年的正确选择。很遗憾你不能废弃答案。 - Janus Troelsen
1
也许对于HTML文档来说这是一个不错的解决方案。但是对于XML文档存在一个未解决的问题 https://code.google.com/p/wicked-good-xpath/issues/detail?id=7 - dizel3d
请注意,此刻链接中的下载未能正常工作。我发现最快的解决方案是从 NPM 包(此处)中获取。 - Kat
该项目已经因为Google Code即将关闭而迁移到GitHub。我刚刚更新了链接。 - Dominator008

6

Google's AJAXSLT开源项目完美符合所述要求。

正如他们自己所说:

“AJAXSLT是JavaScript中XSLT的实现。因为XSLT使用XPath,它也是XPath的一种实现,可以独立于XSLT使用。这种实现的优点在于,它使得XSLT在比本地提供更多浏览器上统一可用,并且如果需要,可以将其扩展到更多浏览器上。 AJAXSLT对于那些努力实现高级Web应用程序跨浏览器兼容性的开发人员非常有趣。 "

更新:在2010年底,Michael Kay使用GWT将他的Saxon XSLT 2.0处理器编译为Javascript(从而使其适用于所有5个主要浏览器)。很可能会很快出现轻量级的浏览器内Saxon。


截至2011年11月,Kay的Saxon客户端版(CE)仍处于alpha测试阶段。 - james.garriss
@james.garriss:这并不意味着它没有被使用。在xsl-list上有一些有趣的讨论线程关于这个话题。我怀疑Saxonica的主要问题不是技术上的问题,而更多地是找到适合Saxon CE盈利的正确商业模式。 - Dimitre Novatchev
链接页面现在有以下内容:“自从这个库在2005年实现以来,浏览器和Web应用程序的世界已经发生了变化。现在有更多的浏览器原生实现XSLT和XPath,更重要的是,越来越少的Web应用程序使用XML作为它们的传输数据格式,并转向更合适和实用的JSON。一种更简单、更灵活的将数据绑定到HTML的方法是使用google-jstemplate在浏览器端显示Web应用程序。” - Wayne
@lwburk:我不会惊讶地看到Saxon CE被所有主要浏览器用作其标准的基于JS的XSLT 2.0处理器。 - Dimitre Novatchev

4

1
谢谢!我已经看了两个。XPath插件用于jQuery,它只是将XPath表达式转换为jQuery选择表达式。即使对于简单的情况,它也无法正常工作。 - Fedor Sheremetyev
截至今天,这些库目前不可用。 - jb.

4

这是我使用的内容

// xpath.js
// ------------------------------------------------------------------
//
// a cross-browser xpath class.
// Derived form code at http://jmvidal.cse.sc.edu/talks/javascriptxml/xpathexample.html.
//
// Tested in Chrome, IE9, and FF6.0.2
//
// Author     : Dino
// Created    : Sun Sep 18 18:39:58 2011
// Last-saved : <2011-September-19 15:07:20>
//
// ------------------------------------------------------------------

/*jshint browser:true */

(function(globalScope) {
    'use strict';

    /**
     * The first argument to this constructor is the text of the XPath expression.
     *
     * If the expression uses any XML namespaces, the second argument must
     * be a JavaScript object that maps namespace prefixes to the URLs that define
     * those namespaces.  The properties of this object are taken as prefixes, and
     * the values associated to those properties are the URLs.
     *
     * There's no way to specify a non-null default XML namespace. You need to use
     * prefixes in order to reference a non-null namespace in a query.
     *
     */

    var expr = function(xpathText, namespaces) {
        var prefix;
        this.xpathText = xpathText;    // Save the text of the expression
        this.namespaces = namespaces || null;  // And the namespace mapping

        if (document.createExpression) {
            this.xpathExpr = true;
            // I tried using a compiled xpath expression, it worked on Chrome,
            // but it did not work on FF6.0.2.  Threw various exceptions.
            // So I punt on "compiling" the xpath and just evaluate it.
            //
            // This flag serves only to store the result of the check.
            //

                // document.createExpression(xpathText,
                // // This function is passed a
                // // namespace prefix and returns the URL.
                // function(prefix) {
                //     return namespaces[prefix];
                // });
        }
        else {
            // assume IE and convert the namespaces object into the
            // textual form that IE requires.
            this.namespaceString = "";
            if (namespaces !== null) {
                for(prefix in namespaces) {
                    // Add a space if there is already something there
                    if (this.namespaceString.length>1) this.namespaceString += ' ';
                    // And add the namespace
                    this.namespaceString += 'xmlns:' + prefix + '="' +
                        namespaces[prefix] + '"';
                }
            }
        }
    };

    /**
     * This is the getNodes() method of XPath.Expression.  It evaluates the
     * XPath expression in the specified context.  The context argument should
     * be a Document or Element object.  The return value is an array
     * or array-like object containing the nodes that match the expression.
     */
    expr.prototype.getNodes = function(xmlDomCtx) {
        var self = this, a, i,
            doc = xmlDomCtx.ownerDocument;

        // If the context doesn't have ownerDocument, it is the Document
        if (doc === null) doc = xmlDomCtx;

        if (this.xpathExpr) {
            // could not get a compiled XPathExpression to work in FF6
            // var result = this.xpathExpr.evaluate(xmlDomCtx,
            //     // This is the result type we want
            //     XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
            //     null);

            var result = doc.evaluate(this.xpathText,
                xmlDomCtx,
                function(prefix) {
                    return self.namespaces[prefix];
                },
                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                null);

            // Copy the results into an array.
            a = [];
            for(i = 0; i < result.snapshotLength; i++) {
                a.push(result.snapshotItem(i));
            }
            return a;
        }
        else {
            // evaluate the expression using the IE API.
            try {
                // This is IE-specific magic to specify prefix-to-URL mapping
                doc.setProperty("SelectionLanguage", "XPath");
                doc.setProperty("SelectionNamespaces", this.namespaceString);

                // In IE, the context must be an Element not a Document,
                // so if context is a document, use documentElement instead
                if (xmlDomCtx === doc) xmlDomCtx = doc.documentElement;
                // Now use the IE method selectNodes() to evaluate the expression
                return xmlDomCtx.selectNodes(this.xpathText);
            }
            catch(e2) {
                throw "XPath is not supported by this browser.";
            }
        }
    };


    /**
     * This is the getNode() method of XPath.Expression.  It evaluates the
     * XPath expression in the specified context and returns a single matching
     * node (or null if no node matches).  If more than one node matches,
     * this method returns the first one in the document.
     * The implementation differs from getNodes() only in the return type.
     */
    expr.prototype.getNode = function(xmlDomCtx) {
        var self = this,
                doc = xmlDomCtx.ownerDocument;
        if (doc === null) doc = xmlDomCtx;
        if (this.xpathExpr) {

            // could not get compiled "XPathExpression" to work in FF4
            // var result =
            //     this.xpathExpr.evaluate(xmlDomCtx,
            //     // We just want the first match
            //     XPathResult.FIRST_ORDERED_NODE_TYPE,
            //     null);

            var result = doc.evaluate(this.xpathText,
                xmlDomCtx,
                function(prefix) {
                    return self.namespaces[prefix];
                },
                XPathResult.FIRST_ORDERED_NODE_TYPE,
                null);
            return result.singleNodeValue;
        }
        else {
            try {
                doc.setProperty("SelectionLanguage", "XPath");
                doc.setProperty("SelectionNamespaces", this.namespaceString);
                if (xmlDomCtx == doc) xmlDomCtx = doc.documentElement;
                return xmlDomCtx.selectSingleNode(this.xpathText);
            }
            catch(e) {
                throw "XPath is not supported by this browser.";
            }
        }
    };


    var getNodes = function(context, xpathExpr, namespaces) {
        return (new globalScope.XPath.Expression(xpathExpr, namespaces)).getNodes(context);
    };

    var getNode  = function(context, xpathExpr, namespaces) {
        return (new globalScope.XPath.Expression(xpathExpr, namespaces)).getNode(context);
    };


    /**
     * XPath is a global object, containing three members.  The
     * Expression member is a class modelling an Xpath expression.  Use
     * it like this:
     *
     *   var xpath1 = new XPath.Expression("/kml/Document/Folder");
     *   var nodeList = xpath1.getNodes(xmldoc);
     *
     *   var xpath2 = new XPath.Expression("/a:kml/a:Document",
     *                                   { a : 'http://www.opengis.net/kml/2.2' });
     *   var node = xpath2.getNode(xmldoc);
     *
     * The getNodes() and getNode() methods are just utility methods for
     * one-time use. Example:
     *
     *   var oneNode = XPath.getNode(xmldoc, '/root/favorites');
     *
     *   var nodeList = XPath.getNodes(xmldoc, '/x:derp/x:twap', { x: 'urn:0190djksj-xx'} );
     *
     */

    // place XPath into the global scope.
    globalScope.XPath = {
        Expression : expr,
        getNodes   : getNodes,
        getNode    : getNode
    };

}(this));

你应该将这个放在Sourceforge或类似的网站上。 - james.garriss
现在这在IE10上失败了。 - Niels Steenbeek

3
请查看http://dev.abiss.gr/sarissa/项目。他们已经将大部分与XML相关的API迁移到IE,还在文档对象上评估了方法。而且,jQuery没有XPath处理器,它只有一个非常简单的路径选择器,例如:/a/b/c。

2
这是JavaScript中最新的跨浏览器XPath实现: https://github.com/andrejpavlovic/xpathjs 它功能齐全,经过单元测试,并且得到了很好的支持。最棒的部分是它还支持命名空间!

1

您可能想尝试使用支持XPath 2.0语法的jQuery XPath插件,它可以跨浏览器工作。


0
您可以利用每个浏览器现有的本地DOM支持。为此,您需要创建自己的包装器,原因是浏览器之间存在差异。您可以查看{{link1:http://dotnetcaffe.blogspot.com}} 此致敬礼

谢谢提供链接!我曾经放弃寻找库并实现了一个简单的浏览器特定包装器,围绕本地DOM。我会尝试使用你的。 - Fedor Sheremetyev

0

FormFaces(JS中的XForms实现)具有可靠的XPath引擎,可以轻松地提取并独立使用。


0

我认为你可以在这里使用Cameron McCormack的xpath库。它对我来说完美地工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接