Load XML-File -> Add items -> Reorder -> Write XML-File [PHP & SimpleXML] - php

SUMMARY
Load existing XML-File
Add 2 items to every child
Change order of children
Write new XML-File
DETAILED EXPLANATION
This is a shortened sample of the Source-XML-File
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<warehouse>
<inventory>
<name>Name 1</name>
<markers>
<marker>red</marker>
<marker>yellow</marker>
<marker>green</marker>
</markers>
(...)
</inventory>
<inventory>
<name>Name 2</name>
<markers>
<marker>blue</marker>
<marker>pink</marker>
<marker>brown</marker>
</markers>
(...)
</inventory>
<inventory>
<name>Name 3</name>
<markers>
<marker>black</marker>
<marker>white</marker>
<marker>marron</marker>
</markers>
(...)
</inventory>
</warehouse>
1. Load existing XML-File
Load the XML-File into a variable.
$source_xmlfile = "source.xml";
$source_xml_file_contents = simplexml_load_file($source_xmlfile);
2. Add 2 items to every child
Add 2 children to the first inventory with name and value.
$source_xml_file_contents->warehouse->inventory[0]->addChild("new1", "0.12345");
$source_xml_file_contents->warehouse->inventory[0]->addChild("new2", "17");
3. Change order of children
This is the part that doesn't work for me, no matter what I tried.
My idea was the following:
$destination_xml_file_contents = new SimpleXMLElement('<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?><warehouse></warehouse>');
foreach($source_xml_file_contents->warehouse->inventory as $inventory){
$destination_xml_file_contents->warehouse->addChild("inventory", $inventory);
}
The basic idea is that I take a child (<inventory>) out of <warehouse> and store in in a variable or maybe an array (which would be way better and easier) containing everything that <inventory> contains (including attributes and child nodes and so on).
After I have stored all <inventory> somewhere else I take a new XML and put the stored <inventory> in a new order into the new XML.
However it seems impossible to save a whole child into a variable (or an array) and add it to another XML later.
Since the order is not necessarily in order a simple sort wouldn't help me out here and since I wasn't able to get sorting to work I am at a loss here.
4. Write new XML-File
Save the new XML content to a new XML-File.
$new_xml_file = "destination.xml";
$destination_xml_file_contents->asXML($new_xml_file);
This is a shortened sample of the destination-XML-File
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<warehouse>
<inventory>
<name>Name 2</name>
<markers>
<marker>blue</marker>
<marker>pink</marker>
</marker>brown</marker>
</markers>
(...)
<new1>0.6789</new1>
<new2>115</new2>
</inventory>
<inventory>
<name>Name 1</name>
<markers>
<marker>red</marker>
<marker>yellow</marker>
</marker>green</marker>
</markers>
(...)
<new1>0.4567</new1>
<new2>4</new2>
</inventory>
<inventory>
<name>Name 3</name>
<markers>
<marker>black</marker>
<marker>white</marker>
</marker>marron</marker>
</markers>
(...)
<new1>0.1234</new1>
<new2>17</new2>
</inventory>
</warehouse>
Can someone help me to figure out how to make this work?
Right now I'm resorting to parse the whole XML-File and rewrite it line by line with string since SimpleXML doesn't seem to work.

There are a few things which I'm not sure about, but hopefully this code will show the principle of copying the nodes from one document to another (and adding content) using DOMDocument. I've added comments to the code to help, if there is anything you are unsure about, then let me know.
$inFile = "data.xml";
$input = new DOMDocument();
$input->load($inFile);
$all_inventory = $input->getElementsByTagName("inventory");
// Add new content to each item
foreach ( $all_inventory as $inventory ) {
$inventory->appendChild ( $input->createElement("new1", "0.12345"));
$inventory->appendChild ( $input->createElement("new2", "17"));
}
$outFile = "out.xml";
$output = new DOMDocument();
// Create base document for destination
$output->loadXML('<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?><warehouse />');
// Get the root element as we will be adding content to this
$outputRoot = $output->documentElement;
foreach ( $all_inventory as $inventory ) {
// Copy the element from the input document to the output document
// Note this does not add it anywhere, just makes it available
// The true option says make a deep copy
$import = $output->importNode($inventory, true);
// Add the newly imported node to the output document root
$outputRoot->appendChild($import);
}
$output->save($outFile);
The output is almost just a copy of the input document with the extra data. You could in the second foreach() loop add some logic to order the output elements in some other format, which I'm not sure what that is.
Also note there is a minor XML flaw in your document...
</marker>green</marker>

Related

How to create a basic XML file in PHP

I want a basic XML generated in PHP and the file can be saved in a directory and be edited...
This is the structure:
<?xml version="1.0" encoding="UTF-8"?>
<user>
<id> 1254</id>
<userName>Melanie Woods</userName>
<channel>
<host> www.natgeo.org </host>
<item> http://animals.natgeo.com/feed.xml </item>
<item> http://birds.natgeo.com/feed.xml </item>
</channel>
/////....The can add as many <'channels'> as they want....
/////And as many <'items'>' in the channel as they want
<channel>
<host> www.bbc.com </host>
<item> http://america.bbc.com/feed.xml </item>
<item> http://asia.bbc.com/feed.xml </item>
<item> http://europe.bbc.com/feed.xml </item>
<item> http://australiue.bbc.com/feed.xml </item>
</channel>
</user>
I have tried so many approaches since some hours now and still can't figure out the best approach to achieve this.
The challenge is how to save the file and call it when the User thinks of adding or removing an item or channel form the XML file.
Let's say they want to Add:
<channel>
<host>www.cnn.com</host>
<item>america.news.cnn.com/fedd.xml</item>
</channel>
and they wish to remove:
<item> http://asia.bbc.com/feed.xml </item> from the bbc.com <channel>
How to pull the file and add the new channel?
What am trying now is using:
<?php
$xml = new SimpleXMLElement("<?xml version=\"1.0\" encoding=\"utf-8\" ?><user></user>");
$id= $xml->addChild('id');
$username = $xml->addChild('userName');
for ($i = 1; $i <= 8; ++$i) {
$channel= $xml->addChild('channel');
$channel->addChild('host', 'www.bbc.com');
$channel->addChild('item','http://asia.bbc.com/feed.xml');
} /**/
Header('Content-type: text/xml');
print($xml->asXML());
?>
This works but doesn't save the file and suffers from adding random amount of items to random number of channels instantaneous.
EDIT: One more thing, when ran, this code produces a Single line XML file difficult to read and **It does not Validate **.
Validator says:
Sorry
This feed does not validate.
line 2, column 0: Undefined root element: user [help]
<user><id/><userName/><channel><host>www.bbc.com</host><item>http://asia.bbc
...
Wish it could produce multiple lines as the sample XML above
Any suggestion?
Just to save you can use $xml->asXML('path/filename.xml');
You can have a look at this post to get some help Using SimpleXML and DOMDocument Together.

Using unset() to delete node in XML; PHP/simplexml_load_file

I'm trying to use unset() to delete a node in an XML using PHP and can't figure out what is going on here. It doesn't seem to work correctly and I've seen a lot of other questions of similar nature on here but they don't seem to address this issue directly. Here's what my XML looks like:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<user>
<name>Test Name 1</name>
<email>test#test.com</email>
<spouse/>
</user>
<user>
<name>Test Name 2</name>
<email>anotherone#test.com</email>
<spouse>Test Name 3</spouse>
</user>
</data>
My loop that I'm using is like this:
url = 'data/users.xml';
$xml = simplexml_load_file($url);
foreach($xml->user as $theUser){
if($theUser->email[0]=="test#test.com"){
echo "test";
unset($theUser);
}
}
When the e-mail matches "test#test.com" I want to be able to delete that whole user node. It seems that this should work but I can't figure out why it wouldn't? Any help would be greatly appreciated. Thank you!
SimpleXML is fine, no need to switch to DOM, unset() is working fine, if you do it right:
unset($theUser[0]);
see it working: https://eval.in/228773
However there will be a problem with your foreach() if you delete a node mid-loop.
I suggest to use xpath() instead of a loop, IMO elegant and the code is much simpler.
$users = $xml->xpath("/data/user[email='test#test.com']");
will create an array of all <user> with that email-address.
unset($users[0][0]);
will delete the first user in that array.
foreach ($users as $user) unset($user[0]);
will delete the whole array.
see this in action: https://eval.in/228779
SimpeXML is not really meant for changing to the XML structure. Just a simple way of reading the XML.
If you want to manipulate the XML structure you should use the dom functions and more specifically the dom_import_simplexml. This function allows you to import a SimpleXML element and turn it into a DomElement that can be used for manipulation and that includes deletion.
Here is a code sample that solves your problem and demonstrates the usage of dom_import_simplexml.
<?php
$xmlData = '<?xml version="1.0" encoding="UTF-8"?>
<data>
<user>
<name>Test Name 1</name>
<email>test#test.com</email>
<spouse/>
</user>
<user>
<name>Test Name 2</name>
<email>anotherone#test.com</email>
<spouse>Test Name 3</spouse>
</user>
</data>';
$xml = simplexml_load_string($xmlData);
foreach($xml->user as $theUser){
if($theUser->email == 'test#test.com'){
$dom = dom_import_simplexml($theUser);
$dom->parentNode->removeChild($dom);
}
}
echo $xml->asXml();
When reading this code you might be thinking why this works since we dont save the new structure anywhere after we have executed the removeChild function. This works because the DOM functions does not create copies of the underlying objects but instead manipulates them directly.
Result
<?xml version="1.0" encoding="UTF-8"?>
<data>
<user>
<name>Test Name 2</name>
<email>anotherone#test.com</email>
<spouse>Test Name 3</spouse>
</user>
</data>

update element from node in XML

First of all, I just found this site a few days ago and am very happy it exists.
I am facing a problem with updating a child element from a XML Node.
The xml looks like this:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="books.xsl"?>
<books>
<book>
<id>1</id>
<bookname>Title 1</bookname>
<bookurl>SomeURLToMyBook</bookurl>
<clicks>0</clicks>
</book>
<book>
<id>2</id>
<bookname>Title 2</bookname>
<bookurl>SomeURLToMyBook</bookurl>
<clicks>0</clicks>
</book>
</books>
I do know how to retrieve the node for book with (e.g.) ID 2 using:
$xml= simplexml_load_file('books.xml);
I then use xpath to find the correct node. as in:
$booknum= $_GET('booklist'); //booklist is the parameter in the querystring
$arrOutput = $xml->xpath("//*[id='".$booknum."']");
What I try to achieve is to update the clicks element and then save the outcome into the existing XML.
I found several code examples on this site but neither one seems to work for me.
I probably do something wrong but I am not really familiar with PHP (learning new things every day!)
So if anybody is willing to help me out I would be grateful.
TIA

How can I delete a child from an XML file via simplexml?

I've got the following xml file:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<number_of_gr>
3
</number_of_gr>
<group id="0">
<name>Admins</name>
<backend>1</backend>
<every_plugin_feature> 1 </every_plugin_feature>
</group>
<group id="1">
<name>Users</name>
<backend>0</backend>
<every_plugin_feature>0</every_plugin_feature>
</group>
<group id="2">
<name>Moderators</name>
<backend>0</backend>
<every_plugin_feature>0</every_plugin_feature>
</group>
</root>
For Example: I want to delete the group with the id="0". But I don't know how to delete a child with specified attribute in simplexml.
I've tried this code:
<?php
$xml = simplexml_load_file("../xml/groups.xml");
$delgroup = $xml->xpath("/root/group[#id='".$_GET['group']."'");
unset($delgroup);
$xml-> asXML("../xml/groups.xml");
?>
But it doesn't work.
After the process, I'll fill the gap with the id=1, but I can do it without help.
My question is: How to delete the specified group?
You are almost there, just a little tweak:
$delgroup = $xml->xpath("//group[#id='".$_GET['group']."'")[0];
unset($delgroup[0]);
see it working: http://codepad.viper-7.com/ZVXs4O
This requires PHP >= 5.4.
To see a bit of theory behind it: Remove a child with a specific attribute, in SimpleXML for PHP --> see hakre's answer.
PS: Remember to change <number_of_gr> - or delete this node from the XML, because you can always get this number by...
$groups = $xml->xpath("//group");
$numberofgroups = count($groups);

PHP domDocument to remove child nodes of a child node

How do I remove a parent node of a child node, but keep all the children?
The XML file is this:
<?xml version='1.0'?>
<products>
<product>
<ItemId>531<ItemId>
<modelNumber>00000</modelNumber>
<categoryPath>
<category><name>Category A</name></category>
<category><name>Category B</name></category>
<category><name>Category C</name></category>
<category><name>Category D</name></category>
<category><name>Category E</name></category>
</categoryPath>
</product>
</products>
Basically, I need to remove the categoryPath node and the category node, but keep all of the name nodes inside of the product node. What I am aiming for is a document like this:
<?xml version='1.0'?>
<products>
<product>
<ItemId>531<ItemId>
<modelNumber>00000</modelNumber>
<name>Category A</name>
<name>Category B</name>
<name>Category C</name>
<name>Category D</name>
<name>Category E</name>
</product>
</products>
Is there PHP built in function to do this? Any pointers would be appreciated, I just do not know where to start because there are many child nodes.
Thanks
A good approach to process XML data is to use the DOM facility.
It's quite easy once you get introduced to it. For example:
<?php
// load up your XML
$xml = new DOMDocument;
$xml->load('input.xml');
// Find all elements you want to replace. Since your data is really simple,
// you can do this without much ado. Otherwise you could read up on XPath.
// See http://www.php.net/manual/en/class.domxpath.php
$elements = $xml->getElementsByTagName('category');
// WARNING: $elements is a "live" list -- it's going to reflect the structure
// of the document even as we are modifying it! For this reason, it's
// important to write the loop in a way that makes it work correctly in the
// presence of such "live updates".
while($elements->length) {
$category = $elements->item(0);
$name = $category->firstChild; // implied by the structure of your XML
// replace the category with just the name
$category->parentNode->replaceChild($name, $category);
}
// final result:
$result = $xml->saveXML();
See it in action.

Categories