本文的灵感来自一个诸事不顺的周末。我的那一位决定和同事去拉斯维加斯进行一次庆祝旅行,恰好我计划去宜家家居挑一个书柜,这样我在搬到雷得蒙几个月之后终于可以将我的书松绑了。在宜家家居逛了两个小时之后,我发现一个陈列的书柜与我房间的色调很相配,只是一些必要的配件缺货。最终我订购了这种书柜,空着手先回家了。然而,我已经把书都解开了,它们散落在房间的各个角落。这成为整理我日益增长的书库的最好机会,当然我选择使用 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>
设计好简单的格式后,我决定在上面运行一些实际的查询来看看这种格式是否满足我的需要。我尝试在 System.Xml.XmlNode 类中使用 SelectSingleNode Method 的第一个查询如下:
//*[position() = 1]/@on-loan
我本来的意思是“选择文档中的所有节点,然后给我第一个节点的 on-loan 属性。”查询返回以下内容:
on-loan="yes"
因此,对问题“我是否有书借出?”的答案是 yes。然而,当我模拟如果一本书被借出之后我没有更新根元素上的 on-loan 值会出现什么情况时发生了一件有趣的事情。我从根元素中删除 on-loan 属性,再次运行查询。结果如下:
on-loan="Sanjay"
缩写及它们的真实意思
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
必须注意到比较运算符(<、>、<=、>=)不执行字符串值的字典式比较功能。