因为XML胜于处理是多种数据,所以必须有一种可以在XML代码中定位数据的方式。这就是XPath,它是专门用于定位匹配模式的一个或多个节点的小语言。
XPath简介
每个XPath表达式都有两部分:一个上下文节点为和一个节点模式。上下文节点人急偎亲了节点为模式的起始位置。节点为模式是由一个或多个节点选择器组成的字符串。
<?xml version="1.0"?> <employees> <employee title="Software Engineer"> <name>Nicholas C. Zakas</name> </employee> <employee title="Salesperson"> <name>Jim Smith</name> </employee> </employees>
请看Xpath表达式:
employee/name
如果上下文节点是<employees/>,则上面的XPath表达式就匹配了<name>Nicholas...</name>和<name>Jim S...</name>。斜杠表示从父节点到子节点为的关系。这个XPath表达式表示:从<employees/>起,匹配位于<employee/>元素下的子节点<name/>元素。如果要选择<employee/>元素的每个<name/>元素,则表达式要变成:
employee[position()=1]/name
在XPath中,方框记号用于为某个节点为提供更加确切的信息。这个例子使用了XPath中的position()函数,它用于返回元素在父节点下的位置。第一个子节点的位置为1。
除了位置和名称外,还可以使用不同的方法来匹配元素。假设要选择所有title属性等于'Salesperson'的<employee/>元素,则XPath表达式变成:
employee[@title = 'Salesperson']
表达式中的@是attribute的缩写 。
XPath是一种十分强大的表达式可以令在DOM文档中查找指定节点为变得很容易。因为,IE和Mozilla都在DOM实现中引用了XPath支持。
IE中的XPath支持
微软的XML DOM中每个节点都有两个可用于获取匹配XPath模式的节点为的方法:selectNodes() ,用于返回匹配某个模式的节点的集合;selectSingleNode() ,用于返回匹配给定模式的第一个节点。
使用前面一节中的数据,可用下面的代码选择所有<employee/>元素下的<name/>元素:
//选择所有<employee/>元素下的<name/>元素 var lstNodes = oXmlDom.documentElement.selectNodes("employee/name");
因为这里selectNodes()是作为oXmlDom.documentElement的方法调用的,所以文档元素被看作XPath表达式的上下文节点 。
方法返回包含所有匹配给定模式的 NodeList 。下面我们可以这样迭代所有的元素:
for (var i = 0; i < lstNodes.length; i++) { alert(lstNodes[i]); }
如果没有匹配给定模式的节点,还是会返回NodeList。如果为空,则它的length属性值等于0 。
如果只需要匹配模式的第一个元素,则可以使用selectSingleNode()。
var oElement = oXmlDom.documentElement.selectSingleNode("employee/name");
如果发现了节点,则selectSingleNode()方法返回一个Element,否则返回null 。
Mozilla中的XPath支持
Mozilla是根据DOM标准来实现对XPath的支持的。但比微软的复杂得多。
虽然有好多与XPath相关的对象,最重要的两个是:XPathEvaluator 和XPathResult 。
XPathEvaluator利用方法evaluate()计算XPath表达式,其方法定义如下:
evaluate(String expression , Node contextNode , XPathNSResolver resolver , short type , nsISupports result )
evaluate方法具有5个参数,其参数含义如下。
- expression:XPath表达式。
- contextNode:上下文节点,evaluate方法将在其内部进行查询。
- resolver:命名空间解释函数,当XPath中存在命名空间时,需要指定该参数进行命名空间的解释。
- type:结果类型,有10种不同的结果类型,分别对应于XPathResult对象中定义的10个常数。
- result:该参数可以是一个存在的XPathResult对象,用于保存XPath查询的结果;也可以是null,evaluate方法将新建一个XPathResult对象来保存结果。
命名空间解释程序,只有在XML代码用到了XML命名空间时才是必要的,所以通常为空,置为null。
返回结果类型是以下十个常量之一:
XPathResult .ANY_TYPE ——返回符合XPath表达式类型的数据
XPathResult .ANY_UNORDERED_NODE_TYPE ——返回匹配节点的节点集合,但顺序可能与文档中的节点顺序不匹配
XPathResult .BOOLEAN_TYPE ——返回布尔值
XPathResult .FIRST_ORDERED_NODE_TYPE ——返回只包含一个节点的节点集合,且这个节点是在文档中第一个匹配的节点
XPathResult .NUMBER_TYPE ——返回数字值
XPathResult .ORDERED_NODE_ITERATOR_TYPE ——返回匹配节点的节点集合的迭代器,顺序为节点在文档中出现的顺序,这是最常用到的结果类型
XPathResult .ORDERED_NODE_SNAPSHOT_TYPE ——返回节点集合快照,在文档外捕获节点,这样将来对文档的任何修改都不会影响这个节点列表。节点集合中的节点与它们出现在文档中的顺序一样
XPathResult .STRING_TYPE ——返回字符串值
XPathResult .UNORDERED_NODE_ITERATOR_TYPE ——返回匹配节点的节点集合的迭代器,不过顺序可能不会按照节点在文档中出的顺序排列
XPathResult .UNORDERED_NODE_SNAPSHOT_TYPE ——返回节点集合快照,在文档外捕获节点,这样将来对文档的任何修改都不会影响这个节点列表。节点集合中的节点与它们出现在文档中的顺序不一定一样
指定的结果类型决定了如何获取结果的值。下面是个典型的例子:
var oEvaluator = new XPathEvaluator(); var oResult = oEvaluator.evaluate("employee/name", oXmlDom.documentElement, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); if (oResult != null) { var oElement = oResult.iterateNext(); while (oElement) { alert(oElement.tagName); oElement = oResult.iterateNext(); } }
这个例子使用了XPathResult.ORDERED_NODE_ITERATOR_TYPE 结果,它是最常用到的结果类型。如果没有节点匹配XPath表达式,evaluate()返回null ,否则,它返回一个XPathResult对象,如果结果是个节点迭代子,不管它是有序还是无序的,都可以不断用iterateNext()方法获取在结果中的每个匹配结果 。如果没有更多的匹配的节点,iterateNext()返回null 。
可以用节点迭代子为模拟微软为Mozilla创建一个selectNodes()方法:
if (isMoz) { Element.prototype.selectNodes = function (sXPath) { var oEvaluator = new XPathEvaluator(); var oResult = oEvaluator.evaluate(sXPath, this, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); var aNodes = new Array; if (oResult != null) { var oElement = oResult.iterateNext(); while (oElement) { aNodes.push(oElement); oElement = oResult.iterateNext(); } } return aNodes; }; Document.prototype.selectNodes = Element.prototype.selectNodes; }
给Element类添加selectNodes()方法以模仿IE中的行为。调用evaluate()时,把用this关键字作为上下文节点(这也是IE的工作方式) ,然后,在结果数组中放入匹配的节点。新的方法使用如下:
var aNodes = oXmlDom.documentElement.selectNodes("employee/name"); for (var i = 0; i < aNodes.length; i++) { alert(aNodes[i].xml); }
如果指定了XPathResult.ORDERED_NODE_SNAPSHOT_TYPE 快照结果类型(不管是有序的还是无序的),都可使用snapshotItem()以及snapshotLength()方法 ,如下例所示:
var oEvaluator = new XPathEvaluator(); var oResult = oEvaluator.evaluate('employee/name', oXmlDom.documentElement, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); if (oResult != null) { for(var i = 0; i < oResult.snapshotLength; i++){ alert(oResult.snapshotItem(i).tagName); } }
在这个例子中,snapshotLength返回节点的数量,snapshotItem()返回快照中给定位置上的节点(类似于NodeList的length和item()) 。
XPathResult.FIRST_ORDERED_NODE_TYPE 结果返回每个匹配的节点,可通过singNodeValue 属性来访问:
var oEvaluator = new XPathEvaluator(); var oResult = oEvaluator.evaluate('employee/name', oXmlDom.documentElement, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); alert(oResult.singleNodeValue.xml);
这段代码可用来模拟IE的selectSingleNode()方法:
if (isMoz) { Element.prototype.selectSingleNode = function (sXPath) { var oEvaluator = new XPathEvaluator(); var oResult = oEvaluator.evaluate(sXPath, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); if (oResult != null) { return oResult.singleNodeValue; } else { return null; } }; Document.prototype.selectSingleNode = Element.prototype.selectSingleNode; }
这个方法的用法与在IE中的一样:
alert(oXmlDom.documentElement.selectSingleNode("employee/name"));
XPathResult类型最后部分是布尔类型、数字类型与字符串类型。其中,每个结果类型中都用相应的 booleanValue 、numberValue 和 stringValue 来返回单个值。
对于布尔类型XPathResult.BOOLEAN_TYPE ,一般返回true,只需有一个节点匹配了XPath表达式,否则返回false:
var oEvaluator = new XPathEvaluator(); var oResult = oEvaluator.evaluate("employee/name", oXmlDom.documentElement, null, XPathResult.BOOLEAN_TYPE, null); alert(oResult.booleanValue);
在此例中,如果任何节点匹配了 'employee/name',booleanValue属性都会等于true。
对于数字类型XPathResult.NUMBER_TYPE ,XPath表达式必须使用返回数字的XPath函数,诸如count() ,它可用于计算匹配给定模式的节点的个数:
var oEvaluator = new XPathEvaluator(); var oResult = oEvaluator.evaluate("count(employee/name)", oXmlDom.documentElement, null, XPathResult.NUMBER_TYPE, null); alert(oResult.numberValue);
这段代码输出了匹配模式 'employee/name'(2个)。如果不使用这种特殊的XPath函数来使用这个方法,numberValue则为NaN。
对于字符串类型XPathResult.STRING_TYPE ,evaluate()方法查找每个匹配XPath表达式的节点,然后返回第一个子节点的值(假设第一个子节点是文本节点),否则结果是空字符串。例:
var oEvaluator = new XPathEvaluator(); var oResult = oEvaluator.evaluate("employee/name", oXmlDom.documentElement, null, XPathResult.STRING_TYPE, null); alert(oResult.stringValue);
代码将输出'Nicholas C.Zakas',因为这是在<employee/>元素下<name/>元素中的第一个文本节点。
另外,可以使用XPathResult.ANY_TYPE ,这将使evaluate()根据XPath表达式返回最合适的结果类型 。一般来说,这个结果类型是个布尔值,字符串值或都是无序的节点迭代子。要判断返回结果类型的真真类型,可使用resultType 属性:
var oEvaluator = new XPathEvaluator(); var oResult = oEvaluator.evaluate("employee/name", oXmlDom.documentElement, null, XPathResult.ANY_TYPE, null); if(oResult != null){ switch(oResult.resultType){ case XPathResult.STRING_TYPE: //处理字符串类型 ... break; case XPathResult.NUMBER_TYPE: //处理数字类型 ... break; case XPathResult.BOOLEAN_TYPE: //处理布尔类型 ... break; case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: //处理迭代类型 ... break; default: //处理其他类型 } }
从上面一些示例可以看出,Mozilla中的XPath计算远比IE的复杂,当然也更加强大,通过使用自定义的 selectNodes() 和 selectSingleNode()方法,可以在两种浏览器上使用同样的代码进行XPath计算。
Mozilla上XPath命名空间的支持
对于具有命名空间的XML文档,进行XPath查询时必须指定resolver参数,用于解释命名空间的URI。
<?xml version="1.0" encoding="UTF-8"?> <Books xmlns:pub="http://www.pub.com" xmlns:author="http://www.author.com"> <pub:Book> <Title>Ajax In Action</Title> <author:Author>Dave Crane</author:Author> </pub:Book> <pub:Book> <Title>Professional Ajax</Title> <author:Author>Nicholas C.Zakas</author:Author> </pub:Book> </Books>
定义nsResolver函数如下,其作用是根据输入的prefix参数返回相应的命名空间URI字符串。
function nsResolver(prefix) { // 命名空间的名称/URI对 var ns = { "pub": "http://www.pub.com", "author": "http://www.author.com" }; // 根据命名空间的前缀返回其URI return ns[prefix] || null; }
在books_ns.xml中查询所有<Author>节点的XPath表达式为:
/Books/pub:Book/author:Author
进行XPath查询的代码如下:
<script type="text/javascript" src="detect.js"></script> <script type="text/javascript" src="xmldom.js"></script> <script type="text/javascript"> function nsResolver(prefix) { // 命名空间的名称/URI对 var ns = { "pub": "http://www.pub.com", "author": "http://www.author.com" }; // 根据命名空间的前缀返回其URI return ns[prefix] || null; } var oXmlDom = new XmlDom(); oXmlDom.onreadystatechange = function () { if (oXmlDom.readyState == 4 || (oXmlDom.getReadyState && oXmlDom.getReadyState() == 4)) { // 创建XPathEvaluator对象 var xpe = new XPathEvaluator(); // 查询<Author>节点 var iterator = xpe.evaluate("/Books/pub:Book/author:Author", oXmlDom, nsResolver, XPathResult.ANY_TYPE, null); // 获取查询结果 var node; while (node = iterator.iterateNext()) { // 输出节点的XML字符串 alert(node.xml); } } }; oXmlDom.load("test.xml"); </script>
Mozilla上运行结果: