SimpleXML Search Children and Retrieve all Nodes - php

More or less I need guidance to move forward with this ..
Have a page that displays each node in a container (does stuff to it)
XML
<archive>
<data id="1111">
<name>Name</name>
<text>Lots of stuff and things</text>
<etc>Etc</etc>
</data>
<data id="2222">
<name>Name</name>
<text>Different stuff and things</text>
<etc>Etc</etc>
</data>
<data id="3333">
<name>Name</name>
<text>More stuff and things</text>
<etc>Etc</etc>
</data>
// and so on
</archive>
This portion takes the XML and echos the values etc ..
$master = array_slice($xml_get->xpath('data'), $start_page, 25);
$master = array_reverse($master);
foreach($master as $arc) {
$last_name = $arc[0]->name;
$last_data = $arc[0]->data;
$last_etc = $arc[0]->etc;
// does stuff with values
}
What I want to do is have a search field that takes that search keyword and searches all the children and then foreach every node+children that matches.
Honestly I just would appreciate some direction on how to accomplish this. I know how to individually grab a node via the id= but after that .. need guidance.

As a quick sample, to search the <text> element using XPath (I've altered the sample data you give to show the difference in what it selects)
$data = '<archive>
<data id="1111">
<name>Name</name>
<text>Lots of stuff and things</text>
<etc>Etc</etc>
</data>
<data id="2222">
<name>Name</name>
<text>Different stuff and things</text>
<etc>Etc</etc>
</data>
<data id="3333">
<name>Name</name>
<text>More stuff and other things</text>
<etc>Etc</etc>
</data>
</archive>';
$xml_get = simplexml_load_string($data);
$textSearch = "stuff and things";
$matches = $xml_get->xpath('//data[contains(text,"'.$textSearch.'")]');
foreach($matches as $arc) {
echo "text=".$arc->text.PHP_EOL;
}
Outputs..
text=Lots of stuff and things
text=Different stuff and things
The XPath - //data[contains(text,"'.$textSearch.'")] basically says to find any <data> element which has a <text> element which has a value that contains the string being searched for. You can alter which field it uses by just changing text

Related

Adding Nodes to Existing XML

The problem i was having is the Root XML was being produced every time it writes to the XML.
The Main issue was setting up Child and Defining the Root. From the help of Łza
I now understand the Root XML Node is ignored.
So then you setup and create a Child and then add your content, And example of the correct format is.
$xml = simplexml_load_file('FILENAME.xml'); // Load XML File Need to add IF Statment to create if does not exist
$result = $xml->addchild('Result'); // Ignore Root NODE and Add Child Results
$result->addChild('Time', gmdate('D-M-Y -H:i:s')); // Rest of the below adds Child to Result and outputs results
$result->addChild('Channel', $Site);
$result->addChild('Type', '**');
$result->addChild('Process', $Status);
$result->addChild('SKU', $code->SKU);
$result->addChild('item', $item);
$result->addChild('Status', '$Feedback');
$result->addChild('ErrorID', '$Error');
$result->addChild('Message', '$Message');
$xml->asXml('FILENAME.xml'); //Write to file would be
// All of the above Code is using variables from another part of the script
The output would be
<Root>
<Result>
<Time>Fri-May-2013 -09:15:22</Time>
<Channel>20</Channel>
<Type>**</Type>
<Process>Update</Process>
<SKU>98746524765</SKU>
<Item/>
<Status>Problem</Status>
<ErrorID>999-Error</ErrorID>
<Message>Unknown file format support</Message>
</Result>
<Result>
<Time>Fri-May-2013 -09:15:22</Time>
<Channel>20</Channel>
<Type>**</Type>
<Process>Update</Process>
<SKU>5412254785</SKU>
<Item/>
<Status>Problem</Status>
<ErrorID>123-Error</ErrorID>
<Message>Invalid Item</Message>
</Result>
</Root>
Thanks
Try to use SimpleXMLElement library instead hardcoded xml creation. This is maybe more complicate to use at the begining, but much more safe (I mean avoid possible errors in xml structure when you hardcode the xml) and easy to use when you just get start to use it.
And easy to add/remove nodes, childnodes.
This is an example for your code:
$xml = new SimpleXMLElement('<xml/>');
$data = $xml->addChild('data');
$result = $data->addChild('Result');
$result->addChild('Time', gmdate('D-M-Y -H:i:s'));
$result->addChild('Channel', $SiteID);
// ... and the same way create all your xml nodes.
// if you want add next <result> node witch all elements repeat the code, (or put it in loop if you want more <result> elements):
$result = $data->addChild('Result');
$result->addChild('Time', gmdate('D-M-Y -H:i:s'));
$result->addChild('Channel', $SiteID);
// and after create all nodes save the file:
$xml->asXml('DHError.xml');
above code will create xml:
<xml>
<data>
<Result>
<Time>Fri-May-2013 -12:14:39</Time>
<Channel>data</Channel>
</Result>
<Result>
<Time>Fri-May-2013 -12:14:39</Time>
<Channel>data</Channel>
</Result>
</data>
</xml>
Thats it. Then if you need to load and process the xml it would be easy:
To load the File simply use:
$xml2 = simplexml_load_file('DHError.xml');
// to add new node <Result>:
$resultNext = $xml2->data->addchild('Result');
$resultNext->addChild('Time', gmdate('D-M-Y -H:i:s'));
$resultNext->addChild('Channel', $SiteID);
//and save file
$xml2->asXml('DHError.xml');
this create a xml:
<?xml version="1.0" ?>
<xml>
<data>
<Result>
<Time>Fri-May-2013 -12:27:24</Time>
<Channel>data</Channel>
</Result>
<Result>
<Time>Fri-May-2013 -12:27:24</Time>
<Channel>data</Channel>
</Result>
<Result>
<Time>Fri-May-2013 -12:27:24</Time>
<Channel>data</Channel>
</Result>
</data>
</xml>

Trouble reading XML tag attributes with PHP

Given the following
<data type="new">
<time>1</time>
<time>2</time>
</data>
<data type="old">
<time>3</time>
<time>4</time>
</data
The goal being to type something along the following
<?php
$test = simplexml_load_file('http://....')
echo $test->data['type="old"']->time[0] //I want this to return 3
?>
and get the value of 3!
I hate tried using attributes but I'm still on the search for the solution.
Thanks,
JTC
The actually doc I'm trying to parse from
<data type="current observations">
<time-layout time-coordinate="local">
<start-valid-time period-name="current">2013-05-27T13:53:00-04:00</start-valid-time>
</time-layout>
</data>
I attempted the following but got an Invalid expression warning.
$weather = simplexml_load_file('http://...');
$time=$weather->xpath('//data[#type="current observations"]/"time-layout"/"start-valid- time"');
echo $time[0];
Here's a code snippet using SimpleXMLElement and XPath. I've added a root datas node so the XML could be parsed, I'm guessing yours is well formed.
<?php
$xml = <<<XML
<datas>
<data type="new">
<time>1</time>
<time>2</time>
</data>
<data type="old">
<time>3</time>
<time>4</time>
</data>
</datas>
XML;
$sxe = new SimpleXMLElement($xml);
$time = $sxe->xpath('//data[#type="old"]/time');
echo $time[0];
Output
3
OK, taking your actual XML document which is well-formed, you could do this...
$data = $weather->xpath('/data[#type="current observations"]');
$time_layout = $data[0]->xpath('time-layout[#time-coordinate="local"]');
$start_valid_time = $time_layout[0]->xpath('start-valid-time[#period-name="current"]');
echo (string)$start_valid_time[0];
See it in action here.

PHP SimpleXML add child to each parent repeatedly

I have this kind of XML:
<?xml version="1.0" encoding="utf-8"?>
<data>
<stats>
</stats>
<params>
</params>
<results>
<record id='SJDGH'>
<item>abc</item>
<item>def</item>
<item>ghi</item>
</record>
<record id='OIIO'>
<item>abc</item>
<item>def</item>
<item>ghi</item>
</record>
</results>
</data>
I'm generating a new <item> for every <record> in <results> in a loop:
// $data is SimpleXml objec from XML above
foreach ($data->results->record as $record)
{
$newitem = 'New item!'.time().$record->attributes()->id;
}
Somehow in this loop i need to change the SimpleXML object ($data) to contain new items in every <record>.
is it possible?
I needed a little guessing, but this might what you're looking for:
$records = $data->results->record;
foreach($records as $record)
{
$value = sprintf('New Item! %s / id:%s', time(), $record['id']);
$record->item[] = $value;
}
$data->asXML('php://output');
See it in action.
I think you might want to use addChild.
Check it out here: http://php.net/manual/en/simplexmlelement.addchild.php

Exporting defined elements from XML

I would like to export (or to keep) the subtags which are with the defined attribute in XML. As I don't know the name of this process, I can't find any relevant information about it on the net. And since it's hard to explain, I decided to put an examples for my issue.
Let's say, I have this XML file:
<results>
<result idSite="1">
<row>
<label>category</label>
<visits>2</visits>
<idsubdatatable>5</idsubdatatable>
<subtable>
<row>
<label>uncategorized</label>
<visits>2</visits>
<idsubdatatable>6</idsubdatatable>
<subtable>
<row>
<label>/index</label>
<visits>2</visits>
<url>http://mysite1.com/category/uncategorized/</url>
</row>
</subtable>
</row>
</subtable>
</row>
<row>
<label>about</label>
<visits>1</visits>
<idsubdatatable>7</idsubdatatable>
<subtable>
<row>
<label>/index</label>
<visits>1</visits>
<url>http://mysite1.com/about/</url>
</row>
</subtable>
</row>
</result>
<result idSite="2">
<row>
<label>/calendar</label>
<visitors>1</visitors>
<url>http://mysite2.com/calendar</url>
</row>
</result>
</results>
And I have to parse the results and keep only the rows which are with a <url> attribute. Like this:
After parsing I have to combine these rows in a new XML file, and the final result must be like this:
<result>
<row>
<label>/index</label>
<visits>2</visits>
<url>http://mysite1.com/category/uncategorized/</url>
</row>
<row>
<label>/index</label>
<visits>1</visits>
<url>http://mysite1.com/about/</url>
</row>
<row>
<label>/calendar</label>
<visitors>1</visitors>
<url>http://mysite2.com/calendar</url>
</row>
</result>
Generally I want to do this process in PHP but it maybe in other languages too.
So, if you have any idea to solve this problem, please comment.
I would use an xpath query to find all url nodes inside row nodes. Then, just append the parent node of each url element you find to a new DomDocument like so:
$xml = '...';
$dom = new DomDocument();
$dom->preserveWhiteSpace = FALSE;
$dom->loadXML($xml);
$new_dom = new DomDocument();
$result = $new_dom->createElement('result');
$new_dom->appendChild($result);
$xpath = new DOMXPath($dom);
$rows = $xpath->query('//row/url');
for ($i=0;$i<$rows->length;$i++) {
$node = $new_dom->importNode($rows->item($i)->parentNode, TRUE);
$result->appendChild($node);
}
$new_dom->formatOutput = TRUE;
echo $new_dom->saveXML();
I'd use simplexml to read as your input, so your parsing would be easy. And then, i'd create a recursive function such as:
function isUrlElement($element){
foreach($element->children() as $children){
if($children->getName() == 'url'){
return true;
}else{
isUrlElement($children);
}
}
}
Now this is far from complete, but you could make it recursive calling it for each children. When this returns true, you'd know you found a node that has URL as a children. Use that $element node to for example add it to an array of simplexmlelements and then just foreach it back into XML.
Does that make sense?

PHP & XML Read element by attribute value

$xml = '<?xml version="1.0"?>
<entries>
<response>
<category>client</category>
<action>Greeting</action>
<code>1000</code>
<msg>Your Connection with API Server is Successful</msg>
<resData>
<data name="svDate">2010-10-10 02:27:14</data>
</resData>
</response>
<response>
<category>client</category>
<action>Login</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L116:no value</value>
</response>
<response>
<category>domain</category>
<action>InfoDomain</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L125:no value</value>
<resData>
<data name="domain">google.com</data>
<data name="crDate">2004-12-16</data>
<data name="exDate">2013-12-16</data>
</resData>
</response>
</entries>';
$xml = simplexml_load_string($xml);
$domain = $xml->response[2]->resData[0]->data[0];
$crdate = $xml->response[2]->resData[0]->data[1];
$exdate = $xml->response[2]->resData[0]->data[2];
With the above code i can get the values.
But how can i get the values by attribute value?
For example i want to get the values with something like this:
$domain = $xml->response[2]->resData[0]->data["domain"];
$crdate = $xml->response[2]->resData[0]->data["crdate"];
$exdate = $xml->response[2]->resData[0]->data["exdate"];
One more question.
If i have two elements with the same name?
For example i would like to parse the dns. How could i do it?
The xml code is like this:
<?xml version="1.0"?>
<entries>
<response>
<category>client</category>
<action>Greeting</action>
<code>1000</code>
<msg>Your Connection with API Server is Successful</msg>
<resData>
<data name="svDate">2010-10-10 02:27:14</data>
</resData>
</response>
<response>
<category>client</category>
<action>Login</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L116:no value</value>
</response>
<response>
<category>domain</category>
<action>InfoDomain</action>
<code>1000</code>
<msg>Command completed successfully</msg>
<value>L125:no value</value>
<resData>
<data name="domain">google.com</data>
<data name="crDate">2004-12-16</data>
<data name="exDate">2013-12-16</data>
<data name="dns">ns1.google.com</data>
<data name="dns">ns2.google.com</data>
</resData>
</response>
</entries>
As you can see the ns1 and ns2 have the same name. name="dns".
How can i parse each one in a different variable?
Thank you!
With the element["attribute"] syntax, attribute is the name of an attribute on the element. It is not the value of some randomly chosen attribute belonging to an element.
The example below creates an array containing a mapping for the data elements of name attribute value to text value.
$data = array();
foreach ($xml->response[2]->resData->data as $d) {
$data[strtolower($d['name'])] = (string) $d;
}
// Now you can access the values via $data['domain'], $data['crdate'], etc.
Nick, your code expects XML structured like:
<resData>
<data domain="google.com" crdate="2004-12-16" exdate="2013-12-16" />
</resData>
Edit due to question change
In a marvelous dose of eating my own words, due to the change in the question an XPath approach would be more appropriate (don't you love OPs who do that?).
You can easily get an array of the name="dns" elements with a basic XPath expression.
$xml = simplexml_load_string($xml);
$dns = $xml->xpath('response[category="domain"]/resData/data[#name="dns"]');
You can use XPath to to do queries against your XML, e.g.
$entries = simplexml_load_string($xml);
$dataElements = $entries->xpath('/entries/response/resData/data[#name="dns"]');
foreach ($dataElements as $dataElement) {
echo $dataElement;
}
The above XPath finds all <data> elements with a name attribute of "dns" that are direct children of the given element hierarchy, e.g.
<entries>
…
<response>
…
<resData>
…
<data name="dns">
IMO this is easier and more appropriate than having the extra step of copying over the values into an array which would disconnect it from the actual DOM tree and which you would have to repeat for all the elements you want to map this way. XPath is built-in. You just query and get the result.
Because $dataElements is an array, you can also access the elements in it individually with
echo $dataElements[0]; // prints "ns1.google.com"
echo $dataElements[1]; // prints "ns2.google.com"
Note that the $dataElements array actually contains SimpleXmlElements connected to the main document. Any changes you do to them will also be reflected in the main document.

Categories