SimpleXML: trouble with parent with attributes - php

Need help with updating some simplexml code I did along time ago. The XML file I'm parsing from is formatted in a new way, but I can't figure out how to navigate it.
Example of old XML format:
<?xml version="1.0" encoding="UTF-8"?>
<pf version="1.0">
<pinfo>
<pid><![CDATA[test1 pid]]></pid>
<picture><![CDATA[http://test1.image]]></picture>
</pinfo>
<pinfo>
<pid><![CDATA[test2 pid]]></pid>
<picture><![CDATA[http://test2.image]]></picture>
</pinfo>
</pf>
and then the new XML format (note "category name" added):
<?xml version="1.0" encoding="UTF-8"?>
<pf version="1.2">
<category name="Cname1">
<pinfo>
<pid><![CDATA[test1 pid]]></pid>
<picture><![CDATA[http://test1.image]]></picture>
</pinfo>
</category>
<category name="Cname2">
<pinfo>
<pid><![CDATA[test2 pid]]></pid>
<picture><![CDATA[http://test2.image]]></picture>
</pinfo>
</category>
</pf>
And below the old code for parsing that doesn't work since the addition of "category name" in the XML:
$pinfo = new SimpleXMLElement($_SERVER['DOCUMENT_ROOT'].'/xml/file.xml', null, true);
foreach($pinfo as $resource)
{
$Profile_id = $resource->pid;
$Image_url = $resource->picture;
// and then some echo´ing of the collected data inside the loop
}
What do I need to add or do completely different? I tried with xpath,children and sorting by attributes but no luck - SimpleXML has always been a mystery to me :)

You were iterating over all <pinfo> elements located in the root element previously:
foreach ($pinfo as $resource)
Now all <pinfo> elements have moved from the root element into the <category> elements. You now need to query those elements first:
foreach ($pinfo->xpath('/*/category/pinfo') as $resource)
The now wrong named variable $pinfo is standing a bit in the way so it better do some more changes:
$xml = new SimpleXMLElement($_SERVER['DOCUMENT_ROOT'].'/xml/file.xml', null, true);
$pinfos = $xml->xpath('/*/category/pinfo');
foreach ($pinfos as $pinfo) {
$Profile_id = $pinfo->pid;
$Image_url = $pinfo->picture;
// ... and then some echo´ing of the collected data inside the loop
}

The category elements exist as their own array when you load the XML file. The XML you are used to parsing is contained within. All you need to do is wrap your current code with another foreach. Other than that there isn't much to change.
foreach($pinfo as $category)
{
foreach($category as $resource)
{
$Profile_id = $resource->pid;
$Image_url = $resource->picture;
// and then some echo´ing of the collected data inside the loop
}
}

Related

When converting an XML document to an Array in php, is there a way to convert it back and to save it as an XML file where attributes are elements?

I am trying to read an XML file in PHP, edit some values and save it back.
I do it by opening the XML file in php. I then convert it using SimpleXML into an array. After doing the manipulation needed, I am struggling in returning that array into the XML file in the same format due to how my XML elements are converted into attributes. Hence when I go from array to XML, my elements (which are attributes now) are saved as attributes in the updated XML file. I would like to know if it's possible to preserve XML elements when converting back from php array to XML.
A random XML example with two elements, lets call it myFile.xml
<XML>
<Project Element1 = 'some random value' Element2='Will be stored as attribute instead'>
</XML>
The php code I would run to convert it into an array
<?php
$xml = simplexml_load_file("myFile.xml") or die("Error: Cannot create object");
$arrayXML = json_decode(json_encode((array)$xml), TRUE);
$arrayXML["Project"]["attributes"]["Element1"] = "updated value"
// I will then run some array to XML converter code here found online
// took it from here https://stackoverflow.com/questions/1397036/how-to-convert-array-to-simplexml
function array_to_xml( $data, &$xml_data ) {
foreach( $data as $key => $value ) {
if( is_array($value) ) {
if( is_numeric($key) ){
$key = 'item'.$key; //dealing with <0/>..<n/> issues
}
$subnode = $xml_data->addChild($key);
array_to_xml($value, $subnode);
} else {
$xml_data->addChild("$key",htmlspecialchars("$value"));
}
}
}
$xml_data = new SimpleXMLElement();
array_to_xml($arrayNexus,$xml_data);
saving generated xml file;
$result = $xml_data->asXML('myFile.xml');
?>
Something like this would then generate an XML file like this
<XML>
<Project>
<attribute>
<Element1>updated value</Element1>
<Element2><Will be stored as attribute instead</Element2>
</attribute>
</Project>
</XML>
When the result I would like to have would be
<XML>
<Project Element1 = 'updated value' Element2='Will be stored as attribute instead'>
</XML>
I could write my own XML converter but if there exist already methods out there, can someone show me the way?
Don't convert the XML - you will loose data if you don't use specific formats like JsonML. It is much easier to use DOM. Use Xpath expressions to fetch the nodes and modify them.
$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);
// iterate the first 'Project' element
foreach($xpath->evaluate('(/XML/Project)[1]') as $project) {
// change the attribute value
$project->setAttribute('Element1', 'updated value');
}
echo $document->saveXML();
Output:
<?xml version="1.0"?>
<XML>
<Project Element1="updated value" Element2="Will be stored as attribute instead"/>
</XML>
Xpath
'XML' document element/XML
'Project' child elements/XML/Project
Limit to first found node(/XML/Project)[1]
This example uses the position in the result list as a condition but if the project has an id attribute you could use this to find the element: /XML/Project[#id="example-id"].

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!";
}

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.

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>

php only appending 3 out of 5 child nodes

This code is only appending 3 of the 5 name nodes. Why is that?
Here is the original XML:
It has 5 name nodes.
<?xml version='1.0'?>
<products>
<product>
<itemId>531670</itemId>
<modelNumber>METRA ELECTRONICS/MOBILE AUDIO</modelNumber>
<categoryPath>
<category><name>Buy</name></category>
<category><name>Car, Marine & GPS</name></category>
<category><name>Car Installation Parts</name></category>
<category><name>Deck Installation Parts</name></category>
<category><name>Antennas & Adapters</name></category>
</categoryPath>
</product>
</products>
Then is run this PHP code. which is suppossed to appened ALL name nodes into the product node.
<?php
// load up your XML
$xml = new DOMDocument;
$xml->load('book.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".
foreach ($xml->getElementsByTagName('product') as $product ) {
foreach($product->getElementsByTagName('name') as $name ) {
$product->appendChild($name );
}
$product->removeChild($xml->getElementsByTagName('categoryPath')->item(0));
}
// final result:
$result = $xml->saveXML();
echo $result;
?>
The end result is this and it only appends 3 of the name nodes:
<?xml version="1.0"?>
<products>
<product>
<itemId>531670</itemId>
<modelNumber>METRA ELECTRONICS/MOBILE AUDIO</modelNumber>
<name>Buy</name>
<name>Antennas & Adapters</name>
<name>Car Installation Parts</name>
</product>
</products>
Why is it only appending 3 of the name nodes?
You can temporarily add the name elements to an array before appending them, owing to the fact that you're modifying the DOM in real time. The node list generated by getElementsByTagName() may change as you are moving nodes around (and indeed that appears to be what's happening).
<?php
// load up your XML
$xml = new DOMDocument;
$xml->load('book.xml');
// Array to store them
$append = array();
foreach ($xml->getElementsByTagName('product') as $product ) {
foreach($product->getElementsByTagName('name') as $name ) {
// Stick $name onto the array
$append[] = $name;
}
// Now append all of them to product
foreach ($append as $a) {
$product->appendChild($a);
}
$product->removeChild($xml->getElementsByTagName('categoryPath')->item(0));
}
// final result:
$result = $xml->saveXML();
echo $result;
?>
Output, with all values appended:
<?xml version="1.0"?>
<products>
<product>
<ItemId>531670</ItemId>
<modelNumber>METRA ELECTRONICS/MOBILE AUDIO</modelNumber>
<name>Buy</name><name>Car, Marine & GPS</name><name>Car Installation Parts</name><name>Deck Installation Parts</name><name>Antennas & Adapters</name></product>
</products>
You're modifying the DOM tree as you're pulling results from it. Any modifications to the tree that cover the results of a previous query operation (your getElementsByTagName) invalidate those results, so you're getting undefined results. This is especially true of operations that add/remove nodes.
You're moving nodes as you're iterating through them so 2 are being skipped. I'm not a php guy so I can't give you the code to do this, but what you need to do is build a collection of the name nodes and iterate through that collection in reverse.
A less complicated way to do it is to manipulate the nodes with insertBefore
foreach($xml->getElementsByTagName('name') as $node){
$gp = $node->parentNode->parentNode;
$ggp = $gp->parentNode;
// move the node above gp without removing gp or parent
$ggp->insertBefore($node,$gp);
}
// remove the empty categoryPath node
$ggp->removeChild($gp);

Categories