Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use namespaces when writing XML file with SimpleXML

I’m writing a Google products RSS feed with SimpleXML in PHP. I’ve got my products coming from the database and creating the RSS file fine, but having problems when it comes to namespaces.

I’ve Googled and search Stack Overflow and come across dozens of posts of how to parse XML feeds containing namespaces, but my issue is actually authoring an XML file with a namespace.

Here is what the file should look like:

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

And here is my code:

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

How do I introduce the g namespace, both the xmlns declaration in the root rss element, and then as a prefix for id, price, brand, condition and image_link in each item element?

like image 210
Martin Bean Avatar asked Dec 20 '12 11:12

Martin Bean


People also ask

What are some examples of SimpleXML?

Examples Basic SimpleXML usage Dealing with XML errors SimpleXMLElement— The SimpleXMLElement class SimpleXMLElement::addAttribute— Adds an attribute to the SimpleXML element SimpleXMLElement::addChild— Adds a child element to the XML node SimpleXMLElement::asXML— Return a well-formed XML string based on SimpleXML element

Is there an XMl Parser for SimpleXML?

Here is a very robust SimpleXML parser. Can be used to load files, strings, or DOM into SimpleXML, or can be used to perform the reverse when handed SimpleXML. <?php * XMLParser Class File

What is an XML namespace?

This topic provides basic information about XML namespaces. What Is an XML Namespace? An XML namespace is a collection of names that can be used as element or attribute names in an XML document. The namespace qualifies element names uniquely on the Web in order to avoid conflicts between elements with the same name.

How to convert a simplexmlelement to an array?

$xml = simplexml_load_string($file); $array = (array)$xml; up down 6 xaviered at gmail dot com¶ 9 years ago Here is a recursive function that will convert a given SimpleXMLElement object into an array, preserving namespaces and attributes.


3 Answers

This can been done using the SimpleXMLElement interface. IMHO this is a hack of the dodgiest order, but it works for now.

The key point being that it works as follows, for now, but may not continue to work. As such I'm in no way recommending this over the accepted answer by @DaveRandom. Rather, I'm including this answer here so that others in future can read this and save themselves some time searching for a SimpleXMLElement way of doing it, and just go with DaveRandom's DOM based approach :-)

You can "trick" the parser to stop it from dropping your g: namespace prefix from your element names, by putting a "rubbish" prefix prior to your real one, eg "blah:g:condition".

I've seen variations of this answer for use in attribute prefixes, but not for element prefixes. And those all seem to suggest using "xmlns:yourPrefix:yourAttribute" and passing in the fully qualified names space as the third parameter, where in reality (at least, from my own personal testing) the xmlns: part can be pretty much anything (including whitespace!) prior to a colon :, but there has to be something prior to that first colon, ie ":g:condition" won't work. And unless you actually create a node that declares the namespace and prefix, the rendered XML would be invalid (ie the namespace prefix you hack in against your elements will have no declaration).

So, based on your original code, you'd do the following. Note also the explicit addition of the namespace in the root node declaration (although this can probably be done via the API - but why bother?).

$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']);
}
like image 145
Sepster Avatar answered Nov 14 '22 22:11

Sepster


Here is an example of how to do this using 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

like image 28
DaveRandom Avatar answered Nov 14 '22 21:11

DaveRandom


This can be done by adding the namespace declaration into the root element creation along with adding the namespace to the addChild function.

The modification to the original code:

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

Would yield the response:

<?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>
like image 32
Sean Avatar answered Nov 14 '22 21:11

Sean