How to get all node values from a feed - php

I have following feed:
$result = apiCall('somevalue', 'somevalue', array('somevalue' => $some_string));
which is in json format. I can very easy to turn it into xml with
$xml = simplexml_load_string($result);
The xml feed looks as follow:
<?xml version="1.0" encoding="ISO-8859-15"?>
<response>
<status>success</status>
<code>200</code>
<message>
<a>
<g>val1</g>
<b>val2</b>
<c>val3</c>
</a>
<d>
<e>val4</e>
<f>val5</f>
</d>
</message>
</response>
Is there a simple and fast way to get all node values (i.e. val1, val2, val3, val4, val5 and so on)?

I wrote something that I think will work:
<?php
$xml = <<<EOD
<?xml version="1.0" encoding="ISO-8859-15"?>
<response>
<status>success</status>
<code>200</code>
<message>
<a>
<g>val1</g>
<b>val2</b>
<c>val3</c>
</a>
<d>
<e>val4</e>
<f>val5</f>
</d>
</message>
</response>
EOD;
$output = array();
$test = simplexml_load_string($xml);
$result = $test->xpath('message//*[not(*)]');
while(list(, $node) = each($result)) {
array_push($output, (string) $node);
}
var_dump($output);
?>
I haven't tested it on a lot of things (attributes), but on the provided input it gives the expected output.
What it does is like you can see an xpath expression that retrieves any child of the message node.
Of course, since there are also nodes with even more childnodes in it (like aand d) we need to filter those out. So the last part containing the not(*) means that it will only select those childs which do not have any sub child.
I'm not an XPATH expert so there are probably faster ways of achieving this, but I think this will do.

I came to a similar conclusion like Dimitri M, it's based on a previous Q&A How to select all leaf nodes using XPath expression?.
Leaf-Nodes are the outermost nodes, which are those you're looking for, here the text() nodes in your case.
$nodeValues = array_map(
'trim',
simplexml_load_string($xml)->xpath('message//*[not(*)]/text()')
);
Given that $xml is your XML input, $nodeValues is then an array of all those strings that represent the node values you're looking for. Exemplary:
Array
(
[0] => val1
[1] => val2
[2] => val3
[3] => val4
[4] => val5
)

Related

Add node to XML with condition

I have the following XML file:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<person>
<id>1</id>
<name>Jane</name>
<surname>Smith</surname>
</person>
<person>
<id>2</id>
<name>John</name>
<surname>Doe</surname>
</person>
</root>
And I have the following CSV file:
id;phone
1;12345678
2;78903456
I work with PHP. I need to do with XML something like this:
Add a phone number element to the person where id is...
For example: Add a phone element with value 12345678 to the person element with id 1.
As the content of the XML will vary, it will probably be easier to XPath to find the entry you want to update...
$telephoneList = [["id"=> 1, "phone" => "12345678"],
["id"=> 2, "phone" => "78903456"]];
$xml = simplexml_load_file("a.xml");
foreach ( $telephoneList as $telephone) {
$person = $xml->xpath("//person[id={$telephone['id']}]");
if ( count($person) == 1 ) {
$person[0]->addChild("phone", $telephone['phone']);
}
}
echo $xml->asXML();
This tries to find the <person> element with an <id> with the value from the csv. If this is found, it will add in the phone number using addChild()
It's just a case of reading in the CSV file and process it as above.
With SimpleXML, you can use the addChild() method.
$file = 'xml/config.xml';
$xml = simplexml_load_file($file);
$galleries = $xml->galleries;
$gallery = $galleries->addChild('gallery');
$gallery->addChild('name', 'a gallery');
$gallery->addChild('filepath', 'path/to/gallery');
$gallery->addChild('thumb', 'mythumb.jpg');
$xml->asXML($file);
Be aware that SimpleXML will not "format" the XML for you, however going from an unformatted SimpleXML representation to neatly indented XML is not a complicated step and is covered in lots of questions here.
You can loop the $xml->children() from the SimpleXMLElement and then check if for (string)$a->id === "1". Then use addChild to add your phone element with value 12345678 to the person element.
foreach ($xml->children() as $a) {
if ((string)$a->id === "1") {
$a->addChild("phone", "12345678");
}
}
Demo

Namespaces and XPath

I'm exploring XML and PHP, mostly XPath and other parsers.
Here be the xml:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:foo="http://www.foo.org/" xmlns:bar="http://www.bar.org">
<actors>
<actor id="1">Christian Bale</actor>
<actor id="2">Liam Neeson</actor>
<actor id="3">Michael Caine</actor>
</actors>
<foo:singers>
<foo:singer id="4">Tom Waits</foo:singer>
<foo:singer id="5">B.B. King</foo:singer>
<foo:singer id="6">Ray Charles</foo:singer>
</foo:singers>
<items>
<item id="7">Pizza</item>
<item id="8">Cheese</item>
<item id="9">Cane</item>
</items>
</root>
Here be my path & code:
$xml = simplexml_load_file('xpath.xml');
$result = $xml -> xpath('/root/actors');
echo '<pre>'.print_r($result,1).'</pre>';
Now, said path returns:
Array
(
[0] => SimpleXMLElement Object
(
[actor] => Array
(
[0] => Christian Bale
[1] => Liam Neeson
[2] => Michael Caine
)
)
)
Whereas a seemingly similar line of code, which I would have though would result in the singers, doesnt. Meaning:
$result = $xml -> xpath('/root/foo:singers');
Results in:
Array
(
[0] => SimpleXMLElement Object
(
)
)
Now I would've thought the foo: namespace in this case is a non-issue and both paths should result in the same sort of array of singers/actors respectively? How come that is not the case?
Thank-you!
Note: As you can probably gather I'm quite new to xml so please be gentle.
Edit: When I go /root/foo:singers/foo:singer I get results, but not before. Also with just /root I only get actors and items as results, foo:singers are completely omitted.
SimpleXML is, for a number of reasons, simply a bad API.
For most purposes I suggest PHP's DOM extension. (Or for very large documents a combination of it along with XMLReader.)
For using namespaces in xpath you'll want to register those you'd like to use, and the prefix you want to use them with, with your xpath processor.
Example:
$dom = new DOMDocument();
$dom->load('xpath.xml');
$xpath = new DOMXPath($dom);
// The prefix *can* match that used in the document, but it's not necessary.
$xpath->registerNamespace("ns", "http://www.foo.org/");
foreach ($xpath->query("/root/ns:singers") as $node) {
echo $dom->saveXML($node);
}
Output:
<foo:singers>
<foo:singer id="4">Tom Waits</foo:singer>
<foo:singer id="5">B.B. King</foo:singer>
<foo:singer id="6">Ray Charles</foo:singer>
</foo:singers>
DOMXPath::query returns a DOMNodeList containing matched nodes. You can work with it essentially the same way you would in any other language with a DOM implementation.
You can use // expression like:
$xml -> xpath( '//foo:singer' );
to select all foo:singer elements no matter where they are.
EDIT:
SimpleXMLElement is selected, you just can't see the child nodes with print_r(). Use SimpleXMLElement methods like SimpleXMLElement::children to access them.
// example 1
$result = $xml->xpath( '/root/foo:singers' );
foreach( $result as $value ) {
print_r( $value->children( 'foo', TRUE ) );
}
// example 2
print_r( $result[0]->children( 'foo', TRUE )->singer );

display data from XML using php simplexml

I have a piece of XML which is as follows
<records count="2">
<record>
<firstname>firstname</firstname>
<middlename>middlename</middlename>
<lastname>lastname</lastname>
<namesuffix/>
<address>
<street-number>demo</street-number>
<street-pre-direction/>
<street-name>demo</street-name>
<street-post-direction/>
<street-suffix>demo</street-suffix>
<city>demo</city>
<state>NY</state>
<zip>demo</zip>
<zip4>demo</zip4>
<county>demo</county>
</address>
<phonenumberdetails>
<phonenumber>demo</phonenumber>
<listed>demo</listed>
<firstname>demo</firstname>
</phonenumberdetails>
<dob day="" month="" year=""/>
<age/>
<date-first month="10" year="1999"/>
<date-last month="04" year="2011"/>
</record>
<record>
<firstname>firstname</firstname>
<middlename>middlename</middlename>
<lastname>lastname</lastname>
<namesuffix/>
<address>
<street-number>demo</street-number>
<street-pre-direction/>
<street-name>demo</street-name>
<street-post-direction/>
<street-suffix>demo</street-suffix>
<city>demo</city>
<state>NY</state>
<zip>demo</zip>
<zip4>demo</zip4>
<county>demo</county>
</address>
<phonenumberdetails>
<phonenumber>demo</phonenumber>
<listed>demo</listed>
<firstname>demo</firstname>
</phonenumberdetails>
<dob day="" month="" year=""/>
<age/>
<date-first month="10" year="1999"/>
<date-last month="04" year="2011"/>
</record>
</records>
Now, I have been able to get all the data in PHP using SimpleXML except for the date-first and date-last elements. I have been using code listed below
$dateFirst = 'date-first';
$dateLast = 'date-last';
$streetNumber = 'street-number';
$streetPreDirection = 'street-pre-direction';
$streetName = 'street-name';
$streetPostDirection = 'street-post-direction';
$streetSuffix = 'street-suffix';
$unitDesignation = 'unit-designation';
$unitNumber = 'unit-number';
foreach ($reportDataXmlrecords->records->record as $currentRecord) {
echo $currentRecord->$dateFirst['month'].'/'.$currentRecord->$dateFirst['year'];
echo $currentRecord->$dateLast['month'].'/'.$currentRecord->$dateLast['year'];
echo $currentRecord->address->$streetNumber;
$currentRecord->address->$streetName; // ......and so on
}
where $reportDataXmlrecords is the part of the simpleXML object from the parent node of
But the first two echo's don't print anything and all the other are printing correctly, specifically, I cant access the data in
<date-first month="10" year="1999"/>
<date-last month="04" year="2011"/>
Also for debugging if I do
print_r($currentRecord->$dateFirst);
it prints
SimpleXMLElement Object (
[#attributes] => Array ( [month] => 10 [year] => 1999 )
)
Any help would be greatly appreciated. Thank you.
You problem is when you do
$currentRecord->$dateFirst['month']
PHP will first evaluate $dateFirst['month'] as a whole before trying to use it as a property
$dateFirst = 'date-first';
var_dump( $dateFirst['month'] ); // gives "d"
because strings can be accessed by offset with array notation, but non-integer offsets are converted to integer and because casting 'month' to integer is 0, you are trying to do $currentRecord->d:
$xml = <<< XML
<record>
<date-first month="jan"/>
<d>foo</d>
</record>
XML;
$record = simplexml_load_string($xml);
$var = 'date-first';
echo $record->$var['month']; // foo
You can access hyphenated properties with curly braces:
$record->{'date-first'}['month'] // jan
On a sidenote, when the XML shown in your question is really the XML you are loading with SimpleXml, e.g. when <records> is the root node, then doing
$reportDataXmlrecords->records->record
cannot work, because $reportDataXmlrecords is already the root node and you'd have to omit the ->records if you want to iterate over the record elements in it.

PHP: simplexml_load_string

Using PHP's simplexml_load_string how do I get the values of a tag has a particular attribute set to a particular value?
Once you have loaded your XML data, you should be able to use the SimpleXMLElement::xpath method to do an XPath query on it, to find a specific element.
For example, considering your have an XML String, and load it this way :
$xmlString = <<<TEST
<root>
<elt plop="test">aaa</elt>
<elt plop="huhu">bbb</elt>
</root>
TEST;
$xml = simplexml_load_string($xmlString);
You could use the following portion of code to find the <elt> tag for which the plop attribute has the value huhu :
$elt = $xml->xpath('//elt[#plop="huhu"]');
var_dump($elt);
And you'd get this kind of output :
array
0 =>
object(SimpleXMLElement)[2]
public '#attributes' =>
array
'plop' => string 'huhu' (length=4)
string 'bbb' (length=3)
<?php
// heredoc use
$string = <<<XML
<?xml version='1.0'?>
<document>
<title>Forty What?</title>
<from>Joe</from>
<to>Jane</to>
<body>
I know that's the answer -- but what's the question?
</body>
</document>
XML;
$xml = simplexml_load_string($string);
echo $xml->title; // you can get "Forty What?" value`enter code here`
?>

Getting cdata content while parsing xml file

I have an xml file
<?xml version="1.0" encoding="utf-8"?>
<xml>
<events date="01-10-2009" color="0x99CC00" selected="true">
<event>
<title>You can use HTML and CSS</title>
<description><![CDATA[This is the description ]]></description>
</event>
</events>
</xml>
I used xpath and and xquery for parsing the xml.
$xml_str = file_get_contents('xmlfile');
$xml = simplexml_load_string($xml_str);
if(!empty($xml))
{
$nodes = $xml->xpath('//xml/events');
}
i am getting the title properly, but iam not getting description.How i can get data inside
the cdata
SimpleXML has a bit of a problem with CDATA, so use:
$xml = simplexml_load_file('xmlfile', 'SimpleXMLElement', LIBXML_NOCDATA);
if(!empty($xml))
{
$nodes = $xml->xpath('//xml/events');
}
print_r( $nodes );
This will give you:
Array
(
[0] => SimpleXMLElement Object
(
[#attributes] => Array
(
[date] => 01-10-2009
[color] => 0x99CC00
[selected] => true
)
[event] => SimpleXMLElement Object
(
[title] => You can use HTML and CSS
[description] => This is the description
)
)
)
You are probably being misled into thinking that the CDATA is missing by using print_r or one of the other "normal" PHP debugging functions. These cannot see the full content of a SimpleXML object, as it is not a "real" PHP object.
If you run echo $nodes[0]->Description, you'll find your CDATA comes out fine. What's happening is that PHP knows that echo expects a string, so asks SimpleXML for one; SimpleXML responds with all the string content, including CDATA.
To get at the full string content reliably, simply tell PHP that what you want is a string using the (string) cast operator, e.g. $description = (string)$nodes[0]->Description.
To debug SimpleXML objects and not be fooled by quirks like this, use a dedicated debugging function such as one of these: https://github.com/IMSoP/simplexml_debug
This could also be another viable option, which would remove that code and make life a little easier.
$xml = str_replace("<![CDATA[", "", $xml);
$xml = str_replace("]]>", "", $xml);

Categories