2010-08-04 5 views
7

Ho trascorso intere giornate con le funzioni DOM di PHP ma non riesco a capire come funziona ancora. :( Ho un semplice file XML che sembra ok, ma non posso usarlo come penso, quando ho creato la sua strutturaSemplificazione dell'analisi XML DOM in PHP: come?

Esempio frammento XML:.

-pages //root element 
    -page id="1" //we can have any number of pages 
     -product id="364826" //we can have any number of products 
      -SOME_KIND_OF_VALUE 
      -ANOTHER_VALUE 
      ... 

La mia idea originale era di accelerare il flusso di lavoro del mio cliente in modo butto fuori vecchi CSV e iniziato a utilizzare XMLs

Problema 1:.. Quando ho raggruppamento di prodotti in pagina sto usando setIdAttribute per impedire la memorizzazione nella stessa pagina nella struttura di più di una volta questo funziona bene fino a che la lettura non accada perché questi ID sono legati a qualche tipo di DTD (basato su getElementById).

Domanda 1: come posso scrivere un semplice DTD che fornisce tali informazioni necessarie in modo da posso usare getElementById in fase di lettura troppo?

Problema 2: Perché ho pagine che mi piacerebbe caricare meno informazioni che posso. Questo è il motivo per cui ho creato l'attributo id sulle pagine. Ora non posso accedere direttamente alla mia pagina id = "2" perché il problema 1 sopra (getElementById non ha senso al momento). In qualche modo posso riuscito a recuperare le informazioni necessarie su ogni prodotto su una determinata pagina, ma il mio codice sembra spaventoso:

$doc  = DOMDocument::load('data.xml'); 
$xpath = new DOMXPath($doc); 
$query = '/pages/page[' . $page . ']'; //$page is fine: was set earlier 
$products = $xpath->query($query); 
$_prods = $doc->getElementsByTagName('product'); 
foreach($_prods as $product){ 
    foreach($product->childNodes as $node){ 
     echo $node->nodeName . ": " . $node->nodeValue . "<br />"; 
    } 
} 

Queston 2: Credo che il codice di cui sopra è l'esempio su come non analizzare un XML. Ma a causa della mia conoscenza limitata delle funzioni DOM di PHP, non posso scriverne uno più pulito da solo. Ho provato qualche soluzione banale ma nessuno di loro ha funzionato per me.

Per favore aiutatemi se potete.

Grazie, Fabrik

+0

Suppongo che abbia appena fornito una descrizione della struttura del documento XML? Perché non è XML quello che hai postato (voglio solo essere sicuro;)). –

+0

Ovviamente è solo un contorno. XML si convalida bene e sembra diverso dal mio codice: o – fabrik

+0

le funzioni simplexml sarebbero troppo semplici per le tue esigenze? – stillstanding

risposta

12

Risoluzione Problema 1:

Il W3C defines: il significato dell'attributo xml:id come un attributo ID in documenti XML e definisce l'elaborazione di questo attributo per identificare gli ID del assenza di convalida, senza recuperare risorse esterne e senza fare affidamento su un sottoinsieme interno.

In altre parole, quando si utilizza

$element->setAttribute('xml:id', 'test'); 

non è necessario chiamare setIdAttribute, né specificare un DTD o schema. DOM riconoscerà l'attributo xml:id quando utilizzato con getElementById senza dover convalidare il documento o altro. Questo è l'approccio meno sforzo. Nota comunque che, a seconda del tuo sistema operativo e della versione di libxml, non avrai il getElementById per funzionare.

Solving Problem2:

Anche con ID non essendo fetchable con getElementById, si può ancora molto a prenderli con XPath:

$xpath->query('/pages/page[@id=1]'); 

sarebbe sicuramente lavorare. E si può anche recuperare i figli di prodotto per una specifica pagina direttamente:

$xpath->query('//pages/page[@id=1]/products'); 

Oltre a questo, c'è ben poco si può fare per rendere il codice DOM aspetto meno prolissa, perché è davvero un'interfaccia verbose. Deve essere, perché DOM is a language agnostic interface, again defined by the W3C.


EDIT dopo commento qui sotto

Si sta lavorando, come ho spiegato sopra. Ecco un caso di prova completo per te. La prima parte è per che scrive nuovi file XML con DOM. È qui che devi impostare l'attributo xml:id. Si utilizza questo anziché l'attributo id regolare, non assegnato a un altro nome.

// Setup 
$dom = new DOMDocument; 
$dom->formatOutput = TRUE; 
$dom->preserveWhiteSpace = FALSE; 
$dom->loadXML('<pages/>'); 

// How to set a valid id attribute when not using a DTD or Schema 
$page1 = $dom->createElement('page'); 
$page1->setAttribute('xml:id', 'p1'); 
$page1->appendChild($dom->createElement('product', 'foo1')); 
$page1->appendChild($dom->createElement('product', 'foo2')); 

// How to set an ID attribute that requires a DTD or Schema when reloaded 
$page2 = $dom->createElement('page'); 
$page2->setAttribute('id', 'p2'); 
$page2->setIdAttribute('id', TRUE); 
$page2->appendChild($dom->createElement('product', 'bar1')); 
$page2->appendChild($dom->createElement('product', 'bar2')); 

// Appending pages and saving XML 
$dom->documentElement->appendChild($page1); 
$dom->documentElement->appendChild($page2); 
$xml = $dom->saveXML(); 
unset($dom, $page1, $page2); 
echo $xml; 

Questo creerà un file XML in questo modo:

<?xml version="1.0"?> 
<pages> 
    <page xml:id="p1"> 
    <product>foo1</product> 
    <product>foo2</product> 
    </page> 
    <page id="p2"> 
    <product>bar1</product> 
    <product>bar2</product> 
    </page> 
</pages> 

Quando si leggere nel XML di nuovo, la nuova istanza DOM non sa più che avete dichiarato il non-namespace id attributo come Attributo ID con setIdAttribute. Sarà ancora in XML, ma l'attributo id sarà solo un attributo regolare. You have to be aware that ID attributes are special in XML.

// Load the XML we created above 
$dom = new DOMDocument; 
$dom->loadXML($xml); 

Ora per alcuni test:

echo "\n\n GETELEMENTBYID RETURNS ELEMENT WITH XML:ID \n\n"; 
foreach($dom->getElementById('p1')->childNodes as $product) { 
    echo $product->nodeValue; // Will output foo1 and foo2 with whitespace 
} 

I lavori di cui sopra, perché un parser DOM compatibile deve riconoscere xml:id è un attributo ID, indipendentemente da qualsiasi DTD o schema. Questo è spiegato nelle specifiche collegate sopra. Il motivo per cui viene generato lo spazio è perché, a causa dell'output formattato, esistono nodi DOMText tra il tag di apertura, i due tag di prodotto e i tag di chiusura, quindi stiamo ripetendo più di cinque nodi. Il concetto di nodo è fondamentale per capire quando si lavora con XML.

echo "\n\n GETELEMENTBYID CANNOT FETCH NORMAL ID \n\n"; 
foreach($dom->getElementById('p2')->childNodes as $product) { 
    echo $product->nodeValue; // Will output a NOTICE and a WARNING 
} 

che questo non funziona, perché id non è un attributo ID. Affinché il parser DOM lo riconosca come tale, è necessario un DTD o Schema e l'XML deve essere convalidato su di esso.

echo "\n\n XPATH CAN FETCH NORMAL ID \n\n"; 
$xPath = new DOMXPath($dom); 
$page2 = $xPath->query('/pages/page[@id="p2"]')->item(0); 
foreach($page2->childNodes as $product) { 
    echo $product->nodeValue; // Will output bar1 and bar2 
} 

XPath d'altra parte è letterale sugli attributi, il che significa che è possibile interrogare il DOM per l'elemento di pagina con l'attributo id se getElementById non è disponibile. Nota che per interrogare la pagina con ID p1, dovresti includere lo spazio dei nomi, ad es. @xml:id="p1".

echo "\n\n XPATH CAN FETCH PRODUCTS FOR PAGE WITH ID \n\n"; 
$xPath = new DOMXPath($dom); 
foreach($xPath->query('/pages/page[@id="p2"]/product') as $product) { 
    echo $product->nodeValue; // Will output bar1 and bar2 w\out whitespace 
} 

E come detto, è anche possibile utilizzare XPath per interrogare qualsiasi altra cosa nel documento.Questo non genererà spazi bianchi, perché restituirà solo gli elementi product sotto la pagina con id p2.

È anche possibile attraversare l'intero DOM da un nodo. È una struttura ad albero. Dal momento che DOMNode è la classe più importante in DOM, è necessario acquisire familiarità con esso.

echo "\n\n TRAVERSING UP AND DOWN \n\n"; 
$product = $dom->getElementsByTagName('product')->item(2); 
echo $product->tagName; // 'product' 
echo $dom->saveXML($product); // '<product>bar1</product>' 

// Going from bar1 to foo1 
$product = $product->parentNode // Page Node 
        ->parentNode // Pages Node 
        ->childNodes->item(1) // Page p1 
        ->childNodes->item(1); // 1st Product 

echo $product->nodeValue; // 'foo1' 

// from foo1 to foo2 it is two(!) nodes because the XML is formatted 
echo $product->nextSibling->nodeName; // '#text' with whitespace and linebreak 
echo $product->nextSibling->nextSibling->nodeName; // 'product' 
echo $product->nextSibling->nextSibling->nodeValue; // 'foo2' 

Su un sidenote, sì, ho un refuso nel codice originale qui sopra. È product non products. Ma trovo difficilmente giustificabile affermare che il codice non funziona quando tutto ciò che devi cambiare è un s. Sembra proprio troppo voler essere sfasato.

+0

L'impostazione 'id' di una pagina prima di scrivere il file XML funziona correttamente. Quando leggo l'XML non posso/non voglio impostare gli attributi perché mi piacerebbe leggere il sorgente XML basato su questi attributi. Quindi il Problema 1 non è ancora risolto. Problema 2 sicuramente non risolto, la prima query XPath non riesce. Anche il secondo fallisce perché non ho il nodo 'products', invece ho molti nodi' product' all'interno di una pagina. (Questo è stato definito nella mia domanda.) – fabrik

+0

@fabrik entrambi i problemi sono risolti. Vedi il mio aggiornamento per prova. – Gordon

+1

È fantastico! Grazie per la tua profonda spiegazione. È veloce e fa esattamente quello che voglio. Tranne una cosa, ma è colpa mia: ho commesso un errore nel frammento XML di esempio perché ho bisogno anche del nome e del valore del nodo, quindi ho bisogno di due foreach ancora una volta: o Naturalmente accetterò la tua risposta perché è il trucco . Grazie ancora! – fabrik