Restructure XML in PHP - php

I'm provided a XML file with this structure:
<items>
<item>
<images>
<image>A</image>
<image>B</image>
<image>C</image>
</images>
.
.
.
</item>
</items>
However the import function of my Shop requires the following format:
<items>
<item>
<images>
<image>A</image>
<image1>B</image>
<image2>C</image>
</images>
.
.
.
</item>
</items>
At first I was thinking I would do this simply in Java since it would be pretty easy to read line by line and restructure the document but I would love to have it so I can just visit a url and this is done for me.
Here is the approach I took:
<?php
$xml = simplexml_load_file('data.xml');
// Loop over items in original xml
for($i = 0; $i < count($xml->item); $i++)
{
$images;
if( ($c = count($xml->item[$i]->images->image)) > 1)
{
$images = $xml->item[$i]->images;
// Remove entry
unset($xml->item[$i]->images);
$xml->item[$i]->addChild('images');
for($y = 0; $y < count($images->image); $y++)
{
if($y == 0)
{
$xx = $xml->item[$i]->images->addChild('image', $images->image[$y]);
}else {
$xml->item[$i]->images->addChild('image' . $y, $images->image[$y]);
}
}
var_dump($images);
}
}
$xml->asXML('POTO.xml');
The dilemma I have tho is that none of the childs get added to images. I have been told I need to restructure the whole document but this is kind of silly if I var_dump just after removing the images node the node and it's children are all removed however when I go to add images node back to item node it and var_dump the node it shows the node was added to item node as a child however when I try to add image to images nothing gets added.
Do I really need to restructure the whole document because it seems simpler to do it in Java then. Or did I miss something.

Okay, I think the key problem here is that assigning the images to $images doesn't work like you'd expect. While such an assignment of a primitive makes a copy, assigning an object makes a reference to the same object (similar to a pointer you've worked with lower level languages). They do this because an object can be larger than a primitive, and unnecessary duplication of that is just wasteful.
However, in this case, the result is that when you unset the images from the main SimpleXML object, you're also destroying the instance in $images. Fortunately, there is a solution. You can clone the object, rather than assigning it. See http://www.php.net/manual/en/language.oop5.cloning.php
I modified your sample code here and it seems to work: http://3v4l.org/ZmRZV
Edit: cleaned up my code a little and included it:
<?php
//$xml = simplexml_load_file('data.xml');
$xml = simplexml_load_string('<items>
<item>
<images>
<image>A</image>
<image>B</image>
<image>C</image>
</images>
</item>
</items>');
// Loop over items in original xml
foreach ($xml->item as $item) {
if (count($item->images->image) > 1) {
// Clone entry
$images = clone $item->images;
// Remove entry and replace with empty
unset($item->images);
$item->addChild('images');
for ($i = 0; $i < count($images->image); $i++) {
$num = ($i > 0) ? $i : '';
$item->images->addChild('image'.$num, $images->image[$i]);
}
}
}
//$xml->asXML('POTO.xml');
echo $xml->asXML();
Looks like this doesn't work prior to version 5.2.1 (http://3v4l.org/m0VoD), but that's going back pretty far, really. Rasmus doesn't want anyone running less than 5.4 (http://www.youtube.com/watch?v=anr7DQnMMs0&t=10m27s), and I'm inclined to agree.
For the record, this behavior, of copying only the reference is not a quirk of PHP. It's a common practice. Further reading: http://en.wikipedia.org/wiki/Cloning_(programming)

Related

Check if child exists? - SimpleXML (PHP)

I have different XML files where I renamed for each XML file all individual tags, so that every XML file has the same tag name. That was easy because the function was customized for the XML file.
But instand of writing 7 new functions for each XML file now I want to check if a XML file has a specidifed child or not. Because if I want to say:
foreach ($items as $item) {
$node = dom_import_simplexml($item);
$title = $node->getElementsByTagName('title')->item(0)->textContent;
$price = $node->getElementsByTagName('price')->item(0)->textContent;
$url = $node->getElementsByTagName('url')->item(0)->textContent;
$publisher = $node->getElementsByTagName('publisher')->item(0)->textContent;
$category = $node->getElementsByTagName('category')->item(0)->textContent;
$platform = $node->getElementsByTagName('platform')->item(0)->textContent;
}
I get sometimes: PHP Notice: Trying to get property of non-object in ...
For example. Two different XML sheets. One contains publisher, category and platform, the other not:
XML 1:
<products>
<product>
<desc>This is a Test</desc>
<price>11.69</price>
<price_base>12.99</price_base>
<publisher>Stackoverflow</publisher>
<category>PHP</category>
</packshot>
<title>Check if child exists? - SimpleXML (PHP)</title>
<url>http://stackoverflow.com/questions/ask</url>
</product>
</products>
XML 2:
<products>
<product>
<image></image>
<title>Questions</title>
<price>23,90</price>
<url>google.de/url>
<platform>Stackoverflow</platform>
</product>
</products>
You see, sometimes one XML file contains publisher, category and platform but sometimes not. But it could also be that not every node of a XML file contains all attributes like in the first!
So I need to check for every node of a XML file individual if the node is containing publisher, category or/and platform.
How can I do that with SimpleXML?
I thought about switch case but at first I need to check which childs are contained in every node.
EDIT:
Maybe I found a solution. Is that a solution or not?
if($node->getElementsByTagName('platform')->item(0)){
echo $node->getElementsByTagName('platform')->item(0)->textContent . "\n";
}
Greetings and Thank You!
One way to rome... (working example)
$xml = "<products>
<product>
<desc>This is a Test</desc>
<price>11.69</price>
<price_base>12.99</price_base>
<publisher>Stackoverflow</publisher>
<category>PHP</category>
<title>Check if child exists? - SimpleXML (PHP)</title>
<url>http://stackoverflow.com/questions/ask</url>
</product>
</products>";
$xml = simplexml_load_string($xml);
#set fields to look for
foreach(['desc','title','price','publisher','category','platform','image','whatever'] as $path){
#get the first node
$result = $xml->xpath("product/{$path}[1]");
#validate and set
$coll[$path] = $result?(string)$result[0]:null;
#if you need here a local variable do (2 x $)
${$path} = $coll[$path];
}
#here i do array_filter() to remove all NULL entries
print_r(array_filter($coll));
#if local variables needed do
extract($coll);#this creates $desc, $price
Note </packshot> is an invalid node, removed here.
xpath syntax https://www.w3schools.com/xmL/xpath_syntax.asp
Firstly, you're over-complicating your code by switching from SimpleXML to DOM with dom_import_simplexml. The things you're doing with DOM can be done in much shorter code with SimpleXML.
Instead of this:
$node = dom_import_simplexml($item);
$title = $node->getElementsByTagName('title')->item(0)->textContent;
you can just use:
$title = (string)$item->title[0];
or even just:
$title = (string)$item->title;
To understand why this works, take a look at the SimpleXML examples in the manual.
Armed with that knowledge, you'll be amazed at how simple it is to see if a child exists or not:
if ( isset($item->title) ) {
$title = (string)$item->title;
} else {
echo "There is no title!";
}

for loop putting two similar entries in one loop in php

I wrote a code according to which I can write one XML file into number of XML files depending on number of records.
I have 14 records in one XML file having different tags in XML file.
I am trying to create XML files having having 2 records each, so in the end I should be having 7 XML files.
But instead that I'm getting 14 files only, each file having two similar records.
Following is my XML format, which is getting created dynamically. Format is coming correctly. But two similar entries are coming in each file. So instead of 7 its writing 14 files.
<?xml version="1.0"?>
<data>
<Product>
<PID>SGLDN7XJ2FPZH8G8</PID>
<PNAME>Miami Blues Aviator Sunglasses</PNAME>
</Product>
</data>
Following is my file which is making XML files. I think something is wrong in initializing the for loop:
$docOutput = new DOMDocument("1.0");
$root = $docOutput->createElement("data");
$docOutput->appendChild($root);
if (!$sxmlReading = simplexml_load_file('firstxml.xml')) {
throw new RuntimeException('Error reading XML file');
}
$x = 0;
foreach ($sxmlReading as $syn) {
for ($i = $x * 2; $i < ($x + 1) * 2; $i++) {
$id = $docOutput->createElement("PID");
$idText = $docOutput->createTextNode($syn->productId);
$id->appendChild($idText);
$title = $docOutput->createElement("PNAME");
$titleText = $docOutput->createTextNode($syn->productname);
$title->appendChild($titleText);
$book = $docOutput->createElement("Product");
$book->appendChild($id);
$book->appendChild($title);
$root->appendChild($book);
$docOutput->formatOutput = true;
echo "<xmp>" . $docOutput->saveXML() . "</xmp>";
$docOutput->save("xml$x.xml") or die("Error");
}
$x++;
}
Please check the code and highlight my mistake...
It is writing 14 files because you are calling the DOMDocument::save() method 14 times.
Sounds more like a typo to me honestly, I can not see anything wrong with that. PHP just does as you've written it to do.

Two dimensional array

i've tried to find this out by myself before asking but cant really figure it out.
What I have is a loop, it's actually a loop which reads XML data with simplexml_load_file
Now this XML file has data which I want to read and put into an array.. a two dimensional array actually..
So the XML file has a child called Tag and has a child called Amount.
The amount is always differnt, but the Tag is usually the same, but can change sometimes too.
What I am trying to do now is:
Example:
This is the XML example:
<?xml version="1.0"?>
<Data>
<Items>
<Item Amount="9,21" Tag="tag1"/>
<Item Amount="4,21" Tag="tag1"/>
<Item Amount="6,21" Tag="tag2"/>
<Item Amount="1,21" Tag="tag1"/>
<Item Amount="6,21" Tag="tag2"/>
</Data>
</Items>
Now i have a loop which reads this, sees what tag it is and adds up the amounts.
It works with 2 loops and two different array, and I would like to have it all in one array in single loop.
I tried something like this:
$tags = array();
for($k = 0; $k < sizeof($tags); $k++)
{
if (strcmp($tags[$k], $child['Tag']) == 0)
{
$foundTAG = true;
break;
}
else
$foundTAG = false;
}
if (!$foundTAG)
{
$tags[] = $child['Tag'];
}
and then somewhere in the code i tried different variations of adding to the array ($counter is what counts the Amounts together):
$tags[$child['Tag']][$k] = $counter;
$tags[$child['Tag']][] = $counter;
$tags[][] = $counter;
i tried few other combinations which i already deleted since it didnt work..
Ok this might be a really noob question, but i started with PHP yesterday and have no idea how multidimensional arrays work :)
Thank you
this is how you can iterate over the returned object from simple xml:
$xml=simplexml_load_file("/home/chris/tmp/data.xml");
foreach($xml->Items->Item as $obj){
foreach($obj->Attributes() as $key=>$val){
// php will automatically cast each of these to a string for the echo
echo "$key = $val\n";
}
}
so, to build an array with totals for each tag:
$xml=simplexml_load_file("/home/chris/tmp/data.xml");
$tagarray=array();
// iterate over the xml object
foreach($xml->Items->Item as $obj){
// reset the attr vars.
$tag="";
$amount=0;
// iterate over the attributes setting
// the correct vars as you go
foreach($obj->Attributes() as $key=>$val){
if($key=="Tag"){
// if you don't cast this to a
// string php (helpfully) gives you
// a psuedo simplexml_element object
$tag=(string)$val[0];
}
if($key=="Amount"){
// same as for the string above
// but cast to a float
$amount=(float)$val[0];
}
// when we have both the tag and the amount
// we can store them in the array
if(strlen($tag) && $amount>0){
$tagarray[$tag]+=$amount;
}
}
}
print_r($tagarray);
print "\n";
This will break horribly should the schema change or you decide to wear blue socks (xml is extremely colour sensitive). As you can see dealing with the problem child that is xml is tedious - yet another design decision taken in a committee room :-)

How to iterate through an XML element node with dynamic children

I currently have the following XML structure:
<root>
<maininfo>
<node>
<tournament_id>3100423</tournament_id>
<games>
<a_0>
<id>23523636</id>
<type>
<choice_4>
<choice_id>345</choice_id>
<choice_4>
<choice_9>
<choice_id>345</choice_id>
<choice_9>
... etc
</type>
</a_0>
<a_1></a_1>
<a_2></a_2>
...etc
</games>
</info>
</node>
</root>
I can easily get the id of the first node element "a_0" by just doing:
maininfo[0]->a_3130432[0]->games[0]->a_1[0]->id;
My issue is:
How do I automatically iterate (with a foreach) through all a_0, a_1, a_2 and get the values of each of these node elements and all of their children like "345" in <choice_id>345</choice_id>?
The ending numbers of a_0, a_1 + the children of choice_4, choice_9, are dynamically created and there are no logic in the _[number] counting up with +1 for each next element.
As it has been outlined previously on Stackoverflow (for example in Read XML dynamic PHP) and as well generally in the PHP manual (for example in Basic SimpleXML usage), you can iterate over all child elements by using foreach.
For example to go over all a_* elements, it's just
foreach ($xml->maininfo->node->games[0] as $name => $a) {
echo $name, "\n";
}
Output:
a_0
a_1
a_2
You then want to iterate over these their ->type children again. This is possible in pure PHP by putting one foreach into a another:
foreach ($xml->maininfo->node->games[0] as $name => $a) {
echo $name, "\n";
if (!$a->type[0]) {
continue;
}
foreach ($a->type[0] as $name => $choice) {
echo ' +- ', $name, "\n";
}
}
This now outputs:
a_0
+- choice_4
+- choice_9
a_1
a_2
This starts to get a bit complicated. As you can imagine since XML is famous for it's tree structures, you're not the first one running into this problem. Therefore a query-language to get elements from an XML document has been invented: Xpath.
With Xpath you can access XML data as if it was a file-system. As I know that each a_* element is a child of games and each choice_* element a child of type, it's pretty straight forward:
/*/maininfo/node/games/*/type/*
^ ^ ^
| | choice_*
root |
a_*
In PHP Simplexml this looks like:
$choices = $xml->xpath('/*/maininfo/node/games/*/type/*');
foreach ($choices as $choice) {
echo $choice->getName(), ': ', $choice->choice_id, "\n";
}
Output:
choice_4: 345
choice_9: 345
As this example shows, the data is now retrieved with a single foreach.
If you as well need access to the <a_*> elements, you need to have multiple foreach's or your own iteration but that is even a more advanced topic which I'd say would extend over the limits of your question.
I hope this is helpful so far. See as well SimpleXMLElement::children() which also gives all children (like ->games[0] in the first example). All example codes are as well available as a working, interactive online-demo.
If I understand it well, you can do something like:
for($i = 0; $i < $max; ++$i){
$a = $parentNode->{'a_'.$i};
}
You can do this very easily using SimpleXML :
<?php
$xmlStr = "<?xml version='1.0' standalone='yes'?>
<root>
<maininfo>
<node>
<tournament_id>3100423</tournament_id>
<games>
<a_0>
<id>23523636</id>
<type>
<choice_4>
<choice_id>345</choice_id>
</choice_4>
<choice_9>
<choice_id>345</choice_id>
</choice_9>
</type>
</a_0>
<a_1></a_1>
<a_2></a_2>
</games>
</node>
</maininfo>
</root>";
$xmlRoot = new SimpleXMLElement($xmlStr);
$i = 0;
foreach($xmlRoot->maininfo[0]->node[0]->games[0] as $a_x)
{
echo $i++ . " - " . htmlentities($a_x->asXML()) . "<br/>";
}
?>
I have modified some parts of your XML string to make it syntactically correct. You can view the results at http://phpfiddle.org/main/code/56q-san

xml and php getting tag elements with certain element and outputting

I am have two xml files.. I first get one and loop through it then I need to take an id from the first xml file and find it in the second one and echo out the results associated with that id. If I were to do this with SQL I would simply do this:
$query = (SELECT * FROM HotelSummary WHERE roomTypeCode = '$id') or die();
while($row=mysql_fetch_array($query)){
$name = $row['Name'];
}
echo $name;
How can I do this is in xml and php??
I recommend you to read the DOMDocument documentation.
It's quite heavy but also powerful (not always clear what happens, but the Internet shold always give you a solution)
You can simply walk through your first document, finding your Id and then find your DOMElement via an XPath.
<?php
$dom = new DOMDocument();
$dom->load('1.xml');
foreach ($dom->getElementsByTagName('article') as $node) {
// your conditions to find out the id
$id = $node->getAttribute('id');
}
$dom = new DOMDocument();
$dom->load('2.xml');
$xpath = new DOMXPath($dom);
$element = $xpath->query("//*[#id='".$id."']")->item(0);
// would echo "top_2" based on my example files
echo $element->getAttribute('name');
Based on following test files:
1.xml
<?xml version="1.0" encoding="UTF-8"?>
<articles>
<article id="foo_1">
<title>abc</title>
</article>
<article id="foo_2">
<title>def</title>
</article>
</articles>
2.xml
<?xml version="1.0" encoding="UTF-8"?>
<tests>
<test id="foo_1" name="top_1">
</test>
<test id="foo_2" name="top_2">
</test>
</tests>
Use SimpleXML to create an object representation of the file. You can then loop through the elements of the Simple XML object.
Depending on the format of the XML file:
Assuming it is:
<xml>
<roomTypeCode>
<stuff>stuff</stuff>
<name>Skunkman</name>
</roomTypeCode>
<roomTypeCode>
<stuff>other stuff</stuff>
<name>Someone Else</name>
</roomTypeCode>
</xml>
It would be something like this:
$xml = simplexml_load_file('xmlfile.xml');
for($i = 0; $i < count($xml->roomTypeCode); $i++)
{
if($xml->roomTypeCode[$i]->stuff == "stuff")
{
$name = $xml->roomTypeCode[$i]->name;
}
}
That connects to the XML file, finds how many roomTypeCode entries there are, searches for the value of "stuff" within and when it matches it correctly, you can access anything having to do with that XML entry.

Categories