创建xml并解析xml
2008年1月11日更新 : 初始化解析器并加载文档中的第三个代码部分已修改为: $reader->open('http://www.cafeaulait.org/today.atom');
来自$reader->XML('http://www.cafeaulait.org/today.atom');
PHP 5引入了XMLReader
,这是一个用于读取可扩展标记语言(XML)的新类。 与SimpleXML或文档对象模型(DOM)不同, XMLReader
在流模式下运行。 即,它从头到尾读取文档。 您可以从一开始就开始处理内容,然后再查看结尾的内容。 这使得它非常快,非常有效并且非常节省内存。 您需要处理的文档越大,这一点就越重要。
与XML的简单API(SAX)不同, XMLReader
是请求解析器,而不是请求解析器。 这意味着您的程序处于控制之中。 您不会告诉解析器看到解析器时会看到什么,而是告诉解析器何时去获取文档的下一部分。 您要求内容,而不是对此做出React。 另一种思考方式: XMLReader
是Iterator设计模式的实现,而不是Observer设计模式的实现。
样本问题
让我们从一个简单的例子开始。 假设您正在编写一个PHP脚本,该脚本接收XML-RPC请求并生成响应。 更具体地说,假设请求类似于清单1。文档的根元素是methodCall
,其中包含methodName
元素和params
元素。 方法名称是sqrt
。 params
元素包含一个param
元素,该param
元素包含需要平方根的double
精度型。 不使用命名空间。
清单1.一个XML-RPC请求
<?xml version="1.0"?>
<methodCall>
<methodName>sqrt</methodName>
<params>
<param>
<value><double>36.0</double></value>
</param>
</params>
</methodCall>
这是PHP脚本需要执行的操作:
- 检查方法名称,如果不是
sqrt
,则生成故障响应(此脚本知道如何处理的唯一方法)。 - 查找参数,如果该参数不存在或类型错误,则生成故障响应。
- 否则,计算平方根。
- 以清单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,这是超文本传输协议(HTTP)请求的原始内容。 然后可以将此字符串传递给阅读器的XML()
函数:
$request = $HTTP_RAW_POST_DATA;
$reader->XML($request);
您可以在任何地方解析任何字符串。 例如,它可以是程序中的字符串文字,也可以从本地文件中读取。 您还可以使用open()
函数从外部URL加载数据。 例如,此语句准备解析我的Atom提要之一:
$reader->open('http://www.cafeaulait.org/today.atom');
无论您在哪里获取原始数据,现在都可以设置阅读器并准备解析。
阅读文件
read()
函数将解析器前进到下一个标记。 最简单的方法是在while
循环中遍历整个文档:
while ($reader->read()) {
// processing code goes here...
}
完成后,关闭解析器以释放它持有的所有资源,并为下一个文档重置它:
$reader->close();
在循环内部,解析器位于特定的节点上:元素的开头,元素的结尾,文本节点,注释,等等。 您可以通过检查以下属性来找出解析器正在查看的内容:
-
localName
是节点的本地无前缀名称。 -
name
是节点的可能带前缀的名称。 对于诸如注释之类的没有名称的#comment
,它是##comment
,##text
,##document
等,就像在DOM中一样。 -
namespaceURI
是节点名称namespaceURI
的统一资源标识符(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);
}
在这里,response respond()
函数构建XML-RPC响应并将其发送到客户端。 但是,在证明这一点之前,我需要解决其他问题。 不能绝对保证请求文档中的double
元素仅包含一个文本节点。 它可能包含一些以及注释和处理说明。 例如,它可能看起来像这样:
<value><double>
<!--value follows-->6.<!--fractional part next-->0
</double></value>
一个可靠的解决方案需要获取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 "<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><double>" .
sqrt($input)
. "</double></value>
</param>
</params>
</methodResponse>";
}
您甚至可以像使用HTML一样直接将响应的文字部分嵌入PHP页面。 清单7演示了这种技术。
清单7.文字XML
function respond($input) {
?><?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><double>"<?php
echo sqrt($input);
?>
</double></value>
</param>
</params>
</methodResponse>
<?php
}
错误处理
到目前为止,我隐式假定输入文档的格式正确。 但是,这并不能保证。 像任何XML解析器一样,要求XMLReader
在检测到格式正确的错误后立即停止处理。 如果这样做,则read()
函数将返回false。
从理论上讲,解析器可以报告直到发现的第一个错误的数据。 但是,在我的小文档实验中,它几乎立即出错。 基础解析器正在准备文档的很大一部分,将其缓存,然后一次分发一部分。 因此,它倾向于过早地检测错误。 为了安全起见,请勿假设您能够在第一个格式正确的错误之前解析内容。 此外,不要假设您在解析器错误之前不会看到任何内容。 如果您只想接受格式正确的完整文档,请确保脚本在看到文档末尾之前不会做任何不可逆的操作。
如果解析器检测到格式错误,则read()
函数将回显这样的错误消息(如果打开了详细的错误报告,因为它应该在开发服务器上):
<br />
<b>Warning</b>: XMLReader::read() [<a href='function.read'>function.read</a>]:
< value><double>10</double></value> in <b>/var/www/root.php</b>
on line <b>35</b><br />
您可能不想将其复制到用户看到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显示了针对此特定形式的XML-RPC请求的简单RELAX NG模式。
清单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脚本中,或者使用setRelaxNGSchemaSource()
从外部文件或URL中setRelaxNGSchema()
。 例如,假设清单9在文件sqrt.rng中,则以下是加载模式的方法:
reader->setRelaxNGSchema("sqrt.rng")
在开始解析文档之前,请执行此操作。 解析器在读取文档时对照架构检查文档。 要检查文档是否有效,请调用isValid()
,如果文档有效(到目前为止),则返回true;否则,返回false。 清单10演示了完整的完成程序,包括所有错误处理。 这应该接受任何合法的输入并返回正确的值,并拒绝所有不正确的请求。 我还添加了一个fault()
方法,该方法在出现问题时发送XML-RPC错误响应。
清单10.完整的XML-RPC平方根服务器
<?php
header('Content-type: application/xml');
// try grammar
$schema = "<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>";
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)
{
?>
<methodResponse>
<params>
<param>
<value><double><?php
echo sqrt($input);
?></double></value>
</param>
</params>
</methodResponse>
<?php
}
function fault($code, $message)
{
echo "<?xml version='1.0'?>
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>" . $code . "</int></value>
</member>
<member>
<name>faultString</name>
<value>
<string>" . $message . "</string>
</value>
</member>
</struct>
</value>
</fault>
</methodResponse>";
}
属性
在拉式解析的正常过程中看不到属性。 要读取属性,请在元素的开头停止,然后通过名称或数字请求特定的属性。
传递要获取的属性名称getAttribute()
以在当前元素上找到该属性的值。 例如,以下语句询问当前元素的id
属性:
$id = $reader->getAttribute("id");
如果属性位于名称空间中(例如xlink:href
getAttributeNS()
,则调用getAttributeNS()
,分别将本地名称和名称空间URI作为第一个和第二个参数传递。 (前缀无关紧要。)例如,以下语句请求http://www.w3.org/1999/xlink/命名空间中xlink:href
属性的值:
$href = $reader->getAttributeNS("href", "http://www.w3.org/1999/xlink/");
如果该属性不存在,则这两个方法都将返回一个空字符串。 (这是错误的。它们应该返回null。当前的设计使得很难区分值为空字符串的属性和根本不存在的属性。)
如果只想知道元素上的所有属性,而又不事先知道它们的名称,那么当读者位于元素上时,请调用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
非常值得您考虑。
翻译自: https://www.ibm.com/developerworks/opensource/library/x-pullparsingphp/index.html
创建xml并解析xml