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

79

У меня есть следующий 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 и хотел бы увидеть, как я могу получить содержимое каждого элемента для хранения в базе данных.

Шади Альмосри
источник
1
Вы читали некоторые из примеров, добавленных пользователями в документации PHP? php.net/manual/en/class.xmlreader.php#61929 может помочь.
Макрамли 02

Ответы:

225

Все зависит от того, насколько велика единица работы, но я предполагаю, что вы пытаетесь <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

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

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

XMLReader + SimpleXML

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

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

XMLReader + DOM

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

  • Минусы: работа с DOM утомляет. Это что-то среднее между XMLReader и SimpleXML. Не такой сложный и неудобный, как XMLReader, но на много световых лет от работы с SimpleXML.

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

Джош Дэвис
источник
1
есть ли способ сделать это исключительно с помощью XMLReader или нет никаких преимуществ?
Шади Альмосри 03
2
Вы можете сделать это полностью с помощью XMLReader. Преимущество состоит в том, что он будет быстрее и потребляет немного меньше памяти. Недостатком является то, что запись займет значительно больше времени и будет намного сложнее отлаживать.
Джош Дэвис,
2
Почему вы просто не использовали $ z-> next ('product') при переходе к первому узлу продукта?
благоухающий
Я не помню этот конкретный код, извините. Если бы я не добавил об этом никаких примечаний, возможно, я просто упустил возможность.
Джош Дэвис,
1
Большая часть синтаксического анализа на основе XMLReader может быть выражена / заключена в шаблон итератора. Я собрал для этого несколько полезных итераторов и фильтров: git.io/xmlreaderiterator ( gist )
hakre
10

Для 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();
try5tan3
источник
1
Несмотря на то, что код является гораздо более подробным и ручным способом обхода XML, это сохранит ваше здравомыслие, поскольку DOMDocument и SimpleXML, как правило, заставляют вас гадать, что будет возвращено.
b01
6

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

$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();
Фрэнсис Льюис
источник
6

Большая часть моей жизни, связанной с синтаксическим анализом 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();
}
Иосия
источник
2
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));
    }
}
себоб
источник
2

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();
?>

Затем вам решать, как поступить с узлом, возвращаемым XMLReader :: expand () .

Percutio
источник
как вы заставите его перейти к следующему узлу после того, как он завершит обработку одного?
Шади Альмосри 03
24
Также относительно того, что XMLReader хорошо документирован на php.net, я бы не согласился, это одна из худших документированных функций, которые я видел, и я долгое время использовал php.net, и это было первое место, куда я направился, чтобы решить эту проблему раньше спрашиваю здесь :)
Шади Альмосри 03
Я не уверен, что вы понимаете, как XMLReader :: read () переходит от одного узла к другому. Класс XMLReader также использует libxml, хорошо известную библиотеку, которая также доступна для PHP, если вы хотите взглянуть на нее.
Percutio,
12
Идея о том, что XMLReader хорошо документирован, - нонсенс. Проблема в том, что если вы не знаете, с чего начать, это ничего вам не скажет: предоставление подробного списка методов класса бесполезно, если у вас нет первого представления о том, какие из них следует вызывать.
xgretsch
0

Это работает лучше и быстрее для меня


<html>
<head>
<script>
function showRSS(str) {
  if (str.length==0) {
    document.getElementById("rssOutput").innerHTML="";
    return;
  }
  if (window.XMLHttpRequest) {
    // code for IE7+, Firefox, Chrome, Opera, Safari
    xmlhttp=new XMLHttpRequest();
  } else {  // code for IE6, IE5
    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }
  xmlhttp.onreadystatechange=function() {
    if (this.readyState==4 && this.status==200) {
      document.getElementById("rssOutput").innerHTML=this.responseText;
    }
  }
  xmlhttp.open("GET","getrss.php?q="+str,true);
  xmlhttp.send();
}
</script>
</head>
<body>

<form>
<select onchange="showRSS(this.value)">
<option value="">Select an RSS-feed:</option>
<option value="Google">Google News</option>
<option value="ZDN">ZDNet News</option>
<option value="job">Job</option>
</select>
</form>
<br>
<div id="rssOutput">RSS-feed will be listed here...</div>
</body>
</html> 

** Серверный файл **


<?php
//get the q parameter from URL
$q=$_GET["q"];

//find out which feed was selected
if($q=="Google") {
  $xml=("http://news.google.com/news?ned=us&topic=h&output=rss");
} elseif($q=="ZDN") {
  $xml=("https://www.zdnet.com/news/rss.xml");
}elseif($q == "job"){
  $xml=("https://ngcareers.com/feed");
}

$xmlDoc = new DOMDocument();
$xmlDoc->load($xml);

//get elements from "<channel>"
$channel=$xmlDoc->getElementsByTagName('channel')->item(0);
$channel_title = $channel->getElementsByTagName('title')
->item(0)->childNodes->item(0)->nodeValue;
$channel_link = $channel->getElementsByTagName('link')
->item(0)->childNodes->item(0)->nodeValue;
$channel_desc = $channel->getElementsByTagName('description')
->item(0)->childNodes->item(0)->nodeValue;

//output elements from "<channel>"
echo("<p><a href='" . $channel_link
  . "'>" . $channel_title . "</a>");
echo("<br>");
echo($channel_desc . "</p>");

//get and output "<item>" elements
$x=$xmlDoc->getElementsByTagName('item');

$count = $x->length;

// print_r( $x->item(0)->getElementsByTagName('title')->item(0)->nodeValue);
// print_r( $x->item(0)->getElementsByTagName('link')->item(0)->nodeValue);
// print_r( $x->item(0)->getElementsByTagName('description')->item(0)->nodeValue);
// return;

for ($i=0; $i <= $count; $i++) {
  //Title
  $item_title = $x->item(0)->getElementsByTagName('title')->item(0)->nodeValue;
  //Link
  $item_link = $x->item(0)->getElementsByTagName('link')->item(0)->nodeValue;
  //Description
  $item_desc = $x->item(0)->getElementsByTagName('description')->item(0)->nodeValue;
  //Category
  $item_cat = $x->item(0)->getElementsByTagName('category')->item(0)->nodeValue;


  echo ("<p>Title: <a href='" . $item_link
  . "'>" . $item_title . "</a>");
  echo ("<br>");
  echo ("Desc: ".$item_desc);
   echo ("<br>");
  echo ("Category: ".$item_cat . "</p>");
}
?> 

Адели Айодеджи
источник