面向 PHP 开发人员的 XML

本系列文章分为三部分,这是第一部分;介绍了 PHP5 XML 实现,帮助那些不熟悉使用 PHP 处理 XML 的新手使用 PHP 环境中的 DOM SimpleXML 读取、解析、操作并编写简单短小的 XML 文件。

1 部分: PHP XML 开发 15 分钟快速入门

简介

如今的应用程序开发环境很难忽视 XML 的重要性。如果原来从未在 PHP 中处理过 XML,或者没有接触 PHP5,这份关于 PHP5 新增 XML 功能的入门指南可以告诉您处理 XML 是多么简单。本系列文章分为三部分,这是第一部分,主要介绍了能够快速入门的 API,通过例子说明对于简单的、可预测的和相对较小的 XML 文档,使用 SimpleXML,在必要的时候结合 DOM,是一种理想的办法。这些恰恰是 Ajax 应用程序中所传递的那些文档,比如提交表单的内容或者 Web 服务应用程序编程接口(API)如 weather.com 的响应。

 XML 基础

XML 有初步的了解可以帮助您理解 XML 对于 PHP 开发人员的重要性,理解和创建简单的 XML 文档。

关于 XML

可扩展标记语言(XML)被称为标记语言和基于文本的数据存储格式,这要看对谁来说。它是标准通用标记语言(SGML)的一个子集,采用文本方式应用和描述信息的树状结构。XML 是很多语言/格式的基础,如 Really Simple Syndication (RSS)Mozilla XML User Interface Language (XUL)Macromedia Maximum eXperience Markup Language (MXML)Microsoft eXtensible Application Markup Language (XAML) 以及开放源代码的 Java XML UI Markup Language (XAMJ)。既然 XML 到处都存在,说明它确实很重要。每个人都想跟上 XML 的潮流。

编写 XML

XML 的基本数据单位是元素。元素使用起始标记,如 <book> 和结束标记,如 </book> 分隔开。有起始标记必须有结束标记。如果缺少结束标记,XML 文档就不是结构良好的,解析器就不能正确地解析文档。标记的名称通常反映元素所包含内容的类型。可以设想,book 元素应该包含图书的标题,比如 Great American Novel(如清单 1 所示)。标记之间的内容,包括空白,称为字符数据。

清单 1. XML 示例文档

<books>

 <book>

 <title>Great American Novel</title>

 <characters>

 <character>

 <name>Cliff</name>

 <desc>really great guy</desc>

 </character>

 <character>

 <name>Lovely Woman</name>

 <desc>matchless beauty</desc>

 </character>

 <character>

 <name>Loyal Dog</name>

 <desc>sleepy</desc>

 </character>

 </characters>

 <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 元素和属性名可以包含大写字母 A-Z、小写字母 a-z、数字 0-9、一些特殊字符和非英文字符以及三种标点符号:连字符、下划线和句点。名字中不能出现其他标点符号。

XML 是大小写敏感的。这个例子中,<Book> <book> 描述了两种不同的元素。两种形式都允许。使用 <Book> <book> 描述两种不同的元素可能不是一种好办法,因为很容易出现笔误。

每个 XML 文档都有且只有一个根元素。根元素是 XML 文档中惟一没有父元素的元素。上例中的根元素是 <books>。多数 XML 文档包含父元素和孩子元素。<books> 元素只有一个子元素,即 <book><book> 元素有四个子元素,<title><characters><plot> <success><characters> 元素有三个子元素,都是 <character> 元素。每个 <character> 元素都有两个子元素,即 <name> <desc>

除了形成父子关系的元素嵌套之外,XML 元素还可以具有属性。属性是附加到元素起始标记上的名值对。名称和值之间用等号 “=” 分开。值用单引号或双引号括起来。上面的清单 1 中,<success> 元素有两个属性,"bestseller" "bookclubs"。关于属性的用法在 XML 开发者之间有不同的考虑。多数包含在属性的信息也可以放在子元素中。一些开发者坚持认为属性信息应该是元数据,即关于数据的信息,而不是数据本身。数据本身应该包含在元素中。是否使用属性实际上取决于数据的性质以及如何从 XML 中提取数据。

 XML 的力量

XML 的优点之一是比较简单,可以使用简单的文本编辑器或者字处理程序来编写 XML,不需要专门的工具或软件。XML 的基本语法由嵌套元素组成,部分元素具有属性和内容。元素通常包括两个标记,一个起始标记和一个结束标记,分别用 <tag > < /tag > 表示。XML 大小写敏感,而且空白是有意义的。看起来和众所周知的 HTML 很相似,但和 HTML 不同的是 XML 允许用更具描述性的名称命名标记。XML 的其他优点包括自描述、同时供人类和机器读取、支持 Unicode(这使它支持不同语言的国际化)、严格的语法和解析要求。不幸的是,UTF-8 PHP5 中有问题,这一不足也是推动 PHP6 开发的动力之一。

 XML 的弱点

XML 非常罗嗦,带来的后果是存储体积大,消耗带宽多。尽管人们应该能够读取它,但是很难想象一个人会去阅读包含 7 百万个节点的 XML 文件。最基本的解析器功能支持的数据类型不多,因此处理不规则或少见的数据(经常会遇到这种情况)成为最主要的困难。

结构良好的 XML

符合全部 XML 语法规则的 XML 文档是结构良好的。结构不良好的文档从技术上讲就不是 XML<br> 之类的 HTML 标记在 XML 中是不允许的,要想成为结构良好的 XML,必须写成 <br />。解析器不能正确解析结构不良好的 XML。此外,XML 文档有且只能有一个根元素。可以将根元素看成是有无穷层的文件柜。虽然只有一个文件柜,但是在其中放什么和放多少没有什么限制。有数不清的抽屉和夹子可以存放信息.

 PHP 基础

本文的多数读者曾经用过 PHP,但不一定清楚它的历史和发展。

关于 PHP

超文本预处理器(PHP)是一种跨平台的脚本语言,用于编写动态网页和服务器端应用程序软件。最初被称为 Personal Home Page/Form Interpreter (PHP/FI),后来在 Suraski Gutmans 的手中获得了新生,这两个人在 1998 6 月推出了 PHP3。他们的公司 Zend Technologies 仍然控制着 PHP 的发展。

PHP5 2004 7 月发布,以 Zend Engine II 为基础,提供了很多新特性,其中包括:

  • 对面向对象编程的全新支持
  • 更好地支持 MySQL
  • 更好地支持 XML,这正是我们所关心的

 PHP5 XML

虽然 PHP 在以前的版本中就提供了 XML 支持,但是随着 PHP5 的出现这种支持大大强化了。由于 PHP4 XML 的支持比较有限,比如默认情况下只提供基于 SAX 的解析器、PHP4 DOM 没有实现 W3C 标准,对于 PHP5,可以说 PHP XML 开发人员是重新发明了轮子,符合常用的标准。

 PHP5 新增 XML 特性

PHP5 新增 XML 特性

PHP5 包括彻底重新编写的和新增加的扩展,如 SAX 解析器、DOMSimpleXMLXMLReaderXMLWriter XSLT 处理程序。所有这些扩展都以 libxml2 为基础。

除了自 PHP4 改进的 SAX 支持以外,PHP5 还同时支持符合 W3C 标准的 DOM SimpleXML 扩展。默认情况下同时支持 SAXDOM SimpleXML。如果熟悉其他语言中的 DOM,使用 PHP 实现类似的功能会更简单。

 PHP5 中读取、操纵和写入 XML

如果使用 PHP5 读取、操作、编写 XML,而且处理的 XML 文档简单、可预测、比较小,则 SimpleXML,必要的时候再加上 DOM,是最理想的选择。

快速入门的 API

PHP5 众多的 API DOM SimpleXML 是最为人所熟悉(DOM)和最简单的(SimpleXML)。而且多数情况下,就像后面的例子一样,也是功能最完善的。

 DOM 扩展

文档对象模型(DOM)是表示 HTML XML 文档的 W3C 标准对象集合,将这些对象结合起来的标准模型以及访问和操纵它们的标准接口。很多厂商支持 DOM 作为其私有数据结构和 API 的接口,由于开发人员对其比较熟悉,给 DOM 模型增加了不少权威色彩。DOM 很容易理解和使用,因为其内存结构模仿了原始 XML 文档。为了把信息传递给应用程序,DOM 创建和 XML 文件元素树完全相同的对象树,每个 XML 元素都用树中的一个节点表示。DOM 是基于树的解析器。因为 DOM 要构造整个文档树,要花费大量的内存和处理器时间。因此,性能问题决定了使用 DOM 很难处理大型文档。本文中主要把 DOM 扩展用于导入 SimpleXML 格式(作为字符串)和输出 DOM 格式的 XML(作为 XML 文件)或者相反。

 SimpleXML

我们选择 SimpleXML 扩展解析 XML 文档。SimpleXML 扩展需要 PHP5 并包括和 DOM 的互操作性,以便编写 XML 文件和内置的 XPath 支持。SimpleXML 最适合简单的、类似记录的数据,比如从同一个应用程序其他部分传递来的 XML 文档或字符串。如果 XML 文档不是很复杂,嵌套不太深,没有混合内容,使用 SimpleXML 要比 DOM 简单得多,就像其名字所说的那样。如果使用已知的文档结构就更可靠。

简单的例子

这些例子使用 DOM SimpleXML 处理较小的、一般的 XML 文件。

 

使用 DOM

DOM 是在浏览器中使用的、用 JavaScript 操作的 W3C DOM 规范。方法都是一样的,因此可以使用熟悉的编码技术。清单 2 示范了使用 DOM 创建 XML 字符串和 XML 文档并设置格式以便查看。

清单 2. 使用 DOM

 <?php

 

 //Creates XML string and XML document using the DOM

 $dom = new DomDocument('1.0');

 

 //add root - <books>

 $books = $dom->appendChild($dom->createElement('books'));

 

 //add <book> element to <books>

 $book = $books->appendChild($dom->createElement('book'));

 

 //add <title> element to <book>

 $title = $book->appendChild($dom->createElement('title'));

 

 //add <title> text node element to <title>

 $title->appendChild($dom->createTextNode('Great American

 Novel'));

 

 //generate xml

 $dom->formatOutput = true; // set the formatOutput attribute of

 domDocument to true

 // save XML as string or file

 $test1 = $dom->saveXML(); // put string in test1

 $dom -> save('test1.xml'); // save as file

 ?>

 

生成的输出文件如清单 3 所示。

清单 3. 输出文件

  <?xml version="1.0"?>

 <books>

 <book>

 <title>Great American Novel</title>

 </book>

 </books>

 

清单 4 DOMElement 对象中导入了 SimpleXMLElement 对象,示范了 DOM SimpleXML 的互操作。

清单 4. 互操作性,第 1 部分 —— DOM 导入 SimpleXML

 <?php

 

 $sxe = simplexml_load_string('<books><book><title>Great American

Novel</title></book></books>');

 

 if ($sxe === false) {

 echo 'Error while parsing the document';

 exit;

 }

 

 $dom_sxe = dom_import_simplexml($sxe);

 if (!$dom_sxe) {

 echo 'Error while converting XML';

 exit;

 }

 

 $dom = new DOMDocument('1.0');

 $dom_sxe = $dom->importNode($dom_sxe, true);

 $dom_sxe = $dom->appendChild($dom_sxe);

 

 echo $dom->saveXML('test2.xml');

 

 ?>

 

清单 5 中的函数将 DOM 文档中的节点转化成 SimpleXML 节点。然后可以作为真正的 SimpleXML 元素来使用这个新的对象。如果出现错误则返回 FLASE

清单 5. 互操作性,第 2 部分 —— SimpleXML 导入 DOM

 <?php

 $dom = new domDocument;

 $dom->loadXML('<books><book><title>Great American

Novel</title></book></books>');

 if (!$dom) {

 echo 'Error while parsing the document';

 exit;

 }

 

 $s = simplexml_import_dom($dom);

 

 echo $s->book[0]->title; // Great American Novel

 ?>

使用 SimpleXML

我们选择 SimpleXML 扩展来解析 XML 文档。SimpleXML 扩展包括与 DOM 的互操作性,用于编写 XML 文件和内置的 XPath 支持。SimpleXML DOM 更容易使用,就像名称所预示的那样。

如果不熟悉 PHP,清单 6 对测试 XML 文件进行格式化,可供后面复用。

清单 6. 将测试 XML 文件格式化成 PHP include 文件 example.php 以备后面使用

 <?php

 $xmlstr = <<<XML

 <?xml version='1.0' standalone='yes'?>

 <books>

 <book>

 <title>Great American Novel</title>

 <characters>

 <character>

 <name>Cliff</name>

 <desc>really great guy</desc>

 </character>

 <character>

 <name>Lovely Woman</name>

 <desc>matchless beauty</desc>

 </character>

 <character>

 <name>Loyal Dog</name>

 <desc>sleepy</desc>

 </character>

 </characters>

 <plot>

 Cliff meets Lovely Woman. Loyal Dog sleeps, but wakes up to bark

 at mailman.

 </plot>

 <success type="bestseller">4</rating>

 <success type="bookclubs">9</rating>

 </book>

 </books>

 XML;

 ?>

 

Ajax 应用程序中,可能需要从 XML 文档提取邮政编码和查询数据库。清单 7 从上面的示例 XML 中直接提取 <plot>

清单 7. 提取节点 —— 有多么简单?

 

 

  <?php

 

 include 'example.php';

 

 $xml = new SimpleXMLElement($xmlstr);

 

 echo $xml->book[0]->plot; // "Cliff meets Lovely Woman. Loyal

 Dog..."

 ?>

 

另一方面,也许还要提取分为多行的地址。当一个元素的多个实例是一个父元素的子元素,通常需要使用迭代技术。如清单 8 所示。

清单 8. 提取元素的多个实例

 <?php

 

 include 'example.php';

 

 $xml = new SimpleXMLElement($xmlstr);

 

 /* For each <book> node, echo a separate <plot>. */

 foreach ($xml->book as $book) {

 echo $book->plot, '<br />';

 }

 

 ?>

 

除了读取元素名称及其值以外,SimpleXML 也能访问元素的属性。清单 9 中就像访问数组成员一样访问元素的属性。

清单 9. SimpleXML 访问元素的属性

//Input XML file repeated for your convenience

 

 <?php

 $xmlstr = <<<XML

 <?xml version='1.0' standalone='yes'?>

 

 <books>

 <book>

 <title>Great American Novel</title>

 <characters>

 <character>

 <name>Cliff</name>

 <desc>really great guy</desc>

 </character>

 <character>

 <name>Lovely Woman</name>

 <desc>matchless beauty</desc>

 </character>

 <character>

 <name>Loyal Dog</name>

 <desc>sleepy</desc>

 </character>

 </characters>

 <plot>

 Cliff meets Lovely Woman. Loyal Dog sleeps, but wakes up to bark

 at mailman.

 </plot>

 <success type="bestseller">4</rating>

 <success type="bookclubs">9</rating>

 </book>

 </books>

 XML;

 ?>

 

 <?php

 include 'example.php';

 

 $xml = new SimpleXMLElement($xmlstr);

 

 /* Access the <success> nodes of the first book.

 * Output the success indications, too. */

 foreach ($xml->book[0]->success as $success) {

 switch((string) $success['type']) { // Get attributes as element indices

 case 'bestseller':

 echo $success, ' months on bestseller list';

 break;

 case 'bookclubs':

 echo $success, ' bookclub listings';

 break;

 }

 }

 ?>

 

要把元素或属性和字符串进行比较,或者将其传递给需要字符串参数的函数,必须使用(string)强制转换成字符串。否则,默认情况下 PHP 将元素看作对象,如清单 10 所示。

清单 10. 调用字符串或丢弃

  <?php

 

 include 'example.php';

 

 $xml = new SimpleXMLElement($xmlstr);

 

 if ((string) $xml->book->title == 'Great American Novel') {

 print 'My favorite book.';

 }

 

 htmlentities((string) $xml->book->title);

 ?>

 

 

SimpleXML 中的数据不一定是常数。清单 11 生成了一个新的 XML 文档(如后所示),和原来的相同,只不过新的 XML 文件中把 Cliff 换成了 Big Cliff

清单 11. 使用 SimpleXML 修改文本节点

  <?php

 $xmlstr = <<<XML

 <?xml version='1.0' standalone='yes'?>

 <books>

 <book>

 <title>Great American Novel</title>

 <characters>

 <character>

 <name>Big Cliff</name>

 <desc>really great guy</desc>

 </character>

 <character>

 <name>Lovely Woman</name>

 <desc>matchless beauty</desc>

 </character>

 <character>

 <name>Loyal Dog</name>

 <desc>sleepy</desc>

 </character>

 </characters>

 <plot>

 Cliff meets Lovely Woman. Loyal Dog sleeps, but wakes up to bark

 at mailman.

 </plot>

 <success type="bestseller">4</rating>

 <success type="bookclubs">9</rating>

 </book>

 </books>

 XML;

 ?>

 

 <?php

 

 include 'example.php';

 $xml = new SimpleXMLElement($xmlstr);

 

 $xml->book[0]->characters->character[0]->name = 'Big Cliff';

 

 echo $xml->asXML();

 ?>

 

 

PHP 5.1.3 以后,SimpleXML 就能够方便地添加子元素和属性了。清单 12 根据原始文档输出一个 XML 文档,包含新的字符和描述符。

清单 12. 使用 SimpleXML 添加子元素和文本节点

  <?php

 $xmlstr = <<<XML

 <?xml version='1.0' standalone='yes'?>

 <books>

 <book>

 <title>Great American Novel</title>

 <characters>

 <character>

 <name>Cliff</name>

 <desc>really great guy</desc>

 </character>

 <character>

 <name>Lovely Woman</name>

 <desc>matchless beauty</desc>

 </character>

 <character>

 <name>Loyal Dog</name>

 <desc>sleepy</desc>

 </character>

 <character>

 <name>Yellow Cat</name>

 <desc>aloof</desc>

 </character>

 </characters>

 <plot>

 Cliff meets Lovely Woman. Loyal Dog sleeps, but wakes up to bark

 at mailman.

 </plot>

 <success type="bestseller">4</rating>

 <success type="bookclubs">9</rating>

 </book>

 </books>

 XML;

 ?>

 

 <?php

 include 'example.php';

 $xml = new SimpleXMLElement($xmlstr);

 

 $character = $xml->book[0]->characters->addChild('character');

 $character->addChild('name', 'Yellow Cat');

 $character->addChild('desc', 'aloof');

 

 $success = $xml->book[0]->addChild('success', '2');

 $success-> $rating->addAttribute('type', 'reprints');

 

 echo $xml->asXML();

 ?>

 

2 部分: 高级 XML 解析技术

简介

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 InterfacesAPI)。通过例子说明对于处理简单、可预测并且不大的 XML 文档,SimpleXML(必要的时候与文档对象模型(DOM)结合使用)是一种理想的选择。

 XML PHP5

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

PHP5 提供了一些全新的和重新编写的 XML 解析扩展。其中包括将整个 XML 文档加载到内存中的 SimpleXMLDOM XSLT 处理程序。也有每次把 XML 文档的一部分加载到内存中的 Simple API for XML (SAX) XMLReaderSAX 的功能和在 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 APIPHP 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);

 

?>


3 部分: 读、操纵和写 XML 的高级技术

简介

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

 PHP5 中的 XML

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

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

使用 DOM 操纵 XML

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

使用 DOM

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

本文中的例子使用 Yahoo 搜索 APIPHP5 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');

 

?>

 

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值