I have a xml file with structure like this:
<categories>
<category>
<id></id>
<name></name>
</category>
...
</categories>
<products>
<product>
<id></id>
<name></name>
</product>
...
</products>
<params>
<param>
<id></id>
<name></name>
</param>
...
</params>
<product_params>
<product_param>
<param_id></param_id>
<product_id></product_id>
</product_param>
...
</product_params>
How do I display product nodes, right category (that matches id) and all params for that product?
I tried doing something like this:
$xml = simplexml_load_file('file.xml');
$products = $xml->products;
$product_params = $xml->product_params->product_param;
foreach ($products->product as $product) {
echo '<p>'.$product->name.'</p>';
for($i=0; $i < count($product_params) ;$i++) {
if($product->id == $product_params[$i]->product_id) {
}
}
}
The file is too big and script crashes. Plus with my "solution" I would need at least one more loop nested in there.
Related
I have a feed with products, all the products have a child node called 'category' with a value. I can't find a way to return all products with a certain category value.
The XML looks something like this
<product>
<name>xxxx</name>
<category>Category A</category>
</product>
<product>
<name>xxxx</name>
<category>Category B</category>
</product>
<product>
<name>xxxx</name>
<category>Category A</category>
</product>
<product>
<name>xxxx</name>
<category>Category B</category>
</product>
<product>
<name>xxxx</name>
<category>Category B</category>
</product>
I've tried looping through the XML, using PHP code like this:
$xml = simplexml_load_file('file.xml');
foreach ($xml as $product) {
if ((string) $product['category'] == 'Category A') {
echo (string) $product['name'];
}
}
Expected outcome is to return/echo other child nodes for that product. What would be the best approach for this?
Your approach seems sound, I'm not familiar enough with SimpleXML to say why it's not working. But, since you asked for the best approach, I'm partial to DomDocument and XPath myself:
$xml = <<< XML
<?xml version="1.0"?>
<products>
<product>
<name>xxxx</name>
<category>Category A</category>
</product>
<product>
<name>xxxx</name>
<category>Category B</category>
</product>
<product>
<name>xxxx</name>
<category>Category A</category>
</product>
<product>
<name>xxxx</name>
<category>Category B</category>
</product>
<product>
<name>xxxx</name>
<category>Category B</category>
</product>
</products>
XML;
$dom = new DomDocument;
$dom->loadXML($xml);
$xpath = new DomXPath($dom);
$search = "Category A";
$nodes = $xpath->query("//product[category='$search']/name");
foreach ($nodes as $node) {
printf("%s\n", $node->textContent);
}
For SimpleXML, after a little digging it looks like it needs to access elements with object notation, not array notation. This worked for me:
$x = simplexml_load_string($xml);
foreach ($x->product as $product) {
if ((string) $product->category == 'Category A') {
echo (string) $product->name;
}
}
But I maintain that learning DOM and XPath methods will serve you better in the long run; they're both well established standards that are used in many languages. Knowledge about SimpleXML is not something you can transfer to another environment.
I am having nested XML, I want to remove only parent node < items> in xml document keeping all its child nodes.
<root>
<items>
<Product>
<name> </name>
<size> </size>
<images>
<img1></img1>
<img2></img2>
</images>
</Product>
<Product>
<name> </name>
<size> </size>
<images>
<img1></img1>
<img2></img2>
</images>
</Product>
</items>
</root>
Expected Output -
<root>
<Product>
<name> </name>
<size> </size>
<images>
<img1></img1>
<img2></img2>
</images>
</Product>
<Product>
<name> </name>
<size> </size>
<images>
<img1></img1>
<img2></img2>
</images>
</Product>
</root>
I have researched & tried a lot, on removing the < items> node all its child nodes are also getting deleted. Please help if there is any way using DOMDocument or any other way in php.
Well, Geza Boems answer is not exactly what I meant. Using Xpath you can fetch the items nodes for iteration. This is a stable result, so you can iterate it while modifying the DOM.
$document = new DOMDocument();
$document->loadXML($input);
$xpath = new DOMXpath($document);
foreach ($xpath->evaluate('//items') as $itemsNode) {
// as long that here is any child inside it
while ($itemsNode->firstChild instanceof DOMNode) {
// move it before its parent
$itemsNode->parentNode->insertBefore($itemsNode->firstChild, $itemsNode);
}
// remove the empty items node
$itemsNode->parentNode->removeChild($itemsNode);
}
echo $document->saveXML();
As #ThW mentioned, you have to collect the child nodes in ITEMS, then insert them into ROOT, and finally delete ITEMS.
$input = "
<root>
<items>
<Product>
<name> </name>
<size> </size>
<images>
<img1></img1>
<img2></img2>
</images>
</Product>
<Product>
<name> </name>
<size> </size>
<images>
<img1></img1>
<img2></img2>
</images>
</Product>
</items>
</root>";
$doc = new DOMDocument();
$ret = $doc->loadXML($input);
$root = $doc->firstChild;
$nodes_to_insert = array();
$nodes_to_remove = array();
foreach($root->childNodes as $items) {
if($items->nodeName != "items") {
continue;
}
$nodes_to_remove[] = $items;
foreach($items->childNodes as $child) {
if($child->nodeType != XML_ELEMENT_NODE) {
continue;
}
$nodes_to_insert[] = $child;
}
}
foreach($nodes_to_insert as $node) {
$root->appendChild($node);
}
foreach($nodes_to_remove as $node) {
$root->removeChild($node);
}
var_dump($doc->saveXML());
This code will search for all "items" tag within root, not only one. Inside "items", it will search all normal node (ELEMENT type, but no TEXT node, etc.)
In the last line there is a dump, but normally you will not see anything in a browser, because of the XML header line. But if you take a look at page source, the result will be shown.
PS: it is quite important to not modify xml structure when you walk it. That's why i do only collection first, then the insert and delete actions.
I'm trying to combine multiple XML files, using SimpleXML if possible. I'm just trying to append products, children, and child data from file 2 into file 1. I'm not trying to merge elements, just append file 2 to the bottom of file 1, and so on. (Though I guess this is technically merging merchandiser elements?) The files contain the same schema and will both look similar to the example below, only thing that will be changing is the actual text. This is just XML for two different products, I added a large space in between products so that it's easier to see where it ends.
<merchandiser xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="merchandiser.xsd">
<header>
<merchantId>35928</merchantId>
<merchantName>Sunspel Clothing</merchantName>
<createdOn>01/14/2016 02:03:31</createdOn>
</header>
<product product_id="14633" name="Cotton Socks" sku_number="1588/102">
<category>
<primary>Accessories</primary>
<secondary>Men's~~Socks</secondary>
</category>
<URL>
<product>
http://click.linksynergy.com/link?id=D*rqD2paIXY&offerid=191965.14633&type=15&murl=http%3A%2F%2Fwww.sunspel.com%2Fuk%2Fcotton-sock-black.html
</product>
<productImage>
http://www.sunspel.com/media/catalog/product/cache/1/image/9df78eab33525d08d6e5fb8d27136e95/1/5/1588-102-new.jpg
</productImage>
</URL>
<description>
<short>
Our new cotton socks are designed by Sunspel and crafted in an Italian factory steeped in years of experience, skill and heritage. They are made from the highest quality, extra-long staple Egyptian cotton yarn which, prior to knitting is combed, twisted and mercerised to enhance the comfort, shine and absorption of the fabric as well as its resistance to pilling and shrinking.
</short>
</description>
<discount currency="GBP">
<type>amount</type>
</discount>
<price currency="GBP">
<retail>15.00</retail>
</price>
<shipping>
<availability>in-stock</availability>
</shipping>
<pixel>
http://ad.linksynergy.com/fs-bin/show?id=D*rqD2paIXY&bids=191965.14633&type=15&subid=0
</pixel>
</product>
<product product_id="15115" name="Cotton Socks" sku_number="1589/236">
<category>
<primary>Accessories</primary>
<secondary>Men's~~Socks~~Men's</secondary>
</category>
<URL>
<product>
http://click.linksynergy.com/link?id=D*rqD2paIXY&offerid=191965.15115&type=15&murl=http%3A%2F%2Fwww.sunspel.com%2Fuk%2Fmens-cotton-socks-navy-stripes.html
</product>
<productImage>
http://www.sunspel.com/media/catalog/product/cache/1/image/9df78eab33525d08d6e5fb8d27136e95/1/5/1588-236-new.jpg
</productImage>
</URL>
<description>
<short>
Our new cotton socks are designed by Sunspel and crafted in an Italian factory steeped in years of experience, skill and heritage. They are made from the highest quality, extra-long staple Egyptian cotton yarn which, prior to knitting is combed, twisted and mercerised to enhance the comfort, shine and absorption of the fabric as well as its resistance to pilling and shrinking.
</short>
</description>
<discount currency="GBP">
<type>amount</type>
</discount>
<price currency="GBP">
<retail>17.00</retail>
</price>
<shipping>
<availability>in-stock</availability>
</shipping>
<pixel>
http://ad.linksynergy.com/fs-bin/show?id=D*rqD2paIXY&bids=191965.15115&type=15&subid=0
</pixel>
</product>
<product product_id="15116" name="Cotton Socks" sku_number="1589/711">
<category>
<primary>Accessories</primary>
<secondary>Men's~~Socks~~Men's</secondary>
</category>
<URL>
<product>
http://click.linksynergy.com/link?id=D*rqD2paIXY&offerid=191965.15116&type=15&murl=http%3A%2F%2Fwww.sunspel.com%2Fuk%2Fmens-cotton-socks-charcoal-melange-stripes.html
</product>
<productImage>
http://www.sunspel.com/media/catalog/product/cache/1/image/9df78eab33525d08d6e5fb8d27136e95/1/5/1588-711-new.jpg
</productImage>
</URL>
<description>
<short>
Our new cotton socks are designed by Sunspel and crafted in an Italian factory steeped in years of experience, skill and heritage. They are made from the highest quality, extra-long staple Egyptian cotton yarn which, prior to knitting is combed, twisted and mercerised to enhance the comfort, shine and absorption of the fabric as well as its resistance to pilling and shrinking.
</short>
</description>
<discount currency="GBP">
<type>amount</type>
</discount>
<price currency="GBP">
<retail>17.00</retail>
</price>
<shipping>
<availability>in-stock</availability>
</shipping>
<pixel>
http://ad.linksynergy.com/fs-bin/show?id=D*rqD2paIXY&bids=191965.15116&type=15&subid=0
</pixel>
</product>
With some foreach statements I'm able to append all product children and attributes, but this doesn't actually give me the child data.
$file1 = '35928_3210820_mp.xml';
$file2 = '39153_3210820_mp.xml';
$fileout = 'ukmerge.xml';
$xml1 = simplexml_load_file( $file1 );
$xml2 = simplexml_load_file( $file2 ); // loop through the product and add them and their attributes to xml1
$product = $xml2->product;
$prod = $xml2->merchandiser->header->product;
$category = $product->category;
$url = $product->URL;
$description = $product->description;
foreach( $xml2->children() as $child ) {
$new = $xml1->addChild( $child->getName() , htmlspecialchars($child) );
foreach( $child->attributes() as $key => $value ) {
$new->addAttribute( $key, $value );
}
} $fh = fopen( $fileout, 'w') or die ( "can't open file $fileout" );
fwrite( $fh, $xml1->asXML() );
fclose( $fh );
When I try to add on from there then everything gets messed up and nothing is in the correct place/order anymore. I'd also like to put this into a function since I'm going to be doing it often. Any help is greatly appreciate as I've been struggling with this for a few days now and have scowered over a few dozen stackoverflow and php.net threads.
One of the things that's confusing me is the <merchandiser> and <header> tags that every file starts with. Once the merchandiser tag ends it is the end of the document so I need to take only what's inside the merchandiser tag of file 2 and append it inside the merchandiser tag of file 1. The header tag just confuses me cause I'm not sure if it's gets in the way or not.
As preliminary note, your XML sample is malformed. Also it is not coherent with your code (i.e. there is not ->merchandiser->header->product ).
So, in this example I will use a different sample, like this one (file1.xml):
<root>
<product>
<name>Product 1</name>
</product>
<product>
<name>Product 2</name>
</product>
</root>
and this one (file2.xml):
<root>
<product>
<name>Product 3</name>
</product>
<product>
<name>Product 4</name>
</product>
</root>
You don't want to use DOMDocument->importNode() due “it kept throwing a lot of errors”.
You can use DOMDocument in conjunction with SimpleXML and dom_import_simplexml() function.
First of all, prepare destination XML: load the file with SimpleXML, create a DOMDocument using dom_import_simplexml() and set $parent variable to <root> element:
$dst = simplexml_load_file( 'file1.xml' );
$dst = dom_import_simplexml( $dst )->ownerDocument;
$parent = $dst->getElementsByTagName( 'root' )->item(0);
Then, load second file with SimpleXML:
$src = simplexml_load_file( 'file2.xml' );
Through a foreach() loop, import each <product> element from SimpleXML to DOMDocument and appent it as child of $parent node:
foreach( $src->product as $product )
{
$node = dom_import_simplexml( $product );
$node = $dst->importNode( $node, 1 );
$parent->appendChild( $node );
}
Now, your merged XML is ready. You can print it using $dst->saveXML().
I've not be able to product a correctly indented XML. BTW to do this, you can reload-it:
$final = new DOMDocument();
$final->loadXML( $dst->saveXML(), LIBXML_NOBLANKS );
$final->formatOutput = True;
echo $final->saveXML();
Final output:
<?xml version="1.0"?>
<root>
<product>
<name>Product 1</name>
</product>
<product>
<name>Product 2</name>
</product>
<product>
<name>Product 3</name>
</product>
<product>
<name>Product 4</name>
</product>
</root>
foreach($resultXML->products->children() as $product) {
echo "<p>".$product->{'advertiser-name'}." - ".$product->price."</p>
<p>".$product->{'description'}."</p>";
}
Suppose I wanted to screen out the ones that had the same title, and only display the first title that appears in the return results.
I'm not working with my own database, this is all about what's displayed.
I suppose the easiest way would be to keep track of the titles in an array, and checking it each iteration.
$titles = array();
foreach($resultXML->products->children() as $product) {
if (in_array($product->title, $titles) continue;
$titles[] = $product->title;
echo "<p>".$product->{'advertiser-name'}." - ".$product->price."</p>
<p>".$product->{'description'}."</p>";
}
Assuming that the title is contained in $product->title. You could do something fancier through array functions, but I don't see a reason to make a simple problem complicated.
You have not provided any exemplary XML, so given for
<?xml version="1.0" encoding="UTF-8"?>
<example>
<products>
<product>
<title>First Product</title>
<advertiser-name>First Name</advertiser-name>
</product>
</products>
<products>
<product>
<title>Second Product</title>
<advertiser-name>First Name</advertiser-name>
</product>
</products>
<products>
<product>
<title>Third Product</title>
<advertiser-name>Second Name</advertiser-name>
</product>
</products>
</example>
You want to get all product elements with an advertiser-name that is not an advertiser-name of all preceding product elements.
So for the XML above, that would be the 1st and 3rd product element.
You can write that down as an XPath expression:
/*/products/product[not(advertiser-name = preceding::product/advertiser-name)]
And as PHP code:
$xml = simplexml_load_string($buffer);
$expr = '/*/products/product[not(advertiser-name = preceding::product/advertiser-name)]';
foreach ($xml->xpath($expr) as $product) {
echo $product->asXML(), "\n";
}
This produces the following output:
<product>
<title>First Product</title>
<advertiser-name>First Name</advertiser-name>
</product>
<product>
<title>Third Product</title>
<advertiser-name>Second Name</advertiser-name>
</product>
So one answer to your question therefore is: Only query those elements from the document you're interested in. XPath can be used for that with SimpleXMLElement.
Related questions:
Implementing condition in XPath
I have been asked to parse a simple file which is stored as an XML file, the data is to be then put into a mysql database.
However I have absolutely no clue what to do and after looking online all the examples given seem either too complicated for my problem or not the right solution. The XML file looks like this:
<shop>
<products>
<product id="1" name="Cornetto" price="1.20" description="Traditional Cornetto" />
<product id="2" name="Smarties" price="1.00" description="Smarties Icecream" />
</products>
<stocks>
<stock id="1" amount="242" price="pounds" />
<stock id="2" amount="11" price="pounds" />
</stocks>
I've tried looking at SimpleXML and I think that's the direction I have to go but I just have no idea.
Any help or pointers would be great.
I personally like the normal XMl formatting so I changed it since its a bit more readable but this is how you can use it:
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<shop>
<products>
<product>
<id>1</id>
<name>Cornetto</name>
<price>1.20</price>
<description>Traditional Cornetto</description>
</product>
<product>
<id>2</id>
<name>Smarties</name>
<price>1.00</price>
<description>Smarties Icecream</description>
</product>
</products>
<stocks>
<stock>
<id>1</id>
<amount>242</amount>
<price>pounds</price>
</stock>
<stock>
<id>2</id>
<amount>11</amount>
<price>pounds</price>
</stock>
</stocks>
</shop>
XML;
Handling part:
$xml = new SimpleXMLElement($xmlstr);
echo 'single value: <br />';
echo $xml->products->product[0]->id; // get single value
echo '<br /><br />';
//Loop trough multiple products
echo 'multiple values: <br />';
foreach($xml->products->product as $product)
{
echo $product->id.' - ';
echo $product->name.' - ';
echo $product->price.' - ';
echo $product->description;
echo '<br/>';
}
Assuming the file is called data.xml
$string = file_get_contents('data.xml') reads the entire file into $string.
$xml = new SimpleXMLElement($string); parses that string, and converts it into an object tree similar to the actual document. So if that's the document -
<root>
<b>
<c>first</c>
<c>second</c>
</b>
</root>
The SimpleXMLElement object would be used like:
$xml->b // gets all children of b (c[0] and c[1])
print $xml->b->c[0] // gets the first c, will print "first"
You can use for example SimpleXMLElement and xpath
<?php
$xmlStr = <<<EOF
<?xml version="1.0"?>
<shop>
<products>
<product id="1" name="Cornetto" price="1.20" description="Traditional Cornetto" />
<product id="2" name="Smarties" price="1.00" description="Smarties Icecream" />
</products>
<stocks>
<stock id="1" amount="242" price="pounds" />
<stock id="2" amount="11" price="pounds" />
</stocks>
</shop>
EOF;
$xml=new SimpleXMLElement($xmlStr);
// get product line with xpath for example
$products=$xml->xpath("/shop/products/product");
if ($products) {
// loop over each product node
foreach ($products as $product) {
// do whatever you want with the data
echo("id=>".$product["id"].", name=>".$product["name"]."<br/>");
}
}
// same for stock
// get product line with xpath for example
$stocks=$xml->xpath("/shop/stocks/stock");
if ($stocks) {
// loop over each product node
foreach ($stocks as $stock) {
// do whatever you want with the data
echo("id=>".$stock["id"].", amount=>".$stock["amount"]."<br/>");
}
}
?>
$xml = simplexml_load_file($filename);
foreach($xml->product as $product) {
foreach($product->attributes() as $name => $attribute) {
echo "$name = $attribute";
}
}
$xml = simplexml_load_file($filename);
foreach($xml->products->product as $not)
{
foreach($not->attributes() as $a => $b)
{
echo $a,'="',$b,"\"<br />";
}
}