XPath与Xquery
XPath 和 XQuery 在某些方面很相似。XPath 还是 XQuery 完整不可分割的一部分。这两种语言都能够从 XML 文档或者 XML 文档存储库中选择数据。本文简要介绍了 XPath 和 XQuery,以及如何使用 XQuery 对 XPath 的扩展。
虽然 XPath 和 XQuery 都能实现一些相同的功能,但是 XPath 比较简洁而 XQuery 更加强大和灵活。对于很多查询来说 XPath 非常合适。比如,从 XML 文档中的部分记录建立电话号码的无序列表,使用 XPath 实现最简单。但是如果需要表达更复杂的记录选择条件的表达式、转换结果集或者进行递归查询,则需要使用 XQuery。
XPath
XPath 是一种领域专用语言(Domain-Specific Language, DSL),并很快成为了其他更通用语言的重要组成部分。编程语言通过模块和类把 XPath 结合进来,有时候甚至直接进入语言的语法。这种情况和以前正则表达式的情况类似。
由于从 XML 文档中提取特定的数据时,XPath 能够为开发人员节省大量的时间和精力,XPath 得以流行起来。即使从未接触过 XPath 的人也能很快利用其强大的功能。比如清单 1 中的 XML 片段。
清单 1. XML 文档
<users> <user> <name> <first>Lola</first> <last>Solis</last> </name> <age>2</age> </user> <user> <name> <first>Nina</first> <last>Serafina</last> </name> <age>4</age> <visits> <first>2008-01-15</first> <last>2008-02-15</last> </visits> </user> <user> <name> <first>Tracy</first> <last>Keller</last> </name> <age>35</age> </user> </users>
如果需要获得文档中青少年的姓氏列表,可使用下面的 XPath 表达式。
清单 2. 选择 18 岁以下用户的姓氏
/user[age lt 18]/name/last/text() (: Result Solis Serafina :)
设想一下如果不使用 XPath,提取同样的数据您得编写多少代码。即便有正则表达式的帮助,如何排除所访问节点最后一个标记中的值也需要动点脑筋。
上面的 XPath 表达式不但准确而且非常清晰易懂。即便是对于不懂 XPath 的人,快速地扫一眼也能明白这个表达式的作用。XPath 之所以有效是因为它非常强大,而且有很长的发展历史。无论结构多么复杂,这种语言都能够理解 XML 文档中的节点,而且更重要的是,能理解这些节点之间的关系。因此可以编写精确的表达式来表示元素和元素值,以及属性、处理指令等等。
许多 XML 查询很难用比 XPath 更清晰、更准确的方式来表达。但是 XPath 固有的 DSL 特性及其设计的目标,却为程序员带来了很大的局限性。下面一节简要介绍了 XQuery 语言,并举出了 XPath 无法单独解决的问题。这些问题要求程序员越过 XPath 而求助于 XQuery 之类的工具。
XQuery
既然 XQuery 天生支持 XPath 并将其作为 XQuery 语法的一部分,XQuery 显然能完成 XPath 所能完成的任何任务。但是 XQuery 是图灵完备的(Turing-complete),可以被看作是一种通用语言,因而很容易克服 XPath 的诸多局限,但代价是复杂性略有增加。
概述
XQuery 采用一种简单的语法,混合了 XML、XPath、注释、函数以及将其结合在一起的专用表达式语法。XQuery 代码完全由表达式组成,没有语句。所有的值都是序列,对于这种语言来说简单性是最重要的。因此表达式 Hi
和 2 * 2
都是合法的 XQuery 代码,不需要任何准备或者修改就能执行。XQuery 是一种高端的、强类型的函数语言(没有副作用),非常适合表达从 XML 文档或者大型 XML 存储库(repository)中获取数据的查询。最后一点,它和 SQL 非常类似。但是 XQuery 还额外提供了表达对结果集进行任意转换的功能。如同从 XML 文档中检索数据应该使用 XPath 一样,从大型 XML 存储库中检索和转换数据时则应该使用 XQuery。
转换结果集
XPath 最突出的局限性是没有提供任何办法转换结果集。假设需要将前面 XPath 查询(清单 2)返回的结果按照字母顺序排列,如清单 3 所示。
清单 3. 按字母顺序排列的结果
Serafina Solis
使用 XPath 无法做到。此时必须使用其他语言(比如 XQuery)编写代码或者使用某种专门的私有 XPath 扩展来对结果排序。
另一方面,XQuery 允许对结果排序或者将其转换成 HTML、CSV、SQL 及其他任何基于文本的格式。XQuery 最常用的转换类型是 XML 到 XML 的转换。通常大型的 XML 数据库包含有客户端应用程序不需要的、大量复杂的、互相纠缠的 XML 文档。XQuery 允许客户端精确描述希望服务器返回的 XML 文档类型。通过提供 XQuery 接口,服务器可以避免以多种模式保存数据。此外,使用 XQuery 转换客户端数据也通常比用 Perl、Java 或其他常用计算机语言转换简单快捷得多。当然,在当前的实现中检索数据的同时使用 XQuery 转换数据,要比以后用 XSLT 转换要快得多。
为了同时输入记录选择条件和结果转换指令,XQuery 提供了被称为 FLWOR(读作 “flower”)表达式的特性。这个缩写词中的字母分别代表for
、let
、where
、order by
和 return
。这是组成 FLWOR 表达式的元素。FLOWR 表达式至少包含其中的部分元素,大体上按照缩写词所预示的顺序。所有 FLOWR 表达式都以 for 或 let 表达式开始,以 return 表达式结束。如果熟悉 SQL,您可能已经明白我前面所说的意思了。下面是一个简单的 FLOWR 表达式,借用了 Edwin Markham 的诗 “Outwitted”(如清单 4 所示)。
清单 4. 简单的 FLWOR 表达式
let $xml:= <a> <one>She drew a circle that shut me out</one> <two>Heretic rebel, a thing to flout</two> </a> return $xml//one/text() (: Result "She drew a circle that shut me out" :)
清单 5 展示如何将一个简单 FLOWR 表达式应用于 清单 1 中的 XML(为了简单起见,清单中用 _XML from Listing 1_
代替了真正的 XML)。
清单 5. 简单的 FLWOR 表达式
let $xml:= _XML from Listing 1_ for $user in $xml//user[age lt 18] order by $user/name/last return $user/name/last/text() (: Result Serafina Solis :)
如果希望查询返回 HTML 片段并把结果表示成有序列表,可使用清单 6 所示的 XQuery。
清单 6. 输出 HTML 有序列表的 FLWOR 表达式
let $xml:= _XML from Listing 1_ return <ol>{ for $user in $xml//user[age lt 18] order by $user/name/last return <li>{$user/name/last/text()}</li> }</ol> (: Result <ol><li>Serafina</li><li>Solis</li></ol> :)
请注意 清单 6 中 XML 和 XQuery 是如何直观有效地结合在一起的。
回页首
表示更复杂的记录选择条件
除了转换检索的数据外,使用 XQuery 查找数据也比 XPath 好得多。XQuery 和 XPath 经常重叠,可以帮助程序员更好地表达查询。比如,清单 7 把 XPath 表达式片段 age lt 18
放到了 FLWOR 表达式的 where
子句中。
清单 7. 在 XQuery 中使用 XPath 约束
let $xml:= _XML from Listing 1_ return <ol>{ for $user in $xml//user where $user/age lt 18 order by $user/name/last return <li>{$user/name/last/text()}</li> }</ol>
清单 7 中的表达式的结果和 清单 6 中表达式的结果完全一样。但 XQuery where
子句要比 XPath 表示约束的语法灵活得多。XQuery where
子句可以包含任意复杂的嵌套表达式,甚至可以包含函数调用。XQuery 对于记录选择表达式没有任何限制。
回页首
使用函数和递归
XPath 不支持函数,但 XQuery 提供了一批重要的内置函数和运算符,而且还允许用户定义自己的函数。XQuery 函数是强类型的,支持递归,可声明为内部函数或外部函数。内部函数是函数体紧跟函数声明之后的标准函数。外部函数是一种开放实现的函数声明类型,用户可以用不同的编程语言定义函数体。
虽然递归不一定是开发人员执行日常任务的最佳方法,但是对于包含多层嵌套节点的 XML 来说非常方便。比如清单 8 中定义的 transform-names
函数。
清单 8. 修改 XML 文档中节点名称的函数
(: Part 1 :) define function transform-names($node as node()) as node() { element{replace(name($node), "_", "-")} { $node/text(), for $subnode in $node/* return transform-names($subnode) } } (: Part 2 :) let $xml:= <item> <item_type>book</item_type> <contributors> <author> <first_name>Charles</first_name> <last_name>Edward</last_name> <home_address> <home_street>206 S. Solomon St.</home_street> <home_city>New Orleans</home_city> <home_state>LA</home_state> <home_zip>70119</home_zip> </home_address> </author> <artist> <last_name>Salinas</last_name> </artist> </contributors> </item> return transform-names($xml) (: Result <item> <item-type>book</item-type> <contributors> <author> <first-name>Charles</first-name> <last-name>Edward</last-name> <home-address> <home-street>206 S. Solomon St.</home-street> <home-city>New Orleans</home-city> <home-state>LA</home-state> <home-zip>70119</home-zip> </home-address> </author> <artist> <last-name>Salinas</last-name> </artist> </contributors> </item> :)
transform-names
函数是 清单 8 中的第一段代码,可以接受任何复杂的 XML 文档或者节点。对于每个 XML 标记名,该函数将其中的下划线 (_) 该为连字符 (-)。
在这种情况下执行递归使得遍历文档结构变得非常简单。而且,该函数精炼(只有 3 行代码!)、易于维护,可用于不含属性的任何 XML 文档或者节点。虽然初看起来要完全理解这个函数有点困难 —— 尤其是对于那些很少使用递归的程序员来说 —— 但是很容易猜到如果删除下划线而不是替换为连字符时应该如何修改函数。
回页首
表达连接(Join)
XPath 没有提供在查询中连接 XML 节点的方法。但是,和 SQL 提供了在查询中表示表连接的自然语法一样,XQuery 也提供了一种直观(至少对于 SQL 用户而言)的方法连接 XML 节点集。清单 9 说明了如何在 XQuery 中使用连接。
清单 9. XQuery 连接表达式
(: Part 1 :) let $authors:= <authors> <author> <name>Harold Abelson</name> <books> <isbn>978-0-07-000422-1</isbn> <isbn>978-0-262-01063-4</isbn> </books> </author> <author> <name>Paul Graham</name> <books> <isbn>978-0-13-370875-2</isbn> <isbn>978-0-13-030552-7</isbn> <isbn>978-0-596-00662-4</isbn> </books> </author> <author> <name>Apostolos-Paul Refenes</name> <books> <isbn>978-0-471-94364-8</isbn> <isbn>978-981-02-2819-4</isbn> </books> </author> </authors> (: Part 2 :) let $books:= <books> <book> <title>Structure and Interpretation of Computer Programs</title> <isbn>978-0-07-000422-1</isbn> </book> <book> <title>Turtle Geometry</title> <isbn>978-0-262-01063-4</isbn> </book> <book> <title>ANSI Common LISP</title> <isbn>978-0-13-370875-2</isbn> </book> <book> <title>On LISP</title> <isbn>978-0-13-030552-7</isbn> </book> <book> <title>Hackers and Painters</title> <isbn>978-0-596-00662-4</isbn> </book> <book> <title>Neural Networks in the Capital Markets</title> <isbn>978-0-471-94364-8</isbn> </book> <book> <title>Neural Networks in Financial Engineering</title> <isbn>978-981-02-2819-4</isbn> </book> <book> <title>Handbook of Artificial Intelligence</title> <isbn>978-0-201-16889-1</isbn> </book> <book> <title>Artificial Intelligence Programming</title> <isbn>978-0-89859-609-0</isbn> </book> <book> <title>A New Guide to Artificial Intelligence</title> <isbn>978-0-89391-607-7</isbn> </book> <book> <title>Artificial Intelligence</title> <isbn>978-0-08-034112-5</isbn> </book> <book> <title>Artificial Intelligence</title> <isbn>978-0-631-18385-3</isbn> </book> </books> (: Part 3 :) return <books-complete-info>{ for $book in $books/* for $author in $authors/* where $book/isbn = $author//isbn and ( contains($book/title, "LISP") or contains($book/title, "Neural")) order by $book/title return <book>{$book/*, $author/name}</book> }</books-complete-info>
清单 9 的第 1 和第 2 部分将 XML 文档赋给变量 authors 和 books。books 中的一些节点和 authors 中的节点有关,book 节点的 ISBN 出现在 author 节点的列表中。
清单第 3 部分的 XQuery 连接表达式组成了一个新的 XML 文档 books-complete-info(先看看 清单 10),其中的 book 节点包含作者的姓名。
清单 9 中第 3 部分代码有几点需要注意。开始部分的两个 for
表达式向 XQuery 表明这是一个连接表达式。where 子句在概念上类似于 SQL 的连接语句。但是要注意 author 节点可能有多个 ISBN,这就要求 where
子句必须说明:“book 的 ISBN 在作者的 ISBN 之中”。更像是 SQL where 子句中的子选择,但是 XQuery 语法看起来更直观自然。当然 XQuery 表达式也更精确。
清单 10. XQuery 连接表达式的结果
<books-complete-info> <book> <title>ANSI Common LISP</title> <isbn>978-0-13-370875-2</isbn> <name>Paul Graham</name> </book> <book> <title>On LISP</title> <isbn>978-0-13-030552-7</isbn> <name>Paul Graham</name> </book> <book> <title>Neural Networks in the Capital Markets</title> <isbn>978-0-471-94364-8</isbn> <name>Apostolos-Paul Refenes</name> </book> <book> <title>Neural Networks in Financial Engineering</title> <isbn>978-981-02-2819-4</isbn> <name>Apostolos-Paul Refenes</name> </book> </books-complete-info>
回页首
结束语
XPath 作为一种成熟的 DSL,应该成为您提取深藏在 XML 文档,或者存储库中的数据时的首选。但是 XPath 的设计目标不是用于处理各种类型的问题。通过本文可以看到,XQuery 在很大程度上扩展了 XPath,如果数据筛选条件很复杂,或者需要返回排序的结果集(特殊的格式化)及进行其他转换,可以选择 XQuery。