DOM3级XPath – 学习画板

首页 » XML » DOM3级XPath

DOM3级XPath

DOM3级XPath规范定义了在DOM中对XPath表达式求值的接口。要确定某些浏览器是否支持DOM3级XPath,可以使用以下JavaScript代码:

var supportsXPath = document.implementation.hasFeature("XPath", "3.0");

在DOM3级XPath规范定义的类型中,最重要的两个类型是XPathEvaluator和XPathResult。XPathEvaluator用于在特定的上下文中对XPath表达式求值。这个类型有下列3个方法。

  • createExpression(expression, nsresolver):将XPath表达式及相应的命名空间信息转换成一个XPathExpression,这是查询的编译版。在多次使用同一个查询时很有用。
  • createNSResolver(node):根据node的命名空间信息创建一个新的XPathNSResolver对象。在基于使用命名空间的XML文档求值时,需要使用XPathNSResolver对象。
  • evaluate(expression, context, nsresolver, type, result):在给定的上下文中,基于指定的命名空间信息来对XPath表达式求值。剩下的参数指定如何返回结果。

在Firefox、Safari、Chrome和Opera中,Document类型通常都是与XPathEvaluator接口一起实现的。换句话说,在这些浏览器中,既可以创建XPathEvaluator的新实例,也可以使用Document实例中的方法。

在上面这三个方法中,evaluate()是最常用的。这个方法接收5个参数:XPath表达式、上下文节点、命名空间求解器、返回结果的类型和保存结果的XPathResult对象。其中,第三个参数只在XML代码中使用了XML命名空间时有必要指定:如果XML代码中没有使用命名空间,则这个参数应该指定为null。第四个参数的取值范围是下列常量之一。

  • XPathResult.ANY_TYPE:返回与XPath表达式匹配的数据类型。
  • XPathResult.NUMBER_TYPE:返回数值。
  • XPathResult.STRING_TYPE:返回字符串值。
  • XPathResult.BOOLEAN_TYPE:返回布尔值。
  • XPathResult.UNORDERED_NODE_ITERATOR_TYPE:返回匹配的节点集合,但集合中节点的次序不一定与它们在文档中的次序一致。
  • XPathResult.ORDERED_NODE_ITERATOR_TYPE:返回匹配的节点集合,集合中节点的次序与它们在文档中的次序一致。这是最常用的结果类型。
  • XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:返回节点集合的快照,由于是在文档外部捕获节点,因此对文档的后续操作不会影响到这个节点集合。集合中节点的次序与它们在文档中的次序一致。
  • XPathResult.ANY_UNORDERED_NODE_TYPE:返回匹配的节点集合,但集合中节点的次序不一定与它们在文档中的次序一致。
  • XPathResult.FIRST_ORDERED_NODE_TYPE:返回只包含一个节点的节点集合,包含的这个指定的结果类型决定了如何取得结果的值。下面来看一个典型的例子。
var xmldom = (new DOMParser()).parseFromString("<employees><employee title=\"Software Engineer\"><name>Nicholas C. Zakas</name></employee><employee title=\"Salesperson\"><name>Jim Smith</name></employee></employees>", "text/xml");
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
if(result !== null) {
    var node = result.iterateNext();
    while(node) {
        alert(node.tagName);
        node = node.iterateNext();
    }
}

这个例子中为返回结果指定的是XPathResult.ORDERED_NODE_ITERATOR_TYPE,也是最常用的结果类型。如果没有节点匹配XPath表达式,evaluate()返回null;否则,它会返回一个XPathResult对象。这个XPathResult对象带有的属性和方法,可以用来取得特定类型的结果。如果节点是一个节点迭代器,无论是次序一致还是次序不一致的,都必须要使用iterateNext()方法从节点中取得匹配的节点。在没有更多的匹配节点时,iterateNext()返回null。

如果指定的是快照结果类型,就必须使用snapshotItem()方法和snapshotLength属性,例如:

var xmldom = (new DOMParser()).parseFromString("<employees><employee title=\"Software Engineer\"><name>Nicholas C. Zakas</name></employee><employee title=\"Salesperson\"><name>Jim Smith</name></employee></employees>", "text/xml");
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if(result !== null) {
    for(var i = 0, len = result.snapshotLength; i < len; i++) {
        alert(result.snapshotItem(i).tagName);
    }
}

这里,snapshotLength返回的快照中节点的数量,而snapshotItem()则返回快照中给定位置的节点(与NodeList中的length和item()相似)。

1.单节点结果

指定常量XPathResult.FIRST_ORDERED_NODE_TYPE会返回第一个匹配的节点,可以通过结果的singleNodeValue属性来访问该节点。例如:

var xmldom = (new DOMParser()).parseFromString("<employees><employee title=\"Software Engineer\"><name>Nicholas C. Zakas</name></employee><employee title=\"Salesperson\"><name>Jim Smith</name></employee></employees>", "text/xml");
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if(result !== null) {
    alert(result.singleNodeValue.tagName);
}

与前面的查询一样,在没有匹配节点的情况下,evaluate()返回null。如果有节点返回,那么就可以通过singleNodeValue属性来访问它。

2.简单类型结果

通过XPath也可以取得简单的非节点类型,这时候就要使用XPathResult的布尔值、数值和字符串类型了。这几个结果类型分别会通过booleanValue、numberValue和stringValue属性返回一个值。对于布尔值类型,如果至少有一个节点与XPath表达式匹配,则求值结果返回true,否则返回false。来看下面的例子。

var xmldom = (new DOMParser()).parseFromString("<employees><employee title=\"Software Engineer\"><name>Nicholas C. Zakas</name></employee><employee title=\"Salesperson\"><name>Jim Smith</name></employee></employees>", "text/xml");
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null, XPathResult.BOOLEAN_TYPE, null);
alert(result.booleanValue);

在这个例子中,如果有节点匹配”employee/name”,则booleanValue属性的值就是true。

对于数值类型,必须在XPath表达式参数的位置上指定一个能够返回数值的XPath函数,例如计算与给定模式匹配的所有节点数量的count()。来看下面的例子。

var xmldom = (new DOMParser()).parseFromString("<employees><employee title=\"Software Engineer\"><name>Nicholas C. Zakas</name></employee><employee title=\"Salesperson\"><name>Jim Smith</name></employee></employees>", "text/xml");
var result = xmldom.evaluate("count(employee/name)", xmldom.documentElement, null, XPathResult.NUMBER_TYPE, null);
alert(result.numberValue);

以上代码会输出与”employee/name”匹配的节点数量(即2)。如果使用这个方法的时候没有指定与前例类似的XPath函数,那么numberValue的值将等于NaN。

对于字符串类型,evaluate()方法会查找与XPath表达式匹配的第一个节点,然后返回其第一个子节点的值(实际上是假设第一个子节点为文本节点)。如果没有匹配的节点,结果就是一个空字符串。来看一个例子。

var xmldom = (new DOMParser()).parseFromString("<employees><employee title=\"Software Engineer\"><name>Nicholas C. Zakas</name></employee><employee title=\"Salesperson\"><name>Jim Smith</name></employee></employees>", "text/xml");
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null, XPathResult.STRING_TYPE, null);
alert(result.stringValue);

这个例子的输出结果中包含着与”element/name”匹配的第一个元素的第一个节点中包含的字符串。

3.默认类型结果

所有XPath表达式都会自动映射到特定的结果类型。像前面那样设置特定的结果类型,可以限制表达式的输出。而使用XPathResult.ANY_TYPE常量可以自动确定返回结果的类型。一般来说,自动选择的结果类型可能是布尔值、数值、字符串值或一个次序不一致的节点迭代器。要确定返回的是什么结果类型,可以检测结果的resultType属性,如下面的例子所示:

var xmldom = (new DOMParser()).parseFromString("<employees><employee title=\"Software Engineer\"><name>Nicholas C. Zakas</name></employee><employee title=\"Salesperson\"><name>Jim Smith</name></employee></employees>", "text/xml");
var result = xmldom.evaluate("employee/name", xmldom.documentElement, null, XPathResult.ANY_TYPE, null);
if(result !== null) {
    switch(result.resultType) {
    case XPathResult.STRING_TYPE:
        //处理字符串类型
        break;
    case XPathResult.NUMBER_TYPE:
        //处理数值类型
        break;
    case XPathResult.BOOLEAN_TYPE:
        //处理布尔值类型
        break;
    case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
        //处理次序不一致的节点迭代器类型
        break;
    default:
        //处理其它可能的结果类型
    }
}

显然,XPathResult.ANY_TYPE可以让我们更灵活地使用XPath,但却要求有更多的处理代码来处理返回的结果。

4.命名空间支持

对于利用了命名空间的XML文档,XpathEvaluator必须知道命名空间信息,然后才能正确地进行求值。处理命名空间的方法也不止一种。我们以下面的XML代码为例。

<?xml version = ?>
<w3cmm:books xmlns:w3cmm="https://www.w3cmm.com/">
    <w3cmm:book>
        <w3cmm:title>Professional JavaScript for Web Developers</w3cmm:title>
        <worx:author>Nicholas C. Zakas</worx:author>
    </w3cmm:book>
    <w3cmm:book>
        <w3cmm:title>Professional Ajax</w3cmm:title>
        <worx:author>Nicholas C. Zakas</worx:author>
        <worx:author>Jeremy McPeak</worx:author>
        <worx:author>Joe Fawcett</worx:author>
    </w3cmm:book>
</w3cmm:books>

在这个XML文档中,所有元素定义都来自https://www.w3cmm.com/命名空间,以前缀w3cmm标识。如果要对这个文档使用Xpath,就需要定义要使用的命名空间;否则求值将会失败。

处理命名空间的第一种方法是通过createNSResolver()来创建XPathNSResolver对象。这个方法接收一个参数,即文档中包含命名空间定义的节点。对于前面的XML文档来说,这个节点就是文档元素<w3cmm:books>,它的xmlns特性定义了命名空间。可以把这个节点传递给createNSResolver(),然后可以像下面这样在evaluate()中使用返回的结果。

var xmldom = (new DOMParser()).parseFromString("<?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>", "text/xml");
var nsresolver = xmldom.createNSResolver(xmldom.documentElement);
var result = xmldom.evaluate("w3cmm:book/w3cmm:author", 
                            xmldom.documentElement, nsresolver,
                            XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

alert(result.snapshotLength);

在将nsresolver对象传入到evaluate()之后,就可以确保它能够理解XPath表达式中使用的w3cmm前缀。读者可以试一试使用相同的表达式,如果不使用XPathNSResolver的话,就会导致错误。

处理命名空间的第二种方法就是定义一个函数,让它接收一个命名空间前缀,返回关联的URI,例如:

var nsresolver = function(prefix) {
        switch(prefix) {
        case "w3cmm":
            return "https://www.w3cmm.com/";
            //其他前缀
        }
    };
var result = xmldom.evaluate("count(w3cmm:book/w3cmm:author)", xmldom.documentElement, nsresolver, XPathResult.NUMBER_TYPE, null);
alert(result.numberValue);

在不确定文档中的哪个节点包含命名空间定义的情况下,这个命名空间解析函数就可以派上用场了。只要你知道前缀和URI,就可以定义一个返回该信息的函数,然后将它作为第三个参数传递给evaluate()即可。

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