PHP中的XML拉模式解析
日期:2007年9月23日 作者: 查看:[大字体 中字体 小字体]-
研究与 PHP 5 捆绑在一起的 XMLReader 库,它使 PHP 页面能够以高效的流模式来处理 XML 文档。
PHP 5 引入了新的类 XMLReader,用于读取可扩展标记语言(Extensible Markup Language,XML)。与 SimpleXML 或文档对象模型(Document Object Model,DOM)不同,XMLReader 以流模式进行操作。即它从头到尾读取文档。在文档后面的内容编译完成之前,可以先处理已编译好的文档前面的内容,从而实现非常快速、非常高效、非常节省地使用内存。需要处理的文档越大,这个特点就越发重要。
libxml
这里所说的 XMLReader API 位于 Gnome Project 中用于 C 和 C++ 的 libxml 库之上。实际上 XMLReader 只是在 libxml 的 XmlTextReader API 之上的很薄的 PHP 层。XmlTextReader 本身是模仿 .NET 的 XmlTextReader 类和 XmlReader 类,尽管并不具有与这些类相似的代码。
与 Simple API for XML (SAX) 不同,XMLReader 是推解析器,而不是拉解析器。这意味着程序是可以控制的。您将告诉解析器何时获取下一个文档片段,而不是在解析器看到文档后告诉您所看到的内容。您将请求内容,而不是对内容进行反应。从另一个角度来考虑这个问题:XMLReader 是 Iterator 设计模式的实现,而不是 Observer 设计模式的实现。
示例问题
先从简单例子开始讨论。假定正在编写 PHP 脚本,用来接收 XML-RPC 请求并生成响应。更具体一些,假定请求如清单 1 所示。文档的根元素是 methodCall,它包含 methodName 元素和 params 元素。方法的名称是 sqrt。params 元素包含一个 param 元素,param 元素包含 double,double 的平方根是希望得到的值。没有使用名称空间。
清单 1. XML-RPC 请求
以下是引用片段:
<?xml version="1.0"?>
<methodCall>
<methodName>sqrt</methodName>
<params>
<param>
<value><double>36.0</double></value>
</param>
</params>
</methodCall>下面是 PHP 脚本需要完成的工作:
1、检查方法名,如果不是 sqrt(它是该脚本懂得如何处理的惟一方法),则生成错误响应。
2、找到参数,如果参数不存在或参数类型错误,则生成错误响应。
3、另外,计算平方根。
4、在表单中返回结果,如清单 2 所示。
清单 2. XML-RPC 响应
以下是引用片段:
<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value><double>6.0</double></value>
</param>
</params>
</methodResponse>下面我们逐步展开说明。
初始化解析器并载入文档
第一步是创建新的解析器对象。创建操作很简单:
以下是引用片段:
$reader = new XMLReader();接着,需要为它提供一些用于解析的数据。对于 XML-RPC,这是超文本传输协议(Hypertext Transfer Protocol,HTTP)请求的原始主体。然后可以将该字符串传递到读取器的 XML() 函数:
填充原始发送数据
以下是引用片段:
$request = $HTTP_RAW_POST_DATA;
$reader->XML($request);如果发现 $HTTP_RAW_POST_DATA 是空的,则将以下代码行添加到 php.ini 文件:
以下是引用片段:
always_populate_raw_post_data = On可以解析任何字符串,无论它是从何处获取的。例如,可以是程序中的一串文字或从本地文件读取。还可以使用 open() 函数从外部 URL 载入数据。例如,下面的语句准备解析其中一个 Atom 提要:
以下是引用片段:
$reader->XML('http://www.cafeaulait.org/today.atom');无论是从何处获取原始数据,现在已建立了阅读器并为解析做好准备。
读取文档
read() 函数使解析器前进到下一个标记。最简单的方法是在 while 循环中遍历整个文档:
以下是引用片段:
while ($reader->read()) {
// processing code goes here...
}完成遍历后,关闭解析器以释放它所持有的任何资源,并且重置解析器以便用于下一个文档:
以下是引用片段:
$reader->close();在循环内部,将解析器放置在特殊节点上:元素的起点、元素的终点、文本节点、注释等等。通过检查以下属性,可以发现解析器正在查看的内容:
- localName 是本地的、未带前缀的节点名。
- name 是可能的节点前缀名。对于像注释这种没有名称的节点,包括 #comment、#text、#document 等等,与 DOM 中的一样。
- namespaceURI 是节点名称空间的统一资源标识符(Uniform Resource Identifier,URI)。
- nodeType 是代表节点类型的整数 —— 例如,2 代表属性节点,7 代表处理指令。
- prefix 是节点的名称空间前缀。
- value 是节点的下一个文本内容。
- 如果节点有文本值,hasValue 值为 true;否则,值为 false。
当然,并非所有节点类型都具有所有这些属性。例如,文本节点、CDATA 部件、注释、处理指令、属性、空格、文档类型和 XML 声明具有值。而其它节点类型(最重要的是元素和文档)则没有值。通常,程序将使用 nodeType 属性来断定它所查找的内容,然后做出适当的响应。清单 3 展示了简单的 while 循环,该循环使用这些函数来打印它所查看的内容。清单 4 展示了将清单 1 输入程序后的输出。
清单 3. 解析器所查看的内容
以下是引用片段:
while ($reader->read()) {
echo $reader->name;
if ($reader->hasValue) {
echo ": " . $reader->value;
}
echo "\n";
}清单 4. 清单 3 的输出
以下是引用片段:
methodCall
#text:
methodName
#text: sqrt
methodName
#text:
params
#text:
param
#text:
value
double
#text: 10
double
value
#text:
param
#text:
params
#text:
methodCall大多数程序并非这么简单。它们接受特定格式的输入,并以某种方式来处理输入。在 XML-RPC 例子中,仅需要读取输入中的一个元素:double 元素,该元素应该只有一个。为此,查找名称为 double 的元素的起点:
以下是引用片段:
if ($reader->name == "double"
&& $reader->nodeType == XMLReader::ELEMENT) {
// ...
}该元素可能有单个文本子节点,可以通过将解析器前进到下一个节点来进行读取,如下所示:
以下是引用片段:
if ($reader->name == "double" && $reader->nodeType == XMLReader::ELEMENT) {
$reader->read();
respond($reader->value);
}在这里 respond() 函数构建了 XML-RPC 响应并将它发送到客户机。但是,在展示上述操作前,还有一些事情需要处理。不能绝对保证请求文档中的 double 元素仅包含一个文本节点。可能包含多个文本节点,以及注释和处理指令。例如,可能看起来像以下代码:
以下是引用片段:
<value><double>
<!--value follows-->6.<!--fractional part next-->0
</double></value>嵌套元素
该模式存在一个潜在的缺陷。嵌套的 double 元素(例如 61.2)将违背该算法。然而它将成为无效的 XML-RPC;并且不久您将看到如何使用 RELAX NG 验证来拒绝所有此类文档。在诸如可扩展超文本标记语言(Extensible Hypertext Markup Language,XHTML)之类的文档类型中,允许相同元素互相包含(例如 table 元素包含在另一个 table 元素中),因此您还需要知道元素的深度,从而确保结束标记与开始标记之间进行正确匹配。
一个健壮的解决方案需要获得 double 元素的所有文本子节点,将它们连接起来,并且仅将结果转换为 double。必须小心避免任何注释或可能出现的其它非文本节点。这有一点复杂,但并不是十分复杂,如清单 5 所示。
清单 5. 累积来自一个元素的所有文本内容
以下是引用片段:
while ($reader->read()) {
if ($reader->nodeType == XMLReader::TEXT
$reader->nodeType == XMLReader::CDATA
$reader->nodeType == XMLReader::WHITESPACE
$reader->nodeType == XMLReader::SIGNIFICANT_WHITESPACE) {
$input .= $reader->value;
}
else if ($reader->nodeType == XMLReader::END_ELEMENT
&& $reader->name == "double") {
break;
}
}您可以暂时忽略文档中的其它任何内容。(稍后将添加更多的错误处理。)
构建响应
正如它的名称所暗示的,XMLReader 仅仅用于读取。相应的 XMLWriter 类正在开发中,但还不能投入到生产。幸运的是,写入 XML 比读取 XML 要容易得多。首先,应使用 header() 函数来设置响应的媒体类型。对于 XML-RPC 来说,媒体类型是 application/xml。例如:
以下是引用片段:
header('Content-type: application/xml');通常直接将内容显示在页面上,如清单 6 中的 respond() 函数所示。
清单 6. Echo XML
以下是引用片段:
function respond($input) {
echo "
" .
sqrt($input)
. "
";
}甚至可以将响应的文字部分直接嵌入 PHP 页面中,就像使用 HTML 时一样。清单 7 展示了该技术。
清单 7. 文字表示的 XML
以下是引用片段:
function respond($input) {
?>
"
echo sqrt($input);
?>
}
错误处理
到现在为止,一直隐含假定输入文档是格式规范的文档。但是不能保证情况都是如此。像任何 XML 解析器一样,只要发现一个规范格式错误,XMLReader 就必须停止处理。如果是这样的话,read() 函数将返回 false。
从理论上讲,解析器将报告数据直到发现第一个错误。但是在对小型文档进行试验时,几乎是立刻显示错误信息。底层解析器将预解析大块文档,对它进行缓存,然后每次分发出一小块文档。因此往往会过早地检查错误。出于安全考虑,不要假定在发现第一个规范格式错误之前能够解析内容。此外,也不要假设解析错误出现之前看不到任何内容。如果希望只接受完整的、格式规范的文档,那么请确保在看到文档终点之前脚本不能进行任何不可逆操作。
如果解析器检测到规范格式错误,那么 read() 函数将显示如下错误消息(如果启用了详细错误报告,且位于开发服务器上时):
以下是引用片段:
Warning: XMLReader::read() [function.read]:
< value>10 in /var/www/root.php
on line 35您可能不希望将它复制到用户所看到的 HTML 页面中。更好的方法是在 $php_errormsg 环境变量中捕获错误消息。为此,需要启用 php.ini 文件中的 track_errors 配置选项:
以下是引用片段:
track_errors = On默认情况下,track_errors 选项是关闭的;这在 php.ini 中是显式指定的,因此请确保更改了该行代码。如果提早在 php.ini 中添加了上述一行代码(正如最初我所进行的操作),则后面的 track_errors = Off 代码将重写先前的代码。
该程序仅将响应发送到完整的、格式良好的输入。(也是有效的,不过将实现这点。)因此您需要等待,直到完成了文档的解析(已经跳出 while 循环)。这时,检查是否设置了 $php_errormsg 变量。如果没有进行设置,则文档是格式良好的文档,然后发送 XML-RPC 响应消息。如果设置了该变量,则文档不是格式良好的文档,并发送 XML-RPC 错误响应。如果有人请求负数的平方根,也将发送错误响应。清单 8 展示以上操作。
清单 8. 检查文档格式是否良好
以下是引用片段:
// set up the request
$request = $HTTP_RAW_POST_DATA;
error_reporting(E_ERROR E_WARNING E_PARSE);
if (isset($php_errormsg)) unset(($php_errormsg);
// create the reader
$reader = new XMLReader();
// $reader->setRelaxNGSchema("request.rng");
$reader->XML($request);
$input = "";
while ($reader->read()) {
if ($reader->name == "double" && $reader->nodeType == XMLReader::ELEMENT) {
while ($reader->read()) {
if ($reader->nodeType == XMLReader::TEXT
$reader->nodeType == XMLReader::CDATA
$reader->nodeType == XMLReader::WHITESPACE
$reader->nodeType == XMLReader::SIGNIFICANT_WHITESPACE) {
$input .= $reader->value;
}
else if ($reader->nodeType == XMLReader::END_ELEMENT
&& $reader->name == "double") {
break;
}
}
break;
}
}
// make sure the input was well-formed
if (isset($php_errormsg) ) fault(21, $php_errormsg);
else if ($input < 0) fault(20, "Cannot take square root of negative number");
else respond($input);这是 XML 流处理中简单的常见模式。解析器将填写一个数据结构,当完成文档时该数据结构将起作用。通常数据结构要比文档本身简单。这里所使用的数据结构尤其简单:一个字符串。
验证
到目前为止,对于验证数据是否位于所预期的地方,并没有给予关注。实现该验证的最简单的方法是检查文档的模式。XMLReader 支持 RELAX NG 模式语言;清单 9 展示了简单的 RELAX NG 模式,用于这个特定的 XML-RPC 请求表单。
libxml 版本
在 libxml 的早期版本中,RELAX NG 有一些严重错误,XMLReader 取决于 libxml 库。请确保所使用的版本至少是 2.06.26 版。很多系统(包括 Mac OS X Tiger)捆绑了较早的、有错误的 libxml 版本。
清单 9. XML-RPC 请求
以下是引用片段: <element name="methodCall" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <element name="methodName"> <value>sqrt</value> </element> <element name="params"> <element name="param"> <element name="value"> <element name="double"> <data type="double"/> </element> </element> </element> </element> </element>可以使用 setRelaxNGSchemaSource() 将模式作为一串文字直接嵌入 PHP 脚本,或者使用 setRelaxNGSchema() 从外部文件或 URL 读取模式。例如,假定清单 9 位于 sqrt.rng 文件中,下面将展示如何载入模式:
以下是引用片段:
reader->setRelaxNGSchema("sqrt.rng")在开始解析文档 之前,执行上述操作。解析器在进行读取时将检查文档的模式。若要检查文档是否有效,则调用 isValid(),如果文档是有效的(目前为止),则返回 true,否则,返回 false。清单 10 展示了完整的程序,包括所有错误处理。这样将接受任何合法输入,然后返回正确的值,而且将拒绝所有不正确的请求。我还添加了 fault() 方法,当发生故障时将发送 XML-RPC 错误响应。
清单 10. 完整的 XML-RPC 平方根服务器
以下是引用片段:
header('Content-type: application/xml');
// try grammar
$schema = "
xmlns='http://relaxng.org/ns/structure/1.0'
datatypeLibrary='http://www.w3.org/2001/XMLSchema-datatypes'>
sqrt
";
if (!isset($HTTP_RAW_POST_DATA)) {
fault(22, "Please make sure always_populate_raw_post_data = On in php.ini");
}
else {
// set up the request
$request = $HTTP_RAW_POST_DATA;
error_reporting(E_ERROR E_WARNING E_PARSE);
// create the reader
$reader = new XMLReader();
$reader->setRelaxNGSchema("request.rng");
$reader->XML($request);
$input = "";
while ($reader->read()) {
if ($reader->name == "double" && $reader->nodeType == XMLReader::ELEMENT) {
while ($reader->read()) {
if ($reader->nodeType == XMLReader::TEXT
$reader->nodeType == XMLReader::CDATA
$reader->nodeType == XMLReader::WHITESPACE
$reader->nodeType == XMLReader::SIGNIFICANT_WHITESPACE) {
$input .= $reader->value;
}
else if ($reader->nodeType == XMLReader::END_ELEMENT
&& $reader->name == "double") {
break;
}
}
break;
}
}
if (isset($php_errormsg) ) fault(21, $php_errormsg);
else if (! $reader->isValid()) fault(19, "Invalid request");
else if ($input < 0) fault(20, "Cannot take square root of negative number");
else respond($input);
$reader->close();
}
function respond($input)
{
?>
echo sqrt($input);
?>
}
function fault($code, $message)
{
echo "
faultCode
" . $code . "
faultString
" . $message . "
";
}
属性
在正常的推解析期间不会看到属性。若要读取属性,请停止在元素的起点处,通过名称或编号来请求特定属性。
将需要的属性名称传递到 getAttribute(),以便在当前元素上查找该属性的值。例如,下面的语句请求当前元素的 id 属性:
以下是引用片段:
$id = $reader->getAttribute("id");如果属性位于名称空间中 —— 例如,xlink:href —— 则调用 getAttributeNS(),将本地名称和名称空间 URI 分别作为第一个和第二个参数进行传递。(前缀是无关紧要的。)例如,下面的语句将请求 http://www.w3.org/1999/xlink/ 名称空间中 xlink:href 属性的值:
以下是引用片段:
$href = $reader->getAttributeNS("href", http://www.w3.org/1999/xlink/);如果属性不存在,那么这两种方法都将返回空字符串。(这是不正确的。它们应该返回 null。当前设计很难区分值为空字符串的属性和值根本不存在的属性。)
属性次序
在 XML 文档中,属性次序并不重要,并且不受解析器的保护。这里用于属性索引的编号仅仅是为了方便起见。不能保证开始标记中的第一个属性就是属性 1,第二个就是属性 2 等等。不要编写依赖于属性次序的代码。
如果仅希望了解元素上的所有属性,并且事先并不知道属性名,那么当读取器位于元素上时,调用 moveToNextAttribute()。一旦解析器位于属性节点上,就可以读取属性的名称、名称空间以及元素所使用的相同属性的值。例如,以下代码片段将打印当前元素的所有属性:
以下是引用片段:
if ($reader->hasAttributes and $reader->nodeType == XMLReader::ELEMENT) {
while ($reader->moveToNextAttribute()) {
echo $reader->name . "='" . $reader->value . "'\n";
}
echo "\n";
}对于 XML API 来说非常难得的是,XMLReader 允许从元素的起点 或终点 读取属性。为了避免重复计算,确认代码类型是 XMLReader::ELEMENT 而不是 XMLReader::END_ELEMENT 是很重要的,后者也可能拥有属性。
结束语
XMLReader 是添加到 PHP 程序员工具箱中的一个很有用的工具。与 SimpleXML 不同,它是处理所有文档(而不是部分文档)的完整 XML 解析器。与 DOM 不同,它可以处理大于可用内存的文档。与 SAX 不同,它将程序置于控制之下。如果 PHP 程序需要接受 XML 输入,则 XMLReader 是很值得考虑的一个工具。(出处:急速软件下载学院)
-
- PHP中的XML拉模式解析 相关文章:
- ·危机重重 2007年度网络安全分析报告
- ·Linux系统网络配置详细解析
- ·诊断和分析提高搜索引擎网站排名
- ·简单分析2007年11月十大网络安全漏洞
- ·简单分析Script脚本跨站攻击漏洞技术
- ·深入分析Windows操作系统死机问题
- ·解析Leopard的备份工具:Time Machine
- ·详细分析操作系统死机的问题
- ·电子杂志的创编制作过程解析
- ·分析Windows操作系统死机问题
- PHP中的XML拉模式解析 相关软件
- ·SK魔兽录像分析器 V1.4 Beta 3
- ·文物典藏系列-故宫馆藏文房四宝赏析
- ·中国古籍白话解析系列合集(典藏版V1.1)
- ·《股票常识与技术分析》
- ·大败笔:34个最新的营销失败案例分析
- ·英语迷津-相似词语辨析
- ·金融炼金术:证券分析的逻辑
- ·佳作赏析
- ·股市基本分析
- ·梦的解析
- 特别声明:本站除部分特别声明禁止转载的专稿外的其他文章可以自由转载,但请务必注明出处和原始作
- 者.文章版权归文章原始作者所有.对于被本站转载文章的个人和网站,我们表示深深的谢意。如果本站转
- 载的文章有版权问题请联系编辑人员,我们尽快予以更正. 转载请注明来源:http://www.hackhome.com
上一篇:你是否已经开始防范网络安全陷阱?
下一篇:Tetsuya Takenomata标志设计作品欣赏
精品推荐
热点TOP10
- ·一个Struts实现分页,增删改查,Tiles,国际化的DEMO
- ·学习教程:Java Annotation 高级应用
- ·Windows XP下PHP+MySQL环境搭建
- ·smarty技术学习,修改php模板的一点体会
- ·PHP中的sprintf()函数的学习研究笔记
- ·PHP技巧:使用APC缓存优化PHP程序
- ·windows下Apache2+php4+MySql5集成简明教程
- ·PHP预定义变量
- ·突破Windows 2003 PHP服务器的新思路
- ·用Apache反向代理设置对外的WWW和文件服务器
- ·Mysql的常用命令
- ·如何在Form域中Post大于100K字节的数据
- ·详细学习PHP中对文件和目录的操作方法
- ·急速搭建属于自己的Discuz!6论坛
- ·如何利用PHP会话显示出当前在线的用户
- ·PHP编程中break及continue两个流程控制指令
- ·PHP项目将联合起来转向PHP5
- ·初学解决疑惑:PHP是什么?
- ·Discuz!5.5 JS的调用
- ·PHP4将结束更新并在明年退出历史的舞台
特别推荐
- ·PHP入门指导:如何学习PHP?
- ·Php+Mysql注入专题
- ·PHP:实现给上传图片加水印的程序代码
- ·PHP动态网站开发中常用的8个小技巧
- ·PHP初学者:配置Apache服务器并且设置DNS
- ·初学PHP指导:php.ini 配置详细选项
- ·好文分享:PHP入门的学习方法
- ·急速搭建属于自己的Discuz!6论坛
- ·PHP入门:初学来看配置文件PHP.INI的中文注释
- ·php.ini配置,php.ini 中的 php-5.2.0 配置指令详解
- ·总结:一些PHP学习过程中的心得和经验
- ·新手入门:JSP初学者必须掌握的语法点
- ·PHP技巧:PHP脚本编程中的文件系统函数库
- ·详细讲解PHP编程中分页显示的制作
- ·成为PHP编程高手应该遵循的三个准则
- ·谈谈生成静态页面的一些经验
- ·新手入门:详细解述七个软件开发的原则
- ·简单的webservice开发例子
- ·PHP木马攻击的防御之道
- ·基础入门:动态网页制作PHP中Session的概念
