虽然编程领域 — 特别是 Java 编程 — 不断发展,可供选择的标准的数量也在增长。换句话说,越来越多的 API 得到 Sun 的认可或者批准。标准化的结果是越来越多的开发者背弃了自己最具竞争力的技术,而去学习新技术。
要掌握的最有吸引力和价值的是那些和数据处理有关的工具和 API。无论应用程序多么酷或者智能,说到底只有能够处理数据才有用。同时,虽然 API 数量的不断增长,但流行和通用的数据格式数量却不断减少。虽然有些数据管理员仍然使用面向对象的数据库管理系统或者 XML 驱动的数据库,但关系数据库(RDBMS)已成为主流,而且仍然是大部分数据管理员的选择。因此 Java 开发人员必须通过 JDBC(数据库连接)或者 JDO (Java Data Objects) 与 SQL 数据库交互。
![]() |
|
数据库之外的数据基本上也都以 XML 作为标准数据格式。XML 虽然冗长但是健壮,Java 语言中处理 XML 的 API 可能比其他语言都多。无论是解析、数据绑定还是转换,如果应用程序不能处理 XML,就会被认为有局限性甚至有点落后。
两者看似无关,— 数据往往保存在 SQL 数据库中,数据库之外的数据越来越多地采用 XML 格式 —,但是却造成了一些独特的问题。SQL 数据库容易查询,而 XML 文档则不是这样。消费者希望能够方便地搜索数据,数据库中的数据查询方便,但是 XML 文档中的数据就不那么容易了。显然,将 XML 格式的数据塞到数据库中以方便搜索的做法是行不通的。于是就出现了 XQuery — 相应的也就有了 XQuery API for Java (XQJ)。
![]() |
|
简言之,XQuery 就是用于定义 XML 文档搜索的一种语言。就像 SQL 赋予了 SELECT 和 FROM 特定的含义一样 — 在一定的上下文中 — XQuery 定义了正斜杠(/)和地址符号(@)以及其他一些关键字和关键字符的意义。
XQuery 的核心包括三部分:
- XPath 规范:在 XML 文档中选择零个、一个或多个节点的方法。
- 选择特定 XML 文档、为 XPath 返回的节点增加选择条件的附加语法
- API — 比如 XQJ,XQuery for Java API— 用具体的编程语言对 XQuery 表达式求值
要真正精通 XQuery,必须切实掌握好这三个方面。对于 Java 程序员来说,显然就意味着要学习 XPath、学习新增的 XPath 语法结构,然后将其与基于 Java 的 API 结合起来对 XML 文档使用 XQuery 表达式。
好消息是 XPath 和 XQuery 语法都非常简单明了。如果曾经使用 UNIX® shell、Mac OS X 终端或者 DOS 窗口研究过目录结构,就具备了必要的基础。掌握了小于(<)、大于(>)和等于(=)这些运算符的基本用法外,您称得上是大半个 XPath 专家了。
事实上,XQuery 差不多完全依赖于另一个 XML 规范,即 XPath 规范。XPath 的作用基本上就是定义创建指向 XML 文档某一部分的路径 的方法。比如,XPath /play/act/scene 就是指 play 根元素下的 act 元素下的所有 scene 元素。
最基本的 XPath 使用元素名和正斜杠。默认情况下,XPath 以 XML 文档中的当前位置开始。因此如果使用 DOM,比如导航到 speech 元素,然后指定路径 speaker,则指向当前位置的 speech 元素中的所有 speaker 元素。因此 XPath 的求值是相对于文档中的位置而言的。
要移动到文档的根元素,可在路径前加上正斜杠。无论在文档的什么位置,/play 可以查找名为 play 的根元素。用 ../ 可以选择当前元素的父元素。应该能够看出这实际上非常类似与目录结构。路径 ../../personae/title 将从当前元素上溯两层,然后依次寻找 personae 和嵌套在其中的 title 元素。
除了元素外还有很多可供选择的其他内容。比如这个 XPath:/cds/cd@title。它返回根元素 cds 下 cd 元素上所有的 “title” 属性。
要记住,@ 返回的不是属性的值而是属性本身。因此,@isbn 将选择所有名为 isbn 属性而不是这些属性的值。此外,XPath 中的属性指的是属性名和属性值(请参阅后面的 关于节点 一节)。
![]() |
|
就像选择元素和属性一样,也可选择元素中的文本。如果 XPath 以元素名结束,比如 /cds/cd/title,则选择的是元素 — 而且不 包括这些元素中的文本。如果需要元素中的文本,可使用 text() 语法。因此要取得所有 CD 的文字标题,可以使用 /cds/cd/title/text() 这样的路径。该路径不提供任何元素,而是指定元素中的文本。
有效使用 XPath 的一个关键是要认识到 XPath 始终计算的是节点集。这个集合可能包含零个、一个或多个节点,但 XPath 的结果必定 是集合。多数人在编写 XPath 的时候认为:该路径返回一个元素、一个属性或者文本,但事实并非如此。不过这也不是绝对的。
如果使用 DOM,应该已经对节点有所了解了。对于 DOM 来说,XML 文档中的一切 — 元素、属性和文本 — 都是节点。元素有元素节点,属性(包括属性值)有属性节点,元素中的文本则是文本节点。因此路径 ../../personae/title 最终将选择 title 元素,实际上返回一个节点集。这个集合可能包含零个(没有匹配的元素)、一个或者多个节点。集合中的所有节点都是名为 “title” 的元素。
随着路径变得更加复杂,有可能选择一个更大的集合 — 也许同时包括属性和元素,或者同时包括文本和元素 — 无论如何这些路径最终都是选择一个节点集。牢记这一点是正确使用 XQuery 的关键。通过 XPath 选择一个节点集,而使用 XQuery 则通常是按照一定条件选择这些节点的一个子集,也有可能连接多个节点集然后应用搜索条件。只要记住集合中的元素可能有多种类型(元素、文本或者属性),就能更好地编写路径并保证得到预期的结果。
前面我们看到了如何根据节点名(元素和属性)以及父节点(文本或者选择给定节点的所有子节点)来选择节点集。这本身已经非常强大了,不过 XPath 还提供了更多的选择性,即使用所谓的谓词。
![]() |
|
谓词是用于已有节点集的部分表达式。谓词放在方括号 [ 和 ] 中。作用于谓词左侧的路径所定义的节点集。比如路径 /cds/cd 选择了根元素 “cds” 中的所有 cd 元素。假设需要第一张唱片,可以使用谓词来实现:/cds/cd[1]。它返回路径 /cds/cd 选择的第一个节点。
要记住,谓词作用于谓词本身左侧的节点集。但是这并不意味着谓词只能出现在 XPath 的最后面。可以将 XPath 本身看作是路径的集合,每个路径都返回一个节点集,路径后面的部分作用于该集合并进一步细化。因此 /cds/cd/title 实际上是三个路径:
/cds返回根元素 “cds”(只含一个元素节点的集合)cd(相对于上一个节点集)返回上一节点集中嵌套的所有cd元素title(同样相对于上一节点集)返回上一节点集中嵌套的所有title元素
谓词必须作用于节点集,但是可作用于任何 节点集。/cds[1]/cd[2]/title[1] 这样的路径是完全合法的。它选择了 /cds 所选节点集中的第一项,/cds[1]/cd 所选节点的第二项,/cds[1]/cd[2]/title 所选节点的第一项。
注意:该路径中有些部分毫无意义,比如使用 / 选择根元素然后再使用 [1] 谓词总是返回集合中的第一个(惟一的一个)元素。只有当节点集本身为空,即给出的根元素名称和文档实际的根元素不同,这种情况下才不会返回任何元素。但是作为一个例子 — 包括从技术的角度看 — XPath 本身以及使用的谓词都没有问题。
当然,只能用数值引用位置的 API 作用是很有限的。这样的模型中,您必须知道需要的项到底在什么位置。不过 XPath 提供了更多的选择。首先,在谓词中可以使用 last() 函数选择集合中的最后一项,不论集合中包含多少项。/cds/cd[last] 选择文档中最后一个 cd。
使用 position() 函数还可以选择在特定位置之前或者之后的所有项。position() 函数返回给定节点在集合中的位置。比如,假设需要前 5 张唱片,可以使用路径 /cds/cd[position()<6]。这样就能选择 position() 结果小于 6 的所有节点。
最后 — 对于这里关于 XPath 的简要介绍而不是详细讨论来说 — 还可以根据节点的子元素或者节点的属性来选择节点。就像 XPath 后面的部分依赖于前面路径所决定的节点集一样,集合的谓词也依赖于所应用的集合。谓词除了能够使用小于(<)和大于(>)这样的运算符外,还可根据选中节点的数据而不仅仅是这些节点在整个集合中的位置进行选择。
比方说要选取属性 “style” 为 “folk” 的所有 CD。表达式应该首先选择全部 CD,然后把这些 CD 的 style 属性和 “folk” 进行比较。其 XPath 形式为 /cds/cd[@style='folk']。按照前面的说明,这个表达式的意思应该很清楚了。首先,通过 /cds/cd 选择了一个节点集。然后使用谓词 @style 取出每个节点的 “style” 属性。前面已经提到,@ 表示属性。并且该属性是关于已经选择的节点集的(即所有 cd 元素)。接下来把这些属性的值和字符串 “folk” 比较。属性匹配的返回,其他的则在结果集中排除掉。
该方法也可用于所选集合的嵌套元素。假设文档结构如清单 1 所示。
<cds> <cd style="some-style"> <title>CD title</title> <track-listing> <track>Track title</track> <!-- More track elements... --> </track-listing> </cd> <!-- More CDs... --> </cds> |
比方说如果希望取得包含 10 条或更多音轨的 CD。首先要选择全部 CD 元素,即前面多次用到的路径:/cds/cd。然后使用谓词得到关于所选集合的特定节点集的项数,即嵌套在需要返回的节点集之中的 track-listing 元素下所包含的 track 元素节点的个数。最后还需要对这些节点计数,XPath 提供了 count() 函数。此外还需要把这个数字与 10 比较。于是得到了路径:/cds/cd[count(track-listing/track) >= 10]。
如果阅读得足够仔细,可能会注意到 XPath 对元素文本和属性的处理是不一致的。前面,我提到属性节点包括属性及其值作为一个信息单位来处理。但是在谓词中,@type 这样的表达式引用的不是整个 type 属性节点而仅仅是属性值。从而能够与其他值进行比较(如 @type='reggae')。
同样,也可在谓词中引用元素文本,如 /cds/cd[title='Revolver']。这里用嵌套在 cd 中的 type 元素的值和 “Revolver” 比较。而且和属性节点一样,违背了看待元素的几条标准规则。一般来说,元素节点是文本节点的父节点,但这里谓词实际上引用了元素中的文本。
这种轻微的规则违背不是大问题,只要您知道有这种情况,而且能够转换对元素和属性的两种不同思考方式即可。只需要明确什么时候将属性及其值看作一个节点,什么时候引用属性值;类似的,也知道什么时候元素包含其他文本节点,什么时候文本值能够和其他值比较。
XPath 无疑非常强大,但也有其局限性。最突出的是,它很大程度上只适合静态数据。换句话说,需要针对特定文档编写 XPath 查询,提供和元素、属性、文本比较的具体数据来使用谓词和 XPath。此外,XPath 也没有任何控制结构(如 if/else 语句),除了简单的比较外也不能执行任何处理。
坦白地说,这些限制对多数非程序员来说算不上大问题。但是对于 Java(或者 C#、Python)程序员,习惯了完整的编程语言的强大功能,很快就想到需要 XPath 本身功能之外的方法搜索 XML 文档。于是 XQuery 理所当然地登场了。
XQuery 中很少使用但是非常重要的一个特性是能够指定应用 XPath 的文档。前面我们已经将路径 /cds/cd[title='Revolver'] 应用到了一个特定的文档,但在 XQuery 中可使用 doc() 函数指定文档。因此如果搜索 catalog.xml,可使用 XQuery 表达式 doc("catalog.xml")/cds/cd[title='Revolver']。
这个小函数的好处在于可以编写代码来以编程的方式选择文档(比如根据用户的输入),或者遍历一组文档(比如网络中所有的 iTunes 目录)并分别应用该语句。
当然除了简单地文档选择外,XQuery 还提供了更多的功能。它的 FLWOR 功能尤为强大。FLWOR 是 “for、let、where、order by、return” 的缩写。这都是可用于 XQuery 表达式以便得到更精确结果的子句。
![]() |
|
对于 SQL 老手来说,应该感到有些熟悉了。 WHERE 和 ORDER BY 都是 SQL 查询中常见的部分。对于程序员来说, for 应该比较眼熟。下面是关于 FLWOR 子句的简要说明:
- for:使用
for遍历节点集。在很多方面,for就将节点集的当前值赋给变量从而操作该变量。 - let:使用
let可以为变量赋值,但是(很快将看到)和其他 FLWOR 子句相比,let用的比较少。 - where:
where允许向节点集应用选择条件,除了 XPath 原有的机制意外。当然,您将看到在很多查询中where并不比 XPath 强,只不过把 XPath 的谓词转移了一个地方。 - order by:
order by子句不改变或者筛选数据,仅用于排序结果集,XPath 只能按位置排序,该子句允许按照其他数据排序。 - return:使用
return子句允许您操作操作节点集,随后返回除该节点集以外的结果。有可能在选择节点集、排序并筛选之后,只返回结果的子元素,return是实现这种功能的关键。
我们再稍微深入地看看这些子句。
for 子句的用法和 Java 和 C# 中的用法基本相同。其格式如下:
for $variable-name in XPath ... |
其中的变量名可以是任何一般的标识符,如 x。变量一般最好根据用途命名(如 firstName 或r title),不过由于这个变量基本上是一个循环计数器,也可使用单个字母。
XPath 没有限制。/cds/cd 是一个很好的例子。再比如:
for $cd in doc("catalog.xml")/cds/cd
...
|
如此而已。因此变量 $cd 将取得 XPath /cds/cd 返回的每个节点的值。其中的 ... 代表 XQuery 表达式的其他部分,后面将讲到。
对于程序员来说,这个语句实际上和下面的语句没有区别:
for (int i = 0; i<cdArray.length; i++) {
CD cd = cdArray[i];
// Process CD
}
|
或者像列表那样:
for (Iterator i = cdList.iterator(); i.hasNext(); ) {
CD cd = (CD)i.next();
// Process each CD
}
|
先暂缓上面对 XQuery 的讨论,let 子句用于为变量赋值。您可能已经看到,XQuery 中通过在标识符前加上美元符号($)来定义变量。多数情况下按照上述方法使用 XQuery 中的变量,即通过 for 子句隐式建立变量,而不是使用 let 子句显式地定义变量。
不过也有可能需要使用显式的变量。应该这样做:
let $docName := 'catalog.xml' for $cd in doc($docName)/cds/cd ... |
这里将搜索的文档赋给一个变量。XQuery 中的赋值使用 := — 除非曾经用过 Pascal— 可能有点奇特。如果在更真实的环境中,可能用一个函数迭代遍历一组 XML 文档,依次将这些文档名赋给 $docName。这样就能选择每个 文档中的每个 cd 元素,按照同样的方式分别处理。
为了完成查询,我们先跳过 FLWOR 中的排序子句。现在我们已经得到了:
let $docName := 'catalog.xml' for $cd in doc($docName)/cds/cd ... |
接下来就要返回一些东西了。查询选择了所有 cd 元素,但是返回这些元素没有多少用处 — 虽然这就是需要的节点集。这里不返回这些元素,我们假设需要某种更容易识别的内容,比如存储在 title 元素中的每张 CD 标题。这就用到 return 了。
let $docName := 'catalog.xml' for $cd in doc($docName)/cds/cd return $cd/title/text() |
首先选中了所有的 cd 元素。然后依次将得到的节点赋给 $cd。最后, return 子句返回的不是元素本身,而是包含目标数据的子元素 “title”。
一定不要 犯下面的错误:
let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
return /cds/cd/title/text()
|
这里有三个明显的问题:
- 首先它忘记了 XQuery 的目的,返回了一个 XPath 路径而没有执行任何查询。
- 其次,没有使用
doc($docName)返回数据,该变量指定了要从中选择 CD 的文档。 - 最后,也是最重要的,该表达式将忽略对
for子句返回的节点集所执行的筛选或排序操作。
后面还要用到这样的查询,其重要性将更加明显。目前,先要记住必须保证 for 子句定义的变量同时出现在 return 子句中。这一简单的法则可以保证查询得到预期的结果。
where 增强了 XQuery 的选择能力。XQuery 中的 where 子句和 SQL 中一样,在选择中增加 where 子句是为了进一步细化结果集。这是一个非常简单的例子:
let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
where $cd@type = 'reggae'
return $cd/title/text()
|
该查询返回所有瑞格舞曲唱片的标题。不需要进一步解释,where 子句非常简单。通过 and 还可建立更加复杂的条件:
let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd
where $cd@type = 'reggae'
and count($cd/track-listing/track) > 10
return $cd/title/text()
|
返回包含 10 个曲子以上的所有瑞格唱片。
另一种重要的 where 应用类型是执行连接。比如有一个清单 2 所示的 XML 文件。
<cds> <cd style="some-style"> <title>CD title</title> <artist id="289" /> <track-listing> <track>Track title</track> <!-- More track elements... --> </track-listing> </cd> <!-- More CDs... --> <artists> <artist id="289"> <firstName>Bob</firstName> <lastName>Marley</lastName> </artist> <!-- More artist elements --> </artists> </cds> |
它扩展了 清单 1 所示 XML 文档的结构。增加了 artist 元素,该元素用 id 属性标识,每张 CD 至少有一个 artist 元素嵌套在 cd 元素中。
使用 XQuery 可以实现唱片和艺术家的连接。比如:
let $docName := 'catalog.xml' for $cd in doc($docName)/cds/cd, $artist in doc($docName)/cds/artists/artist where $cd/artist/$id = $artist/$id and $artist/lastName = 'Marley' return $cd/title/text() |
这里有两点需要注意。您第一次看到 for 子句可以定义多个变量。除了 CD 外,该语句还定义了 $artist,从而处理 artist 元素集合。
其次,where 子句使用 where $cd/artist/$id = $artist/$id 连接了 CD 和艺术家。记住,这样将匹配每张 CD 和每位艺术家,从而得到相当于 SQL 连接的效果。然后进一步选择:$artist/lastName = 'Marley'。从而得到姓氏为 “Marley” 的所有艺术家。但这同时是一个连接,return 子句返回 CD 标题。从而得到了姓氏为 “Marley” 的艺术家的所有唱片的标题。
这正是 XQuery 独有的功能。可以对 XML 文档(很多可能没考虑到进行高级搜索)执行复杂的、类似 SQL 的连接和选择。
如果说 where 和对应的 SQL 结构非常 相似,那么 order by 就是和对应的 SQL 结构完全 相同了。可以根据任何能够通过 XPath 引用的数据对结果排序:
let $docName := 'catalog.xml' for $cd in doc($docName)/cds/cd, $artist in doc($docName)/cds/artists/artist where $cd/artist/$id = $artist/$id and $artist/lastName = 'Marley' order by $cd/release/@date return $cd/title/text() |
这里返回的 CD 标题按照每张 CD 的子元素 release 的 date 属性排列。默认情况按升序排列,如果愿意也可明确说明:
let $docName := 'catalog.xml' for $cd in doc($docName)/cds/cd, $artist in doc($docName)/cds/artists/artist where $cd/artist/$id = $artist/$id and $artist/lastName = 'Marley' order by $cd/release/@date ascending return $cd/title/text() |
当然也可按照降序排列。下面的查询首先返回最新发行的唱片:
let $docName := 'catalog.xml' for $cd in doc($docName)/cds/cd, $artist in doc($docName)/cds/artists/artist where $cd/artist/$id = $artist/$id and $artist/lastName = 'Marley' order by $cd/release/@date descending return $cd/title/text() |
注意:如果没有模式或者使用的 XQuery 处理程序不知道 date 属性是日期类型,该语句可能会出现错误。更糟的是,有可能把日期作为文本数据按照字母顺序来排序。不过基本上所有的现代处理程序都能识别日期,因此很少出现这种情况。
排序还可以包括更多的条件。比如,上面的表达式返回姓氏为 “Marley” 的所有 艺术家(不仅仅是 Bob)的唱片,按照发行日期排序的话不同艺术家的作品就会混在一起。可以进一步改进该表达式,首先按名字然后按发行期排序:
let $docName := 'catalog.xml'
for $cd in doc($docName)/cds/cd,
$artist in doc($docName)/cds/artists/artist
where $cd/artist/$id = $artist/$id
and $artist/lastName = 'Marley'
order by $artist/firstName, $cd/release/@date
return $cd/title/text()
|
请注意,首先按 $artist/lastName 然后按 $artist/firstName 排序没有必要,因为结果中的姓氏都一样。
为了在 Java 环境中使用 XQuery,前面的介绍似乎太罗嗦了。但是,选择 XQJ(常见的 Java XQuery API 缩写)的多数程序员至少要对 XPath 和 XQuery 有所了解。现在我们介绍的东西比基础稍多一点,可以在 Java 程序中使用这些表达式了。
XQuery for Java API 是在 Sun 的支持下作为 Java Community Process, JSR 225(链接见 参考资料)的一部分开发的。规范本身涉及到多个不同的供应商(包括 Sun、Nokia、BEA、Oracle、Intel 等)和一些知名人士(比如 Jason Hunter,servlet、JDOM 和 XML 方面的名人)。因此,能够避免维系于特定的数据库供应商或者 XML 产品厂家。
不幸的是,还没有 XQJ 的 Sun 的标准实现。专家组的多数供应商都致力于在其产品中提供 XQJ 实现,这就意味着必须解决某些和特定供应商有关的问题。当然,对于长期使用 XML 的人来说,这和二十世纪初的 XML 解析器与 XSL 处理程序之争没有什么区别。随着时间的推移 XQJ 将标准化,Sun 几乎肯定会发布自己的 XQJ 版本或者 XQJ 实现的包装器 API,就像 XML 解析器与 XSL 处理程序的 JAXP 一样。
学习 XQJ 最简单的办法是从 DataDirect 下载免费的试用版。必须填写一份非常烦人的表单,不过此后就可以使用足够长的时间 — 至少能用到本文结束。请访问 DataDirect XQuery 下载站点(链接见 参考资料)。还必须进入需要访问的数据库 — 即使选择 XML Documents Only 选项。配置好这些选项之后,就会得到一封电子邮件说明到哪里下载 JAR 文件 datadirectxquery.jar。
安装过程有点麻烦,首先需要解压下载的 datadirectxquery.jar。可以使用 jar 命令。但首先要建立安装目录,然后将 JAR 文件解压到该目录。运行 jar 命令:
[bdm0509:~/Desktop] mkdir xqj [bdm0509:~/Desktop] cd xqj [bdm0509:~/Desktop/xqj] jar xvf ../datadirectxquery.jar inflated: XQueryInstaller.jar inflated: ddxqj.jar inflated: ExtensionTool.jar inflated: Readme.txt inflated: 3rdPartySoftware.txt inflated: Fixes.txt inflated: installer.properties |
现在打开新建的目录并双击 XQueryInstaller.jar 文件。如果系统中安装了 Java,将打开 GUI 安装程序。
提示您选择试用版还是注册版,选择 trial。接下来需要选择安装目录。输入的目录一定要具有写文件的权限。我选择的是 /usr/local/java/xqj,首先要保证能够写入 /usr/local/java 目录。安装过程还将建立一个子目录 — 该例中的目录名为 xqj — 然后将 DataDirect XQuery 文件放入该目录。最后运行安装并单击 Finish。
完成之后打开新建目录看看其中的内容,应该如下所示:
[bdm0509:/usr/local/java] cd xqj [bdm0509:/usr/local/java/xqj] ls 3rdPartySoftware.txt examples lib Fixes.txt help planExplain Readme.txt javadoc src |
RSS订阅







收 藏
推 荐