Unable to add namespace to an attribute with PHP's SimpleXML
If you want to add an attribute from the namespace/prefix i
to $node don't bother declaring the namespace beforehand. Just use the third parameter of addAttribute() to provide the namespace uri for the prefix you're using in the first parameter.
$node = new SimpleXMLElement('<root></root>');
$node->addAttribute("i:somename", "somevalue", 'http://www.w3.org/2001/XMLSchema-instance');
echo $node->asXml();
prints
<?xml version="1.0"?>
<root xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:somename="somevalue"/>
If the attribute itself isn't needed, you can then remove it with unset()
, leaving the namespace declaration.
unset($node->attributes('i', TRUE)['somename']);
PHP SimpleXMLElement addAttribute namespaces syntax
solution 1: add a prefix to the prefix
<?php
$node = new SimpleXMLElement('<Product/>');
$node->addAttribute("xmlns:xmlns:xsi", 'http://www.w3.org/2001/XMLSchema-instance');
$node->addAttribute("xmlns:xmlns:xsd", 'http://www.w3.org/2001/XMLSchema');
echo $node->asXML();
output:
<?xml version="1.0"?>
<Product xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
note: this is a workaround and actually doesn't set the namespace for the attribute, but just quite enough if you are going to echo / save to file the result
solution 2: put namespace directly in the SimpleXMLElement constructor
<?php
$node = new SimpleXMLElement('<Product xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>');
echo $node->asXML();
output is the same as in solution 1
solution 3 (adds additional attribute)
<?php
$node = new SimpleXMLElement('<Product/>');
$node->addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", "xmlns");
$node->addAttribute("xmlns:xsd", 'http://www.w3.org/2001/XMLSchema', "xmlns");
echo $node->asXML();
output adds additional xmlns:xmlns="xmlns"
<?xml version="1.0"?>
<Product xmlns:xmlns="xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
adding a namespace when using SimpleXMLElement
SimpleXML has an unusual quirk where the namespace prefixes are filtered from the root element. I'm not sure why it does this.
However, a workaround I've used has been to basically prefix the prefix, so that the parser only removes the first ones, and leaves the second
$xmlTest = new SimpleXMLElement('<xmlns:ws:Test></xmlns:ws:Test>', LIBXML_NOERROR, false, 'ws', true);
$xmlTest->addAttribute('xmlns:xmlns:ws', 'http://url.to.namespace');
$xmlTest->addAttribute('xmlns:xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
This seems to work for me, though I'm interested to understand why SimpleXML does this exactly.
SimpleXML namespaces and attributes
Using
->children();
at the end, that is with the default namespace, will give you a child-collection of zero elements. As it's empty, SimpleXML internally runs some optimizations. Accessing it again might lead to errors:
$nodes->attributes()['value']
Warning: main(): Node no longer exists in ...
Perhaps the best way is to just leave it out and name the children you're looking for:
$nodes = ... ->extension->children($namespaces["keyvalue"])->kv;
print_r($nodes[1]->attributes()['value']);
Gives this output:
SimpleXMLElement Object
(
[0] => ZBh5ralfPl
)
As it's the second <kv>
element (zero-based) of which the attribute "value" is accessed.
If you want to leave the concrete element out of it, just leave it out (do not add ->children()
). The same example:
$nodes = $xml->response->extension->children($namespaces["keyvalue"])->
extension->children($namespaces["keyvalue"]);
print_r($nodes[1]->attributes()['value']);
This gives exactly the same output:
SimpleXMLElement Object
(
[0] => ZBh5ralfPl
)
as there are only <kv>
elements as children there, so the numbering does not change.
Hope this helps to shed some light. Perhaps better than all this is to use xpath:
$xml->registerXPathNamespace('kv', $namespaces["keyvalue"]);
var_dump($xml->xpath('//kv:kv[@key = "AUTH"]/@value')[0]);
Which gives:
class SimpleXMLElement#6 (1) {
public $@attributes =>
array(1) {
'value' =>
string(10) "ZBh5ralfPl"
}
}
Xpath is specialized on traversal. This is even better when it comes to namespaces.
SimpleXML children's attributes behaves different with and without namespace
The reason for this is not actually anything to do with SimpleXML, but to do with some surprising details of how XML namespaces work, according to the standard.
In your example, you have a namespace declared with the prefix a
, so to declare that an attribute is in that namespace, you must prefix its name with a:
, just as you do with elements:
<a:child a:role="daughter"/>
It seems to be a common assumption that an attribute without a namespace prefix is in the same namespace as the element it is on, but that is not the case. The example above is not equivalent to your example:
<a:child role="daughter"/>
Another case you might see is where there is in a default (unprefixed) namespace:
<person xmlns="http://example.com/foo.bar"><child role="daughter" /></person>
Here, the child
element is in the http://example.com/foo.bar
namespace, but the role
attribute still isn't! As discussed in this related question, the relevant section of the XML Namespaces spec includes this statement:
The namespace name for an unprefixed attribute name always has no value.
That is, an attribute with no namespace prefix is never in any namespace, regardless of what the rest of the document looks like.
So, what effect does this have on SimpleXML?
SimpleXML works on the basis of altering the "current namespace" whenever you use the ->children()
or ->attributes()
methods, and tracking it from then on.
So when you write:
$children = $xml->children('a', true);
or:
$children = $xml->children('http://example.com/foo.bar');
the "current namespace" is foo:bar
. Subsequent use of the ->childElement
or ['attribute']
syntax will look in this namespace - you don't need to call children()
again every time - but your unprefixed attributes won't be found there, because they have no namespace.
When you subsequently write:
$attributes = $children->attributes();
this is interpreted the same way as:
$attributes = $children->attributes(null);
So now, the "current namespace" is null
. Now when you look for the attributes which have no namespace, you will find them.
Simplexml get attributes with a namespace
You need to use the namespace when getting the attribute. The android
namespace is defined as:
http://schemas.android.com/apk/res/android
So you need to pass that to the attributes()
method, like this:
$xml = simplexml_load_string($xmlStr);
echo (string) $xml->application->activity->attributes('http://schemas.android.com/apk/res/android')->name;
Outputs
com.sunil.tweet.MainActivity
Codepad Demo
How to use namespaces when writing XML file with SimpleXML
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
Related Topics
Error: File Is Encrypted or Is Not a Database
Output Text File with Line Breaks in PHP
Overwrite Line in File with PHP
How to Bind an Arbitrary Number of Values to a Prepared Statement in MySQLi
Simplexml Get Attributes with a Namespace
How to Configuring a Xampp Web Server for Different Root Directory
Which Compression Method to Use in PHP
Simple Way to Read Single Record from MySQL
Invoke External Shell Script from PHP and Get Its Process Id
PHP Datetime Round Up to Nearest 10 Minutes
PHP Sample Script for Pagination
How to Display PHP & HTML Source Code on a Page
How to Get Data in Function Extend Controller