XML - merge multiple rows into one - php

I need some assistance with the following issue. I have an xml file with the following structure:
<attributes>
<record ProductId="103" Compatibility="iPhone"/>
<record ProductId="103" Color="Black"/>
<record ProductId="103" Material="Leather"/>
<record ProductId="103" Collection="iPhone"/>
</attributes>
The problem is that I want to generate only one record per ProductId because it makes it easier for me to handle the information. The record should look like:
<record ProductId="103" Compatibility="iPhone" Color="Black" Material="Leather" Collection="iPhone"/>
I believe that using xpath is the way to go, but I just can't get the synthax right. I'm using a php script to parse the xml file.
Any assistance will be greatly appreciated.

Alternative you could just create a new one, but of course, get the necessary attributes first, gather them then apply them to the new one. Like this sample:
$xml_string = '<attributes><record ProductId="103" Compatibility="iPhone"/><record ProductId="103" Color="Black"/><record ProductId="103" Material="Leather"/><record ProductId="103" Collection="iPhone"/></attributes>';
$xml = simplexml_load_string($xml_string);
$record = new SimpleXMLElement('<attributes><record /></attributes>'); // create empty
$data = array();
foreach($xml->record as $entry) {
$attributes = $entry->attributes(); // get attributes
foreach($attributes as $key => $attribute) {
$data[$key] = (string) $attribute; // put it inside a container temporarily
}
}
// add the gathered attributes
foreach($data as $key => $value) {
$record->record->addAttribute($key, $value);
}
echo $record->asXML();
// echo htmlentities($record->asXML());
// <?xml version="1.0"?> <attributes><record ProductId="103" Compatibility="iPhone" Color="Black" Material="Leather" Collection="iPhone"/></attributes>

Related

PHP: getting only 1 result from XML collection

I'm trying to read an XML file with PHP but I'm only getting the first result and can't figure out why.
XML structure:
<main>
<data>
<record>
<column name="title">Some title here</column>
</record>
<record>
<column name="title">Some second title here</column>
</record>
</data>
</main>
Pretty basic. This is the code I'm using to getting the results:
foreach($XML->data->record as $product) {
$title = mysql_real_escape_string($product->title);
}
But this only gives an empty string so I guess I'm looking at the wrong path. The code below does work, but only gives me the first record and not the other records in the XML file.
foreach($XML->data as $product) {
$title = mysql_real_escape_string($product->record->title);
}
The answer is probably easy, but I can't figure it out. Hope someone is willing to help :)
title is the value of the name attribute of the <column> node.
You can't access it the way you do it.
To access the <column> node:
$xml = simplexml_load_string($x); // assume XML in $x
foreach ($xml->data->record as $product) {
echo $product->column;
}
Output:
Some title here
Some second title here
see it working: https://eval.in/506519
If there are many columns under each record...
<record>
<column name="title">Some title here</column>
<column name="car">BMW</column>
<column name="color">red</column>
</record>
... and you want to list all, you need a second loop:
foreach ($xml->data->record as $product) { // same as above
foreach ($product->column as $column) { // iterate all column
echo $column['name'] . ": " . $column . PHP_EOL;
}
}
Output:
title: Some title here
car: BMW
color: red
see it working: https://eval.in/506521
In case you want only <column> nodes with name = "title", check with if:
foreach ($xml->data->record as $product) { // same as above
foreach ($product->column as $column) { // iterate all column
if ($column['name'] == "title") echo $column . PHP_EOL;
}
}
see it working: https://eval.in/506523
And be aware that there is a different way to get all <column> with name = "title" with xpath:
$titles = $xml->xpath("//column[#name = 'title']");
foreach ($titles as $title)
echo $title . PHP_EOL;
see it in action: https://eval.in/506531
For an explanation of the xpath syntax, look at Using xPath to access values of simpleXML and zillion others.
and BTW: as said in comments, don't use mysql_...
Not tested but looks ok. Load the xml file / string into DOMDocument and get all the column nodes and iterate through them
$dom=new DOMDocument;
$dom->loadXML( $strxml );
/*
or
--
$dom->load( $xmlfile );
*/
$col=$dom->getElementsByTagName('column');
foreach( $col as $node ){
echo $node->tagName.' '.$node->getAttribute('title').' '.$node->nodeValue;
}

SimpleXML: trouble with parent with attributes

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
}
}

Merge two xml files based on common attribute

I have two xml files with a similar structure, and I need to merge them based on a common attribute. To be more explicit, here are two samples:
file1.xml
<Products>
<record ProductId="366" ProductName="Test" ProductCategory="Categ1"></record>
</Products>
file2.xml
<Productprices>
<record ProductId="366" ProductPrice="10" ProductVAT="24"></record>
</Productprices>
The common attribute in both files is ProductId. I need to merge all the attributes so that the combined file would look like this:
<Products>
<record ProductId="366" ProductName="Test" ProductCategory="Categ1" ProductPrice="10" ProductVAT="24"></record>
</Products>
Unfortunately, all I have managed to do so far is simply merge the two files, the merged file looks like this:
<Products>
<record ProductId="366" ProductName="Test" ProductCategory="Categ1"></record>
<record ProductId="366" ProductPrice="10" ProductVAT="24"></record>
</Products>
This is the PHP code I used:
$doc1 = new DOMDocument();
$doc1->load('file1.xml');
$doc2 = new DOMDocument();
$doc2->load('file2.xml');
$res1 = $doc1->getElementsByTagName('Products')->item(0);
$items2 = $doc2->getElementsByTagName('record');
for ($i = 0; $i < $items2->length; $i ++) {
$item2 = $items2->item($i);
$item1 = $doc1->importNode($item2, true);
$res1->appendChild($item1);
}
$doc1->save('file1.xml');
Is there any way I can merge all the attributes into one record based on the common ProductId by using DomDocument? I would rather not go into XSLT.
Any help will be greatly appreciated.
Thanks in advance.
I use Xpath to fetch nodes and values from DOM. In your case I see two tasks.
One task to iterate all record elements in on document, fetch the attributes of the matching element from the second document and copy the attributes.
The other task to iterate all record elements in the second document and add them to the first if here is no element with that ProductId.
$xmlOne = <<<'XML'
<Products>
<record ProductId="366" ProductName="Test" ProductCategory="Categ1"></record>
</Products>
XML;
$xmlTwo = <<<'XML'
<Productprices>
<record ProductId="366" ProductPrice="10" ProductVAT="24"></record>
<record ProductId="444" ProductPrice="23" ProductVAT="32"></record>
</Productprices>
XML;
$targetDom = new DOMDocument();
$targetDom->loadXml($xmlOne);
$targetXpath = new DOMXpath($targetDom);
$addDom = new DOMDocument();
$addDom->loadXml($xmlTwo);
$addXpath = new DOMXpath($addDom);
// merge attributes of record elements depending on ProductId
foreach ($targetXpath->evaluate('//record[#ProductId]') as $record) {
$productId = $record->getAttribute('ProductId');
foreach ($addXpath->evaluate('//record[#ProductId='.$productId.']/#*') as $attribute) {
if (!$record->hasAttribute($attribute->name)) {
$record->setAttribute($attribute->name, $attribute->value);
}
}
}
// copy records elements that are not in target dom
foreach ($addXpath->evaluate('//record[#ProductId]') as $record) {
$productId = $record->getAttribute('ProductId');
if ($targetXpath->evaluate('count(//record[#ProductId='.$productId.'])') == 0) {
$targetDom->documentElement->appendChild(
$targetDom->importNode($record)
);
}
}
echo $targetDom->saveXml();
You can use the attribute() function of SimpleXML
$xml = simplexml_load_file($filename);
foreach($xml->Products->record->attributes() as $attribute => $value) {
//do something
}

easy xpath query but no results

Trying to get all URLs values from xml.
I have hundreds of entry exactly in the form like e.g. this entry 16:
<?xml version="1.0" encoding="utf-8" ?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<entries>
<entry id="16">
<revision number="1" status="accepted" wordclass="v" nounclasses="" unverified="false"></revision>
<media type="audio" url="http://website.com/file/65.mp3" />
</entry>
<entry id="17">
....
</entry>
</entries>
</root>
I am using this code but cannot get it to work. Why?
$doc = new DOMDocument;
$doc->Load('data.xml');
$xpath = new DOMXPath($doc);
$query = '//root/entries/entry/media';
$entries = $xpath->query($query);
What is the correc query for that? Best would be to only get the url value.
Your query probably returns the proper elements, but by default gives you the content of the media tag ( which in your case are empty, since the tag is self-closing ).
To get the url attribute of the tag you should use getAttribute(), example :
$entries = $xpath->query('//root/entries/entry/media');
foreach($entries as $entry) {
print $entry->getAttribute("url")."<br/>";
}
Or you should just xpath-query the attribute instead and read out it's value:
$urlAttributes = $xpath->query('//root/entries/entry/media/#url');
#####
foreach ($urlAttributes as $urlAttribute)
{
echo $urlAttribute->value, "<br/>\n";
#####
}
See DOMAttr::$valueDocs:
value
The value of the attribute
I would do that with SimpleXML actually:
$file = 'data.xml';
$xpath = '//root/entries/entry/media/#url';
$xml = simplexml_load_file($file);
$urls = array();
if ($xml) {
$urls = array_map('strval', $xml->xpath($xpath));
}
Which will give you all URLs as strings inside the $urls array. If there was an error loading the XML file, the array is empty.

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