【问题标题】:Selecting parent nodes with XMLReader使用 XMLReader 选择父节点
【发布时间】:2015-06-14 21:06:30
【问题描述】:

我不得不重写部分程序以使用 XMLReader 选择 XML 文件的部分进行处理。

以这个简化的 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."')]/.."
    );

将选择整个 &lt;bet&gt; 节点及其子节点(&lt;selection&gt; 节点)。

然而,我的代码只会选择一个具有给定selectionid&lt;selection&gt; 节点:

$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;
    }
}

如何复制xpath()XMLReader 的行为,以便我选择&lt;bet&gt; 节点及其子节点,而不仅仅是一个&lt;selection&gt; 子节点?

我想问题归结为:我可以通过孩子的属性值选择整个父节点&lt;bet&gt;,例如&lt;selection selectionid="[some_value]"&gt;?

【问题讨论】:

  • 2 件事。实际上,您的 XML 中只有一个选择节点。其次,您的代码应该选择当前的“事件”注释,而不是“投注”节点。 'event' 注释具有 'eventid' 属性。
  • @YasenZhelev,抱歉我已经进行了编辑。事实上,我的代码中有一个变量$value。它也被封装在一个类方法中。我只是复制了简化的表示形式——与 XML 相同——它要大得多。我想问题归结为:我可以通过孩子的属性值选择整个父节点&lt;bet&gt;,例如&lt;selection selectionid="[some_value]"&gt;?
  • 好的,检查我更新的答案。我认为它会帮助您解决问题。

标签: php xml xpath xml-parsing


【解决方案1】:

[忽略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());
    }
}

【讨论】:

  • 我可以对整个 XML 文件的 SimpleXMLElement 执行 xpath() 查询。然而,这并不实用,因为 XML 文件为 14MB。我求助于使用 XMLReader 遍历 XML 以仅选择我需要的节点,然后才将节点转换为 SimpleXMLElement。这与将整个 XML 文件加载为 SimpleXMLElement 导致的性能问题有关。
  • 实际上,当有其他可用的 XML 库时,永远不应该使用SimpleXML,因为它很快就会崩溃。 XMLReader 不支持 XPath - 为什么不简单地使用 DOMXPath?参见例如ibm.com/developerworks/library/x-xpathphp.
  • 嗯,问题是针对 XMLReader 的。我错误地使用了 SimpleXML,并将其作为答案的一部分,只是为了不中断对话。
  • @YasenZhelev 非常感谢您提供的帮助,绝对值得一票。您的代码无论如何都应该完成这项工作,但显然,PHP 在循环中执行如此多的对象创建操作时遇到了麻烦。我之前注意到我的源 XML 文件非常广泛。这可能是我遇到代码过早退出循环的原因。我用不同的配置对其进行了测试。它按预期遍历 XML DOM,但在分配多个对象时遇到了问题。
  • @YasenZhelev 我刚刚再次测试了代码,发现如果您更改$reader-&gt;readInnerXML() itno $reader-&gt;readOuterXML(),它将完成这项工作。如果您认为合适,请修改它,这将是公认的答案。谢谢!
【解决方案2】:

XMLReader 可以将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 sn-ps 将足够小,您可以使用expand()

【讨论】:

    【解决方案3】:

    DOMXPath 据说在性能方面比SimpleXML 更健壮(它还有其他优点,例如它可以正确处理命名空间)。有关 PHP 中几个 XPath 库的讨论,请参见 this IBM article

    我只是好奇在使用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);
    }
    ?>
    

    将您显示的小sn-p作为输入,结果是

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

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

    【讨论】:

    • 只有一个问题。如果DOMXPath 将整个 xml 文件加载到内存中,并且通过对SimpleXMLElement 执行操作,其行为类似于simplexml_load_file,那么由于性能下降,我将无法使用它。我还没有对此进行测试,但我肯定会。
    • @luqo33 确实如此,但正如我所说,我很好奇 SimpleXMLDOMXPath 在这方面是否有任何区别 - 所以请尽快告诉我有机会测试一下。
    • 请注意,我使用XMLReader 遍历文件,然后仅将我需要的节点转换为SimpleXMLElement 以便对它们执行进一步的操作。我想避免将整个 XML 加载为 SimpleXMLElement 对象。到目前为止,我已经设法看到这种方法可以大大加快速度。问题是XMLReader的节点选择能力不如xpath()的强大
    • @luqo33 是的,我知道 - 我也阅读了您之前的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-25
    • 2017-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多