2012-12-20 18 views
5

Sto scrivendo un feed RSS di prodotti Google con SimpleXML in PHP. I miei prodotti provengono dal database e creano il file RSS bene, ma ho problemi quando si tratta di spazi dei nomi.Come utilizzare gli spazi dei nomi durante la scrittura di file XML con SimpleXML

Ho cercato su Google e ricerca Stack Overflow e ho trovato decine di post su come analizzare feed XML contenenti spazi dei nomi, ma il mio problema è in realtà authoring un file XML con uno spazio dei nomi.

Ecco ciò che il file dovrebbe simile:

<?xml version="1.0" encoding="UTF-8" ?> 
<rss version ="2.0" xmlns:g="http://base.google.com/ns/1.0"> 
    <!-- content --> 
</rss> 

E qui è il mio codice:

<?php 

$xml = new SimpleXMLElement('<rss></rss>'); 
$xml->addAttribute('version', '2.0'); 

$xml->addChild('channel'); 
$xml->channel->addChild('title', 'Removed'); 
$xml->channel->addChild('description', 'Removed'); 
$xml->channel->addChild('link', 'Removed'); 

foreach ($products as $product) { 
    $item = $xml->channel->addChild('item'); 
    $item->addChild('title', htmlspecialchars($product['title'])); 
    $item->addChild('description', htmlspecialchars($product['title'])); 
    $item->addChild('link', $product['url']); 
    $item->addChild('id', $product['product_id']); 
    $item->addChild('price', $product['price_latest']); 
    $item->addChild('brand', $product['range']); 
    $item->addChild('condition', 'new'); 
    $item->addChild('image_link', $product['image']); 
} 

Come introdurre lo spazio dei nomi g, sia la dichiarazione xmlns nella radice rss elemento, quindi come prefisso per id, price, brand, condition e image_link in ogni elemento item?

+0

Mi consiglia di utilizzare [DOM] (http://php.net/manual/en/book .dom.php) per questo invece. SimpleXML è proprio questo - * semplice *. Quello che stai facendo ora ha lasciato il regno della semplicità. Hai bisogno di un'API completa per questo. – DaveRandom

+0

Hai un esempio di come aggiungerei un namespace con DOM? –

+0

@MartinBean C'è un esempio in [manuale per createElementNS di DOMDocument] (http://php.net/manual/domdocument.createelementns.php). – Furgas

risposta

6

Ecco un esempio di come fare questo usando DOM:

<?php 

    $nsUrl = 'http://base.google.com/ns/1.0'; 

    $doc = new DOMDocument('1.0', 'UTF-8'); 

    $rootNode = $doc->appendChild($doc->createElement('rss')); 
    $rootNode->setAttribute('version', '2.0'); 
    $rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:g', $nsUrl); 

    $channelNode = $rootNode->appendChild($doc->createElement('channel')); 
    $channelNode->appendChild($doc->createElement('title', 'Removed')); 
    $channelNode->appendChild($doc->createElement('description', 'Removed')); 
    $channelNode->appendChild($doc->createElement('link', 'Removed')); 

    foreach ($products as $product) { 
     $itemNode = $channelNode->appendChild($doc->createElement('item')); 
     $itemNode->appendChild($doc->createElement('title'))->appendChild($doc->createTextNode($product['title'])); 
     $itemNode->appendChild($doc->createElement('description'))->appendChild($doc->createTextNode($product['title'])); 
     $itemNode->appendChild($doc->createElement('link'))->appendChild($doc->createTextNode($product['url'])); 
     $itemNode->appendChild($doc->createElement('g:id'))->appendChild($doc->createTextNode($product['product_id'])); 
     $itemNode->appendChild($doc->createElement('g:price'))->appendChild($doc->createTextNode($product['price_latest'])); 
     $itemNode->appendChild($doc->createElement('g:brand'))->appendChild($doc->createTextNode($product['range'])); 
     $itemNode->appendChild($doc->createElement('g:condition'))->appendChild($doc->createTextNode('new')); 
     $itemNode->appendChild($doc->createElement('g:image_link'))->appendChild($doc->createTextNode($product['image'])); 
    } 

    echo $doc->saveXML(); 

See it working

+1

Grazie, Dave. Perfezionare! Sono appena riuscito a scrivere una soluzione di lavoro che sia molto simile alla tua: https://gist.github.com/4344973 –

+0

@MartinBean Cool :-) Solo il vero diff tra il tuo e il mio è che io uso la creazione di un nodo di testo esplicito per la dinamica valori, che è (IMHO) il modo migliore per gestire le entità di escape, perché è possibile passare i dati non elaborati e verrà eseguito l'escape automatico senza problemi con 'htmlspecialchars()'. Tuttavia ammetto che lo rende * ancora più * verboso - DOM non è un'API succinta, ma è la più flessibile disponibile. – DaveRandom

+0

Buono a sapersi, dato che in realtà ho il titolo del prodotto ecc avvolto in 'htmlspecialchars()' nel codice reale! Lo sostituirò con il metodo di creazione esplicita di un nodo di testo, come hai fatto tu. –

9

Questo può stato fatto utilizzando l'interfaccia SimpleXMLElement. IMHO questo è un hack dell'ordine più difficile, ma funziona per ora.

Il punto chiave è che funziona come segue, per ora, ma potrebbe non continuare a funzionare. In quanto tale, non sto in alcun modo raccomandando questo sulla risposta accettata da @DaveRandom. Piuttosto, sto includendo questa risposta in modo che altri in futuro possano leggerlo e risparmiarsi un po 'di tempo cercando un modo SimpleXMLElement per farlo, e basta seguire l'approccio basato su DOM di DaveRandom :-)

Puoi "ingannare" "il parser per impedirgli di eliminare il prefisso dello spazio dei nomi g: dai nomi degli elementi, inserendo un prefisso" spazzatura "prima di quello reale, ad esempio "blah:g:condition".

Ho visto varianti di questa risposta per l'uso nei prefissi degli attributi, ma non per i prefissi degli elementi. E tutti sembrano suggerire di usare lo "xmlns:yourPrefix:yourAttribute" e passare lo spazio dei nomi completi come terzo parametro, dove in realtà (almeno dai miei test personali) la parte xmlns: può essere praticamente qualsiasi cosa (compresi gli spazi!) Prima di un due punti :, ma ci deve essere qualcosa prima di quei primi due punti, ovvero ":g:condition" non funzionerà. E a meno che non si crei effettivamente un nodo che dichiari lo spazio dei nomi e il prefisso, l'XML reso non sarebbe valido (ad esempio il prefisso dello spazio dei nomi che si incide sui propri elementi non avrà alcuna dichiarazione).

Quindi, in base al codice originale, è necessario effettuare le seguenti operazioni. Notare anche l'aggiunta esplicita dello spazio dei nomi nella dichiarazione del nodo radice (anche se questo può probabilmente essere fatto tramite l'API - ma perché preoccuparsi?).

$xml = new SimpleXMLElement('<rss xmlns:g="http://base.google.com/ns/1.0" />'); // May as well chuck the google ns in the root element declaration here, while we're at it, rather than adding it via a separate attribute. 
$xml->addAttribute('version', '2.0'); 
// $xml->addAttribute('hack:xmlns:g','http://base.google.com/ns/1.0'); //Or could do this instead... 

$xml->addChild('channel'); 
$xml->channel->addChild('title', 'Removed'); 
$xml->channel->addChild('description', 'Removed'); 
$xml->channel->addChild('link', 'Removed'); 

foreach ($products as $product) { 
    $item = $xml->channel->addChild('item'); 
    $item->addChild('title', htmlspecialchars($product['title'])); 
    $item->addChild('description', htmlspecialchars($product['title'])); 
    $item->addChild('link', $product['url']); 
    $item->addChild('hack:g:id', $product['product_id']); 
    $item->addChild('hack:g:price', $product['price_latest']); 
    $item->addChild('hack:g:brand', $product['range']); 
    $item->addChild('hack:g:condition', 'new'); 
    $item->addChild('hack:g:image_link', $product['image']); 
} 
+1

Hai salvato il mio tempo. Grazie Man :) – nit3ch

+0

Grazie vermuch! –

1

Questo può essere fatto aggiungendo la dichiarazione del namespace nella creazione elemento principale con l'aggiunta del namespace alla funzione addChild.

La modifica al codice originale:

$products[] = array(
    "title" => "Foobar", 
    "description" => "Foo", 
    "url" => "https://f.oo.bar", 
    "product_id" => "00001", 
    "price_latest" => "$3.50", 
    "range" => "Foo", 
    "image" => "https://f.oo.bar/image.tiff", 
); 

$xml = new SimpleXMLElement('<rss xmlns:g="http://base.google.com/ns/1.0"/>'); 
$xml->addAttribute('version', '2.0'); 

$xml->addChild('channel'); 
$xml->channel->addChild('title', 'Removed'); 
$xml->channel->addChild('description', 'Removed'); 
$xml->channel->addChild('link', 'Removed'); 

foreach ($products as $product) { 
    $item = $xml->channel->addChild('item'); 
    $item->addChild('title', htmlspecialchars($product['title'])); 
    $item->addChild('description', htmlspecialchars($product['title'])); 
    $item->addChild('link', $product['url']); 
    $item->addChild('id', $product['product_id'], "http://base.google.com/ns/1.0"); 
    $item->addChild('price', $product['price_latest'], "http://base.google.com/ns/1.0"); 
    $item->addChild('brand', $product['range'], "http://base.google.com/ns/1.0"); 
    $item->addChild('condition', 'new', "http://base.google.com/ns/1.0"); 
    $item->addChild('image_link', $product['image'], "http://base.google.com/ns/1.0"); 
} 

print_r($xml->asXML()); 

produrrebbe la risposta:

<?xml version="1.0"?> 
<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0"> 
    <channel> 
    <title>Removed</title> 
    <description>Removed</description> 
    <link>Removed</link> 
    <item> 
     <title>Foobar</title> 
     <description>Foobar</description><link/> 
     <g:id>00001</g:id> 
     <g:price>$3.50</g:price> 
     <g:brand>Foo</g:brand> 
     <g:condition>new</g:condition> 
     <g:image_link>https://f.oo.bar/image.tiff</g:image_link> 
    </item> 
    </channel> 
</rss>