`
zhangziyangup
  • 浏览: 1072870 次
文章分类
社区版块
存档分类
最新评论

高级 XQuery:创建自定义函数,将软件开发的最佳实际添加到 XQuery 表达式

 
阅读更多

XQuery 和 XQuery 函数

这个小节花点时间回顾 XQuery,并简单介绍如何使用 XQuery 函数。

XQuery 快速回顾

这很简单:XQuery 之于 XML 文档犹如 Structured Query Language(SQL)之于关系数据库。XQuery 使开发人员能够使用表达式从 XML 文档提取数据。可以提取的数据包括简单的值或文档的整个子树,比如一个元素及其所有子元素。

为了实现这个目标,XQuery 需要使用 XPath 表达式。这涉及到著名的 FLWOR 表达式:forletwhereorder byreturn。这些表达式为从 XML 文档提取和返回数据提供强大的方法。

该语言的语法基于 XML 文档本身的树状结构。XQuery 能够感知处理指令、属性和元素。

注意,要使 XQuery 能够正确地处理 XML 文档,查询不一定是有效的,但它必须 具有良好的结构。记住,具有良好结构的 XML 文档意味着它遵循 W3C 的 XML 标准(参见 参考资料)。当一个 XML 文档遵循它自身的文档类型定义(Document Type Definition,DTD)或模式时,它就是有效的。

什么是 XQuery 函数

仍然使用关系数据库作为类比,XQuery 与 XML 文档的关系,就像存储过程和关系数据库的关系。那就是,它们都是其他表达式可以调用的用户自定义的例程。对于喜欢使用 Java 的人员,XQuery 函数就像实用程序类中的一个方法,它是静态声明的,但可以公开使用。

看看 清单 1 中的 XQuery 函数,我将解释它的各个部分。为了方便理解,我现在使用的函数也用于后面的模拟 eCommerce 环境中。


清单 1. XQuery 函数

					
declare function local:calculateReceivedIn($delay as xs:integer) 
{
	let $receivedIn := ($delay + $shippingDelay)
	return ($receivedIn)
}; 

这是一个非常基础的函数,但能够在 eCommerce 应用程序中很好地工作。它主要添加了两个值,一个作为该函数的参数,另一个是整个模块的独特变量。以后还会对此进行论述。

首先,要注意函数的声明。这可以通过两个词很直观地实现:declare function。这将通知 XQuery 处理程序所声明的是函数,而不是表达式。

其次,函数的名称分为两部分:local:calculateReceivedIn。冒号前面的部分(local)是名称空间,并且遵循使用名称空间的 XML 标准。在本例中,使用的是 local。一个具有行业强度的应用程序可能拥有几个模块,并且 XQuery 函数将使用特定于这些模块的名称空间。函数名称的另一部分就是:函数名。在本例中是 calculateReceivedIn,因为它计算在输出结果的 “Received in X days” 部分中显示的数字。

接下来便是参数的声明。通常,圆括号中的参数紧接着函数名。参数名以美元符号($)开始。这是标准的 XQuery 实践。注意,您还定义了参数的类型。在本例中,参数的类型为整数,因此将其定义为 as xs:integer。熟悉 XML Schema 规范的人员能够认出这种数据类型的语法。要获得完整的数据类型列表,请参见 参考资料

然后,您将开始学习函数的工作部件。它包含在大括号中,并且使用标准的 XQuery 表达式。它从计算 receivedIn 开始。这通过使用 let 表达式(let 是首字母缩写词 FLWOR 中的 L)来实现。在这里,您只是声明 receivedIn 等于 delayshippingDelay 之和。记住,shippingDelay 是一个全局性变量。在这里它不能声明为参数,因为它已经在前面的 XQuery 模块中声明。此示例会清楚地说明这点。

那么如何调用 XQuery 函数呢?看看 清单 2,考虑来自 XQuery 表达式的代码片段。


清单 2. 调用 XQuery 函数

					
	{local:calculateReceivedIn($minnow/availability/shipping/delay)}

这是 XML 文档的一部分。在虚构的 eCommerce 应用程序中,XML 文档被返回给应用程序,然后用于向用户显示信息。要计算这个产品(在这个例子中是一种鱼饵,形状像米诺鱼)的运输延误,需要在大括号内部调用该函数。使用完全限定的名称(包括名称和空间名),并传入从 XML 文档获取的值。这里的值来自 <delay> 元素,它必须是一个整数,否则 XQuery 会拒绝它。注意,必须使用标准的 XQueryote 表达式来获取参数的值。然后该函数从 XML 文档的 <delay> 元素接收这个值,并将其添加到 $shippingDelay 的值。它将返回这两个数值之和,如清单 2 的输出结果所示。

XQuery 函数的优点

使用 XQuery 函数有很多优点。首先,XQuery 表达式本身就是可读性很强的。看一看 清单 3


清单 3. 没有函数的 XQuery 表达式

					
<shipping-info>
   <received-in>
      {$minnow/availability/shipping/delay/_cnnew1@unit} 
      {
         let $delay := $minnow/availability/shipping/delay
         let $receivedIn := ($delay + $shippingDelay)
         return ($receivedIn)
      }
   </received-in>
</shipping-info>

实际上,清单 3 展示了关于运输信息的整个 XML 子树。计算 $receivedIn 的 XQuery 表达式处于中间的位置,它代替了 清单 1 中的 XQuery 函数。如上所示,没有这个函数之后,这段代码显得复杂一些,并且对于不熟悉该代码的人而言,解析它的时间也要长些。如果 XQuery 函数要执行更加复杂的工作,这段代码的可读性则会更差。

XQuery 函数的另一个优点是关注点分离功能。这个函数使开发人员能够在数据返回之前定义如何操作或处理数据,同时又可以使用 XQuery 表达式的主体部分查找函数需要处理的数据。

XQuery 表达式的最后一个优点是可重用性。在这个例子中,您可以根据一般的运输延迟和用户的地理位置定义一个函数,用于计算某一物品到达用户手中所需的时间长度。再看一看修改后的 清单 3。如果您为每个类型的物品定义了以上函数,并且添加了计算时间长度的逻辑,您则需要在整个 XQuery 表达式过程中反复执行这个操作。不过,在一个函数中定义逻辑使您能够在其他表达式中重用该逻辑。如果逻辑发生改变,您仅需在该函数内部更改它。

编写您的第一个示例

现在我们通过示例进行学习。在这一小节,您将在一个模拟的 eCommerce 环境中实现一个 XQuery 函数。

了解您的业务

如前所述,您将在一个模拟的 eCommerce 环境中实现自己的 XQuery 函数。在这个例子中,您有一个在线业务,称为 fishinhole.com。这是一个营销钓具的 Web 站点。用户浏览产品目录,订购产品,然后指定收货地址。

常用的缩写词
  • API:应用程序编程接口
  • HTML:超文本标记语言
  • IDE:集成开发环境
  • W3C:万维网联盟
  • XML:可扩展标记语言

目录中包含的产品的信息以层次结构的形式存储在一个 XML 文档中。这个 XML 文档包含每个产品的信息,比如它的名称、价格、颜色、说明,以及发货时间。Web 应用程序获取这个 XML 文档并将其转换成可以在用户的浏览器上显示的 HTML 表示。

您将实现一个用于协助特定用例的 XQuery 表达式。在这个例子中,一个经过验证的用户想要在 Web 站点上执行搜索。该用户的目的是搜索一种现状像米诺鱼的鱼饵,并且要求能在特定的时间内收到货物(例如两天以内)。您将使用 XQuery 查询该 XML 文档并只返回符合条件的鱼饵。

XQuery 函数(已在 清单 1 中展示)用来根据用户的地理位置 一般的运输延误计算用户收到鱼饵所需的时间。这是很有必要的,因为 fishinhole.com 的产品销往世界各国,并且运输时间不仅受运输延迟的影响,也受目的地的影响。

XML 文件:fishinhole.xml

看看这个 XML 文件,您将在这个用例中解析和查询它。清单 4 展示了整个 XML 文档(随本教程附带)的一小部分。尽管为了节省空间对该 XML 文档进行了删减,但清单 4 已经足以显示数据的总体结构。


清单 4. XML 文档的结构

清单 4 展示了一种鱼饵并提供了大量与之相关的信息。这些信息包括价格、运输方式、品牌名称、尺寸、样式和颜色。有些信息用属性进行描述,比如 brandstyle。其他信息使用嵌套元素进行描述,比如 <price>。注意,一些元素还对产品进行分类,比如 <casting><minnows>。这就是业务的数据结构。

尤其需要注意 <shipping> 元素。该元素的子元素是 <delay>,它描述运输延迟。前面的用例就用到这个数字。在 清单 4 的示例中,延迟为 0,它表示该产品可以在订购日达到。对于本例,运输延迟都是以天为计算单位的。

开始使用 Java 类文件

接下来,您将使用 Java 代码和 XQJ 编写 Java 类。您将编写一个简单的 Java 类,用作 XQuery 函数的单元测试。

调用这个类 XQueryFunctionTester。它将拥有一个构造函数,与 XML 文档的文件名同名。它还拥有一个 main() 方法,因此您可以执行它。清单 5 展示了这个类。


清单 5. 开始使用 Java 测试类

我们先看一看 main() 方法。这是标准的 Java 应用程序的入口点。在这里,使用将要查询的 XML 文档的文件名实例化 XQueryFunctionTester。注意,我将 XML 文件置于 Java 类包结构的根部。要成功执行该类,就必须将它放到那个位置。我选择硬编码 XML 文档的引用,而不是使用命令行参数,因为使用 IDE 时,硬编码引用更加容易。如果发生了某些更改,我只需在代码上直接改动,然后重新执行。

go() 方法用于执行查询。实例化 XQueryFunctionTester 之后,就脱离了 main() 创建的静态上下文。我现在留空 go()。以后还要处理它。

此外,还要注意,我在这里包含了以后要用到的导入。这些内容大部分来自 XQJ 库,并且随后使用的 XQuery 搜索需要它们。QName 类根据 XML 规范指定一个限定名。FileReader 读取该 XML 文件。XQJ 实现需要 Properties 类。只有使用一个空的构造函数构造它,并将它作为一个方法参数传入之后才能使用它。

在这个测试用例中,用户将搜索运输延迟小于等于特定天数的所有米诺鱼诱饵。因此要编写执行该搜索的方法,如 清单 6 所示。


清单 6. 执行 XQuery 搜索的方法

简要地说,清单 6 中的两个方法构造了一个 XQExpression 对象,并执行它,然后以 String 格式返回结果。

getGenericExpression() 方法为扩展做好准备。如果您想在不同的测试中创建另一个方法,可以使用 getGenericExpression(),而不是为每个新的测试重新编写该方法。这个方法构造一个 XQExpression 对象并将 docName 字段设置为 XML 文件的名称。

getAllMinnowsWithMaximumDelay() 用于测试本文的用例。首先获取上面定义的泛型表达式,然后将某些变量绑定到在 Java 类中定义的值。第一个绑定的变量是 SHIPPING_DELAY_IN_DAYS,它表示虚构用户根据用户的地理位置收到货物所需的时间。在这个例子中,这个方法将变量设置为 3。下一个变量 MAXIMUM_DELAY_FIELD 是用户可以接受的最长运输时间。这个值从 maximumDelay 参数传入。最后,FileReader 读取 XQ 文件(后面将构建该文件)。这样,您就可以查询该文件并以 String 对象的形式返回结果。

注意,XQ 文件也位于类包的根部。即它和 XML 在同一个地方。

要获得更多关于 XQJ 的信息,请参阅 参考资料

清单 7 中,您将返回到 go() 方法,它执行实际的测试。


清单 7. go()init() 方法

首先,go() 方法调用 init() 方法,后者构造 XQJ 数据源并从中创建一个 XQConnection 对象。其次,要确定虚构用户能够接受的最长运输延迟。在这里仅为两天。然后调用前面描述的方法 getAllMinnowsWithMaximumDelay(),并将 delayToUse 作为参数传入。最后,就可以输出结果了。

XQuery 文件:getAllMinnowsWithMaxDelay.xq

现在,可以看一看 XQ 文件了。如 清单 8 所示。这个文件包含搜索和检索 XQuery 表达式和 XQuery 函数。刚才创建的 Java 类将引用这个文件。


清单 8. XQuery 文件

这个 XQ 文件的总体目标是从 XML 文件提取与查询相匹配的元素,并将包含在这些元素中的信息呈现给用户。在这里,您可以在不同的类中使用一系列的 div 标记,该标记是在样式表文件中定义的。可以通过这种方式以可读美观的形式向用户呈现提取的信息。

看一看 XQ 文件的前 3 行。它们的变量名看上去很熟悉。因为您在 getAllMinnowsWithMaximumDelay()getGenericExpression() 方法中使用了这些名称。在这些方法中,值被绑定到这里定义的变量中。

接下来的几行也比较熟悉。这就是前面详细描述的函数。正是该函数返回货物运输延迟(传入的参数)和用户地理运输延迟(在文件的顶部定义的变量之一)之和。

然后是一个标准的 XQuery 表达式。这个表达式返回运输延迟天数小于等于 $maximumDelay 指定的天数的所有米诺鱼诱饵。

在代码的底部,要注意 XQuery calculateReceivedIn 函数的实现。这个函数首先使用一个标准的 XQuery 表达式提取 <delay> 元素的值,然后将这个值作为 calculateReceivedIn 函数所需的惟一参数传递。

综合

如果您已经综合这些函数,现在就可以下载本教程提供的文件了(参见 下载)。

您可以将本教程提供的压缩文件(.zip)提取到硬盘驱动器上的某个测试目录。该文件将 Java 源代码、XML 文件和 XQ 文件放到适当的位置。

接下来,编译 Java 源代码。您需要确保 ddxq.jar 在类路径中。这个库是 XQJ 发布版的一部分,它位于安装 XQJ 的目录下的 /lib 目录中。

如果编译没有问题,就可以运行这些代码了。在命令提示符处,转到刚才将代码解压缩到其中的目录,然后输入 java com.triangleinformationsolutions.article.xquery.XQueryFunctionTester 并使用适当的 -cp 参数指定指向 ddxq.jar 的类路径。只要 java.exe 在类路径中,它应该能够正常运行。

您很有可能从 IDE 运行它,而不是命令提示符。对于这种情况,只需根据 IDE 提供的指令执行一个独立的 Java 应用程序。

成功执行之后,输出结果应该类似于 清单 9。在本教程中,这个结果经过删减。


清单 9. 预期输出结果(经过删减)

如果将 XML 文档和 清单 9 中的输出相比,您将发现它是正确的。深蓝色的鱼饵没有运输延迟(或 0 天延迟)。将它与和 $shippingDelay(该值为 3,如以上的 Java 代码所示)绑定的值相加就等于 3。杂色的鱼饵有 1 天的延迟。它和 3 相加就等于 4,所以运输延迟时间为 Received in 4 day(s)。换句话说,这个测试是成功的!

如何实现价格标签?

XQuery 函数还有其他用法吗?再看一看 清单 9 中的输出。注意,没有显示每种诱饵的价格。用户在 eCommerce 站点上购买诱饵可能是因为它提供便宜的价格。因此,要创建一个显示价格的函数。

现在您可能会问自己 “为什么需要这个函数?我可以将它从 XML 文件提取出来并显示它吗?”要记得 fishinhole.com 是国际性的,它在全球范围内做交易。因此显示的价格要符合特定国家的币种面值。如果您再次看看这个 XML,将发现它已经带有 <international-prices> 元素。

因此,在下一个用例中,要根据特定的国家确定每个产品的价格。这涉及到更改 Java 类和 XQ 文件,但不要更改 XML,因为它已经包含国际价格,如 清单 10 所示。


清单 10. 更改 Java 类

清单 10 与之前创建的 Java 类不同,因为它包含了两个新的静态字段和一个附加的表达式绑定。两个新的字段分别是特定国家的字段名和这个国家的实际名称。在这个示例中,测试用户居住在墨西哥,因此用户期望看到以比索显示的价格。这个新的表达式绑定将字段名(country)绑定到用户的国家名(Mexico)。

清单 11 中的新 XQ 文件与 清单 8 中的 XQ 文件没有很大的区别,但是更改还是很显著的。


清单 11. 新的 XQ 文件

首先,您需要声明新的变量 $country。在前一个清单中就将它与值 Mexico 绑定在一起。

接下来,您将声明新的函数 getPrice。这个函数有几个需要注意的地方。首先,它采用 element 数据类型。这与前一个函数不同,后者采用简单的数据类型(integer)。这是一个很好的示例,因为它演示了不仅可以传入简单类型作为函数的参数,也可以传入元素作为参数。

这个函数需要注意的第二个地方是您指定一个返回类型。在这个例子中,返回一个字符串。因此,您不再需要在函数的主体中使用单词 return。它表明这就是返回的内容。

函数体中使用 if/then 语句,并且将 $country 的值与该应用程序支持的其他国家进行比较。如果匹配,将以所在国家的货币面值返回这个产品的价格。默认的币种是欧元。

最后,主 XQuery 表达式拥有一个新的 div 标记,它在迭代时获取当前产品的价格。此时,将调用 getPrice 函数。它将自身(即整个 <minnow> 元素)作为一个参数传入。

这个教程的代码示例分别包含针对这个示例和前一个示例的文件。这个示例的文件是 XQueryFunctionTesterIncludesPrice.java,而上一个相应的 XQ 文件是 getAllMinnowsWithMaxDelayIncludesPrice.xq。

因此,像以前一样,编译并运行 XQueryFunctionTesterIncludesPrice。执行完毕时,输出结果应该类似于 清单 12


清单 12. 新 XQ 文件的输出

这次测试又成功了!因为您将国家配置为 Mexico,所以将看到以该国的货币面值显示的价格。在这个例子中货币单位为比索。您可以清楚地看到,price 类的 div 包含 100 Pesos 作为价格。在 XML 文档中,数字 100 也是正确的。

重构和嵌套函数

解决问题的办法往往不止一个。要记住,使用 XQuery 函数的优点之一便是它的可重用性。另一个优点便是关注点分离。以此为依据,看看您创建的新函数,如 清单 13 所示。


清单 13. 新函数:尚有改善的余地?

					
declare function local:getPrice($minnowElement as element(minnow)) as xs:string
{
if ($country = 'Mexico') then
(concat((data($minnowElement/international-prices/price[@denomination="peso"]))
  ," Pesos"))

else if ($country = 'Canada') then 
(concat(
 (data($minnowElement/international-prices/price[@denomination="canadian-dollar"]))
  ," Canadian Dollars"))

else if ($country = 'United States') then
(concat((data($minnowElement/international-prices/price[@denomination="dollar"]))
 ," Dollars"))

else 
(concat((data($minnowElement/international-prices/price[@denomination="euro"]))
 ," Euros"))};

该函数的大部分内容都是不断重复的表达式。它在搜索方面惟一更改的地方是 denomination 属性值。这种情况为重构提供了好机会。如何对此进行重构呢?您可以已经猜到,使用另一个函数!是一个嵌套函数吗?对,没错!

有另一个函数处理搜索匹配价格的逻辑之后,如果该逻辑发生改变,您就不需要在同一个函数中对它进行 4 次更改(像 清单 13 中的函数那样)。另外,如果因扩展业务需要添加一个使用另一种货币的国家(比如印度或中国),按照原先的方法问题就更加复杂了,因为又有另一个 地方需要更改逻辑。

通过使用另一个函数,您将获得重用代码带来的两个好处,并且可以使用封装。新的函数处理从 XML 文档体获取实际价格的必要业务逻辑。

因此,创建一个称为 getCountrySpecificPrice 的新函数。这个函数接受两个参数:上述的 <minnow> 元素和表示货币面值名称的属性的实际名称。然后,函数将从该元素获取值作为一个 denomination 属性值。这个属性值与作为参数传递给该函数的名称相匹配。

清单 14 不仅展示了新函数,还演示了如何重写 getPrice 函数,使其包含新的函数。


清单 14. 改进后的新函数

					
declare function local:getCountrySpecificPrice($minnowElement as element(minnow),
$denomination as xs:string) {
let $price := 
   (data($minnowElement/international-prices/price[@denomination=$denomination]))
 
return ($price)
};

declare function local:getPrice($minnowElement as element(minnow)) as xs:string
{
if ($country = 'Mexico') then
(concat(local:getCountrySpecificPrice($minnowElement,"peso")," Pesos"))

else if ($country = 'Canada') then 
(concat(local:getCountrySpecificPrice($minnowElement,"canadian-dollar")
 ," Canadian Dollars"))

else if ($country = 'United States') then 
(concat(local:getCountrySpecificPrice($minnowElement,"dollar")," Dollars"))

else (concat(local:getCountrySpecificPrice($minnowElement,"euro")," Euros"))};

如您所见,最后修改的代码更加干净了。这也很重要,因为它演示了如何在函数中嵌套函数。

本教程的用例以此结束。不过,使用 XQuery 函数可以对本教程给出的 XML 文档和 XQ 文件进行其他实践。您可以开始探索自己喜欢的事情。使用这些源文件实现自己的用例和相应的测试,并且要经常进行备份。

分享到:
评论

相关推荐

    XQuery权威指南(简码·扫描版)

    第3章 表达式:XQuery的组成部分  3.1 表达式种类  3.2 关键字和名称  3.3 查询中的空白符  3.4 字面值  3.5 变量  3.6 函数调用  3.7 注释  3.8 计算顺序和括号  3.9 比较表达式  3.10 条件(ifIthen-else...

    Web Services 教程

    XQuery 添加元素和属性 61 XML 实例文档 61 向结果添加元素和属性 61 XQuery 选择和过滤 64 XML实例文档 64 选择和过滤元素 65 XQuery 函数 68 XQuery 函数 68 XQuery 内建函数 68 函数调用实例 68 XQuery 用户定义...

    Microsoft SQL Server 2005技术内幕: T-SQ程序设计.pdf

     通过本书,你将深入了解T-SQL的高级用法,包括触发器、用户自定义函数、异常处理等。该书解释并比较了SQL Server 2000和SQL Server 2005在数据库开发相关问题上的解决方案,深入讨论了SQL Server 2005中新增的T-...

    SQL Server 2008高级程序设计 4/6

     本书提供了快速创建和部署数据驱动的解决方案来满足业务需求的信息,介绍了新数据类型、索引结构、管理功能和高级时区处理等重要内容,掌握这些知识后,您将使自己的数据库发挥㈩最大功效。  主要内容  ◆除规范...

    SQL Server 2008高级程序设计 5/6

     本书提供了快速创建和部署数据驱动的解决方案来满足业务需求的信息,介绍了新数据类型、索引结构、管理功能和高级时区处理等重要内容,掌握这些知识后,您将使自己的数据库发挥㈩最大功效。  主要内容  ◆除规范...

    SQL Server 2008高级程序设计 6/6

     本书提供了快速创建和部署数据驱动的解决方案来满足业务需求的信息,介绍了新数据类型、索引结构、管理功能和高级时区处理等重要内容,掌握这些知识后,您将使自己的数据库发挥㈩最大功效。  主要内容  ◆除规范...

    webharvest 中文翻译文档

    2. 一个介绍XPath、XQuery 以及 XSLT 函数的网址 http://www.w3school.com.cn/xpath/xpath_functions.asp 3. 另一个参考地址 http://www.ibm.com/developerworks/cn/xml/x-xqueryl/ 可以在XML相关书籍中找到实例。 ...

    SQL Server 2008高级程序设计 2/6

     本书提供了快速创建和部署数据驱动的解决方案来满足业务需求的信息,介绍了新数据类型、索引结构、管理功能和高级时区处理等重要内容,掌握这些知识后,您将使自己的数据库发挥㈩最大功效。  主要内容  ◆除规范...

    SQL Server 2008高级程序设计 3/6

     本书提供了快速创建和部署数据驱动的解决方案来满足业务需求的信息,介绍了新数据类型、索引结构、管理功能和高级时区处理等重要内容,掌握这些知识后,您将使自己的数据库发挥㈩最大功效。  主要内容  ◆除规范...

    SQL Server 2008高级程序设计 1/6

     本书提供了快速创建和部署数据驱动的解决方案来满足业务需求的信息,介绍了新数据类型、索引结构、管理功能和高级时区处理等重要内容,掌握这些知识后,您将使自己的数据库发挥㈩最大功效。  主要内容  ◆除规范...

    Microsoft SQL Server 2005 Express Edition SP3

    SQL Server Express 是独立软件供应商 (ISV)、服务器用户、非专业开发人员、Web 应用程序开发人员、网站宿主以及客户端应用程序编程爱好者的理想之选。 未及时包括在本自述文件中的任何有关 SQL Server Express 的...

Global site tag (gtag.js) - Google Analytics