利用 PHP 模拟 XQuery 和 XInclude 功能

六 28th, 2011 | Filed under 其他, 开源技术

XQuery 和 XInclude 是 XML 工具,用于帮助 web 程序员动态地处理数据。XInclude 让您将多个 XML 文件当作一个文件对待,XQuery 可以处理组合的数据并将之准备包含在输出中,用于 web 页面显示。两者用很少几行代码优雅而高效地执行这一服务。

常用缩略词

  • HTTP:超文本传输协议
  • W3C:万维网联盟
  • XHTML:可扩展超文本标记语言
  • XML:可扩展标记语言
  • XSL:可扩展样式表语言

大多数浏览器可以显示和处理 XML 文件,有的是直接处理,也有的是通过与 XSL 模板协作完成。在理想情况下,浏览器也应该能够直接理解 XQuery 和 XInclude。但是目前它们要支持这些工具,还必须对用户提出不合理的要求 — 比如说要求他们加载附加组件(experimental add-ons)。从大量不同的源获取数据,并组合成一个大型数据集以便进行处理,对于 web 程序员来说是一件辛苦的工作。

通过一个虚构的业务例子,本文首先展示 XQuery 和 XInclude 组合的威力。然后您将学习如何使用 PHP 来模拟 XQuery 和 XInclude 提供的功能。将所有的数据处理放到服务器端去完成,可以克服浏览器不太支持 XQuery 和 XInclude 的问题。另一个好处是,PHP 让您能更多地控制最终的输出表示。

示例:花店协作

假设一个城镇上有三家花店。它们彼此竞争,但是提供各不相同的服务,它们以协作而不是敌对的方式提供服务。它们进行了一些市场调查,了解顾客是怎么买花的。

在花店里,容易腐烂的花卉都集中在短期内大量销售,顾客对其想要的花卉类型和品质都特别讲究。花店的最大利益是,让难保养(例如,需要加水和防害)的花卉尽早售出,以及通过在适当的时间和适当的地点提供适当的花卉,取悦捉摸不透的顾客群。否则,顾客就会流往能够满足其需求的竞争方。

调查表明,顾客不喜欢打电话询问在哪里可以买到需要的花卉,大量的电话对花店来说也不方便。在季节开始,所有的花店都有丰富的花卉。在季节后期,除非进行良好的规划,否则花卉将开始不断地缺货。顾客寻找矮牵牛花可能必须致电或造访所有三家花店,才能找到心仪的粉色品种。一般来说,顾客想要知道哪一家有哪些花卉,装在多大的瓶子里,数量和价格如何。

提议的设置

三家花店的 IT 经理进行了一次面谈。他们决定创建一个公共网站,顾客可以从网站找到哪家花店库存中有特定的花卉。

尽管所有三家花店都采用计算机管理,但是每一家都使用不同的系统来存储信息。Apples and Things 使用 Microsoft® Access® 数据库系统,Birch Trees Unlimited 使用 Linux® 系统和 MySQL,Carnation Tarnation 则使用 Mac OS® X 和 IBM® DB2。

广而言之,他们决定统一成一种基于 XML 的架构。XML 是一种方便的数据格式,因为他们各自的系统都可以将当前数据导出到云中可用的 XML 文件。一个主 XML 文件使用 XInclude 将单个花店的数据收集到一个中心位置。最后,主 web 页面检查主文件,使用 XQuery 抽取数据,并呈现最终显示。

利用 XInclude 和 XQuery 处理 XML

IT 经理们决定,每家花店将产生一个类似于 清单 1 中所示的 XML 文件:
清单 1. 花店 XML 文件

<store>
  <info>
    <name>...</name>
    <address>... </address>
    <phone>... </phone>
  </info>
  <plants>
    <plant>
      <name>Petunia</name>
      <description>Pink, in 4 inch pots</description>
      <quantity>100</quantity>
      <price>3.00</price>
    </plant>
    <plant>
      <name>Apple tree 'Spartan'</name>
      <description></description>
      <quantity>6</quantity>
      <price>25.00</price>
    </plant>
    ...
  </plants>
</store>

该文件具有一个 store 根元素和两个子元素。info 子元素包含关于花店的一般信息。plants 子元素又包含很多 plant 子元素,每个plant 子元素包含关于该花卉的补充信息,比如说名称、描述、数量和价格。所有三家花店都严格遵循这种模式。

清单 2 是他们的主 XML 文件版本,组合了所有三个花店文件,使它们成为一个大文件,包含关于花卉的详细信息:
清单 2. 主 XML 文件

<?xml version="1.0">
<storeData xmlns:xi="http://www.w3.org/2001/XInclude">
  <xi:include href="http://path-to-apples.xml"/>
  <xi:include href="http://path-to-birches.xml"/>
  <xi:include href="http://path-to-carnations.xml"/>
</storeData>

在 清单 2 中,XInclude 用于使用 HTTP 从云中的三个位置取得数据。处理之后,变成 清单 3 中的样子:
清单 3. 扩展后的主 XML 文件

<?xml version="1.0">
<storeData ...">
  <store>
    <info>
      <name>Apples</name>
      ...
    </info>
    <plants>
      ...
    </plants>
  </store>
  <store>
    <info>
      <name>Birches</name>
      ...
    </info>
    <plants>
      ...
    </plants>
  </store>
  ...
</storeData>

在 清单 3 中,清单 2 中的包含指令被单个花店 XML 文件的内容替代。每个 store 元素现在都是全局 storeData 元素的一个子元素。

现在可以向组合的数据集应用 XQuery 了,跟 清单 4 中一样:
清单 4. 应用 XQuery

<div>{
  for $store in doc("mainXMLfile.xml")/storeData/store
  let $plants := $store/plants/plant
  return
      <div>{
        for $plant in $plants
            order by $plant/name
        return <div>{ concat($plant/quantity," of ",$plant/name," at ",
            $store/info/name," for ",$plant/price," each") }</div>
        }</div>
}</div>

清单 4 利用 div 部分请求 XHTML 输出。迭代通过每家花店,并报告每种花卉的数量、名称、花店名称和价格。输出类似于 清单 5
清单 5. 生成的 XHTML

<div>
    <div>
        <div>6 of Apple tree 'Spartan' at apples for 25.00 each</div>
        <div>100 of Petunia at apples for 3.00 each</div>
    </div>
    <div>
        <div>100 of Coleus at birches for 3.00 each</div>
        <div>6 of Pear tree 'Flemish Beauty' at birches for 25.00 each</div>
    </div>
    <div>
        <div>6 of Orchids at carnation tarnation for 25.00 each</div>
        <div>100 of Roses at carnation tarnation for 3.00 each</div>
    </div>
</div>

您可以使用任何数量的 XQuery 客户端检查这一输出。例如,可以使用开源的基于 Java™ 的 eXist 应用程序来在测试数据库上验证各种 XQuery 命令(包括 清单 4 的查询)的输出。或者,也可以使用 Zorba(一个单独的 XQuery 应用程序)来做这件事情。参见 参考资料,了解关于这些 XQuery 客户端的更多信息。也可以使用 JavaScript 方法。

组合使用 XInclude 和 XQuery 的替代方法是在服务器端执行所有步骤。可以通过使用 PHP 的 SimpleXML 扩展提供的功能而利用 PHP 来做这件事。SimpleXML 以 XHTML 格式直接向浏览器交付过滤的和格式化的数据,从而减轻了一般读者的负担。

利用 PHP 进行包含

首先完成包含步骤,这可以利用简单的字符串替换来完成:

  1. 从一个基本的空 XML 框架开始。
  2. 为每个花店插入一个字符串。
  3. 取得花店的文件。
  4. 利用文件替换花店名称。

结果是一个针对所有花店的大文件。也有其他构建文件的方式,比如说使用 SimpleXML addChild(...) 函数。但是对于本例来说,字符串替换方法更简单一些,并且提供刚好所需的验证。清单 6 展示了字符串替换技术:
清单 6. 通过字符串替换进行包含

<?php
// string substitution approach
$arr = array("apples","birches","carnations");
$myxml = "<?xml version=\"1.0\"?>
<storeData>";
foreach ($arr as $a) { $myxml .= "$a\n"; }
$myxml .= "</storeData>";
// now process includes
foreach ($arr as $a) {
  if (!$f = file_get_contents($a.".xml")) {
    xml_fallback($a);
  } else {
    if (!simplexml_load_string($f)) {
      xml_fallback($a);
    } else {
      xml_include($a,$f);
    }
  }
}
if (!$xml = simplexml_load_string($myxml)) {
  echo "Combined file failed!\n";
} else {
  echo $xml->asXML;
}
//
// functions
//
function xml_include($a,$rep) {
global $myxml;
  $myxml = str_replace($a,$rep,$myxml);
}
function xml_fallback($a) {
global $myxml;
  $rep = "<store><info>
    <name>$a</name>
  </info></store>";
  $myxml = str_replace($a,$rep,$myxml);
}
?>

清单 6 首先声明一个数组,其中包含花店的简写名称,并将这些名称构建到一个基本的 XML 文件中。然后循环通过每个花店的 XML 文件,取得它们的简单文本。文件还可能不存在,那就需要您采取特殊的操作了。如果已经有文件了,就对之进行测试,看它本身是不是有效的 XML。如果无效,就采取跟没有文件时相同的操作,即使用 XInclude 的 Fallback 函数的等价物。在本例中,xml_fallback() 函数替换一个基本的 XML 片段,以保持文件格式良好。如果文件有效,就用 xml_include() 将它替换到主文件中。最后,对组装完全的 XML 文件进行测试,以保证格式良好。在本例中,由于您只测试包含功能,所以一旦成功,就会停止处理并为可见的验证输出 XML。XML 现在已经就绪,可以进行查询处理了。

利用 PHP 进行查询

您现在可以在 PHP 代码中处理 XML 了,要么利用一个 XQuery 应用程序(比如 Zorba),要么通过使用 SimpleXML。

使用 Zorba 进行查询

一个有趣的组合使用 PHP 和 Zorba 的方法是作为一个 PHP Extension Community Library 扩展。(关于此方法的解释,请参见 参考资料 中到文章“Building XQuery-powered applications with PHP and Zorba”的链接。)另外,您也可以通过使用 exec() 来无需使用扩展而从 PHP 调用 Zorba,如 清单 7 中的 PHP 片段所演示的:
清单 7. 无需使用扩展的 PHP 和 Zorba 方法

<?php
...
function run_zorba($myxquery) {
  ...
  exec("zorba -i -q '$myxquery'",$array);
  ...
  $xmlstr = implode("\n",$array);
  $xml = simplexml_load_string($xmlstr);
  echo $xml->asXML();
}
?>

在 清单 7 中,函数首先获得 $myxquery 字符串参数中的 XQuery 请求。然后使用 exec() 函数来运行 Zorba。出于安全考虑,您可能希望将它封装在一个进程分支(process fork)中。exec() 函数利用来自 Zorba 的输出填充 $array 数组。-q 和 -i 选项分别要求运行一个查询和缩排输出。这最后一个选项在调试过程中特别有用,因为它使得 XML 代码更加可读。数组的内容然后被连接成一个长字符串,字符串又被加载到一个 XML 对象中,在本测试案例中还被回显。

使用 SimpleXML 进行查询

尽管使用诸如 Zorba 这样的 XQuery 代理很方便,但是采用以下方式利用 SimpleXML 直接获得数据也是可能的。

此时,假设您已经让 清单 6 中的 $xml 变量加载到了一个 XML 对象中,可以被处理了。清单 8 展示了 XML 数据的最终查询:
清单 8. 利用 SimpleXML 进行查询

<?php
// Query with PHP
include 'mainstore2.php';
if (!$xml = simplexml_load_string($myxml)) {
  die("Cannot read output from XInclude step\n");
}
// begin output
$myout = "\n\n<html><head></head><body>\n";
$myout .= "<div>\n";
foreach ($xml->store as $store) {
  foreach ($store->plants->plant as $plant) {
    $myout .= $plant->quantity." of "
        .$plant->name." at "
        .$store->info->name." for "
        .$plant->price
        ."\n";
  }
}
$myout .= "</div>\n";
$myout .= "</body></html>\n";
echo $myout;
?>

清单 8 从加载 清单 6 的等价物并再次检查 XML 是否格式良好开始。然后循环通过各个花店,再通过各个花店有卖的花卉,最后生成清单 9 中所示的输出。该输出与 清单 5 中展示的输出(由前面的 XQuery 方法产生)相当。您可以根据需要添加 XHTML 标记。
清单 9. 输出到浏览器

<html><head></head><body>
<div>
100 of Petunia at apples for 3.00
6 of Apple tree 'Spartan' at apples for 25.00
100 of Coleus at birches for 3.00
6 of Pear tree 'Flemish Beauty' at birches for 25.00
100 of Roses at carnation tarnation for 3.00
6 of Orchids at carnation tarnation for 25.00
</div>
</body></html>

根据需要,您可以轻松地扩展此类编程,让花店顾客能够基于查询参数(比如价格范围和花卉名称)进行过滤和排序。

结束语

程序员的关注点是,将使用轻松可用的 SimpleXML 库进行的 PHP 编码(一方面)与 PHP 和 特殊的 XInclude 及 XQuery 库提供的中间工具(另一方面)进行比较。只有能够减轻程序员的负担,减少完成某项工作所需的代码并使之更清晰,特殊工具库才有价值。

对于包含来说,插入其他文件中的数据相当直观,使用这两种方法都无需太多编码。

在查询案例中,XQuery 库可以利用 PHP+SimpleXML 方法替代很多必需的代码(’for’ 循环,等等)。

然而,代码越浓缩,对于数据检索来说使用 PHP 实现它的其他功能的机会就越少。举例来说,您可以将 20 行的 PHP+SimpleXML 查询缩短为 1 行 XQuery 查询。代价是失去了能够轻松且清楚地在 20 个独立语句之间插入其他语句的机会。这就是权衡。

参考资料

学习

获得产品和技术

Del.icio.us Google书签 Digg Live Bookmark Technorati Furl Yahoo书签 Facebook 百度搜藏 新浪 ViVi 365Key 网摘 天极网摘 和讯网摘 博拉网 POCO 网摘 饭否 QQ 书签 Digbuzz 我挖网 Mister Wong
标签: , ,
目前还没有任何评论.