I asked before, and got the answers for one instance, now i have to parse multiple repeating instances of XML file into PHP variables; XML file looks like this:
<status>
<client type="s" name="root" desc="" protocol="server" protocolext="" au="0" thid="0x15e9190">
<request="0000" srvid="0000" time="" history="" answered=""></request>
<times login="2013-04-16T10:59:16+0200" online="7001" idle="0"></times>
<connection ip="127.0.0.1" port="0">OK</connection>
</client>
<client type="p" name="user1" desc="" protocol="run1" protocolext="" au="-1" thid="0x15f1790">
<request="0000" srvid="0000" time="" history="2667" answered=""></request>
<times login="2013-04-16T10:59:16+0200" online="7001" idle="6999"></times>
<connection ip="127.0.2.2" port="10002">CONNECTED</connection>
</client>
<client type="p" name="user2" desc="" protocol="run2" protocolext="" au="-1" thid="0x15f32b0">
<request="0000" srvid="0000" time="" history="" answered=""></request>
<times login="2013-04-16T10:59:16+0200" online="7001" idle="7001"></times>
<connection ip="127.0.3.1" port="12001">CONNECTED</connection>
</client>
<client type="p" name="user3" desc="" protocol="run1" protocolext="" au="-1" thid="0x1631170">
<request="0000" srvid="0000" time="" history="" answered=""></request>
<times login="2013-04-16T10:59:16+0200" online="7001" idle="7001"></times>
<connection ip="127.0.4.1" port="9600">CONNECTED</connection>
</client>
</status>
when i do it with Xpath, it works OK, but fetches only first data part into variables;
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
$client_type = $xpath->evaluate('string(/status/client/#type)');
$name = $xpath->evaluate('string(/status/client/#name)');;
$conn_ip = $xpath->evaluate('string(/status/client/connection/#ip)');
and echoing variables:
echo $client_type;
echo $name ;
echo $conn_ip;
it returns only first values:
What would be the best way to pull ALL DATA from the file above?
Getting all the <client> nodes and then looping over them is a good way to make the dom traversal clearer. Here is an example of getting all the clients infos
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
// use the double // to find ALL clients in the document
$clientXpath = "//client";
$clients = $xpath->evaluate($clientXpath);
// foreach client node
foreach ($clients as $ii=>$client) {
// get the type attribute of client node
echo $client->getAttribute('type') . "\n";
// get the name attribute of client node
echo $client->getAttribute('name') . "\n";
// get clients children
$children = $client->childNodes;
foreach ($children as $child) {
// ignore textnodes
if ($child instanceof DomText) {
continue;
}
// now concern ourself only with the connection tag, which
// contains the ip
if ($child->tagName == 'connection') {
print $child->getAttribute('ip') . "\n";
}
}
}
Xpath expression used in DOMXpath, return a DOMNodelist unless the result is casted to a scalar. So the example can be expanded to iterate over a list of nodes. Inside the loop the node is used as a context for the expressions.
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
foreach ($xpath->evaluate('/status/client') as $client) {
var_dump(
$xpath->evaluate('string(#type)', $client),
$xpath->evaluate('string(#name)', $client),
$xpath->evaluate('string(connection/#ip)', $client)
);
}
Demo: https://eval.in/125083
Related
I have a XML file and I'm trying to insert a new node between two others with PHP script.
XML file:
<playlistLog>
<hour>
<track>
<type>take</type>
<url>URL</url>
<title>1473869236.wav</title>
<mix>0</mix>
</track>
(...)
</hour>
</playlistLog>
PHP file:
$xmldoc = new DOMDocument();
$xmldoc->load('../logs/log14-09-2016.xml');
$elem = $xmldoc->getElementsByTagName("track");
$track = $xmldoc->createElement('track');
$type = $xmldoc->createElement('type', 'take');
$track->appendChild($type);
$url = $xmldoc->createElement('url', 'url');
$track->appendChild($url);
$title = $xmldoc->createElement('title', 'title');
$track->appendChild($title);
$mix = $xmldoc->createElement('mix', 'mix');
$track->appendChild($mix);
$xmldoc->documentElement->insertBefore($track,$elem[660]);
$xmldoc->save('../logs/log14-09-2016.xml');
I'm trying to insert the new node before "track" tag number 660 but when I try to open the php file it doesn't work at all.
Can anybody tell me what I am doing wrong?
SOLUTION:
After #ThW response I change a bit what he wrotes and finally the code is doing right:
$document = new DOMDocument();
$document->preserveWhiteSpace = FALSE;
$document->load('../logs/log14-09-2016.xml');
$xpath = new DOMXpath($document);
$previousTrack = $xpath->evaluate('/playlistLog/hour/track')->item(659);
$newTrack = $previousTrack->parentNode->insertBefore($document->createElement('track'),$previousTrack);
$newTrack
->appendChild($document->createElement('type'))
->appendChild($document->createTextNode('take'));
$document->formatOutput = TRUE;
echo $document->save('../logs/log14-09-2016.xml');
$elem[660] is the 661st element node with the tag name track. But its parent node is not the document element. Here is another hour ancestor between. The node you're providing to insertBefore() has a different parent then the node you're adding the new element to.
You can use the $parentNode property to make sure that you append to that node.
Additionally I suggest using Xpath to fetch the track node.
$xml = <<<'XML'
<playlistLog>
<hour>
<track>
<type>take</type>
<url>URL</url>
<title>1473869236.wav</title>
<mix>0</mix>
</track>
</hour>
</playlistLog>
XML;
$document = new DOMDocument();
$document->preserveWhiteSpace = FALSE;
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$previousTrack = $xpath->evaluate('/playlistLog/hour/track[1]')->item(0);
$newTrack = $previousTrack
->parentNode
->insertBefore(
$document->createElement('track'),
$previousTrack
);
$newTrack
->appendChild($document->createElement('type'))
->appendChild($document->createTextNode('take'));
$document->formatOutput = TRUE;
echo $document->saveXml();
In PHP i have this code for making a XML header for the plesk API.
$request = <<<EOF
<packet version="1.6.7.0">
<mail>
<update>
<set>
<filter>
<site-id>$site_id</site-id>
<mailname>
<name>$name</name>
<autoresponder>
<enabled>true</enabled>
<subject>$subject</subject>
<text>$mail_body</text>
<end_date>$date</end_date>
</autoresponder>
</mailname>
</filter>
</set>
</update>
</mail>
</packet>
EOF;
However i get this response: 1014 Parser error: Cannot parse the XML from the source specified
I have put the xml into a formatting of 2, 3 ,4 and tab spacing and it doesnt seem to be able to parse it.
What am i doing wrong?
You can't guess to create a valid XML by string concatenation, especially when you have complex contents like an email text.
No all characters are allowed inside XML tags: you have to properly escape not-allowed characters. Fortunately, php have some parser that do this job for you.
First of all, create an empty XML template (check its validity using a XML validator):
$xml = '<?xml version="1.0" encoding="utf-8" ?>
<packet version="1.6.7.0">
<mail>
<update>
<set>
<filter>
<site-id/>
<mailname>
<name/>
<autoresponder>
<enabled/>
<subject/>
<text/>
<end_date/>
</autoresponder>
</mailname>
</filter>
</set>
</update>
</mail>
</packet>
';
Then, load it into a DOMDocument object and init a DOMXPath object:
$dom = new DomDocument();
$dom->loadXML( $xml );
$xpath = new DOMXPath( $dom );
Then, find each node that you want to change and set/update its node value:
$nodes = $xpath->query( 'mail/update/set/filter/site-id' );
$nodes->item(0)->nodeValue = $site_id;
$nodes = $xpath->query( 'mail/update/set/filter/mailname/name' );
$nodes->item(0)->nodeValue = $name;
For the <autoresponder> children, you can perform a loop through each child, using * at the end of your search pattern:
$nodes = $xpath->query( 'mail/update/set/filter/mailname/autoresponder/*' );
foreach( $nodes as $node )
{
if( 'enabled' == $node->nodeName )
{
$node->nodeValue = 'true';
}
elseif( 'subject' == $node->nodeName )
{
$node->nodeValue = $subject;
}
elseif( 'text' == $node->nodeName )
{
$cdata = $dom->createCDATASection( $mail_body );
$node->appendChild( $cdata );
}
elseif( 'end_date' == $node->nodeName )
{
$node->nodeValue = $date;
}
}
Note the different syntax adopted for mail body: I use a CDATA node here: if your XML doesn't allow CDATA, replace it with standard ->nodeValue syntax. Or — instead — you can have to use CDATA method for all the nodes.
When the XML is ready, you can echo it by:
echo $dom->saveXML();
DOMXPath allow to perform complex searches in the XML tree: it's not mandatory in your case, because you start from a short, empty, unambiguous template. I use it for demonstration purpose, but you can replace a line like this:
$nodes = $xpath->query( 'mail/update/set/filter/site-id' );
with:
$nodes = $dom->getElementsByTagName( 'site-id' );
and it will work fine.
Read more about DOMDocument
Read more about DOMXPath
I'm trying to import one node from one document to another. This throws an 'Wrong Document Error' error:
$xml = <<<XML
<root>
<child1>foo </child1>
<child2> bar</child2>
</root>
XML;
$doc = new DOMDocument;
$new = new DOMDocument;
$doc->loadXML($xml);
$xp = new DOMXPath($doc);
foreach($xp->query('/root/node()') as $element)
{
$new->appendChild($doc->importNode($element, true));
}
echo $new->saveXML();
You want to use $new->importNode... rather than $doc->importNode... as $new is the document you're trying to add the node to.
from this xml file i want to delete picture node according to attribute id.
i have written php code but it does not work.
<gallery>
<organizate>
<organization w="3" h="1" space="17"/>
<organization w="4" h="2" space="17"/>
<organization w="6" h="3" space="7"/>
</organizate>
<pictures>
<picture target="events/preview/10picture1.jpg" title="test1" movie="" text="test1" link="events_calender.php" id="38"/>
<picture target="events/preview/8picture7.jpg" title="test2" movie="" text="cxvxc" link="events_calender.php" id="39"/>
<picture target="events/preview/5picture10.jpg" title="test3" movie="" text="test3" link="events_calender.php" id="40"/>
</pictures>
</gallery>
PHP code
$doc = new DOMDocument();
$doc->formatOutput = TRUE;
$doc->preserveWhiteSpace = FALSE;
$xPath = new DOMXPath($doc);
$doc->load('../Event_gallery.xml');
$query = sprintf('//pictures[./picture[#id="%s"]]', 38);
foreach ($xPath->query($query) as $node) {
$node->parentNode->removeChild($node);
}
$doc->save('../Event_gallery.xml');
I think xpath is not working properly. control is not going in foreach
The problem is you are loading the document after you pass the document to DOMXPath.
Change
$xPath = new DOMXPath($doc);
$doc->load('../Event_gallery.xml');
to
$doc->load('../Event_gallery.xml');
$xPath = new DOMXPath($doc);
Then it should work.
To remove just the picture element with the given id instead of the whole parent, change the XPath to
//pictures/picture[#id="38"]
My question is best phrase as:
Remove a child with a specific attribute, in SimpleXML for PHP
except I'm not using simpleXML.
I'm new to XML for PHP so I may not be doing the best way
I have a xml created using the $dom->save($xml) for each individual user. (not placing all in one xml due to undisclosed reasons)
It gives me that xml declaration <?xml version="1.0"?> (no idea how to make it to others, but that's not the point, hopefully)
<?xml version="1.0"?>
<details>
<person>name</person>
<data1>some data</data1>
<data2>some data</data2>
<data3>some data</data3>
<category id="0">
<categoryName>Cat 1</categoryName>
<categorydata1>some data</categorydata1>
</category>
<category id="1">
<categoryName>Cat 2</categoryName>
<categorydata1>some data</categorydata1>
<categorydata2>some data</categorydata2>
<categorydata3>some data</categorydata3>
<categorydata4>some data</categorydata4>
</category>
</details>
And I want to remove a category that has a specific attribute named id with the DOM class in php when i run a function activated from using a remove button.
the following is the debug of the function im trying to get to work. Can i know what I'm doing wrong?
function CatRemove($myXML){
$xmlDoc = new DOMDocument();
$xmlDoc->load( $myXML );
$categoryArray = array();
$main = $xmlDoc->getElementsByTagName( "details" )->item(0);
$mainElement = $xmlDoc->getElementsByTagName( "details" );
foreach($mainElement as $details){
$currentCategory = $details->getElementsByTagName( "category" );
foreach($currentCategory as $category){
$categoryID = $category->getAttribute('id');
array_push($categoryArray, $categoryID);
if($categoryID == $_POST['categorytoremoveValue']) {
return $categoryArray;
}
}
}
$xmlDoc->save( $myXML );
}
Well the above prints me an array of [0]->0 all the time when i slot the return outside the if.
is there a better way? I've tried using getElementbyId as well but I've no idea how to work that.
I would prefer not to use an attribute though if that would make things easier.
Ok, let’s try this complete example of use:
function CatRemove($myXML, $id) {
$xmlDoc = new DOMDocument();
$xmlDoc->load($myXML);
$xpath = new DOMXpath($xmlDoc);
$nodeList = $xpath->query('//category[#id="'.(int)$id.'"]');
if ($nodeList->length) {
$node = $nodeList->item(0);
$node->parentNode->removeChild($node);
}
$xmlDoc->save($myXML);
}
// test data
$xml = <<<XML
<?xml version="1.0"?>
<details>
<person>name</person>
<data1>some data</data1>
<data2>some data</data2>
<data3>some data</data3>
<category id="0">
<categoryName>Cat 1</categoryName>
<categorydata1>some data</categorydata1>
</category>
<category id="1">
<categoryName>Cat 2</categoryName>
<categorydata1>some data</categorydata1>
<categorydata2>some data</categorydata2>
<categorydata3>some data</categorydata3>
<categorydata4>some data</categorydata4>
</category>
</details>
XML;
// write test data into file
file_put_contents('untitled.xml', $xml);
// remove category node with the id=1
CatRemove('untitled.xml', 1);
// dump file content
echo '<pre>', htmlspecialchars(file_get_contents('untitled.xml')), '</pre>';
So you want to remove the category node with a specific id?
$node = $xmlDoc->getElementById("12345");
if ($node) {
$node->parentNode->removeChild($node);
}
You could also use XPath to get the node, for example:
$xpath = new DOMXpath($xmlDoc);
$nodeList = $xpath->query('//category[#id="12345"]');
if ($nodeList->length) {
$node = $nodeList->item(0);
$node->parentNode->removeChild($node);
}
I haven’t tested it but it should work.
Can you try with this modified version:
function CatRemove($myXML, $id){
$doc = new DOMDocument();
$doc->loadXML($myXML);
$xpath = new DOMXpath($doc);
$nodeList = $xpath->query("//category[#id='$id']");
foreach ($nodeList as $element) {
$element->parentNode->removeChild($element);
}
echo htmlentities($doc->saveXML());
}
It's working for me. Just adapt it to your needs. It's not intended to use as-is, but just a proof of concept.
You also have to remove the xml declaration from the string.
the above funciton modified to remove an email from a mailing list
function CatRemove($myXML, $id) {
$xmlDoc = new DOMDocument();
$xmlDoc->load($myXML);
$xpath = new DOMXpath($xmlDoc);
$nodeList = $xpath->query('//subscriber[#email="'.$id.'"]');
if ($nodeList->length) {
$node = $nodeList->item(0);
$node->parentNode->removeChild($node);
}
$xmlDoc->save($myXML);
}
$xml = 'list.xml';
$to = $_POST['email'];//user already submitted they email using a form
CatRemove($xml,$to);