simplexml_load_string issue accessing attributes - php

My XML looks like this:
<n10:category xmlns:n10="..." some-id="123">
<n10:name xml:lang="x-default">Name Here</n10:name>
<n10:custom-attributes>
<n10:custom-attribute attribute-id="abc1">1</n10:custom-attribute>
<n10:custom-attribute attribute-id="abc2">false</n10:custom-attribute>
<n10:custom-attribute attribute-id="abc3">false</n10:custom-attribute>
...
To access some-id I call:
$xml = simplexml_load_string(...);
foreach ($xml->attributes() as $key => $value) {
if ($key == 'some-id') {
$data['some_id'] = (string) $value;
}
}
The above works. However, when I try to access attributes of custom-attribute, I get back values (like 1, false, false in the example above, but I am unable to get what the attribute-id is equal to on each record. I've tried:
foreach ... $xml->{'custom-attributes'}->attributes() and it returns null
Also, doing var_dump of $xml in the beginning doesn't seem to include the attribute-id at all.
What am I missing?

Use the xpath method to access the custom-attribute nodes, like this:
<?php
$xml = <<<XML
<?xml version='1.0' standalone='yes'?>
<n10:category xmlns:n10="..." some-id="123">
<n10:name xml:lang="x-default">Name Here</n10:name>
<n10:custom-attributes>
<n10:custom-attribute attribute-id="abc1">1</n10:custom-attribute>
<n10:custom-attribute attribute-id="abc2">false</n10:custom-attribute>
<n10:custom-attribute attribute-id="abc3">false</n10:custom-attribute>
</n10:custom-attributes>
</n10:category>
XML;
$sx = simplexml_load_string($xml);
$sx->registerXPathNamespace('n10', '...');
$customAttributes = $sx->xpath('/n10:category//n10:custom-attribute');
foreach ($customAttributes as $ca) {
echo $ca['attribute-id'] . '<br>';
}
It's important to register the custom namespace to be able to access the nodes belonging to said namespace.

Related

how to correctly append xml child elements in php

I'm trying to iterate over an array of movie names and am struggling as shown below. I've included the actual output vs expected output. thanks!
$root = new SimpleXMLElement('<movies/>');
$movies = ['foo', 'bar'];
foreach($movies as $movie_name){
$movie_el = buildMovieTag($movie_name);
$root->addChild($movie_el->asXML());
}
function buildMovieTag($name){
$movie_tag = new SimpleXMLElement('<movie/>');
$movie_tag->addChild('name', $name);
return $movie_tag;
}
I'm expecting this:
<?xml version="1.0"?>
<movies>
<movie><name>foo</name></movie>
<movie><name>bar</name></movie>
</movies>
but am getting this:
<?xml version="1.0"?>
<movies><<?xml version="1.0"?> // extra xml tag
<movie><name>foo</name></movie>
/><<?xml version="1.0"?> // extra xml tag
<movie><name>bar</name></movie>
/></movies>
The new SimpleXMLElement then asXML() is the issue.
addChild is enough to make the element then its return value is the node, then subsequent addChild's on the node make the children.
<?php
$root = new SimpleXMLElement('<movies/>');
$movies = ['foo', 'bar'];
foreach($movies as $movie_name) {
$movie = $root->addChild('movie');
buildMovieTag($movie, $movie_name);
}
function buildMovieTag($node, $name) {
$node->addChild('name', $name);
}
echo $root->saveXML();
<?xml version="1.0"?>
<movies><movie><name>foo</name></movie><movie><name>bar</name></movie></movies>
Edit: if your data is more extensive, structure it so its standard key/value array then loop over the items. The actual issue to the original question is noted on the first line of the answer.
<?php
$root = new SimpleXMLElement('<movies/>');
$movies = [
['name'=>'foo', 'released' => '2020-12-01 00:00:00', 'plot' => 'foo is a foofoo'],
['name'=>'bar', 'released' => '2020-12-02 00:00:00', 'plot' => 'bar is a barbar'],
];
foreach ($movies as $movie) {
buildMovieTag($root->addChild('movie'), $movie);
}
function buildMovieTag($node, $data) {
foreach ($data as $key => $item) {
// just year from datetime string
if ($key === 'released') $item = date_create($item)->format('Y');
$node->addChild($key, $item);
}
}
echo $root->saveXML();
Result (manual formatted)
<?xml version="1.0"?>
<movies>
<movie>
<name>foo</name>
<released>2020</released>
<plot>foo is a foofoo</plot>
</movie>
<movie>
<name>bar</name>
<released>2020</released>
<plot>bar is a barbar</plot>
</movie>
</movies>

foreach loop for xml

I have following xml getting loaded in my PHP code;
<SiteAlarmDetails>
<AlertId>89637</AlertId>
<SiteCode>20157498</SiteCode>
<SiteName>newport</SiteName>
</SiteAlarmDetails>
$alertXml = simplexml_load_string( $tableAlarm->AlarmDetails);
echo (string) $alertXml->AlertId; //prints **89637**
Now I try to traverse this XML nodes;
foreach($alertXml->children() as $alerts)
{
$alertId = (string)$alerts->AlertId;
echo $alertId;//I do not see anything
}
Is above right approach to traverse AlertId in the foreach loop?
Trying simple foreach will be helpful. Just for accessing single value (eg AlertId) you can use $alertXml->AlertId;.
Try this code snippet here
<?php
ini_set('display_errors', 1);
$xmlString=<<<XML
<SiteAlarmDetails>
<AlertId>89637</AlertId>
<SiteCode>20157498</SiteCode>
<SiteName>newport</SiteName>
</SiteAlarmDetails>
XML;
$alertXml = simplexml_load_string( $xmlString);
foreach($alertXml as $key => $child)
{
echo $key ."=".(string)$alertXml->{$key};
echo PHP_EOL;
}
Output:
AlertId=89637
SiteCode=20157498
SiteName=newport

Parsing XML with PHP's simpleXML

I'm learning how to parse XML with PHP's simple XML. My code is:
<?php
$xmlSource = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> <Document xmlns=\"http://www.apple.com/itms/\" artistId=\"329313804\" browsePath=\"/36/6407\" genreId=\"6507\"> <iTunes> myApp </iTunes> </Document>";
$xml = new SimpleXMLElement($xmlSource);
$results = $xml->xpath("/Document/iTunes");
foreach ($results as $result){
echo $result.PHP_EOL;
}
print_r($result);
?>
When this runs it returns a blank screen, with no errors. If I remove all the attributes from the Document tag, it returns :
myApp SimpleXMLElement Object ( [0] => myApp )
Which is the expected result.
What am I doing wrong? Note that I don't have control over the XML source, since it's coming from Apple.
Your xml contains a default namespace. In order to get your xpath query to work you need to register this namespace, and use the namespace prefix on every xpath element you are querying (as long as these elements all fall under the same namespace, which they do in your example):
$xml = new SimpleXMLElement( $xmlSource );
// register the namespace with some prefix, in this case 'a'
$xml->registerXPathNamespace( 'a', 'http://www.apple.com/itms/' );
// then use this prefix 'a:' for every node you are querying
$results = $xml->xpath( '/a:Document/a:iTunes' );
foreach( $results as $result )
{
echo $result . PHP_EOL;
}
For the part about the default namespace, read fireeyedboy's answer. As mentionned, you need to register a namespace if you want to use XPath on nodes that are in the default namespace.
However, if you don't use xpath(), SimpleXML has its own magic that selects the default namespace automagically.
$xmlSource = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> <Document xmlns=\"http://www.apple.com/itms/\" artistId=\"329313804\" browsePath=\"/36/6407\" genreId=\"6507\"> <iTunes> myApp </iTunes> </Document>";
$Document = new SimpleXMLElement($xmlSource);
foreach ($Document->iTunes as $iTunes)
{
echo $iTunes, PHP_EOL;
}
this is general example
foreach ($library->children() as $child)
{
echo $child->getName() . ":\n";
foreach ($child->attributes() as $attr)
{
echo $attr->getName() . ': ' . $attr . "\n";
}
foreach ($child->children() as $subchild)
{
echo $subchild->getName() . ': ' . $subchild . "\n";
}
echo "\n";
}
for more information check this :
http://www.yasha.co/XML/how-to-parse-xml-with-php-simplexml-DOM-Xpath/article-1.html
This line:
print_r($result);
is outside the foreach loop. Maybe you should try
print_r($results);
instead.
Seems if you use the wildcard (//) on xpath it will work. Also, not sure why but if you remove the namespace attribute (xmlns) from the Document element, your current code will work. Maybe because a prefix isn't defined? Anyway, following should work:
$results = $xml->xpath("//iTunes");
foreach ($results as $result){
echo $result.PHP_EOL;
}

PHP parsing XML file with and without namespaces

I need to get a XML File into a Database. Thats not the problem. Cant read it, parse it and create some Objects to map to the DB. Problem is, that sometimes the XML File can contain namespaces and sometimes not. Furtermore sometimes there is no namespace defined at all.
So what i first got was something like this:
<?xml version="1.0" encoding="UTF-8"?>
<struct xmlns:b="http://www.w3schools.com/test/">
<objects>
<object>
<node_1>value1</node_1>
<node_2>value2</node_2>
<node_3 iso_land="AFG"/>
<coords lat="12.00" long="13.00"/>
</object>
</objects>
</struct>
And the parsing:
$obj = new stdClass();
$nodes = array('node_1', 'node_2');
$t = $xml->xpath('/objects/object');
foreach($nodes AS $node) {
if($t[0]->$node) {
$obj->$node = (string) $t[0]->$node;
}
}
Thats fine as long as there are no namespaces. Here comes the XML File with namespaces:
<?xml version="1.0" encoding="UTF-8"?>
<b:struct xmlns:b="http://www.w3schools.com/test/">
<b:objects>
<b:object>
<b:node_1>value1</b:node_1>
<b:node_2>value2</b:node_2>
<b:node_3 iso_land="AFG"/>
<b:coords lat="12.00" long="13.00"/>
</b:object>
</b:objects>
</b:struct>
I now came up with something like this:
$xml = simplexml_load_file("test.xml");
$namespaces = $xml->getNamespaces(TRUE);
$ns = count($namespaces) ? 'a:' : '';
$xml->registerXPathNamespace("a", "http://www.w3schools.com/test/");
$nodes = array('node_1', 'node_2');
$obj = new stdClass();
foreach($nodes AS $node) {
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.$node);
if($t[0]) {
$obj->$node = (string) $t[0];
}
}
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.'node_3');
if($t[0]) {
$obj->iso_land = (string) $t[0]->attributes()->iso_land;
}
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.'coords');
if($t[0]) {
$obj->lat = (string) $t[0]->attributes()->lat;
$obj->long = (string) $t[0]->attributes()->long;
}
That works with namespaces and without. But i feel that there must be a better way. Before that i could do something like this:
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object');
foreach($nodes AS $node) {
if($t[0]->$node) {
$obj->$node = (string) $t[0]->$node;
}
}
But that just wont work with namespaces.
You could make 'http://www.w3schools.com/test/' the default namespace. This way a:objectswould match regardless of whether the document says <a:objects> or <objects>.
If memory usage is not a issue you can even do it with a textual replacement, e.g.
$data = '<?xml version="1.0" encoding="UTF-8"?>
<struct xmlns:b="http://www.w3schools.com/test/">
<objects>
<object>
<node_1>value1</node_1>
<node_2>value2</node_2>
<node_3 iso_land="AFG"/>
<coords lat="12.00" long="13.00"/>
</object>
</objects>
</struct>';
$data = str_replace( // or preg_replace(,,,1) if you want to limit it to only one replacement
'xmlns:b="http://www.w3schools.com/test/"',
'xmlns="http://www.w3schools.com/test/" xmlns:b="http://www.w3schools.com/test/"',
$data
);
$xml = new SimpleXMLElement($data);
$xml->registerXPathNamespace("a", "http://www.w3schools.com/test/");
foreach($xml->xpath('//a:objects/a:object') as $n) {
echo $n->node_1;
}
You can make your XPATH statements more generic by matching on any element * and using a predicate filter to match on the local-name(), which will match on the element name with/without namespaces.
An XPATH like this:
/*[local-name()='struct']/*[local-name()='objects']/*[local-name()='object']/*[local-name()='coords']
Applied to the code sample you were using:
$obj = new stdClass();
$nodes = array('node_1', 'node_2');
$t = $xml->xpath('/*[local-name()="objects"]/*[local-name()="object"]');
foreach($nodes AS $node) {
if($t[0]->$node) {
$obj->$node = (string) $t[0]->$node;
}
}
Take a look at This
http://blog.sherifmansour.com/?p=302
It helped me a lot.

navigating through this xml structure with php

Im trying to loop through and display info from the following xml structure.
<users_list>
−<users type="array">
+<user>
<id>Blah</id>
</user>
+<user></user>
+<user></user>
</users>
<next_link>6</next_link>
<prev_link>4</prev_link>
</users_list>
Im using the following PHP to grab the nodes.
$xml = simplexml_load_string($rawxml);
foreach($xml->users_list AS $key){
$name = $key->users->user->{"id"};
}
$next = $key->{"next_link"};
$prev = $key->{"prev_link"};
Ive tried a couple variations, but i dont see any effect. I either get nothing when i echo my variables, or invalid arguments when on my foreach function
When using SimpleXML, you should always name your variables after the root node they contain, it makes things simpler and obvious:
$users_list = simplexml_load_string(
'<users_list>
<users type="array">
<user>
<id>Blah</id>
</user>
<user></user>
<user></user>
</users>
<next_link>6</next_link>
<prev_link>4</prev_link>
</users_list>'
);
foreach ($users_list->users->user as $user)
{
echo "User ", $user->id, "\n";
}
echo "next: ", $users_list->next_link, "\n";
echo "prev: ", $users_list->prev_link, "\n";
When troubleshooting in PHP, var_dump and print_r are your friend!
If you wish to browse your result like an array, then cast it to an array.
$value = (array) $value;
I did the following:
$xmlStr = '<users_list>
<users type="array">
<user>
<id>Blah</id>
</user>
<user></user>
<user></user>
</users>
<next_link>6</next_link>
<prev_link>4</prev_link>
</users_list>';
$xml = simplexml_load_string($xmlStr);
foreach($xml->users->user AS $key=>$value){
$value = (array) $value;
$name = $value["id"];
var_dump($name);
}
which gives the output:
string(4) "Blah"
NULL
NULL
Check the PHP help documents for further info on simplexml
http://nl2.php.net/manual/en/function.simplexml-load-string.php
http://nl2.php.net/manual/en/class.simplexmlelement.php
print_r($xml) should give you all the information you need.
You will probably find that the actual array is $xml->user_list->users->user,
also casting helps save some time
foreach($xml->user_list->users->user as $value) {
$name = (string) $value->id;
}

Categories