Manipulate xml file with php and dom - php

I have XML file as below:
<?xml version="1.0" encoding="UTF-8"?>
<root version="8.0.0.0">
<songs name="Album">
<row>
<song artist="artist_name">Track1</song>
</row>
<row>
<song artist="artist_name">Track2</song>
</row>
<row>
<song artist="artist_name">Track3</song>
</row>
<row>
<song artist="artist_name">Track4</song>
</row>
</songs>
</root>
Now i want to update this file with some more rows. How i can append data on top of the existing row elements? Also while adding new elements i want to check the tracks like - Track1, Track2 are not duplicates.
Currently i'm manipulating this xml file with php:dom, but its appending data at the bottom of the existing rows.
PHP code used to do above things is
<?php
//Creates XML string and XML document using the DOM
$dom = new DOMDocument();
$dom->formatOutput = true;
$dom->Load('C:/wamp/www/xml/test1.xml');
$root = $dom->firstChild;
$list = $root->childNodes->item(1);
if(isset($_POST['submit'])){
$artistName = $_POST['name'];
$track = $_POST['track'];
$row = $dom->createElement('row');
$list->appendChild($row);
$song = $dom->createElement('song');
$row->appendChild($song);
$song->setAttribute('artist', $artistName);
$wcm_node = $dom->createTextNode($track);
$song->appendChild($wcm_node);
}
// Code to format XML after appending data
$outXML = $dom->saveXML(); // put string in outXML
//now create a brand new XML document
$xml = new DOMDocument();
$xml->preserveWhiteSpace = false;
$xml->formatOutput = true; //yup, going to try to make this format again
//pass the output of the first bunch of stuff we did to the new XML document:
$xml->loadXML($outXML);
//now display the cleanly formatted output without using LIBXML_NOBLANKS (which may have undesired consequences
$xml->save('test1.xml'); // save as file
}
?>
Please let me know, how i can do it.
Thanks

That's not appending but prepending. DOM has a method for that, too:
DOMNode::insertBefore — Adds a new child before a reference node
Example (demo):
$dom = new DOMDocument;
$dom->loadXml('<rows><row xml:id="r1"/></rows>');
$dom->documentElement->insertBefore(
$dom->createElement('row', 'new row'),
$dom->getElementById('r1')
);
$dom->formatOutput = TRUE;
echo $dom->saveXml();
Output:
<?xml version="1.0"?>
<rows>
<row>new row</row>
<row xml:id="r1"/>
</rows>

Related

How to read data from this XML file with PHP?

Making a Connection
$Game_ID = $Game_Search->Game[$i]->id;
$Game_Info_URL = 'http://thegamesdb.net/api/GetGame.php?id='.$Game_ID;
$Game_Info_Output = simplexml_load_file($Game_Info_URL);
Retrieving Data Example
$Game_Info_Images = $Game_Info_Output->Game->Images;
For this question please refer to this URL where I would like to get the Game->Images-> Box Art Side A and Side B. How do I call this?
XML Doc (Just required Fields)
<Data>
<baseImgUrl>http://thegamesdb.net/banners/</baseImgUrl>
<Game>
<Images>
<boxart side="back" width="1518" height="2148" thumb="boxart/thumb/original/back/90-1.jpg">boxart/original/back/90-1.jpg</boxart>
<boxart side="front" width="1530" height="2148" thumb="boxart/thumb/original/front/90-1.jpg">boxart/original/front/90-1.jpg</boxart>
</Images>
</Game>
</Data>
XML Doc (Just required Fields)
<Data>
<baseImgUrl>http://thegamesdb.net/banners/</baseImgUrl>
<Game>
<Images>
<boxart side="back" width="1518" height="2148" thumb="boxart/thumb/original/back/90-1.jpg">boxart/original/back/90-1.jpg</boxart>
<boxart side="front" width="1530" height="2148" thumb="boxart/thumb/original/front/90-1.jpg">boxart/original/front/90-1.jpg</boxart>
</Images>
</Game>
</Data>
To read side="front" width="1530"... simply use;
boxart["Attribute_Name"]
Examples:
Game->Images->boxart[$b]["side"] // Gets the side value front/back
Game->Images->boxart[$b]["width"] // gets the width value
Game->Images->boxart[$b]["height"] // gets the height value
Game->Images->boxart[$b]["thumb"] // gets the thumb value
DomDocument and/or Xpath:
$dom = new DOMDocument();
$dom->load('http://thegamesdb.net/api/GetGame.php?id=90');
$xpath = new DOMXPath($dom);
$baseImgUrl = $xpath->query('//baseImgUrl')->item(0)->nodeValue;
$boxartBackSide = $xpath->query('//Game/Images/boxart[#side="back"]')
->item(0)->nodeValue;
$boxartFrontSide = $xpath->query('//Game/Images/boxart[#side="front"]')
->item(0)->nodeValue;

Set the namespace for an XML tag with PHP

I'd like to create an XML document with a very specific format. It should look similar to this:
<?xml version="1.0" encoding="UTF-8"?>
<ram:FLOW xmlns:ram=\"http://MY_LIBRARY\" xmlns:mar=\"http://ANOTHER_LIBRARY\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
<Header>
<Source>Application1</Source>
<Time>2014-11-12T12:46:39</Time>
<Environment>TEST</Environment>
<Sequence>537</Sequence>
</Header>
<Data>
<mar:OC_DC>
<DC_elements>
<Unit>
<Unit_ID>089789</Unit_ID>
<State>active</State>
</Unit>
<Unit>
<Unit_ID>459008</Unit_ID>
<State>inactive</State>
</Unit>
</DC_elements>
</mar:OC_DC>
</Data>
</ram:FLOW>
I wrote a PHP/MySQL script to generate this document:
<?php
$xml = new SimpleXMLElement("<?xml version=\"1.0\" encoding=\"UTF-8\"?><ram:FLOW xmlns:ram=\"http://MY_LIBRARY\" xmlns:mar=\"http://ANOTHER_LIBRARY\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"></ram:FLOW>");
$header = $xml->addChild('Header');
$header->addChild('Source', $source);
$header->addChild('Time', $time);
$header->addChild('Environment', $env);
$header->addChild('Sequence', $sequence);
$data=$xml->addChild('Data');
$mar_oc_dc=$data->addChild('mar:OC_DC');
$dc_elements=$mar_oc_dc->addChild('DC_elements');
while($condition)
{
// some MySQL code here to extract unit_id and state
$unit=$dc_elements->addChild('Unit');
$unit_id=$unit->addChild('Unit_ID', $unit_id);
$state=$unit->addChild('State', $state);
}
$dom = new DOMDocument();
$dom->preserveWhiteSpace = FALSE;
$dom->formatOutput = TRUE;
$dom->loadXML($xml->asXML());
$handle = fopen("backup/" . $file_name . ".xml", "w");
fwrite($handle, $dom->saveXML());
fclose($handle);
?>
But the result was a little bit different from what I expected:
<?xml version="1.0" encoding="UTF-8"?>
<FLOW xmlns:ram=\"http://MY_LIBRARY\" xmlns:mar=\"http://ANOTHER_LIBRARY\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
<Header>
<Source>Application1</Source>
<Time>2014-11-12T12:46:39</Time>
<Environment>TEST</Environment>
<Sequence>537</Sequence>
</Header>
<Data>
<OC_DC>
<DC_elements>
<Unit>
<Unit_ID>089789</Unit_ID>
<State>active</State>
</Unit>
<Unit>
<Unit_ID>459008</Unit_ID>
<State>inactive</State>
</Unit>
</DC_elements>
</OC_DC>
</Data>
</FLOW>
As you can see, the ram:FLOW tag became FLOW, and the mar:OC_DC tag became OC_DC.
I looked on Stack Overflow and other websites for a solution and didn't manage to find one. Could you please give me a hand with this?
Thank you in advance.
The xmlns:* attributes are namespace definitions (not libraries). The value of that attributes is a unique string that identifies the format/standard the elements belong to.
The attributes define a prefix for the unique string so that the XML document is smaller and more readable.
If you want to create an element (or attribute) inside a namespace you have to provide the namespace. In SimpleXMlElement the third argument is the namespace.
It seems to add the elements to the namespace of the parent node, if no namespace is provided. That means that you have to provide an empty string for any element without a namespace.
$root = new SimpleXMlElement('<ram:FLOW xmlns:ram="http://MY_LIBRARY" xmlns:mar="http://ANOTHER_LIBRARY"/>');
$root->addChild('header', null, '');
$data = $root->addChild('data', null, '');
$data->addChild('mar:OC_DC', null, 'http://ANOTHER_LIBRARY');
echo $root->asXml();
Output:
<?xml version="1.0"?>
<ram:FLOW xmlns:ram="http://MY_LIBRARY" xmlns:mar="http://ANOTHER_LIBRARY">
<header xmlns=""/>
<data xmlns="">
<mar:OC_DC/>
</data>
</ram:FLOW>
I haven't found a way to avoid the empty xmlns attributes.
DOM is more explicit. The create and append logic is separate.
const XMLNS_RAM = 'http://MY_LIBRARY';
const XMLNS_MAR = 'http://ANOTHER_LIBRARY';
$dom = new DOMDocument();
// appending an element with a namespace with define it if needed
$root = $dom->appendChild($dom->createElementNS(XMLNS_RAM, 'ram:FLOW'));
// setting the xmlns attribute explicit avoids the definition in descendant nodes
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:mar', XMLNS_MAR);
$root->appendChild($dom->createElement('header'));
$data = $root->appendChild($dom->createElement('data'));
$data->appendChild($dom->createElementNS(XMLNS_MAR, 'mar:OC_DC'));
$dom->formatOutput = true;
echo $dom->saveXml();
Output:
<?xml version="1.0"?>
<ram:FLOW xmlns:ram="http://MY_LIBRARY" xmlns:mar="http://ANOTHER_LIBRARY">
<header/>
<data>
<mar:OC_DC/>
</data>
</ram:FLOW>

Adding namespace to XML

I'm creating XML response for the one of our clients with the namespace URLs in that using PHP. I'm expecting the output as follows,
<?xml version="1.0" encoding="UTF-8"?>
<ns3:userResponse xmlns:ns3="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="http://www.w3.org/2001/XMLSchema">
<Content>
<field1>fieldvalue1</field1>
</Content>
</ns3:userResponse>
But by using the following code,
<?php
// create a new XML document
$doc = new DomDocument('1.0', 'UTF-8');
// create root node
$root = $doc->createElementNS('http://www.w3.org/2001/XMLSchema-instance', 'ns3:userResponse');
$root = $doc->appendChild($root);
$root->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'ns1:schemaLocation','');
$root->setAttributeNS('http://www.w3.org/2001/XMLSchema', 'ns2:schemaLocation','');
// add node for each row
$occ = $doc->createElement('Content');
$occ = $root->appendChild($occ);
$child = $doc->createElement("field1");
$child = $occ->appendChild($child);
$value = $doc->createTextNode('fieldvalue1');
$value = $child->appendChild($value);
// get completed xml document
$xml_string = $doc->saveXML();
echo $xml_string;
DEMO:
The demo is here, http://codepad.org/11W9dLU9
Here the problem is, the third attribute is mandatory attribute for the setAttributeNS PHP function. So, i'm getting the output as,
<?xml version="1.0" encoding="UTF-8"?>
<ns3:userResponse xmlns:ns3="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="http://www.w3.org/2001/XMLSchema" ns3:schemaLocation="" ns2:schemaLocation="">
<Content>
<field1>fieldvalue1</field1>
</Content>
</ns3:userResponse>
So, is there anyway to remove that ns3:schemaLocation and ns2:schemaLocation which is coming with empty value? I googled a lot but couldn't able to find any useful answers.
Any idea on this would be so great. Please help.
You create this attributes:
$root->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'ns1:schemaLocation','');
$root->setAttributeNS('http://www.w3.org/2001/XMLSchema', 'ns2:schemaLocation','');
remove this lines and they will be removed.
If you want to add some xmlns without using it in code is:
$attr_ns = $doc->createAttributeNS( 'http://www.w3.org/2001/XMLSchema', 'ns2:attr' );
Read this comment: http://php.net/manual/pl/domdocument.createattributens.php#98210

Hide XML declaration in files generated using PHP

I was tesing with a simple example of how to display XML in browser using PHP and found this example which works good
<?php
$xml = new DOMDocument("1.0");
$root = $xml->createElement("data");
$xml->appendChild($root);
$id = $xml->createElement("id");
$idText = $xml->createTextNode('1');
$id->appendChild($idText);
$title = $xml->createElement("title");
$titleText = $xml->createTextNode('Valid');
$title->appendChild($titleText);
$book = $xml->createElement("book");
$book->appendChild($id);
$book->appendChild($title);
$root->appendChild($book);
$xml->formatOutput = true;
echo "<xmp>". $xml->saveXML() ."</xmp>";
$xml->save("mybooks.xml") or die("Error");
?>
It produces the following output:
<?xml version="1.0"?>
<data>
<book>
<id>1</id>
<title>Valid</title>
</book>
</data>
Now I have got two questions regarding how the output should look like.
The first line in the xml file '', should not be displayed, that is it should be hidden
How can I display the TextNode in the next line. In total I am exepecting an output in this fashion
<data>
<book>
<id>1</id>
<title>
Valid
</title>
</book>
</data>
Is that possible to get the desired output, if so how can I accomplish that.
Thanks
To skip the XML declaration you can use the result of saveXML on the root node:
$xml_content = $xml->saveXML($root);
file_put_contents("mybooks.xml", $xml_content) or die("cannot save XML");
Please note that saveXML(node) has a different output from saveXML().
First question:
here is my post where all usable threads with answers are listed: How do you exclude the XML prolog from output?
Second question:
I don't know of any PHP function that outputs text nodes like that.
You could:
read xml using DomDocument and save each node as string
iterate trough nodes
detect text nodes and add new lines to xml string manually
At the end you would have the same XML with text node values in new line:
<node>
some text data
</node>

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?

Categories