PHP - referencing an XML attribute using simpleXML - php

Using the simplexml_load_file method, I am trying to retrieve and display the text of all name elements (from an XML file below) that have an attribute named "type' with the value of 'tablet.' This foreach loop is only displaying the value of the first element. Any advice? Thanks!
$XMLproducts = simplexml_load_file("products.xml");
foreach($XMLproducts->product->attributes() as $a => $b) {
$i = 0;
if ($b == "Tablet") {
echo $XMLproducts->product[$i]->name;
echo "<br>";
}
}
Here is the XML file:
<products>
<product type="Desktop">
<name>Desktop 1</name>
</product>
<product type="Tablet">
<name>Ipad 1</name>
</product>
<product type="Desktop">
<name>Desktop 2</name>
</product>
<product type="Tablet">
<name>Ipad 2</name>
</product>
</products>

As Scuzzy mentioned in the comments, using SimpleXMLElement::xpath simplifies the solution:
foreach ($XMLproducts->xpath('/products/product[#type="Tablet"]/name') as $name) {
echo $name , "<br>";
}

Try this
$XMLproducts = simplexml_load_file("products.xml");
foreach($XMLproducts->products->product as $product) {
foreach ($product->attributes() as $a => $b) {
$i = 0;
if ($b == "Tablet") {
echo $XMLproducts->product[$i]->name;
echo "<br>";
}
}
}

Related

Merge SimpleXML elements by SKU attribute

I have 3 xml files with same structure. What I need to do is merge them together via php.
Take a look at my example file below
1.xml
<data>
<product productsku="ABC1" price="550000" pricesale="" hn="1"></product>
<product productsku="ABC2" price="" pricesale="" hn="0"></product>
</data>
2.xml
<data>
<product productsku="ABC2" price="550000" pricesale="" dn="2"></product>
<product productsku="ABC3" price="" pricesale="" dn="0"></product>
</data>
3.xml
<data>
<product productsku="ABC3" price="550000" pricesale="" gn="3"></product>
<product productsku="ABC4" price="" pricesale="" gn="0"></product>
</data>
I would like to get the following result:
<data><product productsku="ABC1" price="550000" pricesale="" hn="1"></product>
<product productsku="ABC2" price="550000" pricesale="" dn="2" hn="0"></product>
<product productsku="ABC3" price="550000" pricesale="" dn="0" gn="3"></product>
<product productsku="ABC4" price="" pricesale="" gn="0"></product>
</data>
The code that I am trying
<?php
$xml1 = file_get_contents('1.xml');
$xml2 = file_get_contents('2.xml');
$targetDom = new DOMDocument();
$targetDom->loadXml($xml1);
$targetXpath = new DOMXpath($targetDom);
$addDom = new DOMDocument();
$addDom->loadXml($xml2);
$addXpath = new DOMXpath($addDom);
// merge attributes of product elements depending on productsku
foreach ($targetXpath->evaluate('//product[#productsku]') as $product) {
$productsku = $product->getAttribute('productsku');
foreach ($addXpath->evaluate('//product[#productsku='.$productsku.']/#*') as $attribute) {
if (!$product->hasAttribute($attribute->name)) {
$product->setAttribute($attribute->name, $attribute->value);
}
}
}
// copy products elements that are not in target dom
foreach ($addXpath->evaluate('//product[#productsku]') as $product) {
$productsku = $product->getAttribute('productsku');
if ($targetXpath->evaluate('count(//product[#productsku='.$productsku.'])') == 0) {
$targetDom->documentElement->appendChild(
$targetDom->importNode($product)
);
}
}
echo $targetDom->saveXml();
I tried the above, it works fine if the SKU is numeric. But my SKU or ID is not a number. And I have 3 xml files
I tried to find the solution on stackoverflow.
But all is not as I expected.
I'm really not good at this. Please help me through a php snippet.
I found the xml_adopt function here: PHP - SimpleXML - AddChild with another SimpleXMLElement
Then it's a matter of loading each file, keeping track of products and overwriting them if the product has only been seen with no price, then adopting them into a single SimpleXMLElement.
<?php
/*
Question Author: Phan Vũ
Question Answerer: Jacob Mulquin
Question: Merge SimpleXML elements by SKU attribute
URL: https://stackoverflow.com/questions/74625741/merge-simplexml-elements-by-sku-attribute
Tags: php, xml
*/
// https://stackoverflow.com/a/11727581/1427345
function xml_adopt($root, $new) {
$node = $root->addChild($new->getName(), (string) $new);
foreach($new->attributes() as $attr => $value) {
$node->addAttribute($attr, $value);
}
foreach($new->children() as $ch) {
xml_adopt($node, $ch);
}
}
$xml = new SimpleXMLElement('<data></data>');
$files = ['1.xml', '2.xml', '3.xml'];
$to_adopt = [];
foreach ($files as $file) {
$load = simplexml_load_file($file);
foreach ($load->product as $product) {
$sku = (string) $product->attributes()['productsku'];
if (!isset($to_adopt[$sku])) {
$to_adopt[$sku] = $product;
} else {
$price = (string) $to_adopt[$sku]->attributes()['price'];
if (empty($price)) {
$to_adopt[$sku]['price'] = $price;
}
$existing_attributes = ((array) $to_adopt[$sku]->attributes())['#attributes'];
foreach ($product->attributes() as $name => $attribute) {
if (!in_array($name, $existing_attributes)) {
$to_adopt[$sku][$name] = $attribute;
}
}
}
}
}
foreach ($to_adopt as $adopt) {
xml_adopt($xml, $adopt);
}
file_put_contents('output.xml', $xml->asXML());
Yields:
<?xml version="1.0"?>
<data>
<product productsku="ABC1" price="550000" pricesale="" hn="1"/>
<product productsku="ABC2" price="550000" pricesale="" hn="0" dn="2"/>
<product productsku="ABC3" price="550000" pricesale="" dn="0" gn="3"/>
<product productsku="ABC4" price="" pricesale="" gn="0"/>
</data>

Merge duplicate content .xml in php

I am learning the php code.
I'm in a situation where it has to be done as soon as possible.
Please help me reorganize my xml duplicate data.
original .xml file
<products>
<product>
<ID>ID1</ID>
<SKU_parent>SKU1</SKU_parent>
<SKU>MA</SKU>
<price>10</price>
<price-sale>5</price-sale>
<KHO1>10</KHO1>
<KHO2>6</KHO2>
<KHO3>2</KHO3>
</product>
<product>
<ID>ID2</ID>
<SKU_parent>SKU2</SKU_parent>
<SKU>MA2</SKU>
<price>500</price>
<price-sale>200</price-sale>
<KHO1>20</KHO1>
<KHO2>0</KHO2>
<KHO3>0</KHO3>
</product>
<product>
<ID>ID3</ID>
<SKU_parent>SKU2</SKU_parent>
<SKU>MA2</SKU>
<price>500</price>
<price-sale>200</price-sale>
<KHO1>0</KHO1>
<KHO2>30</KHO2>
<KHO3>0</KHO3>
</product>
<product>
<ID>ID2</ID>
<SKU_parent>SKU2</SKU_parent>
<SKU>MA2</SKU>
<price>500</price>
<price-sale>200</price-sale>
<KHO1>0</KHO1>
<KHO2>0</KHO2>
<KHO3>40</KHO3>
</product>
into something like this:
<products>
<product>
<ID>ID1</ID>
<SKU_parent>SKU1</SKU_parent>
<SKU>MA</SKU>
<price>10</price>
<price-sale>5</price-sale>
<KHO1>10</KHO1>
<KHO2>6</KHO2>
<KHO3>2</KHO3>
</product>
<product>
<ID>ID2</ID>
<SKU_parent>SKU2</SKU_parent>
<SKU>MA2</SKU>
<price>500</price>
<price-sale>200</price-sale>
<KHO1>20</KHO1>
<KHO2>30</KHO2>
<KHO3>40</KHO3>
</product>
I've tried searching on stackoverflow but it doesn't seem to work.
I appreciate it when someone help me with this php code
For starters, I converted your XML string into an array.
I reduced your array using SKU_parent as a unique key.
I rebuilt the array again into a XML, using the Francis Lewis function (with some slight modifications, by hardcoding the product node if the array key was numeric) - Source: https://stackoverflow.com/a/19987539/4527645
$xml_string = '<products>
<product>
<ID>ID1</ID>
<SKU_parent>SKU1</SKU_parent>
<SKU>MA</SKU>
<price>10</price>
<price-sale>5</price-sale>
<KHO1>10</KHO1>
<KHO2>6</KHO2>
<KHO3>2</KHO3>
</product>
<product>
<ID>ID2</ID>
<SKU_parent>SKU2</SKU_parent>
<SKU>MA2</SKU>
<price>500</price>
<price-sale>200</price-sale>
<KHO1>20</KHO1>
<KHO2>0</KHO2>
<KHO3>0</KHO3>
</product>
<product>
<ID>ID3</ID>
<SKU_parent>SKU2</SKU_parent>
<SKU>MA2</SKU>
<price>500</price>
<price-sale>200</price-sale>
<KHO1>0</KHO1>
<KHO2>30</KHO2>
<KHO3>0</KHO3>
</product>
<product>
<ID>ID2</ID>
<SKU_parent>SKU2</SKU_parent>
<SKU>MA2</SKU>
<price>500</price>
<price-sale>200</price-sale>
<KHO1>0</KHO1>
<KHO2>0</KHO2>
<KHO3>40</KHO3>
</product>
</products>';
$xml = simplexml_load_string($xml_string, 'SimpleXMLElement', LIBXML_NOCDATA);
$json = json_encode($xml);
$xml_array = json_decode($json, true);
$unique = array_reduce($xml_array['product'], function($final, $article){
static $seen = [];
if (!array_key_exists($article['SKU_parent'], $seen)) {
$seen[$article['SKU_parent']] = NULL;
$final[] = $article;
}
return $final;
});
function to_xml(SimpleXMLElement $object, array $data)
{
foreach ($data as $key => $value) {
if (is_array($value)) {
if (is_numeric($key)) {
$new_object = $object->addChild('product');
}
to_xml($new_object, $value);
} else {
if ($key != 0 && $key == (int) $key) {
$key = "key_$key";
}
$object->addChild($key, $value);
}
}
}
$xml = new SimpleXMLElement('<products/>');
to_xml($xml, $unique);
header('Content-Type: text/xml');
print $xml->asXML();
You can also check a working example of this code here: https://onlinephp.io/c/1d06d
This code eliminates the duplicate product nodes by SKU_parent. In order to merge all the values that are not 0, you have to improve the array_reduce or find another method with array_merge_recursive for example. This is just a starting point.
If you're simply using product->ID as the identifier, you could just do:
$xml = new DOMDocument;
$xml->load('dedup.xml');
$xpath = new DOMXpath($xml);
$dupNodes = $xpath->query('/products/product[ID = preceding-sibling::product/ID]');
foreach ($dupNodes as $dupNode) {
$dupNode->parentNode->removeChild($dupNode);
}
echo $xml->saveXML();

image name in xml child more than one and get limitation with php

i have xml the other array i got it but first one cant how can i solve this
Xml structure like this and get an error code in xml to php First one is not array the second one is array
i Coudnt get the first image children "sitename/11.jpg"
Xml like this
[images] => SimpleXMLElement Object ( [image] => Array ( [0] => sitename/15.jpg [1] => sitename/16.jpg [2] => sitename/17.jpg [3] => sitename/18.jpg ) ) )
[images] => SimpleXMLElement Object ( [image] => sitename/11.jpg ))
<root>
<result>
<node>
<categories>somecategory<categories/>
<images>
<image>sitename/15.jpg</image><image>sitename/16.jpg</image><image>sitename/17.jpg</image><image>sitename/18.jpg</image>
</images>
</node>
<node>
<categories>somecategory<categories/>
<images>
<image>sitename/11.jpg</image>
</images>
</node>
</result>
</root>
function solve_something($xml, $destination)
{
$xml = simplexml_load_file($xml, "SimpleXMLElement", LIBXML_NOCDATA);
$json = json_encode($xml);
$items = json_decode($json, TRUE);
$products = array();
$product_data = array();
$row = 1;
foreach ($items['result']['node'] as $item) {
$product_data['Categories'] = 'categories';
if (isset($item['images']['image'])) {
if (is_array($item['images']['image'])) {
foreach ($item['images']['image'] as $key => $image) {
$key++;
if ($key <= 4) {
$image_name = 'image' . $key;
$product_data[$image_name] = isset($image) ? $image : null;
}
}
} else {
$product_data['image'] = isset($image) ? $image : null;
}
}
$path = createXML($products, $destination);
return $path;
}
The other function code its create the xml file
function createXML($data, $destination)
{
$xmlDoc = new DOMDocument('1.0', 'UTF-8');
$root = $xmlDoc->appendChild($xmlDoc->createElement("root"));
foreach ($data as $key => $product) {
$productA = $root->appendChild($xmlDoc->createElement('product'));
foreach ($product as $key1 => $val) {
if (!empty($val)) {
if ($key1 == 'price' || $key1 == 'tax' || $key1 == 'stockAmount') {
$productA->appendChild($xmlDoc->createElement($key1, $val));
} else {
$ProductKey = $productA->appendChild($xmlDoc->createElement($key1));
$ProductKey->appendChild($xmlDoc->createCDATASection($val));
}
}
}
}
$xmlDoc->formatOutput = true;
fn_rm($destination);
$xmlDoc->save($destination);
return $destination;
}
Code create
<root>
<product>
<categories>somecategory<categories/>
<images>
<image1>sitename/15.jpg
<image2>sitename/16.jpg
<image3>sitename/17.jpg
</images>
</product>
<product>
<categories>somecategory<categories/>
<images>
<image1>sitename/15.jpg
<image2>sitename/16.jpg
<image3>sitename/17.jpg
<image4>sitename/18.jpg
</images>
</product>
</root>
But i want
<root>
<product>
<categories>somecategory<categories/>
<images>
<image1>sitename/15.jpg
<image2>sitename/16.jpg
<image3>sitename/17.jpg
</images>
</product>
<product>
<categories>somecategory<categories/>
<images>
<image1>sitename/11.jpg
</images>
</product>
</root>
There are a few issues with the code
If you want to have 3 images, this part if ($key <= 4) { should be lesser or equal than 2.
You don't really have to return anything (or you want to check for false), as you are writing a file, as the save function returns the number of bytes written or false if an error occurred.
Using $key++; like this in the foreach can also be done using a for loop where you can use $i to append after image
Not sure why you want to use createCDATASection but I have left that part out to get the desired result
As you have multiple sections of node, you may use an $product_data array per iteration to add the values to and after the foreach add $product_data to the $products array to prevent overwriting the values for every loop.
The updated code might look like
function solve_something($xml, $destination)
{
$xml = simplexml_load_string($xml, "SimpleXMLElement", LIBXML_NOCDATA);
$json = json_encode($xml);
$items = json_decode($json, TRUE);
$products = array();
foreach ($items['result']['node'] as $item) {
$product_data = array();
$category = $item["categories"];
$product_data["categories"] = $category;
if (isset($item['images']['image'])) {
if (is_array($item['images']['image'])) {
for ($i = 0; $i < count($item['images']['image']); $i++) {
if ($i < 3) $product_data["image" . ($i + 1)] = $item['images']['image'][$i];
}
} else $product_data["image1"] = $item['images']['image'];
}
$products[] = $product_data;
}
createXML($products, $destination);
}
function createXML($data, $destination)
{
$xmlDoc = new DOMDocument('1.0', 'UTF-8');
$root = $xmlDoc->appendChild($xmlDoc->createElement("root"));
foreach ($data as $key => $product) {
$productA = $root->appendChild($xmlDoc->createElement('product'));
$imagesElm = $xmlDoc->createElement('images');
foreach ($product as $key1 => $val) {
if ($key1 == 'price' || $key1 == 'tax' || $key1 == 'stockAmount' || $key1 === "categories") {
$productA->appendChild($xmlDoc->createElement($key1, $val));
} elseif (substr($key1, 0, 5) === "image") {
$imagesElm->appendChild($xmlDoc->createElement($key1, $val));
}
}
$productA->appendChild($imagesElm);
}
$xmlDoc->formatOutput = true;
$xmlDoc->save($destination);
}
$xml = <<<XML
<?xml version="1.0" encoding="utf-8"?>
<root>
<result>
<node>
<categories>somecategory</categories>
<images>
<image>sitename/15.jpg</image>
<image>sitename/16.jpg</image>
<image>sitename/17.jpg</image>
<image>sitename/18.jpg</image>
</images>
</node>
<node>
<categories>somecategory</categories>
<images>
<image>sitename/11.jpg</image>
</images>
</node>
</result>
</root>
XML;
solve_something($xml, "result.xml");
The xml in result.xml looks like
<?xml version="1.0" encoding="UTF-8"?>
<root>
<product>
<categories>somecategory</categories>
<images>
<image1>sitename/15.jpg</image1>
<image2>sitename/16.jpg</image2>
<image3>sitename/17.jpg</image3>
</images>
</product>
<product>
<categories>somecategory</categories>
<images>
<image1>sitename/11.jpg</image1>
</images>
</product>
</root>

echo specific part of xml string

Assume i have the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product>
<id>1</id>
<name>Apple iPhone 7</name>
</product>
<product>
<id>2</id>
<name>Samsung Galaxy S7</name>
</product>
</products>
With the following code, I'm able to print the whole xml-string:
$xml = simplexml_load_string($string);
echo '<pre>'.htmlentities($xml->asXML()).'</pre>';
I'm now looking for code to echo a specific part of the xml string, let's say product[0].
I want to be able to echo it as follows:
<product>
<id>1</id>
<name>Apple iPhone 7</name>
</product>
Anyone knows the solution?
You can use this functions:
<?php
function xml2ary(&$string) {
$parser = xml_parser_create();
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parse_into_struct($parser, $string, $vals, $index);
xml_parser_free($parser);
$mnary=array();
$ary=&$mnary;
foreach ($vals as $r) {
$t=$r['tag'];
if ($r['type']=='open') {
if (isset($ary[$t])) {
if (isset($ary[$t][0])) $ary[$t][]=array(); else $ary[$t]=array($ary[$t], array());
$cv=&$ary[$t][count($ary[$t])-1];
} else $cv=&$ary[$t];
if (isset($r['attributes'])) {foreach ($r['attributes'] as $k=>$v) $cv['_a'][$k]=$v;}
$cv['_c']=array();
$cv['_c']['_p']=&$ary;
$ary=&$cv['_c'];
} elseif ($r['type']=='complete') {
if (isset($ary[$t])) { // same as open
if (isset($ary[$t][0])) $ary[$t][]=array(); else $ary[$t]=array($ary[$t], array());
$cv=&$ary[$t][count($ary[$t])-1];
} else $cv=&$ary[$t];
if (isset($r['attributes'])) {foreach ($r['attributes'] as $k=>$v) $cv['_a'][$k]=$v;}
$cv['_v']=(isset($r['value']) ? $r['value'] : '');
} elseif ($r['type']=='close') {
$ary=&$ary['_p'];
}
}
_del_p($mnary);
return $mnary;
}
// _Internal: Remove recursion in result array
function _del_p(&$ary) {
foreach ($ary as $k=>$v) {
if ($k==='_p') unset($ary[$k]);
elseif (is_array($ary[$k])) _del_p($ary[$k]);
}
}
$string = '<?xml version="1.0" encoding="UTF-8"?>
<products>
<product>
<id>1</id>
<name>Apple iPhone 7</name>
</product>
<product>
<id>2</id>
<name>Samsung Galaxy S7</name>
</product>
</products>';
$requeststring = xml2ary($string);
echo $requeststring["products"]["_c"]["product"][0]["_c"]["id"]["_v"]; //outputs 1
?>
Just change ["products"] or ["product"] or ["id"] for your own result.

Add element to XML when not existing in child PHP

<?xml version="1.0" encoding="UTF-8"?>
<products>
<product>
<a>product a</a>
<b>data</b>
<c>data</c>
</product>
<product>
<a>product b</a>
<c>data</c>
</product>
</products>
When child-element < B > is missing, i want to add it to the XML file. so the < product > ends up like this.No data has to be added, just the element.
<product>
<a>data</a>
<c>data</c>
<b></b>
</product>
can this be done with simplexml ?
<?php
$xml = simplexml_load_file("xml.xml", NULL, TRUE);
foreach ($xml->children() as $child) {}
This is definitely possible. Here's an example of how to do it (assuming you're not worried about the order in which the child element occurs):
$xml = new SimpleXMLElement('xml.xml', NULL, TRUE);
foreach ($xml->children() as $child) {
if (isset($child->b)) {
continue;
}
$child->b = '';
}
// output to new file
$xml->asXML('xml2.xml');
You can also find an online Demo that contains all data:
<?php
/**
* Add element to XML when not existing in child PHP
* #link http://stackoverflow.com/q/19562757/367456
*/
$xml = new SimpleXMLElement('<r><product><b>hello</b></product><product/><product/></r>');
foreach ($xml->children() as $child) {
if (isset($child->b)) {
continue;
}
$child->b = '';
}
$xml->asXML('php://output');
Program Output (beautified):
<?xml version="1.0"?>
<r>
<product>
<b>hello</b>
</product>
<product>
<b></b>
</product>
<product>
<b></b>
</product>
</r>

Categories