Как использовать XMLReader в PHP?

У меня есть следующий файл XML, файл довольно большой, и я не смог заставить simplexml открывать и читать файл, поэтому я пытаюсь использовать XMLReader без успеха в php

<?xml version="1.0" encoding="ISO-8859-1"?> <products> <last_updated>2009-11-30 13:52:40</last_updated> <product> <element_1>foo</element_1> <element_2>foo</element_2> <element_3>foo</element_3> <element_4>foo</element_4> </product> <product> <element_1>bar</element_1> <element_2>bar</element_2> <element_3>bar</element_3> <element_4>bar</element_4> </product> </products> 

Я, к сожалению, не нашел хорошего учебника по этому поводу для PHP и хотел бы узнать, как я могу получить содержимое каждого элемента для хранения в базе данных.

Все зависит от того, насколько велика единица работы, но я думаю, вы пытаетесь обрабатывать каждый узел <product/> подряд.

Для этого самым простым способом было бы использовать XMLReader для доступа к каждому узлу, а затем использовать SimpleXML для доступа к ним. Таким образом, вы сохраняете низкое потребление памяти, потому что вы обрабатываете один узел за раз, и вы все еще используете простоту использования SimpleXML. Например:

 $z = new XMLReader; $z->open('data.xml'); $doc = new DOMDocument; // move to the first <product /> node while ($z->read() && $z->name !== 'product'); // now that we're at the right depth, hop to the next <product/> until the end of the tree while ($z->name === 'product') { // either one should work //$node = new SimpleXMLElement($z->readOuterXML()); $node = simplexml_import_dom($doc->importNode($z->expand(), true)); // now you can use $node without going insane about parsing var_dump($node->element_1); // go to next <product /> $z->next('product'); } 

Быстрый обзор плюсов и минусов различных подходов:

Только XMLReader

  • Плюсы: быстрый, мало памяти

  • Минусы: чрезмерно трудно писать и отлаживать, требует много кода пользователя, чтобы сделать что-нибудь полезное. Код Userland медленный и подвержен ошибкам. Кроме того, он оставляет вам больше строк кода для

XMLReader + SimpleXML

  • Плюсы: не использует много памяти (только память, необходимая для обработки одного узла), и SimpleXML, как следует из названия, очень проста в использовании.

  • Минусы: создание объекта SimpleXMLElement для каждого узла происходит не очень быстро. Вам действительно нужно сравнить его, чтобы понять, является ли это проблемой для вас. Однако даже скромная машина сможет обрабатывать тысячу узлов в секунду.

XMLReader + DOM

  • Плюсы: использует примерно столько же памяти, как SimpleXML, а XMLReader :: expand () быстрее, чем создание нового элемента SimpleXMLElement. Мне хотелось бы использовать simplexml_import_dom() но в этом случае он не работает

  • Минусы: DOM раздражает работать. Это на полпути между XMLReader и SimpleXML. Не так сложно и неудобно, как XMLReader, но в несколько световых лет от работы с SimpleXML.

Мой совет: напишите прототип с SimpleXML, посмотрите, работает ли он для вас. Если производительность имеет первостепенное значение, попробуйте DOM. Оставайтесь как можно дальше от XMLReader. Помните, что чем больше кода вы пишете, тем выше вероятность того, что вы вводите ошибки или вводите регрессии производительности.

Для xml, отформатированных с атрибутами …

data.xml:

 <building_data> <building address="some address" lat="28.902914" lng="-71.007235" /> <building address="some address" lat="48.892342" lng="-75.0423423" /> <building address="some address" lat="58.929753" lng="-79.1236987" /> </building_data> 

PHP-код:

 $reader = new XMLReader(); if (!$reader->open("data.xml")) { die("Failed to open 'data.xml'"); } while($reader->read()) { if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'building') { $address = $reader->getAttribute('address'); $latitude = $reader->getAttribute('lat'); $longitude = $reader->getAttribute('lng'); } $reader->close(); 

Большая часть моей работы по анализу XML тратится на извлечение самородков полезной информации из грузовиков XML (Amazon MWS). Таким образом, мой ответ предполагает, что вы хотите получить только конкретную информацию, и знаете, где она находится.

Я считаю, что самый простой способ использования XMLReader – узнать, какие теги я хочу получить из них и использовать. Если вы знаете структуру XML и у нее много уникальных тегов, я считаю, что использование первого случая – это просто. Случаи 2 и 3 – это просто показать вам, как это можно сделать для более сложных тегов. Это очень быстро; У меня есть обсуждение скорости над тем, что является самым быстрым парсером XML в PHP?

Самое важное, что нужно учитывать при анализе на основе тегов, это использовать if ($myXML->nodeType == XMLReader::ELEMENT) {... – который проверяет, что мы имеем дело только с открытием узлов и а не пробельные или закрывающие узлы или что-то еще.

 function parseMyXML ($xml) { //pass in an XML string $myXML = new XMLReader(); $myXML->xml($xml); while ($myXML->read()) { //start reading. if ($myXML->nodeType == XMLReader::ELEMENT) { //only opening tags. $tag = $myXML->name; //make $tag contain the name of the tag switch ($tag) { case 'Tag1': //this tag contains no child elements, only the content we need. And it's unique. $variable = $myXML->readInnerXML(); //now variable contains the contents of tag1 break; case 'Tag2': //this tag contains child elements, of which we only want one. while($myXML->read()) { //so we tell it to keep reading if ($myXML->nodeType == XMLReader::ELEMENT && $myXML->name === 'Amount') { // and when it finds the amount tag... $variable2 = $myXML->readInnerXML(); //...put it in $variable2. break; } } break; case 'Tag3': //tag3 also has children, which are not unique, but we need two of the children this time. while($myXML->read()) { if ($myXML->nodeType == XMLReader::ELEMENT && $myXML->name === 'Amount') { $variable3 = $myXML->readInnerXML(); break; } else if ($myXML->nodeType == XMLReader::ELEMENT && $myXML->name === 'Currency') { $variable4 = $myXML->readInnerXML(); break; } } break; } } } $myXML->close(); } 

XMLReader хорошо документирован на PHP-сайте . Это XML Pull Parser, что означает, что он используется для итерации через узлы (или DOM-узлы) данного XML-документа. Например, вы можете пройти весь документ, который вы указали так:

 <?php $reader = new XMLReader(); if (!$reader->open("data.xml")) { die("Failed to open 'data.xml'"); } while($reader->read()) { $node = $reader->expand(); // process $node... } $reader->close(); ?> в <?php $reader = new XMLReader(); if (!$reader->open("data.xml")) { die("Failed to open 'data.xml'"); } while($reader->read()) { $node = $reader->expand(); // process $node... } $reader->close(); ?> 

Тогда вам решать, как обращаться с узлом, возвращаемым XMLReader :: expand () .

 Simple example: public function productsAction() { $saveFileName = 'ceneo.xml'; $filename = $this->path . $saveFileName; if(file_exists($filename)) { $reader = new XMLReader(); $reader->open($filename); $countElements = 0; while($reader->read()) { if($reader->nodeType == XMLReader::ELEMENT) { $nodeName = $reader->name; } if($reader->nodeType == XMLReader::TEXT && !empty($nodeName)) { switch ($nodeName) { case 'id': var_dump($reader->value); break; } } if($reader->nodeType == XMLReader::END_ELEMENT && $reader->name == 'offer') { $countElements++; } } $reader->close(); exit(print('<pre>') . var_dump($countElements)); } } 

Принятый ответ дал мне хороший старт, но принес больше классов и больше обработки, чем мне хотелось бы; так это моя интерпретация:

 $xml_reader = new XMLReader; $xml_reader->open($feed_url); // move the pointer to the first product while ($xml_reader->read() && $xml_reader->name != 'product'); // loop through the products while ($xml_reader->name == 'product') { // load the current xml element into simplexml and we're off and running! $xml = simplexml_load_string($xml_reader->readOuterXML()); // now you can use your simpleXML object ($xml). echo $xml->element_1; // move the pointer to the next product $xml_reader->next('product'); } // don't forget to close the file $xml_reader->close(); 

Этот вопрос давно ушел, но я только что нашел его. Слава Богу.

Моя проблема в том, что я должен прочитать файл ONIX (данные книги) и сохранить его в нашей базе данных. Я использую simplexml_load раньше, и хотя он использовал много памяти, но все еще хорошо для относительно небольшого файла (до 300 МБ). Вне этого размера это катастрофа для меня.

После прочтения, особенно интерпретации Фрэнсиса Льюиса, я использую комбинацию xmlreader и simplexml. Результат является исключительным, использование памяти невелико и вставляет его в базу данных достаточно быстро для меня.

Вот мой код:

 <?php $dbhost = "localhost"; // mysql host $dbuser = ""; //mysql username $dbpw = ""; // mysql user password $db = ""; // mysql database name //i need to truncate the old data first $conn2 = mysql_connect($dbhost, $dbuser, $dbpw); mysql_select_db($db); mysql_query ("truncate ebiblio",$conn2); //$xmlFile = $_POST['xmlFile']; //$xml=simplexml_load_file("ebiblio.xml") or die("Error: Cannot create object"); $reader = new XMLReader(); //load the selected XML file to the DOM if (!$reader->open("ebiblio.xml")) { die("Failed to open 'ebiblio.xml'"); } while ($reader->read()): if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'product'){ $xml = simplexml_load_string($reader->readOuterXML()); $productcode = (string)$xml->a001; $title = (string)$xml->title->b203; $author = (string)$xml->contributor->b037; $language = (string)$xml->language->b252; $category = $xml->subject->b069; $description = (string)$xml->othertext->d104; $publisher = (string)$xml->publisher->b081; $pricecover = (string)$xml->supplydetail->price->j151; $salesright = (string)$xml->salesrights->b090; @$productcode1 = htmlentities($productcode,ENT_QUOTES,'latin1_swedish_ci'); @$title1 = htmlentities($title,ENT_QUOTES,'latin1_swedish_ci'); @$author1 = htmlentities($author,ENT_QUOTES,'latin1_swedish_ci'); @$language1 = htmlentities($language,ENT_QUOTES,'latin1_swedish_ci'); @$category1 = htmlentities($category,ENT_QUOTES,'latin1_swedish_ci'); @$description1 = htmlentities($description,ENT_QUOTES,'latin1_swedish_ci'); @$publisher1 = htmlentities($publisher,ENT_QUOTES,'latin1_swedish_ci'); @$pricecover1 = htmlentities($pricecover,ENT_QUOTES,'latin1_swedish_ci'); @$salesright1 = htmlentities($salesright,ENT_QUOTES,'latin1_swedish_ci'); $conn = mysql_connect($dbhost, $dbuser, $dbpw); mysql_select_db($db); $sql = "INSERT INTO ebiblio VALUES ('" . $productcode1 . "','" . $title1 . "','" . $author1 . "','" . $language1 . "','" . $category1 . "','" . $description1 . "','" . $publisher1 . "','" . $pricecover1 . "','" . $salesright1 . "')"; mysql_query($sql, $conn); $reader->next('product'); } endwhile; ?> 

Я боюсь, что использование XmlReader :: expand () может потреблять довольно много ОЗУ, когда поддерево не так мало. Я не уверен, что это хорошая альтернатива XmlReader. Однако я согласен с тем, что XmlReader действительно слаб и не очень подходит для обработки сложных вложенных XML-деревьев. Мне действительно не нравятся две вещи: во-первых, этот текущий узел не имеет своего пути в дереве XML, доступном как свойство, во-вторых, что при чтении узлов не удается запустить XPath-подобную обработку. Конечно, реальный запрос XPath будет очень трудоемким для больших XML, но вместо этого можно использовать «крючки пути» – например, когда текущий путь к элементу соответствует корневому поддереву, запускается функция / метод PHP. Таким образом, несколько лет назад я разработал свои собственные классы поверх XmlReader. Они не идеальны, и, возможно, я бы лучше написал сегодня, но все же может быть полезен кому-то:

https://bitbucket.org/sdvpartnership/questpc-framework/src/c481a8b051dbba0a6644ab8a77a71e58119e7441/includes/Xml/Reader/?at=master

Я сам создаю XML-путь 'node1 / node2', а затем использовал крючки с совпадениями PCRE, которые менее эффективны, чем XPath, однако для моих нужд было достаточно. Я обработал довольно сложный большой XML с этими классами.