How to parse deep xml on php - php

I have problem with that XML parsing:
<pricecatalog>
<pricecathdr></pricecathdr>
<listofcatalogdetails>
<catalogitem>
<product>
<productid>1515159115</productid>
</product>
</catalogitem>
<catalogitem>
<product>
<productid>251541851</productid>
</product>
</catalogitem>
<catalogitem>
<product>
<productid>15181158</productid>
</product>
</catalogitem>
</listofcatalogdetails>
</pricecatalog>
I use DOM to parse it like that:
$doc = new DOMDocument();
$doc->loadXML($this->response);
$items = $doc->getElementsByTagName("catalogitem");
$i = 0;
foreach($items as $itm){
$i++;
}
echo $i;
I think I have to get 2 if parse will success but I allays get 0.

Related

SImpleXML separate values

I'm trying to get values from an XML file but i want them to separate in diferrent fields.
My XML file:
<PRODUCTS>
<PRODUCT>
<PRODUCT_NUMBER>7375-06</PRODUCT_NUMBER>
<PRODUCT_NAME>Soft ball</PRODUCT_NAME>
<ITEM_COLOR_NUMBER>04;05;10</ITEM_COLOR_NUMBER>
</PRODUCT>
</PRODUCTS>
My code:
<?php
header ("Content-Type:text/xml");
$xmlA = simplexml_load_file('ftp://.../products.xml');
// create empty output xml object
$final = new simpleXMLElement('<?xml version="1.0" encoding="utf-8"?><PRODUCTINFORMATION></PRODUCTINFORMATION>');
$products = $final->addChild("PRODUCTS");
foreach ($xmlA->PRODUCTS->PRODUCT as $proda) {
$prodbaseno = (string)$proda->PRODUCT_NUMBER;
$prodname = (string)$proda->PRODUCT_NAME;
$prodprintid = (string)$proda->ITEM_COLOR_NUMBER;
// build the output xml
$prodnew = $products->addChild('PRODUCT');
$prodnew->addChild('PRODUCT_NUMBER', $prodbaseno);
$prodnew->addChild('PRODUCT_NAME', $prodname);
$prodnew->addChild('ITEM_COLOR_NUMBER', $prodprintid);
}
echo $final->saveXml();
?>
The output:
<PRODUCTINFORMATION>
<PRODUCTS>
<PRODUCT>
<PRODUCT_NUMBER>MO7375-06</PRODUCT_NUMBER>
<PRODUCT_NAME>Soft ball</PRODUCT_NAME>
<ITEM_COLOR_NUMBER>04;05;10</ITEM_COLOR_NUMBER>
</PRODUCT>
</PRODUCTS>
</PRODUCTINFORMATION>
But what i actually need for ITEM_COLOR_NUMBER is:
<ITEM_COLOR_NUMBER>04</ITEM_COLOR_NUMBER>
<ITEM_COLOR_NUMBER>05</ITEM_COLOR_NUMBER>
<ITEM_COLOR_NUMBER>10</ITEM_COLOR_NUMBER>
How can i set that after a ; create a new ITEM_COLOR_NUMBER with the next value?
You might use explode and use ; as the delimiter.
For example
$prodnew = $products->addChild('PRODUCT');
$prodnew->addChild('PRODUCT_NUMBER', $prodbaseno);
$prodnew->addChild('PRODUCT_NAME', $prodname);
foreach (explode(';', $prodprintid) as $item) {
$prodnew->addChild('ITEM_COLOR_NUMBER', $item);
}
Output
<PRODUCTINFORMATION>
<PRODUCTS>
<PRODUCT>
<PRODUCT_NUMBER>7375-06</PRODUCT_NUMBER>
<PRODUCT_NAME>Soft ball</PRODUCT_NAME>
<ITEM_COLOR_NUMBER>04</ITEM_COLOR_NUMBER>
<ITEM_COLOR_NUMBER>05</ITEM_COLOR_NUMBER>
<ITEM_COLOR_NUMBER>10</ITEM_COLOR_NUMBER>
</PRODUCT>
</PRODUCTS>
</PRODUCTINFORMATION>
Php demo

Filtering XML elements by child node value

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.

Delete Parent node keeping all its child elements in nested XML with PHP

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.

Extracting data from xml using php

I'm trying to grab specific data from a XML file using php.
My goal is to feed a function a "number" and get the corresponding price back.
Eg. if I input the number "swv8813" it will return the price "603.00", and if I input the number "swv8814" it will return "700.00".
How can I do this?
<?xml version="1.0" encoding="iso-8859-1" ?>
<Feed>
<Title>CompanyName</Title>
<Email>info#CompanyName.com</Email>
<Products>
<Product>
<Id>4635</Id>
<Number>swv8813</Number>
<Title><![CDATA[&Tradition - Bellevue AJ2 - Floor Lamp White]]></Title>
<Description><![CDATA[]]></Description>
<Category><![CDATA[Lighting]]></Category>
<Stock>0</Stock>
<Price>603.00</Price>
<Discount>0.00</Discount>
<Created>0000-00-00 00:00:00</Created>
</Product>
<Product>
<Id>4635</Id>
<Number>swv8814</Number>
<Title><![CDATA[&Tradition - Bellevue AJ2 - Floor Lamp Black]]></Title>
<Description><![CDATA[]]></Description>
<Category><![CDATA[Lighting]]></Category>
<Stock>0</Stock>
<Price>700.00</Price>
<Discount>0.00</Discount>
<Created>0000-00-00 00:00:00</Created>
</Product>
</Products>
</Feed>
Use this:
$xmlstr = "<Feed>
<Title>CompanyName</Title>
<Email>info#CompanyName.com</Email>
<Products>
<Product>
<Id>4635</Id>
<Number>swv8813</Number>
<Title><![CDATA[&Tradition - Bellevue AJ2 - Floor Lamp White]]></Title>
<Description><![CDATA[]]></Description>
<Category><![CDATA[Lighting]]></Category>
<Stock>0</Stock>
<Price>603.00</Price>
<Discount>0.00</Discount>
<Created>0000-00-00 00:00:00</Created>
</Product>
<Product>
<Id>4635</Id>
<Number>swv8814</Number>
<Title><![CDATA[&Tradition - Bellevue AJ2 - Floor Lamp Black]]></Title>
<Description><![CDATA[]]></Description>
<Category><![CDATA[Lighting]]></Category>
<Stock>0</Stock>
<Price>700.00</Price>
<Discount>0.00</Discount>
<Created>0000-00-00 00:00:00</Created>
</Product>
</Products>
</Feed>";
$feed = new SimpleXMLElement($xmlstr);
function findPrice($feed, $id){
foreach($feed->Products->Product as $product){
if($product->Number == $id){
return $product->Price;
}
}
return null;
}
echo findPrice($feed, 'swv8813');
echo "\n";
echo findPrice($feed, 'swv8814');
See it working at 3v4l.org script
With DOM and Xpath you can fetch the value directly:
$id = 'swv8813';
$xml = file_get_contents('php://stdin');
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$expression = sprintf(
'number(/Feed/Products/Product[Number="%s"]/Price)',
str_replace(["\00", '"'], '', $id)
);
var_dump($expression, $xpath->evaluate($expression));
Output:
string(54) "number(/Feed/Products/Product[Number="swv8813"]/Price)"
float(603)
The id value is not allowed to have zero bytes or double quotes. A simple str_replace takes care of that.
The expression ...
...fetches all Product nodes...
/Feed/Products/Product
...filters them by their Number child...
/Feed/Products/Product[Number="swv8813"]
...gets the Price children for the filtered nodes...
/Feed/Products/Product[Number="%s"]/Price
...and casts the first found Price to a number.
number(/Feed/Products/Product[Number="swv8813"]/Price)

How should I modify this for each statement to only display entries where all of the values are unique?

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

Categories