面向 PHP 开发人员的 XML,第 2 部分: 高级 XML 解析技术

解析大型或复杂 XML 文档的 PHP5 技术

本系列文章分为三部分,这是第二部分,讨论针对大型或复杂 XML 文档的 PHP5 XML 解析技术。本文还介绍了解析扩展的一些背景知识,并特别说明了何种解析方法最适合于何种类型的 XML 文档及其原因。

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

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



2007 年 4 月 09 日

简介

PHP5 提供了更多的 XML 解析技术。James Clark 的 Expat SAX 解析器(现在以 libxml2 为基础)不再是惟一功能完备的解析器。经常需要使用完全符合 W3C 标准的 DOM 解析器进行解析。无论第 1 部分(请参阅 参考资料)介绍的 SimpleXML 还是比 SAX 更简单更快捷的 XMLReader 都提供了另外的解析方法。所有这些 XML 扩展现在都以 GNOME 项目的 libxml2 库为基础。这个统一的库考虑了不同扩展之间的互操作性。本文将介绍 PHP5 XML 解析技术,特别是大型、复杂 XML 文档的解析。还介绍了关于解析技术的一些背景知识,何种方法最适合于何种类型的 XML 文档,如果要作出选择,则应依据何种标准。


SimpleXML

第 1 部分介绍了 XML 的基本信息,主要介绍简单的、入门级的应用程序编程接口(Application Programming Interfaces,API)。通过例子说明对于处理简单、可预测并且不大的 XML 文档,SimpleXML(必要的时候与文档对象模型(DOM)结合使用)是一种理想的选择。

XML 和 PHP5

可扩展标记语言(Extensible Markup Language,XML)不仅被看作是一种标记语言,而且是一种基于文本的数据存储格式,它提供了基于文本的方法来应用和描述信息的树状结构。

PHP5 提供了一些全新的和重新编写的 XML 解析扩展。其中包括将整个 XML 文档加载到内存中的 SimpleXML、DOM 和 XSLT 处理程序。也有每次把 XML 文档的一部分加载到内存中的 Simple API for XML (SAX) 和 XMLReader。SAX 的功能和在 PHP4 中没有变化,但不再以 expat 库为基础而改用了 libxml2 库。如果通过其他语言熟悉了 DOM,则与以前的版本相比,在 PHP5 中使用 DOM 编程将简单得多。


XML 解析基础

解析 XML 有两种基本的方式:树和流。树解析方式需要将整个 XML 文档加载到内存中。树文件结构允许随机访问文档元素和编辑 XML。树型解析的例子包括 DOM 和 SimpleXML。这些解析器都在内存中以不同但可互操作的格式共享树状结构。和树解析方式不同,流解析不需要将整个文档加载到内存中。这里的流和流音频中的流意思很相近。其用途和目的都一样,就是每次提交少量数据以节约带宽和内存。在流解析中,只能访问当前解析的节点,并且不能将 XML 作为一个文档来编辑。流解析器的例子包括 XMLReader 和 SAX。


基于树的解析器

之所以称为基于树的解析器,是因为它们将整个 XML 文档加载到内存中,并把文档的根作为主干,把所有的儿子、孙子和它们的后代以及属性作为分支。最熟悉的基于树的解析器是 DOM。编码最简单的基于树的解析器是 SimpleXML。后面对两者都将作出介绍。

使用 DOM 解析

根据 W3C 的定义,DOM 标准是 “……一种平台和语言中立的接口,能够让程序和脚本动态地访问和更新文档的内容、结构和样式。” GNOME 项目的 libxml2 库用 C 实现了 DOM 及其全部方法。因为所有的 PHP5 XML 扩展都基于 libxml2,所以彼此之间具有完全的互操作性。这种互操作性大大增强了它们的功能。比方说,可以使用流解析器 XMLReader 获取一个元素,将其导入 DOM,然后用 XPath 提取数据。这就大大增加了灵活性。清单 5 给出了一个例子。

DOM 是基于树的解析器。DOM 很容易理解和使用,因为其内存结构与原始 XML 文档相似。DOM 通过创建对象树来向应用程序传递信息,它完全复制了 XML 文件的元素树,每个 XML 元素都是树上的一个节点。DOM 是一种 W3C 标准,由于和其他编程语言的一致性,对于开发人员来说,为 DOM 增加了不少权威性。因为 DOM 要创建整个文档的树,要占用大量内存和处理器时间。

使用 DOM

如果由于受设计或者其他因素的限制必须在解析器领域内耍点小聪明的话,则仅仅从灵活的角度来看应该选择 DOM。使用 DOM 可以构建、修改、查询、验证和转换 XML 文档。可以利用所有的 DOM 方法和属性。多数 DOM level 2 方法的实现都有适当的属性支持。由于非凡的灵活性,使用 DOM 可以解析任意复杂的文档。但是要记住,如果要把很大的 XML 文档一次加载到内存中,则取得灵活性的代价相当高昂。

清单 1 中的例子用 DOM 解析文档,通过 getElementById 检索一个元素。引用 ID 之前需要设置 validateOnParse=true 来验证文档。根据 DOM 标准,它要求 DTD 定义一个 ID 类型的属性 ID。

清单 1. 使用 DOM 处理基本文档
<?php

$doc = new DomDocument;

// We must validate the document before referring to the id
$doc->validateOnParse = true;
$doc->Load('basic.xml');

echo "The element whose id is myelement is: " . 
$doc->getElementById('myelement')->tagName . "\n";

?>

getElementsByTagName() 函数返回类 DOMNodeList 的一个新实例,包含使用给定标记名的元素。当然这个列表需要遍历。迭代 getElementsByTagName() 返回的 NodeList 的同时修改文档结构,会影响迭代的 NodeList(参见清单 2)。不需要验证。

清单 2. DOM getElementsByTagName 方法
DOMDocument {
 DOMNodeList getElementsByTagName(string name);
}

清单 3 中的例子结合使用了 DOM 和 XPath。

清单 3. 使用 DOM 和 XPath 进行解析
<?php

$doc = new DOMDocument;

// We don't want to bother with white spaces
$doc->preserveWhiteSpace = false;

$doc->Load('book.xml');

$xpath = new DOMXPath($doc);

// We start from the root element
$query = '//book/chapter/para/informaltable/tgroup/tbody/row/entry[. = "en"]';

$entries = $xpath->query($query);

foreach ($entries as $entry) {
 echo "Found {$entry->previousSibling->previousSibling->nodeValue}," .
 " by {$entry->previousSibling->nodeValue}\n";
}
?>

说了 DOM 的这么多优点之后,为了强调我的观点,最后再给出一个错误使用 DOM 的例子,然后在后面的例子中解决这个问题。清单 4 中的例子将一个很大的文件加载到 DOM 中,只是为了用 DomXpath 从一个属性中提取数据。

清单 4. 错误使用 DOM 和 XPath,用于大型 XML 文档
<?php

// Parsing a Large Document with DOM and DomXpath
// First create a new DOM document to parse
$dom = new DomDocument();

// This document is huge and we don't really need anything from the tree
// This huge document uses a huge amount of memory 
$dom->load("tooBig.xml");
$xp = new DomXPath($dom);
$result = $xp->query("/blog/entries/entry[@ID = 5225]/title") ;
print $result->item(0)->nodeValue ."\n";

?>

后面清单 5 中的例子仍然使用 DOM 和 XPath,但每次让 XMLReader 用 expand() 传递一个元素的数据。通过这种方法就能将 XMLReader 传递的节点转换成 DOMElement

清单 5. 正确使用 DOM 和 XPath,用于大型 XML 文档
<?php

// Parsing a large document with XMLReader with Expand - DOM/DOMXpath 
$reader = new XMLReader();

$reader->open("tooBig.xml");

while ($reader->read()) {
 switch ($reader->nodeType) {
 case (XMLREADER::ELEMENT):
 if ($reader->localName == "entry") {
 if ($reader->getAttribute("ID") == 5225) {
 $node = $reader->expand();
 $dom = new DomDocument();
 $n = $dom->importNode($node,true);
 $dom->appendChild($n);
 $xp = new DomXpath($dom);
 $res = $xp->query("/entry/title");
 echo $res->item(0)->nodeValue;
 }
 }
 }
}
 
?>

使用 SimpleXML 解析

SimpleXML 扩展是另外一种 XML 文档解析方法。SimpleXML 扩展需要用到 PHP5 并包括内置的 XPath 支持。SimpleXML 最适合处理不复杂的、基本的 XML 数据。如果 XML 文档不是很复杂、层次不深、没有混合内容,则与 DOM 相比 SimpleXML 更简单,正如其名称所暗示的那样。如果处理的文档结构是已知的,就会更加直观。

使用 SimpleXML

SimpleXML 具有 DOM 的很多优点,但是编码更加简单。它允许轻松地访问 XML 树,具有内置的验证机制和 XPath 支持,能够与 DOM 互操作,为其提供读写 XML 文档的支持。可以简单迅速地处理使用 SimpleXML 解析的文档。但是要记住,和 DOM 一样,SimpleXML 的易用性和灵活性的代价也是无法向内存中加载大型 XML 文档。

清单 6 中的代码从示例 XML 中提取 <plot>。

清单 6. 提取 plot 文本
<?php
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<books>
 <book>
 <title>Great American Novel<title>
 <plot>
 Cliff meets Lovely Woman. Loyal Dog sleeps, but
 wakes up to bark at mailman.
 </plot>
 <success type="bestseller">4<success>
 <success type="bookclubs">9<success>
 </book>
<books>
XML;
?>
<?php

$xml = new SimpleXMLElement($xmlstr);
echo $xml->book[0]->plot; // "Cliff meets Lovely Woman. ..."
?>

另一方面,也许还要提取分为多行的地址。如果同一个父元素中存在同一元素的多个实例,通常需要使用迭代技术。清单 7 中的代码演示了此项功能。

清单 7. 提取元素的多个实例
<?php
$xmlstr = <<<XML
<xml version='1.0' standalone='yes'?>
<books>
 <book>
 <title>Great American Novel<title>
 <plot>
 Cliff meets Lovely Woman.
 <plot>
 <success type="bestseller">4<success>
 <success type="bookclubs">9</success>
 <book>
 <book>
 <title>Man Bites Dog</title>
 <plot>
 Reporter invents a prize-winning story.
 </plot>
 <success type="bestseller">22<success>
 <success type="bookclubs">3<success>
 <book>
</books>
XML;
?>
<php

$xml = new SimpleXMLElement($xmlstr);

foreach ($xml->book as $book) {
 echo $book->plot, '<br />';
}
?

除了读取元素名称及其值以外,SimpleXML 也能访问元素的属性。清单 8 所示的代码中,可以像访问数组元素一样访问元素的属性。

清单 8. SimpleXML 访问元素的属性的演示
<?php
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<books>
 <book>
 <title>Great American Novel</title>
 <plot>
 Cliff meets Lovely Woman.
 </plot>
 <success type="bestseller">4</success>
 <success type="bookclubs">9</success>
 </book>
 <book>
 <title>Man Bites Dog</title>
 <plot>
 Reporter invents a prize-winning story.
 <plot>
 <success type="bestseller">22<success>
 <success type="bookclubs">3</success>
 </book>
<books>
XML;
?>
<?php

$xml = new SimpleXMLElement($xmlstr);

foreach ($xml->book[0]->success as $success) {
 switch((string) $success['type']) {
 case 'bestseller':
 echo $success, ' months on bestseller list<br />';
 break;
 case 'bookclubs':
 echo $success, ' bookclub listings<br />';
 break;
 }
}

?>

最后一个例子(参见清单 9)结合使用了 SimpleXML 以及 DOM 和 XMLReader。通过 XMLReader,每次使用 expand() 传递一个元素的数据。这样就能将 XMLReader 传递的节点转换成 DOMElement 再传递给 SimpleXML 了。

清单 9. 结合 SimpleXML 以及 DOM 和 XMLReader 解析大型 XML 文档
<?php

// Parsing a large document with Expand and SimpleXML
$reader = new XMLReader();

$reader->open("tooBig.xml");

while ($reader->read()) {
 switch ($reader->nodeType) {
 case (XMLREADER::ELEMENT):
 if ($reader->localName == "entry") {
 if ($reader->getAttribute("ID") == 5225) {
 $node = $reader->expand();
 $dom = new DomDocument();
 $n = $dom->importNode($node,true);
 $dom->appendChild($n);
 $sxe = simplexml_import_dom($n);
 echo $sxe->title; 
 }
 }
 }
}
 
?>

基于流的解析器

之所以称为基于流的解析器,是因为它们采用和流式音频同样的原理来解析 XML 流,处理一个特殊节点,完成之后则将其完全忘掉。XMLReader 是一种 pull 解析器,其编码方法和数据库查询结果表的游标中非常类似。因此更容易处理不熟悉的或者不可预测的 XML 文件。

使用 XMLReader 解析

XMLReader 扩展是一种基于流的解析器,其类型通常被称为游标类型或者 pull 类型解析器。XMLReader 根据请求从 XML 文档中获取信息。它是基于派生自 C# XmlTextReader 的 API。PHP 5.1 默认包含并启用它,基于 libxml2。在 PHP 5.1 之前,默认不启用 XMLReader 扩展,但是可从 PECL(请参阅 参考资料 中的相关链接)下载。XMLReader 支持名称空间和验证,包括 DTD 和 Relaxed NG。

使用 XMLReader

XMLReader 作为一种流解析器,非常适合解析大型 XML 文档,编码比 SAX 更简单,而且通常速度也快。这是理想的流解析器。

清单 10 中的例子使用 XMLReader 解析大型 XML 文档。

清单 10. XMLReader 解析大型 XML 文件
<?php

$reader = new XMLReader();
$reader->open("tooBig.xml");
while ($reader->read()) {
 switch ($reader->nodeType) {
 case (XMLREADER::ELEMENT):
 if ($reader->localName == "entry") {
 if ($reader->getAttribute("ID") == 5225) {
 while ($reader->read()) {
 if ($reader->nodeType == XMLREADER::ELEMENT) {
 if ($reader->localName == "title") {
 $reader->read();
 echo $reader->value;
 break;
 }
 if ($reader->localName == "entry") {
 break;
 }
 }
 }
 }
 }
 }
}
?>

使用 SAX 解析

Simple API for XML (SAX) 是一种流解析器。事件与读入的 XML 文档相关联,因此 SAX 以回调的方式编码。元素打开关闭标记、元素内容、实体和解析错误都有对应的事件。使用 SAX 解析器而不是 XMLReader 的主要原因在于 SAX 解析器有时候效率更高一些,而且通常更被人们熟悉。SAX 解析器的主要缺点是代码很复杂,比 XMLReader 代码编写起来更难。

使用 SAX

SAX 对于那些曾经在 PHP4 中处理过 XML 的人来说可能比较熟悉,PHP5 中的 SAX 扩展与过去的版本兼容。由于也是流解析器,因此非常适合处理大型文件,但是比不上 XMLReader。

清单 11 中的例子使用 SAX 解析大型 XML 文档。

清单 11. 使用 SAX 解析大型 XML 文件
<?php

//This class contains all the callback methods that will actually
//handle the XML data.
class SaxClass {
 private $hit = false;
 private $titleHit = false;

 //callback for the start of each element
 function startElement($parser_object, $elementname, $attribute) {
 if ($elementname == "entry") {
 if ( $attribute['ID'] == 5225) {
 $this->hit = true;
 } else {
 $this->hit = false;
 }
 }
 if ($this->hit && $elementname == "title") {
 $this->titleHit = true;
 } else {
 $this->titleHit =false;
 }
 }

 //callback for the end of each element
 function endElement($parser_object, $elementname) {
 }

 //callback for the content within an element
 function contentHandler($parser_object,$data)
 {
 if ($this->titleHit) {
 echo trim($data)."<br />";
 }
 }
}

//Function to start the parsing once all values are set and
//the file has been opened
function doParse($parser_object) {
 if (!($fp = fopen("tooBig.xml", "r")));

 //loop through data
 while ($data = fread($fp, 4096)) {
 //parse the fragment
 xml_parse($parser_object, $data, feof($fp));
 }
}

$SaxObject = new SaxClass();
$parser_object = xml_parser_create();
xml_set_object ($parser_object, $SaxObject);

//Don't alter the case of the data
xml_parser_set_option($parser_object, XML_OPTION_CASE_FOLDING, false);

xml_set_element_handler($parser_object,"startElement","endElement");
xml_set_character_data_handler($parser_object, "contentHandler");

doParse($parser_object);

?>

结束语

PHP5 提供了多种改进的解析技术。最常见的有 DOM,它现在完全与 W3C 标准兼容,适合复杂但是相对较小的文档。SimpleXML 适合简单而且不太大的 XML 文档,XMLReader 比 SAX 更简单、更快,是适合大型文档的流解析器。

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • XML for PHP developers, Part 1: The 15-minute PHP-with-XML starter(Cliff Morgan,developerWorks,2007 年 2 月):本系列的第一篇文章介绍了 PHP5 的 XML 实现,说明了在 PHP 环境中处理 XML 是多么简单。
  • 面向 Perl 和 PHP 开发人员的 XML:您可以通过该专题来了解更多与 Perl 和 PHP 相关的 XML 技术。
  • SAX,功能强大的 API(Benoît Marchal,developerWorks,2001 年 8 月):这篇文章介绍了 SAX,将其与 DOM 进行了比较,最后讨论了如何使用 SAX。
  • 用 PHP 读取和编写 XML DOM(Jack Herrington,developerWorks,2006 年 2 月):探讨了读取 XML 的三种方法:DOM 库、SAX 解析器和正则表达式。此外还可以了解如何使用 DOM 和 PHP 文本模板编写 XML。
  • XSLT 是什么类型的语言?(Michael Kay,developerWorks,2005 年 4 月):了解 XSLT 的来历、擅长方面以及为何要使用它。
  • 技巧: 实现 XMLReader(Benoît Marchal,developerWorks,2003 年 3 月):这篇技巧介绍了 XML 管道 API,说明为何熟悉的 XMLReader 接口适合于很多 XML 组件。
  • PHP 中的 SimpleXML 处理(Elliotte Rusty Harold,developerWorks,2006 年 10 月):尝试使用 SimpleXML 扩展,能够帮助 PHP 页面实现查询、搜索、修改和重新发布 XML。
  • PHP V5 迁移指南(Jack Herrington,developerWorks,2006 年 12 月):将 PHP V4 开发的代码迁移到 V5,大大改善代码的可维护性和稳定性。
  • Introducing Simple XML in PHP5(Alejandro Gervasio,Dev Shed,2006 年 6 月):关于 SimpleXML 的系列文章,第一部分介绍了如何利用 PHP 5 simplexml 扩展来简化任务,这个库主要用于解析简单的 XML 文件。
  • PHP Cookbook, Second Edition(Adam Trachtenberg 和 David Sklar,O'Reilly Media,2006 年 8 月):学习构建可用于任何 Web 浏览器的动态 Web 应用程序。
  • XML.com:关于 XML 世界的全面介绍请访问 O'Reilly 的 XML 站点。
  • W3C XML Information:阅读 XML 规范。
  • PHP 开发官方站点:了解关于这种被广泛采用的、特别适合 Web 开发的通用脚本语言的更多信息。
  • Planet PHP:请访问这个 PHP 开发人员社区新闻资源站点。
  • IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 及相关技术的开发人员。
  • XML 技术文档库:developerWorks XML 专区提供了大量技术文章和技巧、教程、标准以及 IBM 红皮书。
  • developerWorks 技术活动网络广播:随时关注技术的最新进展。

获得产品和技术

讨论

条评论

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=208297
ArticleTitle=面向 PHP 开发人员的 XML,第 2 部分: 高级 XML 解析技术
publish-date=04092007