Мне пришлось переписать часть программы, чтобы использовать 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."')]/.." );
выберет весь узел <bet>
и его дочерние узлы ( <selection>
).
Мой код, однако, выберет только один узел <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; } }
Как можно реплицировать поведение xpath()
с помощью XMLReader
чтобы я выбрал узел <bet>
и его дочерние XMLReader
, а не только один XMLReader
<selection>
?
Я думаю, что вопрос сводится к следующему: могу ли я выбрать весь родительский узел <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 будет содержать всех детей с надписью «ставка».
// Решение 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
считается более надежным, чем SimpleXML
отношении производительности (он имеет другие преимущества, например, он может правильно обрабатывать пространства имен). См., Например, эту статью IBM для обсуждения нескольких библиотек XPath в PHP.
Мне просто интересно, если ваша проблема с производительностью будет сохраняться (или все еще быть такой же серьезной) при использовании 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-парсеру, основанному на событиях (pull-style), который не читает весь документ в памяти, как предлагает Ясен.
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 будут достаточно малы, и вы можете использовать expand()
.