Jak korzystać z XMLReader w PHP?

79

Mam następujący plik XML, plik jest dość duży i nie udało mi się otworzyć i odczytać pliku simplexml, więc próbuję XMLReader bez powodzenia w 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>

Niestety nie znalazłem dobrego samouczka na ten temat dla PHP i chciałbym zobaczyć, jak mogę uzyskać zawartość każdego elementu do przechowywania w bazie danych.

Shadi Almosri
źródło
1
Czy przeczytałeś kilka przykładów dodanych przez użytkowników w dokumentacji PHP? php.net/manual/en/class.xmlreader.php#61929 może pomóc.
mcrumley

Odpowiedzi:

225

Wszystko zależy od tego, jak duża jest jednostka pracy, ale myślę, że próbujesz traktować każdy <product/>węzeł po kolei.

W tym celu najprostszym sposobem byłoby użycie XMLReader, aby dostać się do każdego węzła, a następnie użyć SimpleXML, aby uzyskać do nich dostęp. W ten sposób utrzymujesz niskie zużycie pamięci, ponieważ przetwarzasz jeden węzeł na raz i nadal korzystasz z łatwości użytkowania SimpleXML. Na przykład:

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

Szybki przegląd zalet i wad różnych podejść:

Tylko XMLReader

  • Zalety: szybki, zużywa mało pamięci

  • Wady: zbyt trudne do napisania i debugowania, wymaga dużej ilości kodu w przestrzeni użytkownika, aby zrobić cokolwiek pożytecznego. Kod obszaru użytkownika jest powolny i podatny na błędy. Dodatkowo pozostawia ci więcej linii kodu do utrzymania

XMLReader + SimpleXML

  • Zalety: nie zajmuje dużo pamięci (tylko pamięć potrzebna do przetworzenia jednego węzła), a SimpleXML jest, jak sama nazwa wskazuje, naprawdę łatwy w użyciu.

  • Wady: tworzenie obiektu SimpleXMLElement dla każdego węzła nie jest zbyt szybkie. Naprawdę musisz to porównać, aby zrozumieć, czy to dla ciebie problem. Jednak nawet skromna maszyna byłaby w stanie przetworzyć tysiąc węzłów na sekundę.

XMLReader + DOM

  • Zalety: zużywa mniej więcej tyle pamięci co SimpleXML, a XMLReader :: expand () jest szybsze niż tworzenie nowego elementu SimpleXMLE. Chciałbym, żeby było to możliwe, simplexml_import_dom()ale w tym przypadku wydaje się, że nie działa

  • Wady: praca z DOM jest irytująca. Jest w połowie drogi między XMLReader i SimpleXML. Nie tak skomplikowane i niewygodne jak XMLReader, ale lata świetlne od pracy z SimpleXML.

Moja rada: napisz prototyp za pomocą SimpleXML, sprawdź, czy zadziała. Jeśli najważniejsza jest wydajność, wypróbuj DOM. Trzymaj się jak najdalej od XMLReadera. Pamiętaj, że im więcej napiszesz kodu, tym większe prawdopodobieństwo wprowadzenia błędów lub regresji wydajności.

Josh Davis
źródło
1
czy istnieje sposób, aby to zrobić wyłącznie za pomocą XMLReader, czy też nie ma żadnej korzyści?
Shadi Almosri
2
Możesz to zrobić całkowicie za pomocą XMLReader. Zaletą jest to, że byłby szybszy i zużywałby nieznacznie mniej pamięci. Wadą jest to, że pisanie zajęłoby znacznie więcej czasu, a debugowanie byłoby znacznie trudniejsze.
Josh Davis,
2
Dlaczego po prostu nie użyłeś $ z-> next ('product') podczas przechodzenia do pierwszego węzła produktu?
pachnący
Nie pamiętam tego konkretnego kodu, przepraszam. Gdybym nie dodał żadnej notatki na ten temat, może po prostu przeoczyłem taką możliwość.
Josh Davis,
1
Większość analizy opartej na XMLReader może być wyrażona / opakowana we wzorzec iteratora. Skompilowałem kilka przydatnych iteratorów i filtrów do tego: git.io/xmlreaderiterator ( gist )
hakre
10

W przypadku XML sformatowanego przy użyciu atrybutów ...

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>

kod 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
źródło
1
Mimo że kod jest dużo bardziej rozwlekłym i ręcznym sposobem przechodzenia przez XML, oszczędza to twój rozsądek, ponieważ DOMDocument i SimpleXML mają skłonność do zgadywania, co zostanie zwrócone.
b01
6

Zaakceptowana odpowiedź dała mi dobry początek, ale przyniosła więcej zajęć i więcej przetwarzania, niż bym chciał; więc oto moja interpretacja:

$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();
Francis Lewis
źródło
6

Większość mojego życia na analizowaniu XML spędzam na wydobywaniu fragmentów przydatnych informacji z ciężarówek XML (Amazon MWS). W związku z tym moja odpowiedź zakłada, że ​​chcesz tylko określonych informacji i wiesz, gdzie się one znajdują.

Uważam, że najłatwiejszym sposobem korzystania z XMLReader jest wiedzieć, z których tagów chcę usunąć informacje i ich używać. Jeśli znasz strukturę XML i zawiera on wiele unikalnych tagów, stwierdzam, że użycie pierwszego przypadku jest łatwe. Przypadki 2 i 3 mają po prostu pokazać, jak można to zrobić w przypadku bardziej złożonych tagów. To jest niezwykle szybkie; Mam dyskusję na temat szybkości. Jaki jest najszybszy parser XML w PHP?

Najważniejszą rzeczą do zapamiętania podczas przeprowadzania analizowania opartego na tagach, takiego jak ten, jest użycie if ($myXML->nodeType == XMLReader::ELEMENT) {...- które sprawdza, czy mamy do czynienia tylko z otwieranymi węzłami, a nie z białymi znakami, zamykającymi węzłami lub czymkolwiek.

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();
}
Josiah
źródło
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));
    }
}
sebob
źródło
2

XMLReader jest dobrze udokumentowany na stronie PHP . To jest XML Pull Parser, co oznacza, że ​​jest używany do iteracji przez węzły (lub węzły DOM) danego dokumentu XML. Na przykład możesz przejrzeć cały przekazany dokument w ten sposób:

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

Od Ciebie zależy, jak postąpić z węzłem zwróconym przez XMLReader :: expand () .

Percutio
źródło
jak sprawisz, że przejdzie do następnego węzła po zakończeniu przetwarzania jednego?
Shadi Almosri
24
Również co do tego, że XMLReader jest dobrze udokumentowany na php.net, nie zgadzam się, jest to jedna z najgorzej udokumentowanych funkcji, jakie widziałem i używam php.net przez długi czas i było to pierwsze miejsce, w którym chciałem rozwiązać ten problem pytanie tutaj :)
Shadi Almosri,
Nie jestem pewien, czy rozumiesz sposób, w jaki XMLReader :: read () przechodzi z jednego węzła do drugiego. Klasa XMLReader również używa libxml, dobrze znanej biblioteki, która jest również dostępna dla PHP, jeśli chcesz się jej przyjrzeć.
Percutio,
12
Pomysł, że XMLReader jest dobrze udokumentowany, jest nonsensem. Problem polega na tym, że jeśli nie wiesz, od czego zacząć, nigdzie ci to nie powie: podanie listy metod klasowych do prania jest bezużyteczne, jeśli nie masz pierwszego pomysłu, które z nich wywołać.
xgretsch
0

To działa lepiej i szybciej dla mnie


<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> 

** Plik zaplecza **


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

Adeleye Ayodeji
źródło