面向 PHP 开发人员的 XML,第 3 部分: 读、操纵和写 XML 的高级技术

向 DOM 和 SimpleXML API 添加 XSLT

本系列包括三部分,这是最后一部分,进一步讨论在 PHP5 中读、操纵和写 XML 的技术。本文将重点讨论在更复杂的环境中使用我们已经熟悉的 API 如 DOM 和 SimpleXML,并将第一次接触 XSL 扩展。

Cliff Morgan, 自由撰稿人, 自由职业者

Cliff Morgan 是一位独立的顾问,设计和实现 Web 应用程序与网站。



2007 年 4 月 29 日

简介

PHP5 为开发人员提供了更多处理 XML 的工具。新增的和修改的扩展如 DOM、SimpleXML 和 XSL 大大压缩了处理 XML 需要编写的代码。PHP5 中的 DOM 符合 W3C 标准。最重要的是,这些扩展之间具有很强的互操作性,因而提供了更多的功能,比如通过交换格式来扩展可用性、W3C XPath、以及更多全面的功能。这里您将看到输入和输出选项,依赖 Yahoo Web Service REST 协议接口为现已熟知的 DOM 和 SimpleXML 扩展提供更复杂的展示,最后还将讨论 XSL 扩展。


内容回顾

本系列的第一篇文章介绍了 XML 的基本知识。主要介绍简单的入门级应用程序编程接口(API),举例说明了对于简单、可预测和较小的 XML 文档,SimpleXML(有时需要与文档对象模型(DOM)结合使用)是一种理想的技术。第 2 部分横向介绍了 PHP5 中所提供的解析 XML 的 API,包括 SimpleXML、DOM、Simple API for XML (SAX) 和 XMLReader,讨论了针对不同大小和复杂度的 XML 文档使用何种解析技术最为适当。

PHP5 中的 XML

可扩展标记语言(XML)不仅仅是一种标记语言,还是一种基于文本的数据存储格式,提供了应用和描述信息的树状结构的一种基于文本的方法。下面我们将看到 Web 服务上下文中的 XML,也许这是在企业之外推动 XML 迅速壮大的最重要的因素之一。

PHP5 提供了全新的和全部改写的 XML 操作扩展,都以相同的 libxml2 代码为基础。这一公共基础为这些扩展提供了互操作性,扩展了各自的功能。基于树的解析器包括 SimpleXML、DOM 和 XSLT 处理程序。如果熟悉其他语言中的 DOM,则使用 PHP 实现类似的功能会更简单。基于流的解析器包括 Simple API for XML (SAX) 和 XMLReader。SAX 的功能与 PHP4 中一样。


使用 DOM 操纵 XML

DOM 可用于操纵 XML 文件。只有在 XML 文件比较小的情况下使用 DOM 才有效。使用这种方法的优点在于众所周知的 W3C DOM 是一种坚实的标准,它具有丰富的方法和编程的灵活性。DOM 的缺点在于编码比较困难和处理大型文档时存在性能问题。


使用 DOM

使用 DOM 可以构建、修改、查询、验证和转换 XML 文档。所有 DOM 方法和属性都能使用,大部分 DOM level 2 方法都有适当的属性支持。由于非凡的灵活性,可以使用 DOM 解析任意复杂的文档。但是要记住,如果一次将整个大型 XML 文档载入内存,则取得灵活性的代价相当高昂。

本文中的例子使用 Yahoo 搜索 API、PHP5 和 REpresentational State Transfer (REST) 来说明 DOM 在一种有趣的应用程序环境中的使用。Yahoo 选择 REST 是因为多数开发人员认为 REST 以使用 SOAP 的 20% 的代价提供了其 80% 的价值。选择这个应用程序来说明 PHP/XML 是因为 Web 服务的盛行可能是推动 XML 在企业之外迅速增长的最重要因素之一。

REST 通常从服务入口 URL 开始形成一次请求,然后以查询字符串的形式追加搜索参数。清单 1 使用 DOM 扩展解析查询的结果。

清单 1. 使用 DOM 的 Yahoo 示例代码
<?php

//This query does a search for any Web pages relevant to "XML Query"
$query = "http://api.search.yahoo.com/WebSearchService/V1/webSearch?".
 "query=%5C%22XML%20Query%5C%22&appid=YahooDemo";

//Create the DOM Document object from the XML returned by the query
$xml = file_get_contents($query);
$dom = new DOMDocument;
$dom = DOMDocument::loadXML($xml);

function xml_to_result($dom) {

 //This function takes the XML document and maps it to a
 //PHP object so that you can manipulate it later.

 //First, retrieve the root element for the document
 $root = $dom->firstChild;

 //Next, loop through each of its attributes
 foreach($root->attributes as $attr) {
 $res[$attr->name] = $attr->value;
 }

 //Now, loop through each of the children of the root element
 //and treat each appropriately.

 //Start with the first child node. (The counter, i, is for
 //tracking results.
 $node = $root->firstChild;
 $i = 0;

 //Now keep looping through as long as there is a node to work
 //with. (At the bottom of the loop, the code moves to the next
 //sibling, so when it runs out of siblings, the routine stops.
 while($node) {

 //For each node, check to see whether it's a Result element or
 //one of the informational elements at the start of the document.
 switch($node->nodeName) {

 //Result elements need more analysis.
 case 'Result':
 //Add each child node of the Result to the result object,
 //again starting with the first child.
 $subnode = $node->firstChild;
 while($subnode) {

 //Some of these nodes just are just whitespace, which does
 //not have children.
 if ($subnode->hasChildNodes()){

 //If it does have children, get a NodeList of them, and
 //loop through it.
 $subnodes = $subnode->childNodes;
 foreach($subnodes as $n) {

 //Again check for children, adding them directly or
 //indirectly as appropriate.
 if($n->hasChildNodes()) {
 foreach($n->childNodes as $cn){
 $res[$i][$subnode->nodeName][$n->nodeName]=
 trim($cn->nodeValue);
 }
 } else {
 $res[$i][$subnode->nodeName]=trim($n->nodeValue);
 }
 }
 }
 //Move on to the next subnode.
 $subnode = $subnode->nextSibling;
 }
 $i++;
 break;
 //Other elements are just added to the result object.
 default:
 $res[$node->nodeName] = trim($node->nodeValue);
 break;
 }

 //Move on to the next Result of informational element
 $node = $node->nextSibling;
 }
 return $res;
}

//First, convert the XML to a DOM object you can manipulate.
$res = xml_to_result($dom);

//Use one of those "informational" elements to display the total
//number of results for the query.
echo "<p>The query returns ".$res["totalResultsAvailable"].
 " total results The first 10 are as follows:</p>";

//Now loop through each of the actual results.
for($i=0; $i<$res['totalResultsReturned']; $i++) {

 echo "<a href='".$res[$i]['ClickUrl']."'><b>".
 $res[$i]['Title']."</b></a>: ";
 echo $res[$i]['Summary'];

 echo "<br /><br />";
}

?>

使用 SimpleXML 操纵 XML

SimpleXML 扩展适合于操纵不很复杂或者嵌套不太深并且没有混合内容的 XML 文档。SimpleXML 比 DOM 更容易编码,就像名称所暗示的那样。如果处理的文档结构已知,就更加直观。libXML2 架构的互操作性大大增强了 DOM 和 SimpleXML 的灵活性,能够随意在 DOM 和 SimpleXML 之间交换导入格式。

使用 SimpleXML

使用 SimpleXML 操纵文档非常简单,编码也很快捷。下面的代码使用 SimpleXML 扩展解析查询结果。如您所料,下面的 SimpleXML 代码(参见 清单 2)比上面 清单 1 所示的 DOM 代码示例更紧凑。

清单 2. Yahoo SimpleXML 例子
<?php

//This query does a search for any Web pages relevant to "XML Query"
$query = "http://api.search.yahoo.com/WebSearchService/V1/webSearch?".
 "query=%5C%22XML%20Query%5C%22&appid=YahooDemo";

$xml = simplexml_load_file($query);

// Load up the root element attributes
foreach($xml->attributes() as $name=>$attr) {
 $res[$name]=$attr;
}

//Use one of those "informational" elements to display the total
//number of results for the query.
echo "<p>The query returns ".$res["totalResultsAvailable"].
 " total results The first 10 are as follows:</p>";

//Unlike with DOM, where we loaded the entire document into the
//result object, with SimpleXML, we get back an object in the
//first place, so we can just use the number of results returned
//to loop through the Result members.

for($i=0; $i<$res['totalResultsReturned']; $i++) {

 //The object represents each piece of data as a member variable
 //rather than an array element, so the syntax is a little bit
 //different from the DOM version.

 $thisResult = $xml->Result[$i];

 echo "<a href='".$thisResult->ClickUrl."'><b>".
 $thisResult->Title."</b></a>: ";
 echo $thisResult->Summary;

 echo "<br /><br />";
}

?>

清单 3清单 2 所示 SimpleXML 例子的基础上增加了缓存层。任何查询结果都能缓存两个小时。

清单 3. 带有缓存层的 Yahoo SimpleXML 例子
<?php

//This query does a search for any Web pages relevant to "XML Query"
$query = "http://api.search.yahoo.com/WebSearchService/V1/webSearch?".
 "query=%5C%22XML%20Query%5C%22&appid=YahooDemo";

//The cached material should only last for 2 hours, so you need the
//current time.
$currentTime = microtime(true);

//This is where I put my tempfile; you can store yours in a more
//convenient location.
$cache = 'c:\temp\yws_'.md5($query);

//First check for an existing version of the time, and then check
//to see whether or not it's expired.
if(file_exists($cache) &&
 filemtime($cache) > (time()-7200)) {

 //If there's a valid cache file, load its data.
 $data = file_get_contents($cache);
} else {

 //If there's no valid cache file, grab a live version of the
 //data and save it to a temporary file. Once the file is complete,
 //copy it to a permanent file. (This prevents concurrency issues.)
 $data = file_get_contents($query);
 $tempName = tempnam('c:\temp','YWS');
 file_put_contents($tempName, $data);
 rename($tempName, $cache);
}

//Wherever the data came from, load it into a SimpleXML object.
$xml = simplexml_load_string($data);

//From here, the rest of the file is the same.

// Load up the root element attributes
foreach($xml->attributes() as $name=>$attr) {
 $res[$name]=$attr;
}

...

使用 XSL 操纵 XML

可扩展样式表语言(XSL)是一种用于操纵 XML 文档的功能性 XML 语言。根据样式表定义(类似于 CSS 实现规则的方式),使用 XSL 可以将 XML 文档转换成重新定义的 XML 文档、XHTML 文档、HTML 文档或者文本文档。PHP5 的 W3C 标准实现支持与 DOM 和 XPath 之间的互操作。可扩展样式表语言转换(XSLT)是一种基于 libxml2 的 XML 扩展,它的样式表也是 XML 文档。XSLT 将 XML 源树转换成 XML 或者 XML 类型的结果树。这些转换将样式表中指定的一系列规则应用于 XML 数据。XSLT 可以向输出文件中添加或者从其中删除元素或属性。允许开发人员排序或者重新排列元素,决定隐藏或者显示某些元素。不同的样式表可让 XML 提供不同媒介的适当显示,比如屏幕显示和打印显示。XSLT 使用 XPath 在原始 XML 文档中导航。XSLT 转换模型通常涉及到一个 XML 源文件、包含一个或多个处理模板的 XSLT 文件以及一个 XSLT 处理程序。XSLT 文档必须使用 DOM 加载。PHP5 仅支持 libxslt 处理程序。

使用 XSL

XSL 一个有趣的应用是动态地创建 XML 文件以便包含刚从数据库中选择的数据。利用这种技术可以创建完整的 Web 应用程序,在其中用数据库查询得到的 XML 文件编写 PHP 脚本,然后使用 XSL 转换生成真正的 HTML 文档。

这种方法将表示层和业务层彻底分开,从而能够独立地维护这些层。

清单 4 演示了 XML 输入文件、XSL 样式表、XSLT 处理程序和多种可能的输出之间的关系。

清单 4. XML 转换
<?php

// Create new XSLTProcessor
$xslt = new XSLTProcessor();

//Both the source document and the stylesheet must be
//DOMDocuments, but the result can be a DOMDocument,
//a file, or even a String.

// Load the XSLT stylesheet
$xsl = new DOMDocument();
$xsl->load('recipe.xsl');

// Load the stylesheet into the processor
$xslt->importStylesheet($xsl);

// Load XML input file
$xml = new DOMDocument();
$xml->load('recipe.xml');

//Now choose an output method and transform to it:

// Transform to a string
$results = $xslt->transformToXML($xml);
echo "String version:";
echo htmlentities($results);

// Transform to DOM object
$results = $xslt->transformToDoc($xml);
echo "The root of the DOM Document is ";
echo $results->documentElement->nodeName;

// Transform to a file
$results = $xslt->transformToURI($xml, 'results.txt');

?>

结束语

本系列的第一部分讨论了使用文档对象模型和 SimpleXML 执行简单和复杂的解析任务。第 2 部分考察了 XMLReader 的使用,它提供了一种比 SAX 更快更简单的办法。

这篇文章又介绍了如何访问基于 REST 的 Web 服务这类远程文件,如何使用 XSLT 轻松地将 XML 数据输出到字符串、DOM 文档对象或者文件。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source
ArticleID=216270
ArticleTitle=面向 PHP 开发人员的 XML,第 3 部分: 读、操纵和写 XML 的高级技术
publish-date=04292007