创建xml并解析xml_在PHP中提取XML解析

创建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元素。 方法名称是sqrtparams元素包含一个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脚本需要执行的操作:

  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,这是超文本传输​​协议(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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值