PHP simplexml_load_file() merge xml fields with same ID - php

I have 2 XML files for importing products to Woocommerce. Products in both files have the same ID. The only difference is the price field between two XML files.
The first XML is something like this:
<products>
<product>
<product_id>14201</product_id>
<title>some title</title>
<image>some image</image>
<price>10</price>
<category>some category</category>
</product>
</products>
and the second XML file is something like this:
<products>
<product>
<product_id>14201</product_id>
<title>some title</title>
<image>some image</image>
<sale_price>7</sale_price>
<category>some category</category>
</product>
</products>
i want a result like this:
<products>
<product>
<product_id>14201</product_id>
<title>some title</title>
<image>some image</image>
<price>10</price>
<sale_price>7</sale_price>
<category>some category</category>
</product>
</products>
how i can merge all the fields from first XML and only the <sale_price> field from the second XML with PHP simplexml_load_file()?
this is my PHP script:
<?php
$xml = simplexml_load_file("https://mywebsite.com/xmlfile.xml");
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?><products>";
foreach ($xml->products->product as $prod) {
$pid = $prod->product_id;
$category = $prod->category;
$title = $prod->title;
$image = $prod->image;
$price = $prod->price;
?>
<product>
<id><?php echo $pid;?></id>
.....
.....
</product>
<?php
}
echo "</products>";
?>

You could use XPath to match the product id in the second file, but this could mean re-running a query each product.
This code first reads the second file and extracts all of the sale prices and indexes them by the product id. Then it reads the first file and adds in the sale price when there is a match form the stored array...
$sale = simplexml_load_file("b.xml");
$salePrices = [];
foreach ( $sale->product as $product ) {
$salePrices[(string)$product->product_id] = (string)$product->sale_price;
}
$xml = simplexml_load_file("a.xml");
$key = 0;
foreach ( $xml->product as $product ) {
if ( isset($salePrices[(string)$product->product_id])) {
$xml->product[$key]->sale_price = $salePrices[(string)$product->product_id];
}
$key++;
}
echo $xml->asXML();
(Change file names as required)

I would also go with xpath and just query the update values from the second file for the first one. Example with strings, replace the simple_load function to fit your needs.
Throws in case XML does not parse.
May get bogus results in case product_id evaluates to a different integer value (e.g. empty to 0 and there is a product with the id 0).
foreach (($aXml = simplexml_load_string($a))->xpath(
'/*/product'
) as $p)
foreach (simplexml_load_string($b)->xpath(
sprintf('/*/%s[./product_id = %d]/sale_price', $p->getName(), $p->product_id)
) as $u)
$p->{$u->getName()} = $u
;
Example on 3v4l.org: https://3v4l.org/XStUe

Related

how to insert an XML element from one document to another

first xml
<response status="ok">
<Product>
<name>blbla</name>
<productGroupPrimary>test2</productGroupPrimary>
<productGroupSecondary>test</productGroupSecondary>
<purchasePrice>18</purchasePrice>
<retailPrice>29</retailPrice>
<status>active</status>
<productCode>0001</productCode>
</Product>
<Product>
...
</Product>
other xml
<response status="ok">
<StockQuantityInfo productCode="0001" quantityOnStock="5"></StockQuantityInfo>
<StockQuantityInfo productCode="dhzh" quantityOnStock="5"></StockQuantityInfo>
...
</response>
now i would like to use php to make the final XML document look like this
<response status="ok">
<Product>
<name>blbla</name>
<productGroupPrimary>test2</productGroupPrimary>
<productGroupSecondary>test</productGroupSecondary>
<purchasePrice>18</purchasePrice>
<retailPrice>29</retailPrice>
<status>active</status>
<productCode>0001</productCode>
<stock>5</stock>
</Product>
<Product>
...
</Product>
</response>
i have no idea how i could do this i am a beginner
I tried that foreach
foreach ($xml2->Product as $item2) {
$koda2=$item2->productCode;
foreach ($xml3->StockQuantityInfo as $item3) {
$koda3=$item3->productCode;
if ($koda2 ==$koda3 ) {
$zaloga=$item3->quantityOnStock;
$Product=$xml2->Product->addChild('zaloga',$zaloga);
}
}
the result is that it doesn’t do anything to me, there’s no change, I have a mistake somewhere, or I’m thinking in the wrong direction
I suggest using DOM (not SimpleXML) for this. DOMXpath::evaluate() allows you to fetch the quantity for a product directly using an expression.
// bootstrap the documents
$products = new DOMDocument();
$products->loadXML($productsXML);
$productsXpath = new DOMXpath($products);
$stocks = new DOMDocument();
$stocks->loadXML($stockXML);
$stocksXpath = new DOMXpath($stocks);
// iterate the "Product" elements
foreach ($productsXpath->evaluate('//Product') as $product) {
// get the product code
$code = $productsXpath->evaluate('string(productCode)', $product);
// get the quantity of from the stocks response
$expression = 'number(//StockQuantityInfo[#productCode="'.$code.'"]/#quantityOnStock)';
$quantity = $stocksXpath->evaluate($expression);
// output expression and result for demonstration purposes
var_dump(
$expression, $quantity
);
// validate quantity is a number
if (!is_nan($quantity)) {
// append to "Product"
$product
->appendChild(
$products->createElement('Stock')
)
->textContent = $quantity;
}
}
echo "\n", $products->saveXML();
Output:
string(65) "number(//StockQuantityInfo[#productCode="0001"]/#quantityOnStock)"
float(5)
<?xml version="1.0"?>
<response status="ok">
<Product>
<name>blbla</name>
<productGroupPrimary>test2</productGroupPrimary>
<productGroupSecondary>test</productGroupSecondary>
<purchasePrice>18</purchasePrice>
<retailPrice>29</retailPrice>
<status>active</status>
<productCode>0001</productCode>
<Stock>5</Stock></Product>
</response>

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

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)

php xml remove elements not containing a specific word from large file

I am reading an xml file which looks like this but with a lot more products:
<?xml version="1.0" encoding="iso-8859-1"?>
<products>
<product>
<company>company.com</company>
<category>Category A</category>
<brand>Alle!rgica</brand>
<product_name>Name A</product_name>
<productid>6230</productid>
<description>A nice description</description>
<price>125.50</price>
</product>
<product>
<company>Team.com</company>
<category>Category B // something</category>
<brand>New Nordic > Healthcare</brand>
<product_name>Name B</product_name>
<productid>9489</productid>
<description>Active Legs? Buy it now for free</description>
<price>188.00</price>
</product>
</products>
I want to read it and then save it with only products containing the word "free" somewhere in the "product tag" and without the "products" tag and the xml header.
I know how to read the file and save it, but I can't figure out the best approach to remove everything but the products that contain "free".
I tried wth Regex but it didn't seem the best solution (mainly because the matching doesn't properly work):
preg_match_all('/<product>(.*?)(free|free-stuff)(.*?)<\/product>/is', $data, $result);
So in the case of the above the file should only contain:
<product>
<company>Team.com</company>
<category>Category B // something</category>
<brand>New Nordic > Healthcare æøå</brand>
<product_name>Name B</product_name>
<productid>9489</productid>
<description>Active Legs? Buy it now for free</description>
<price>188.00</price>
</product>
use xpath():
$xml = simplexml_load_string($x); // assume XML in $x
$result = $xml->xpath("//product[not(contains(., 'free'))]");
$result contains an array of <product>-nodes as SimpleXML-elements that do not contain "free".
Output:
foreach ($result as $r)
echo $r->asXML();
See it working: https://eval.in/338884
Use this code:
$xml = simplexml_load_file($filename);
foreach($xml->product as $product) {
foreach($product->children() as $child)
// lookup the pattern in all nodes inside product
if ($found = (false !== strpos((string)$child, 'free')))
// Found - we can don't continue searching
break;
// save product found
if ($found) $products[] = $product;
}
print_r( $products);

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