Як використовувати 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 може допомогти.
mcrumley

Відповіді:


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

  • Плюси: швидкий, використовує мало пам'яті

  • Мінуси: надмірно важко писати та налагоджувати, потрібно багато коду Userland, щоб зробити щось корисне. Код Userland повільний і схильний до помилок. Крім того, це залишає вам більше рядків коду для обслуговування

XMLReader + SimpleXML

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

  • Мінуси: створення об’єкта SimpleXMLElement для кожного вузла не дуже швидке. Ви дійсно повинні порівняти його, щоб зрозуміти, чи це для вас проблема. Навіть скромна машина змогла б обробляти тисячу вузлів в секунду.

XMLReader + DOM

  • Плюси: використовує приблизно стільки пам'яті, скільки SimpleXML, а XMLReader :: expand () швидше, ніж створення нового елемента SimpleXMLElement. Я хотів би, щоб це можна було використовувати, simplexml_import_dom()але це, здається, не працює в такому випадку

  • Мінуси: працювати з DOM дратує. Це на півдорозі між XMLReader та SimpleXML. Не такий складний і незграбний, як XMLReader, але за кілька років від роботи з SimpleXML далеко.

Моя порада: напишіть прототип за допомогою SimpleXML, перевірте, чи він працює для вас. Якщо продуктивність має найбільше значення, спробуйте DOM. Тримайтеся якомога далі від XMLReader. Пам’ятайте, що чим більше коду ви пишете, тим вища можливість вводити помилки або вводити регресії продуктивності.


1
чи є спосіб зробити це суто за допомогою XMLReader чи немає переваг?
Shadi Almosri

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

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 () .


як ви змусите його перейти до наступного вузла після його обробки?
Shadi Almosri

24
Також щодо того, як XMLReader добре задокументований на php.net, я б не погодився, це одна з найгірших задокументованих функцій, яку я бачив, і я вже давно використовую php.net, і це було перше місце, куди я звернувся, щоб вирішити це раніше запитую тут :)
Шаді Алмосрі

Я не впевнений, що ви розумієте, як XMLReader :: read () переходить від одного вузла до іншого. Клас XMLReader також використовує libxml, добре відому бібліотеку, яка також доступна для PHP, якщо ви хочете поглянути на неї.
Перкуціо

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>");
}
?> 

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.