Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2371847
  • 博文数量: 473
  • 博客积分: 12252
  • 博客等级: 上将
  • 技术积分: 4307
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-12 10:02
文章分类

全部博文(473)

文章存档

2012年(8)

2011年(63)

2010年(73)

2009年(231)

2008年(98)

分类:

2010-01-29 11:29:36

您是否想过用一种简单的方法将 SQL 结果集转换为 XML?PEAR 包 XML_Query2XML 提供的一种全面性框架可以有效地将数据库查询结果转换为可定制的 XML 文档。本文将介绍这个包,并演示有用的实际应用程序,包括将它与 XSL 和 XPath 结合使用,并与来自外部 Web 服务的数据相结合,创建数据库转储文件。

也许您曾经听过 PEAR,PHP 扩展和应用库(PHP Extension and Application Repository)。这个社区推动的项目的目标是,提供一个广泛的、高质量代码的开源库,协助 PHP 开发人员快速开发应用程序。与 Perl 的 CPAN 存储库的概念类似,PEAR 一直以来都是我首先关注的有趣、有用的 PHP+XML 小部件。这些部件包括:XML_Serializer 类,用于方便地将 PHP 数据结构序列化为 XML 对象;XML_XUL 类,为构造 Mozilla XUL 应用程序提供了一个 API;XML_SVG 类,为通过编程构造 SVG 格式的向量图提供方法;等等。

在本文中,我还将向您介绍 PEAR 的 XML 部分的另一个成员,XML_Query2XML 类。此类提供了一个 API,用于快速有效地将 SQL 结果集转换为格式良好的 XML。如果稍加创新,通过 XSL 转换可以很轻松地将此输出转换为其他格式,或与其他基于 XML 的应用程序集成。


XML_Query2XML 包由 Lukas Feiler 积极地开发和维护,并在 LGPL 许可下发布给 PHP 社区。它需要使用 PHP 5.0(或更高版本)运行。安装它的最简单的方法是使用 PEAR 自动安装程序,此程序应该默认包含在 PHP 构建中。要安装此包,只需在 shell 提示中发出以下命令即可:

shell>pear install XML_Query2XML


PEAR 安装程序将连接到 PEAR 包服务器,下载此包并将其安装到系统中合适的位置。

要手动安装此包,请访问它在 PEAR Web 站点上的主页,下载包归档文件并手动地将这些文件解压缩到所需位置。注意,手动安装过程要求用户对 PEAR 的包组织结构有一些了解。

现在,您还应该意识到一些其他依赖项:

  1. XML_Query2XML 使用 DB、MDB2 或 ADOdb 数据库的抽象层与目标 RDBMS 通信,并因此要求存在这样一个抽象层,而且进行了正确安装,还要求有一个合适的数据库驱动程序。本文所给出的例子使用了 MDB2 抽象层(PEAR 包树的一部分)和它的 MySQL driver MDB2_Driver_mysql。正如前面所提到的,您可以使用 PEAR 自动安装程序安装这两个包,另外,您也可以从 PEAR Web 站点下载它们。 安装方法:

shell>pear install MDB2
shell>pear install MDB2#mysql
shell>pear install MDB2#oci8

shell>apt-get install php5-xsl

 

  1. 本文中的例子使用 MySQL 的示例数据库 world,其中包含了各种预填充的链接表,表中存储了城市数据和国家数据。有关获取和设置 world 数据库的指导,可以参考本文的 参考资料 部分。
  2. 本文中的例子要求 PHP 构建能够支持 PHP 的 DOM、XSL 和 SimpleXML 函数。PHP 5.x 中默认支持这些函数。
  3. 要求具有使用 PHP 的 DOM 和 SimpleXML 函数以及 XML、XPath、XSL 技术的实际经验。

有关这些不同组件的信息及下载链接,请参阅 参考资料

本文中的所有例子都已使用 XML_Query2XML Version 1.2.1 进行了测试。


成功安装完所有需要的组件后,就可以使用下面的示例 PHP 脚本开始探索 XML_Query2XML:


<?php
// include required files

include 'XML/Query2XML.php';
include 'MDB2.php';

try {
    // initialize Query2XML object

    $q2x = XML_Query2XML::factory(MDB2::factory('mysql://root:pass@localhost/world'));
    
    // generate SQL query

    // get results as XML

    $sql = "SELECT * FROM Country";
    $xml = $q2x->getFlatXML($sql);
    
    // send output to browser

    header('Content-Type: text/xml');
    $xml->formatOutput = true;
    echo $xml->saveXML();
} catch (Exception $e) {
    echo $e->getMessage();
}
?>


这段脚本演示了 XML_Query2XML 类的基本用法。首先,脚本包含了 XML_Query2XML 和 MDB2 类文件,然后通过它的 factory() 方法初始化了一个 MDB2 抽象层实例。此方法接受 DSN 作为输入,其中包含了关于 RDBMS 类型、RDBMS 用户名和密码,以及目标数据库名称的信息。得到的 MDB2 实例然后用于初始化 XML_Query2XML 实例,后者由 $q2x 对象表示。

构造 DSN 并创建 XML_Query2XML 对象实例后,即可实际对 RDBMS 执行 SQL 查询并将结果转换为 XML。通过 XML_Query2XML 的 getFlatXML() 方法可以完成这一任务,而该方法经常用于简单的 SELECT 类型的查询。此方法的输出是格式良好的 XML 文档,其中对 SQL 结果集进行了编码。生成的 XML 结果类似于:


<?xml version="1.0" encoding="UTF-8"?>
<root>
  <row>
    <code>AFG</code>
    <name>Afghanistan</name>
    <continent>Asia</continent>
    <region>Southern and Central Asia</region>
    <surfacearea>652090.00</surfacearea>
    <indepyear>1919</indepyear>
    <population>22720000</population>
    <lifeexpectancy>45.9</lifeexpectancy>
    <gnp>5976.00</gnp>
    <gnpold></gnpold>
    <localname>Afganistan/Afqanestan</localname>
    <governmentform>Islamic Emirate</governmentform>
    <headofstate>Mohammad Omar</headofstate>
    <capital>1</capital>
    <code2>AF</code2>
  </row>
  <row>
    <code>NLD</code>
    <name>Netherlands</name>
    <continent>Europe</continent>
    <region>Western Europe</region>
    <surfacearea>41526.00</surfacearea>
    <indepyear>1581</indepyear>
    <population>15864000</population>
    <lifeexpectancy>78.3</lifeexpectancy>
    <gnp>371362.00</gnp>
    <gnpold>360478.00</gnpold>
    <localname>Nederland</localname>
    <governmentform>Constitutional Monarchy</governmentform>
    <headofstate>Beatrix</headofstate>
    <capital>5</capital>
    <code2>NL</code2>
  </row>
  <row>
    <code>ANT</code>
    <name>Netherlands Antilles</name>
    <continent>North America</continent>
    <region>Caribbean</region>
    <surfacearea>800.00</surfacearea>
    <indepyear></indepyear>
    <population>217000</population>
    <lifeexpectancy>74.7</lifeexpectancy>
    <gnp>1941.00</gnp>
    <gnpold></gnpold>
    <localname>Nederlandse Antillen</localname>
    <governmentform>Nonmetropolitan Territory of
    The Netherlands</governmentform>
    <headofstate>Beatrix</headofstate>
    <capital>33</capital>
    <code2>AN</code2>
  </row>
  ...
</root>



仔细查看一下上面的 XML 输出可以看到一个清晰的结构。来自 SQL 结果集的每条记录都表示为一个 元素,而每条记录的单个字段则嵌入到每个相应的 中。被嵌入元素的名称与被查询表中的字段名称对应,而文档元素 — XML 树的根 — 被相应地命名为


当然,从 SQL 查询生成 XML 通常只是完成了一半任务;剩下的一半任务是使用生成的 XML。使用 XML 文档可以做很多工作,但是其中最重要的一项就是使用 XSL Transformation 将其转换为一些其他格式,比如 HTML 或 RSS。明白这一点后,我们来快速制作一个 XSL 样式表,将 清单 2 的 XML 输出转换为一个简单的 HTML 页面。



<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="" version="1.0">
    <xsl:template match="/root">
        <html>
            <head>
                <style type="text/css">
                td { text-align: center; padding: 3px; }
                .head { font-style: italic; }
                </style>
            </head>
            <body>
                <table border="1">
                    <thead>
                        <tr>
                            <xsl:for-each select="row[1]/*">
                                <td class="head">
                                    <xsl:value-of select="local-name(.)"/>
                                </td>
                            </xsl:for-each>
                        </tr>
                    </thead>
                    <tbody>
                        <xsl:apply-templates/>
                    </tbody>
                </table>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="row">
        <tr>
            <xsl:apply-templates/>
        </tr>
    </xsl:template>

    <xsl:template match="row/*">
        <td>
            <xsl:value-of select="."/>
        </td>
    </xsl:template>
</xsl:stylesheet>


在清单 4 中您会看见修改后的 PHP 脚本,它现在使用 PHP 的 XSL 函数转换 XML_Query2XML 生成的输出:


<?php
// include required files

include 'XML/Query2XML.php';
include 'MDB2.php';

try {
    // initalize Query2XML object

    $q2x = XML_Query2XML::factory(MDB2::factory('mysql://root:pass@localhost/world'));
    
    // generate SQL query

    // get results as XML

    $sql = "SELECT * FROM Country";
    $xml = $q2x->getFlatXML($sql);
    
    // read XSL stylesheet data

    $xsl = new DOMDocument;
    $xsl->load('country.xsl');
    
    // initialize XSLT engine

    $xslp = new XSLTProcessor;
    
    // attach XSL stylesheet object

    $xslp->importStyleSheet($xsl);
    
    // perform transformation

    header('Content-Type: text/html');
    echo $xslp->transformToXML($xml);
} catch (Exception $e) {
    echo $e->getMessage();
}
?>


这段脚本的第一部分与 清单 1 类似;它所生成的 XML 文档包含了 SQL 查询的结果并将其作为 DOMDocument 实例存储在 $xml 中。接下来,初始化 XSLTProcessor 类的一个实例,并使用类的 importStyleSheet() 方法导入 XSL 的样式表。transformToXML() 方法接受源 XML 数据作为输入参数,然后使用 XSL 样式表中指定的规则将 XML 文档转换为 HTML 页面。

图 1 给出了输出结果:




前面示例中所展示的 getFlatXML() 方法在只需要快速实现 SQL-to-XML 转换时很有用。但是,如果您需要执行某些更复杂的操作时 — 例如,将某些结果集字段作为属性而不是元素显示,或定义自己的元素名称 — 您应该使用 XML_Query2XML 中的 getXML() 方法。此方法让您能够广泛地定制输出 XML,包括其结构和样式。

清单 5 中给出了一个例子:



<?php
// include required files

include 'XML/Query2XML.php';
include 'MDB2.php';

try {
    // initalize Query2XML object

    $q2x = XML_Query2XML::factory(MDB2::factory('mysql://root:pass@localhost/world'));
    
    // generate SQL query

    // get results as XML

    $sql = "SELECT * FROM Country";
    $xml = $q2x->getXML($sql, array(
            'idColumn' => 'code',
            'rootTag' => 'countries',
            'rowTag' => 'country',
            'attributes' => array('code'),
            'elements' => array('name', 'continent', 'area' => 'surfacearea')
        )
    );
    
    // send output to browser

    header('Content-Type: text/xml');
    $xml->formatOutput = true;
    echo $xml->saveXML();
} catch (Exception $e) {
    echo $e->getMessage();
}
?>


getXML() 方法接受两个参数:待执行的 SQL 查询,和定义 XML 输出格式的一组选项。表 1 说明了前一个清单中每个选项的含义:


 选项  控制内容
 rootTag  文档元素的名称(默认:root)
 rowTag  表示每个结果行的元素的名称(默认:row)
 idColumn  结果集的主键字段
 attributes  一个显示为 XML 属性的字段列表
 elements  一个显示为 XML 元素的字段列表

清单 6 给出了清单 5 的脚本的输出:



<?xml version="1.0" encoding="UTF-8"?>
<countries>
  <country code="AFG">
    <name>Afghanistan</name>
    <continent>Asia</continent>
    <area>652090.00</area>
  </country>
  <country code="NLD">
    <name>Netherlands</name>
    <continent>Europe</continent>
    <area>41526.00</area>
  </country>
  <country code="ANT">
    <name>Netherlands Antilles</name>
    <continent>North America</continent>
    <area>800.00</area>
  </country>
  ...
</countries>


注意,这个 XML 文档没有包含来自 SQL 结果集的所有字段,而是只包含了 elementsattributes 数组中所指定的那些字段,而且 attributes 数组中指定的字段显示为每个 元素的属性,而不是子节点。

您还会记得,输出 XML 中的元素和属性名称默认为相应的字段名称。但是,当使用 getXML() 方法时,您可以通过在 attributeselements 数组中指定替代值作为键-值对,修改这些默认名称。例如:SQL 结果集中的 surfacearea 字段在 XML 输出中简单地显示为 元素。

有关如何定制 getXML() 方法的输出的示例,请查看 XML_Query2XML 手册(参阅 参考资料)。


XML_Query2XML 还提供了一个框架,利用该框架可以使用 XML 将一个结果集的内容嵌入到另一个结果集中。此特性在处理连接或以某种方式链接的查询最为常用;如果您出于性能考虑,需要将一个大查询分解为多个小查询,使用该框架也很方便。

为了更好地理解这一点,我们回到 world 数据库并考虑它的两个表 CountryCity,此二者通过 code 外键相互链接在一起。

现在,假设您希望生成一个 XML 文档树,它将多个 元素嵌入到外部的 元素中。我们再假设您希望将输出限制为每个国家的人口最多的五个城市,并且您希望字段值表示为属性而不是元素。简言之,假设您需要一个如下所示的 XML 文档:



<?xml version="1.0" encoding="UTF-8"?>
<countries>
  <country code="IND" name="India" region="Southern and Central Asia">
    <cities>
      <city name="Mumbai (Bombay)" district="Maharashtra" population="10500000"/>
      <city .../>
      <city .../>
      <city .../>
      <city .../>
    </cities>
  </country>
  <country ...>
      ...
  </country>
  ...
</countries>

清单 8 中的代码用于生成这样的嵌套 XML 文档:




<?php
// include required files

include 'XML/Query2XML.php';
include 'MDB2.php';

try {
    // initalize Query2XML object

    $q2x = XML_Query2XML::factory(MDB2::factory('mysql://root:pass@localhost/world'));
    
    // generate SQL query

    // get results as XML

    $sql_1 = "SELECT * FROM Country";
    $sql_2 = "SELECT * FROM City WHERE CountryCode = ? ORDER BY Population DESC LIMIT 5";
    $xml = $q2x->getXML($sql_1, array(
            'idColumn' => 'code',
            'rootTag' => 'countries',
            'rowTag' => 'country',
            'attributes' => array('code', 'name', 'continent'),
            'elements' => array('cities' => array(
                'sql' => array('data' => array('code'), 'query' => $sql_2),
                'idColumn' => 'id',
                'rootTag' => 'cities',
                'rowTag' => 'city',
                'attributes' => array('name','district','population'))
            )
        )
    );
    
    // send output to browser

    header('Content-Type: text/xml');
    $xml->formatOutput = true;
    echo $xml->saveXML();
} catch (Exception $e) {
    echo $e->getMessage();
}
?>


此处要注意的关键是 elements 数组。清单 5 中该数组只包含一个显示为元素的结果集字段列表,而在这里,该数组执行了一个复杂得多的函数。它首先定义了一个新元素 ,然后将它链接到一个包含名称-值对的选项数组。此选项数组中的惟一新键为 sql 键,它定义了运行内部 SQL 查询以填充 元素。

有必要花点时间了解一下这个 sql 键。此键被链接到一个关联数组,数组本身包含了两个键:

  • data,指定从外部 SQL 查询导入的字段
  • query,指定填充 元素时要运行的内部 SQL 查询

注意,第二个 SQL 查询包含了一个问号(?)占位符 — 运行时,此占位符被 data 数组中指定的字段当前值替代。或者,使用一个实际的例子,如果外部查询返回的记录包含 code 字段的 'IND' 值,则这个 'IND' 值随后将被内插到内部查询中,替代 ? 占位符。

XML_Query2XML 的神奇之处现在应该清楚了。您可以使用单独的 SQL 查询填充每个 elements 数组,从而允许 SQL 结果集被嵌入到无限制的深度。此外,因为每个 elements 数组可以引用父查询的字段,所以可以创建一系列链式查询(与 SQL 连接相似),通过特定的命名字段相互链接。

输出结果如下:



<?xml version="1.0" encoding="UTF-8"?>
<countries>
  <country code="AFG" name="Afghanistan" continent="Asia">
    <cities>
      <city name="Kabul" district="Kabol" population="1780000"/>
      <city name="Qandahar" district="Qandahar" population="237500"/>
      <city name="Herat" district="Herat" population="186800"/>
      <city name="Mazar-e-Sharif" district="Balkh" population="127800"/>
    </cities>
  </country>
  <country code="NLD" name="Netherlands" continent="Europe">
    <cities>
      <city name="Amsterdam" district="Noord-Holland" population="731200"/>
      <city name="Rotterdam" district="Zuid-Holland" population="593321"/>
      <city name="Haag" district="Zuid-Holland" population="440900"/>
      <city name="Utrecht" district="Utrecht" population="234323"/>
      <city name="Eindhoven" district="Noord-Brabant" population="201843"/>
    </cities>
  </country>
  ...
</countries>


走到这一步,生成新的 XSL 样式表说明新 XML 结构就很容易了:



<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="" version="1.0">
    <xsl:template match="/countries">
        <html>
            <head>
                <style type="text/css">
                td { text-align: center; padding: 3px; }
                .head { font-style: italic; }
                </style>
            </head>
            <body>
                <xsl:for-each select="country">
                    <h2><xsl:value-of select="@name"/> - <xsl:value-of
                    select="@continent"/></h2>
                    <table border="1">
                        <thead>
                            <tr>
                                <xsl:for-each select="cities/city[1]/@*">
                                <td class="head">
                                    <xsl:value-of select="name(.)"/>
                                </td>
                                </xsl:for-each>
                            </tr>
                        </thead>
                        <tbody>
                            <xsl:apply-templates/>
                        </tbody>
                    </table>
                </xsl:for-each>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="cities/city">
      <tr>
        <xsl:for-each select="@*">
        <td>
            <xsl:value-of select="."/>
        </td>
        </xsl:for-each>
    </tr>
    </xsl:template>
</xsl:stylesheet>


当然,您接下来也可以修改原始的 PHP 脚本以使用此样式表转换生成的 XML。但是,改动很小:



<?php
// include required files

include 'XML/Query2XML.php';
include 'MDB2.php';

try {
    // initalize Query2XML object

    $q2x = XML_Query2XML::factory(MDB2::factory('mysql://root:pass@localhost/world'));
    
    // generate SQL query

    // get results as XML

    $sql_1 = "SELECT * FROM Country";
    $sql_2 = "SELECT * FROM City WHERE CountryCode = ? ORDER BY Population DESC LIMIT 5";
    $xml = $q2x->getXML($sql_1, array(
            'idColumn' => 'code',
            'rootTag' => 'countries',
            'rowTag' => 'country',
            'attributes' => array('code', 'name', 'continent'),
            'elements' => array('cities' => array(
                'sql' => array('data' => array('code'), 'query' => $sql_2),
                'idColumn' => 'id',
                'rootTag' => 'cities',
                'rowTag' => 'city',
                'attributes' => array('name','district','population'))
            )
        )
    );
    
    // read XSL stylesheet data

    $xsl = new DOMDocument;
    $xsl->load('countries.xsl');
    
    // initialize XSLT engine

    $xslp = new XSLTProcessor;
    
    // attach XSL stylesheet object

    $xslp->importStyleSheet($xsl);
    
    // perform transformation

    header('Content-Type: text/html');
    echo $xslp->transformToXML($xml);
} catch (Exception $e) {
    echo $e->getMessage();
}
?>


图 2 显示了转换后的 XML 的外观:


 

您可以以多种方式使用这种嵌套功能,并且 XML_Query2XML 也提供了各种选项以进一步调整 XML 输出。您可以在 XML_Query2XML 手册中找到详细示例(参阅 参考资料)。


 

阅读(1462) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~