使用 PHP 和 XMLReader 解析 XML

Posted

技术标签:

【中文标题】使用 PHP 和 XMLReader 解析 XML【英文标题】:Parse XML with PHP and XMLReader 【发布时间】:2013-02-27 01:04:04 【问题描述】:

我一直在尝试使用 php 和 XMLReader 解析一个非常大的 XML 文件,但似乎无法获得我正在寻找的结果。基本上,我正在搜索大量信息,如果 a 包含某个邮政编码,我想返回那部分 XML,或者继续搜索直到找到该邮政编码。本质上,我会将这个大文件分解成几个小块,因此不必查看数千或数百万组信息,它可能是 10 或 20 组。

这里有一些我想要的 XML

//search through xml
<lineups country="USA">
//cache TX02217 as a variable
 <headend headendId="TX02217">
//cache Grande Gables at The Terrace as a variable
  <name>Grande Gables at The Terrace</name>
//cache Grande Communications as a variable
  <mso msoId="17541">Grande Communications</mso>
  <marketIds>
   <marketId type="DMA">635</marketId>
  </marketIds>
//check to see if any of the postal codes are equal to $pc variable that will be set in the php
  <postalCodes>
   <postalCode>11111</postalCode>
   <postalCode>22222</postalCode>
   <postalCode>33333</postalCode>
   <postalCode>78746</postalCode>
  </postalCodes>
//cache Austin to a variable
  <location>Austin</location>
  <lineup>
//cache all prgSvcID's to an array i.e. 20014, 10722
   <station prgSvcId="20014">
//cache all channels to an array i.e. 002, 003  
    <chan effDate="2006-01-16" tier="1">002</chan>
   </station>
   <station prgSvcId="10722">
    <chan effDate="2006-01-16" tier="1">003</chan>
   </station>
  </lineup>
  <areasServed>
   <area>
//cache community to a variable $community   
    <community>Thorndale</community>
    <county code="45331" size="D">Milam</county>
//cache state to a variable i.e. TX
    <state>TX</state>
   </area>
   <area>
    <community>Thrall</community>
    <county code="45491" size="B">Williamson</county>
    <state>TX</state>
   </area>
  </areasServed>
 </headend>

//if any of the postal codes matched $pc 
//echo back the xml from <headend> to </headend>

//if none of the postal codes matched $pc
//clear variables and move to next <headend>

 <headend>
 etc
 etc
 etc
 </headend>
 <headend>
 etc
 etc
 etc
 </headend>
 <headend>
 etc
 etc
 etc
 </headend> 
</lineups>

PHP:

<?php
$pc = "78746";
$xmlfile="myFile.xml";
$reader = new XMLReader();
$reader->open($xmlfile); 

while ($reader->read())  
//search to see if groups contain $pc and echo info

我知道我做这件事的难度超出了应有的程度,但尝试操作如此大的文件时有点不知所措。任何帮助表示赞赏。

【问题讨论】:

您实际上在该 XML 块中寻找什么? XPath 是您的朋友。您只想查看是否有任何 包含预定值? 有点。如果我搜索这个大文件,并且一个块包含一个预定的邮政编码,那么我基本上想返回那个块。它会将这个巨大文件的大小减少到 2%。我仍将返回 XML,但我必须参考的数量将大大减少。 【参考方案1】:

为了获得更多XMLReader 的灵活性,我通常会创建自己的iterators that are able to work on the XMLReader object and provide the steps I need。

首先是对所有节点的简单迭代,然后是对具有特定名称的元素的迭代。让我们调用最后一个XMLElementIterator 以读取器和元素名称作为参数。

在您的场景中,我将创建一个迭代器,为当前元素返回 SimpleXMLElement,仅采用 &lt;headend&gt; 元素:

require('xmlreader-iterators.php'); // https://gist.github.com/hakre/5147685

class HeadendIterator extends XMLElementIterator 
    const ELEMENT_NAME = 'headend';

    public function __construct(XMLReader $reader) 
        parent::__construct($reader, self::ELEMENT_NAME);
    

    /**
     * @return SimpleXMLElement
     */
    public function current() 
        return simplexml_load_string($this->reader->readOuterXml());
    

配备此迭代器后,您剩下的工作就是小菜一碟了。首先加载 10GB 的文件:

$pc      = "78746";

$xmlfile = '../data/lineups.xml';
$reader  = new XMLReader();
$reader->open($xmlfile);

然后检查&lt;headend&gt;元素是否包含信息,如果是,则显示数据/XML:

foreach (new HeadendIterator($reader) as $headend) 
    /* @var $headend SimpleXMLElement */
    if (!$headend->xpath("/*/postalCodes/postalCode[. = '$pc']")) 
        continue;
    

    echo 'Found, name: ', $headend->name, "\n";
    echo "==========================================\n";
    $headend->asXML('php://stdout');

这确实实现了您想要实现的目标:遍历大文档(对内存友好),直到找到您感兴趣的元素。然后处理具体元素,它是 XML只要; XMLReader::readOuterXml() 是一个很好的工具。

示例输出:

Found, name: Grande Gables at The Terrace
==========================================
<?xml version="1.0"?>
<headend headendId="TX02217">
        <name>Grande Gables at The Terrace</name>
        <mso msoId="17541">Grande Communications</mso>
        <marketIds>
            <marketId type="DMA">635</marketId>
        </marketIds>
        <postalCodes>
            <postalCode>11111</postalCode>
            <postalCode>22222</postalCode>
            <postalCode>33333</postalCode>
            <postalCode>78746</postalCode>
        </postalCodes>
        <location>Austin</location>
        <lineup>
            <station prgSvcId="20014">
                <chan effDate="2006-01-16" tier="1">002</chan>
            </station>
            <station prgSvcId="10722">
                <chan effDate="2006-01-16" tier="1">003</chan>
            </station>
        </lineup>
        <areasServed>
            <area>
                <community>Thorndale</community>
                <county code="45331" size="D">Milam</county>
                <state>TX</state>
            </area>
            <area>
                <community>Thrall</community>
                <county code="45491" size="B">Williamson</county>
                <state>TX</state>
            </area>
        </areasServed>
    </headend>

【讨论】:

我认为你成功了。这正是我想要做的。但是,我对 PHP 并不十分熟悉,并且无法按照您的示例进行操作。你能再简化一点吗?如果您没有时间,我将继续尝试按原样理解它。感谢您的回复! 我应付了你的例子。在主 php 文件中,我有 include('iterator.php');但是,我收到以下错误:致命错误:在 iterator.php 中找不到类“XMLElementIterator” 如何只使用父类XMLElementIterator而不创建新类? @VinceKronlein:该类的改进变体可在 github 存储库中找到。将内部 XML 转换为 SimpleXML 已经可用(即使对于不那么兼容的旧 PHP/libxml 版本的回退),您可以只使用 new 关键字并将元素的名称传递到 XMLReader 对象旁边。 - github.com/hakre/XMLReaderIterator【参考方案2】:

编辑:哦,你想返回父块?一会儿。

这是一个将所有邮政编码提取到数组中的示例。

http://codepad.org/kHss4MdV

<?php

$string='<lineups country="USA">
 <headend headendId="TX02217">
  <name>Grande Gables at The Terrace</name>
  <mso msoId="17541">Grande Communications</mso>
  <marketIds>
   <marketId type="DMA">635</marketId>
  </marketIds>
  <postalCodes>
   <postalCode>11111</postalCode>
   <postalCode>22222</postalCode>
   <postalCode>33333</postalCode>
   <postalCode>78746</postalCode>
  </postalCodes>
  <location>Austin</location>
  <lineup>
   <station prgSvcId="20014">
    <chan effDate="2006-01-16" tier="1">002</chan>
   </station>
   <station prgSvcId="10722">
    <chan effDate="2006-01-16" tier="1">003</chan>
   </station>
  </lineup>
  <areasServed>
   <area>
    <community>Thorndale</community>
    <county code="45331" size="D">Milam</county>
    <state>TX</state>
   </area>
   <area>
    <community>Thrall</community>
    <county code="45491" size="B">Williamson</county>
    <state>TX</state>
   </area>
  </areasServed>
 </headend></lineups>';

$dom = new DOMDocument();
$dom->loadXML($string);

$xpath = new DOMXPath($dom);
$elements= $xpath->query('//lineups/headend/postalCodes/*[text()=78746]');

if (!is_null($elements)) 
  foreach ($elements as $element) 
    echo "<br/>[". $element->nodeName. "]";

    $nodes = $element->childNodes;
    foreach ($nodes as $node) 
      echo $node->nodeValue. "\n";
    
  

输出:

<br/>[postalCode]78746

【讨论】:

会像if(count($nodes)) echo $string; 一样简单,而不是 foreach 还是有更多? 因为文件太大(可能是演出或更多),我认为解决它的最佳方法是使用 XMLReader 逐个节点。我无法预加载文件,因为它太大了。我不想像 中包含的其他信息一样打印出邮政编码。我想查看一个 块是否包含某个邮政编码,如果是,我想回显整个 块。

以上是关于使用 PHP 和 XMLReader 解析 XML的主要内容,如果未能解决你的问题,请参考以下文章

使用 PHP XMLReader 检测 XML 自闭标签

PHP XMLReader 获取父节点?

使用 XMLreader 读取和解析大型 XML 文件。空值问题

让 PHP 的 XMLReader 不会在无效文档中抛出 php 错误

使用 XMLReader 解析大 XML 文件

XMLReader 是 SAX 解析器、DOM 解析器,还是两者都不是?