独上高楼网站
  •    你所在位置:首页 VS.netXMLXML经验〉在使用 XPath 查询 XML 文档时应了解和避免的事情 (Extreme XML)
  • 在使用 XPath 查询 XML 文档时应了解和避免的事情 (Extreme XML)
  • 作者:佚名  文章来源:http://msdn2.microsoft.com/zh-cn/default.aspx  发布日期:2007-07-07  浏览次数:648
  • 打印这篇文章
  • 本页内容

    老鼠与人的最佳计划

    不仅是表面那么简单

    缩写及它们的真实含义

    提高我们的数学技能

    何时集合不是一个集合?

    标识危机

    我是,故我在

    命名空间和 XPath Redux

    未涉及的内容

    致谢

    老鼠与人的最佳计划

    本文的灵感来自一个诸事不顺的周末。我的那一位决定和同事去拉斯维加斯进行一次庆祝旅行,恰好我计划去宜家家居挑一个书柜,这样我在搬到雷得蒙几个月之后终于可以将我的书松绑了。在宜家家居逛了两个小时之后,我发现一个陈列的书柜与我房间的色调很相配,只是一些必要的配件缺货。最终我订购了这种书柜,空着手先回家了。然而,我已经把书都解开了,它们散落在房间的各个角落。这成为整理我日益增长的书库的最好机会,当然我选择使用 XML 实现此任务。(本文包含一些指向英文站点的链接。)

    返回页首

    不仅是表面那么简单

    我正在构建的 XML 目录的主要目的是建立一个结构来集中存储我所拥有的书的信息,它应该足够灵活,可以进行查询和各种演示,同时也便于携带。下面是该文档初稿的代码段:

    Books.xml

    ?xml version="1.0" encoding="UTF-8" ?>

    bk:books xmlns:bk="urn:xmlns:25hoursaday-com:my-bookshelf" on-loan="yes" >

     bk:book publisher="IDG books" on-loan="Sanjay" >

      bk:title>XML Bible/bk:title>

      bk:author>Elliotte Rusty Harold/bk:author>

     /bk:book>

     bk:book publisher="QUE">

      bk:title>XML By Example/bk:title>

      bk:author>Benoit Marchal/bk:author>

     /bk:book>

    /bk:books>

    我希望能够使用 on-loan 属性来跟踪我是否将书借了出去。根元素的 on-loan 属性指定至少一本书被借了出去,而每个 book 元素的同一属性则指定借书人。现在看来,这可能不是最好的设计,因为它导致在根元素和子元素之间不必要的联系,但是请诸位谅解,这毕竟只是我的初稿。

    设计好简单的格式后,我决定在上面运行一些实际的查询来看看这种格式是否满足我的需要。我尝试在 System.Xml.XmlNode 类中使用 SelectSingleNode 方法的第一个查询如下:

    //*[position() = 1]/@on-loan

    我本来的意思是选择文档中的所有节点,然后给我第一个节点的 on-loan 属性。查询返回以下内容:

    on-loan="yes"

    因此,对问题我是否借出了书?的答案是 yes。然而,当我模拟如果一本书被借出之后我没有更新根元素上的 on-loan 值会出现什么情况时发生了一件有趣的事情。我从根元素中删除了 on-loan 属性,并再次运行查询。结果如下:

    on-loan="Sanjay"

    结果是根元素的某个子元素的值。我怀疑有错,所以又在 MSXML 上尝试查询,还是得到相似的结果。进一步研究使我与小组内的一些 XPath 专家进行了很有启发意义的讨论,并进一步阅读了 Xpath 建议。我发现与多方共同设计的重要语言一样,在处理 XPath 时,有一些古怪、富有个性、不一致的情况和需要避免的陷阱)。

    返回页首

    缩写及它们的真实含义

    XPath 建议列出了许多,这些轴包含了一些与当前选择节点(也称为上下文节点)相关的节点。为避免冗长,指定了某些常用轴的一些缩写。下表显示这些缩写和它们等效的轴。

    缩写

    .

    self::node()

    ..

    parent::node()

    //

    /descendent-or-self::node()/

    @

    attribute::

    另一个事实是,在每个位置步骤或路径表达式上使用的默认轴是 child:: 轴。因此,/bk:books/bk:book 实际等效于 /child::bk:book/child::bk:book,但是这比直接键入要容易得多。

    节点测试用来选择当前轴的 主节点类型的所有节点。* 是节点测试,不是步骤的缩写。最后,带有数字的谓词等效于检查上下文节点的位置是否与该数字相同。这意味着查询 /bk:book[1] 等效于 /bk:book[position()=1]

    有了以上信息,我们可以返回原来出问题的查询,看看为什么会得到意外的结果。//*[position() = 1]/@on-loan 实际是 /descendent-or-self::node()/child::*[position() = 1]/@on-loan 的缩写,它选择文档中的每个节点,检索每个选定节点的第一个子节点的 on-loan 属性。明智地使用圆括号可以迅速解决此问题,(//*)[position() = 1]/@on-loan(它是 (/descendent-or-self::node()/child::*)[position() = 1]/@on-loan 的缩写)是我实际需要的。

    有趣的是,在问题解决之后不久,我意识到本来可以采用以下更简单和更有效的查询实现我的要求:

    /*/@on-loan

    这是一个更好的解决方案,因为它只需要查看文档中的第一个节点。我将保留多个示例,强调为什么一个人应该考虑在某些情况下缩写所表示的内容,以避免令人迷惑不解的结果。

    缩写

    完整查询

    查询结果

    //*[1]

    /descendent-or-self::node()/child::*[position()=1]

    选择文档中每个节点的第一个子节点。

    (//*)[1]

    (/descendent-or-self::node()/child::*)[position()=1]

    选择文档中第一个节点。

    返回页首

    提高我们的数学技能

    涉及关系或算术运算符和字符串的查询通常导致与直觉不相符的结果。XPath 将涉及关系或算术运算符的表达式中的所有操作数转换为数字。不完全是数字值的字符串将转换为 NaN(不是一个数)。下表显示某些 XPath 表达式、表达式隐式转换成的内容以及表达式的结果。

    表达式

    隐式转换

    结果

    '5' + 7

    5 + 7

    12

    '5' + '7'

    5 + 7

    12

    5 + 'a'

    5 + NaN

    NaN

    '5' 7

    5 7

    True

    '5' '7'

    5 7

    True

    '5' 'b'

    5 NaN

    False

    'a' 'b'

    NaN NaN

    False

    'a' > 'b'

    NaN > NaN

    False

    必须注意到比较运算符(〈、>、〈=>=)不执行字符串值的字典式比较功能。

    另一个有趣的算术定义是虽然定义了一元减号(例如 -6 是有效的 XPath 表达式),但是却未定义一元加号(+6 不是有效的 XPath 表达式)。更令人吃惊的是多重否定可以堆叠在一起,却仍然有效。因此,------6 是有效的 XPath 表达式,等效于值 6

    XPath 缺乏对科学/指数表示法的支持将使用户犯错,因为支持它的既有流行的查询语言(如 SQL),也有流行的编程语言(如 C++)。

    在节点集合上结合算术和关系运算的表达式还可能导致令人吃惊的结果。节点集合上的算术运算将集合中第一个节点的值转换为数字,而关系运算符将判断节点集合中的任意节点是否满足条件。下面是一个