Come usare XMLReader in PHP?

Ho il seguente file XML, il file è piuttosto grande e non sono stato in grado di get simplexml per aprire e leggere il file, quindi sto cercando XMLReader senza successo in 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> 

Purtroppo non ho trovato un buon tutorial su questo per PHP e mi piacerebbe vedere come posso get each contenuto di elementi da memorizzare in un database.

Tutto dipende da quanto è grande l'unità di lavoro, ma suppongo che tu stia cercando di trattare each nodo <product/> in successione.

Per questo, il modo più semplice sarebbe utilizzare XMLReader per accedere a ciascun nodo, quindi utilizzare SimpleXML per accedervi. In questo modo, si mantiene basso l'utilizzo della memory perché si sta trattando un nodo alla volta e si sfrutta ancora la facilità d'uso di SimpleXML. Per esempio:

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

Rapida panoramica dei pro e contro dei diversi approcci:

Solo XMLReader

  • Pro: veloce, usa poca memory

  • Contro: eccessivamente difficile da scrivere ed eseguire il debug, richiede un sacco di codice userland per fare qualcosa di utile. Il codice Userland è lento e sobject a errori. Inoltre, ti lascia con più linee di codice da mantenere

XMLReader + SimpleXML

  • Pro: non usa molta memory (solo la memory necessaria per elaborare un nodo) e SimpleXML è, come suggerisce il nome, davvero facile da usare.

  • Contro: la creazione di un object SimpleXMLElement per each nodo non è molto veloce. Devi davvero confrontarlo per capire se è un problema per te. Anche una macchina modesta sarebbe in grado di elaborare migliaia di nodes al secondo.

XMLReader + DOM

  • Pro: utilizza circa la quantità di memory di SimpleXML e XMLReader :: expand () è più veloce rispetto alla creazione di un nuovo SimpleXMLElement. Vorrei che fosse ansible usare simplexml_import_dom() ma in quel caso non sembra funzionare

  • Contro: DOM è fastidioso con cui lavorare. È a metà strada tra XMLReader e SimpleXML. Non così complicato e scomodo come XMLReader, ma lontano anni luce dal lavoro con SimpleXML.

Il mio consiglio: scrivere un prototipo con SimpleXML, vedere se funziona per voi. Se la prestazione è fondamentale, prova DOM. Rimani il più lontano ansible da XMLReader. Ricorda che più codice scrivi, più alta è la possibilità di introdurre bug o di introdurre regressioni di performance.

Per xml formattato con attributi …

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> 

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

La maggior parte della mia vita di analisi XML viene utilizzata per estrarre le informazioni utili dai carichi di XML (Amazon MWS). In quanto tale, la mia risposta presuppone che si desidera solo informazioni specifiche e si sa where si trova.

Trovo che il modo più semplice per usare XMLReader è sapere quali tag voglio che le informazioni escano e usarle. Se conosci la struttura dell'XML e ha molti tag unici, trovo che usare il primo caso sia facile. I casi 2 e 3 servono solo per mostrarti come può essere fatto per tag più complessi. Questo è estremamente veloce; Ho una discussione sulla velocità su Qual è il parser XML più veloce in PHP?

La cosa più importnte da ricordare quando si esegue un parsing basato su tag come questo è usare if ($myXML->nodeType == XMLReader::ELEMENT) {... – che controlla che si tratti solo di aprire nodes e non spazio bianco o nodes di chiusura o qualsiasi altra cosa.

 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 è ben documentato sul sito di PHP . Questo è un XML Parser di pull, il che significa che è usato per iterare attraverso i nodes (o nodes DOM) di un dato documento XML. Ad esempio, puoi esaminare l'integer documento che hai dato in questo modo:

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

Spetta a te decidere come gestire il nodo restituito da 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)); } } 

La risposta accettata mi ha dato un buon inizio, ma ha portto più lezioni e più elaborazione di quanto avrei voluto; quindi questa è la mia interpretazione:

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

Questo argomento è uscito molto tempo fa, ma l'ho appena trovato. Grazie a Dio.

Il mio problema è che devo leggere il file ONIX (dati del libro) e memorizzarlo nel nostro database. Io uso simplexml_load prima e sebbene usasse molta memory ma ancora ok per file relativamente piccoli (fino a 300MB). Oltre quella dimensione è un disastro per me.

Dopo aver letto, specialmente l'interpretazione di Francis Lewis, uso la combinazione di xmlreader e simplexml. Il risultato è eccezionale, l'utilizzo della memory è ridotto e lo inserisco nel database abbastanza velocemente per me.

Ecco il mio codice:

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

Ho paura che usare XmlReader :: expand () possa consumare un bel po 'di RAM quando la sottostruttura non è così piccola. Non sono sicuro che sia una buona alternativa a XmlReader. Tuttavia, sono d'accordo sul fatto che XmlReader sia davvero debole e non è molto adatto per elaborare alberi XML nidificati complessi. In realtà non mi piacciono due cose: innanzitutto, il nodo corrente non ha il suo path nella struttura XML accessibile come properties;, in secondo luogo che non è ansible eseguire l'elaborazione simile a XPath durante la lettura dei nodes. Ovviamente la vera query XPath richiederà molto tempo per XML di grandi size, ma potrebbero essere utilizzati "hook di path", ad esempio quando si triggers il sottoinsieme di routes del path dell'elemento corrente (root). Così ho sviluppato le mie lezioni su XmlReader alcuni anni fa. Non sono perfetti e forse scriverei meglio oggi, tuttavia potrebbe ancora essere utile a qualcuno:

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

Costruisco io stesso il path XML 'node1 / node2', quindi utilizzo gli hook con le corrispondenze PCRE che sono less potenti di XPath, tuttavia erano sufficienti per le mie esigenze. Ho elaborato un XML abbastanza complesso con queste classi.