使用 XMLReader 选择父节点

Selecting parent nodes with XMLReader

我不得不重写部分程序以使用 XMLReader 来处理 XML 文件的 select 部分。

以这个简化的XML为例:

<odds>
    <sport>
        <region>
            <group>
                <event name="English Championship 2014-15" eventid="781016.1">
                    <bet name="Kazanan" betid="12377108.1">
                        <selection selectionid="52411062.1"/>
                        </selection>
                    </bet>
                </event>
            </group>
        </region>
    </sport>
</odds> 

xpath() 的调用:

$bets = $xml->xpath(
    "//odds/sport/region/group/event/bet/selection[contains(@selectionid,'".$selectionToFind."')]/.."
    );

将 select 整个 <bet> 节点及其子节点(<selection> 节点)。

但是,我的代码 select 只有一个 <selection> 节点具有给定的 selectionid:

$reader = new XMLReader;
$reader->open('file.xml');

while($reader->read()) {
    $event = $reader->getAttribute($value); 

    if ($event == 781016.1 ) {
        $node = new SimpleXMLElement($reader->readOuterXML());
        var_dump($node);
        break;
    }
}

如何用 XMLReader 复制 xpath() 的行为,以便我 select <bet> 节点及其子节点,而不仅仅是一个 <selection> 子节点?

我想这个问题可以归结为:我可以 select 整个父节点 <bet> 通过子节点的属性值,例如<selection selectionid="[some_value]">?

[忽略SimpleXML方案,低头看XMLReader一个]

我建议使用 SimpleXMLElement::xpath 方法。

http://php.net/manual/en/simplexmlelement.xpath.php

$xml = new SimpleXMLElement($xml_string);

/* Search for <a><b><c> */
$result = $xml->xpath("/odds/sport/region/group/event/bet");

$result 将包含 'bet' 注释的所有子项。

// XMLReader 解决方案 **********************

$reader = new XMLReader;
$reader->open('file.xml');
$parent_element = null;

while($reader->read()) {
    $selectionid = $reader->getAttribute('selectionid'); 

    if ($selectionid == '52411062.1' ) {
        // use the parent of the node with attribute 'selectionid' = '52411062.1'
        $node = $parent_element;
        var_dump($node);
        break;
    }
    elseif ($reader->name === 'bet') { )
    {
        // store parent element
        $parent_element = new SimpleXMLElement($reader->readOuterXML());
    }
}

DOMXPath is said to be more robust than SimpleXML with respect to performance (it has other advantages, e.g. it can properly deal with namespaces). See for example this IBM article 在 PHP.

中讨论了几个 XPath 库

我很好奇使用 DOMXPath:

时您的性能问题是否会持续存在(或仍然如此严重)
<?php

$doc = new DOMDocument;
$doc->load('sample.xml');
$xpath = new DOMXPath($doc);

$nodes = $xpath->query("/odds/sport/region/group/event/bet[selection/@selectionid = '52411062.1']");

foreach ($nodes as $node)
{
   print $xml = $node->ownerDocument->saveXML($node);
}
?>

将您显示的小片段作为输入的结果是

<bet name="Kazanan" betid="12377108.1">
    <selection selectionid="52411062.1"/>
</bet>

如果这没有帮助,您真的必须求助于基于事件的(拉式)XML 解析器,它不会像 Yasen 所建议的那样将整个文档读入内存。

XML读者可以expand()将当前节点变成DOMNode。这只会将节点及其后代加载到内存中。

之后,您可以使用DOMXPath实例或将节点转换为SimpleXMLElement

$reader = new XMLReader();
$reader->open('data:/text/xml,'.urlencode($xml));

$dom = new DOMDocument();
$xpath = new DOMXpath($dom);

while($reader->read()) {
  if (
    $reader->nodeType == XMLReader::ELEMENT && 
    $reader->localName == 'bet'
  ) {
    $bet= $reader->expand($dom);
    if ($xpath->evaluate('count(selection[@selectionid = "52411062.1"]) > 0', $bet)) {
      var_dump($dom->saveXml($bet));
    }
  }
}

您将始终需要决定在 XMLReader 中实现哪些部分,在 DOM/SimpleXML 中实现哪些部分。在 XMLReader 中,您将必须验证节点并维护状态,但可以避免加载数据。在解析的某一时刻,XML 片段将足够小,您可以使用 expand().