正在建造的 XML 分类的主要目的是建立一个结构来集中存储我所拥有的书的信息,它应该足够灵活,可以进行查询和各种演示......
老鼠与人最好的排列计划 本文的灵感来自一个诸事不顺的周末。我的那一位决定和同事去拉斯维加斯进行一次庆祝旅行,恰好我计划去宜家家居挑一个书柜,这样我在搬到雷得蒙几个月之后终于可以将我的书松绑了。在宜家家居逛了两个小时之后,我发现一个陈列的书柜与我房间的色调很相配,只是一些必要的配件缺货。最终我订购了这种书柜,空着手先回家了。然而,我已经把书都解开了,它们散落在房间的各个角落。这成为整理我日益增长的书库的最好机会,当然我选择使用 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 Method 的第一个查询如下:
//*[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] |
选择文档中第一个节点。 |