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.
Related
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();
I have an XML file in which one child has two categories, but with the same name. I want to add one title to each one. How can we do it in PHP?
This is my XML
<root>
<result>
<node>
<title> Some Title Name</title>
<categories>
<category> categor_one </category>
<category> categor_two </category>
</categories>
</node>
<node>
<title> Some Title Name</title>
<categories>
<category> categor_one </category>
<category> categor_tree </category>
</categories>
</node>
</result>
</root>
But I want to obtain this
<root>
<result>
<node>
<title> Some Title Name</title>
<category>categor_one///categor_two </category>
<category1>categor_one///categor_tree</category1>
</node>
</result>
</root>
I managed to impement a function that only gets correctly the category, but if the title is the same it doesn't work as it just creates a new one.
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();
foreach($items['result']['node'] as $item){
$product_data['title'] = $item['title'];
foreach ($item['categories'] as $category) {
if (is_array($category)) {
$product_data['category'] = implode('///', $category);
} else {
$product_data['category'] = $category;
}
}
$products[] = $product_data;
unset($product_data);
}
$path = createXML($products, $destination);
return $path;
}
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;
}
The output of my code is something like this
<root>
<product>
<title> Some Title Name</title>
<category>categor_one///categor_two </category>
</product>
<product>
<title> Some Title Name</title>
<category>categor_one///categor_tree</category>
</product>
</root>
If you want to keep the data together with the same title, one approach could be to collect that data upfront by using the title as an array key (if it is a valid array key)
When you create the xml, you have the title in the outer foreach loop as the key, and in the inner foreach you can create elements using implode.
Note that in your code you started using product so I took that as a node name.
Example code, which you could use in your code:
$products = array();
$product_data = array();
$xml = simplexml_load_file($xml, "SimpleXMLElement", LIBXML_NOCDATA);
foreach ($xml->result->node as $node) {
$product_data['title'] = (string)$node->title;
foreach($node->categories as $category) {
if (is_array($category)) {
$product_data['category'] = implode('///', $category);
continue;
}
$product_data['category'] = (array)$category->category;
}
$products[(string)$node->title][] = $product_data;
}
$xmlDoc = new DOMDocument('1.0', 'UTF-8');
$root = $xmlDoc->appendChild($xmlDoc->createElement("root"));
foreach ($products as $key => $product) {
$productA = $root->appendChild($xmlDoc->createElement('product'));
$productA->appendChild($xmlDoc->createElement("title", $key));
for ($i = 0; $i < count($product); $i++) {
$productA->appendChild($xmlDoc->createElement("category" . $i, implode("///", $product[$i]["category"])));
}
}
$xmlDoc->formatOutput = true;
echo $xmlDoc->saveXML();
Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<product>
<title> Some Title Name</title>
<category0> categor_one /// categor_two </category0>
<category1> categor_one /// categor_tree </category1>
</product>
</root>
Php demo
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>";
}
}
}
How to read ALL atribute xml:lang values?
Sometimes I do not know how many languages are defined in XMLs data.
<?xml version="1.0" encoding="UTF-8"?>
<offer>
<products>
<product>
<description>
<name xml:lang="eng">English translation</name>
<name xml:lang="lat">Latvian translation</name>
</description>
</product>
<product>
<description>
<name xml:lang="eng">The same English</name>
<name xml:lang="pol">And Polish language</name>
</description>
</product>
</products>
</offer>
I can xml:lang parse in PHP by adding exact language code in xpath
print_r($xml->xpath('products/product/description/name[#xml:lang = "eng"]'));
But I need to add all xml:lang atributes values to parsed array.
Can it be done with PHP SimpleXML?
what about this:
$nodes = $xml->xpath('products/product/description/name[#xml:lang]');
Will return an array of <name>-nodes.
If this is not it, please clarify exactly your desired result.
EDIT
try this to get the xml:lang attributes only:
$langs = $xml->xpath("products/product/description/name[#xml:lang]/#xml:lang");
// $lang is an array of simplexml-elements, transform the values to string like this:
$langs = array_map("strval", $langs);
I'm not 100% on SimpleXML sorry, but I know DomDocument can do what you are after. Hopefully this can be of use to you:
$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>
<offer>
<products>
<product>
<description>
<name xml:lang="eng">English translation</name>
<name xml:lang="lat">Latvian translation</name>
</description>
</product>
<product>
<description>
<name xml:lang="eng">The same English</name>
<name xml:lang="pol">And Polish language</name>
</description>
</product>
</products>
</offer>';
$dom = new DOMDocument();
$dom->loadXML($xmlstring); //or $dom->load('filename.xml');
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//products/product/description/name');
foreach ($nodes as $node) {
echo 'Language: ' . $node->getAttribute('xml:lang') . '<br />';
echo 'Value: ' . $node->nodeValue . '<br /><br />';
}
You can assign $node->getAttribute('xml:lang') to a variable and run some checks to see if it matches 'eng' or whatever you need.
I used xpath as you had in your original post, but you can also use $dom->getElementsByTagName('name') and access values and attributes in much the same way.
I found easier way to access namespaced attributes. You could use $name->attributes("xml", true) function.
Here is working example:
<?php
$xmlString = '
<products>
<product>
<name xml:lang="eng">
Apples
</name>
<name xml:lang="fr">
Pommes
</name>
</product>
<product>
<name xml:lang="eng">
Strawberries
</name>
<name xml:lang="fr">
Fraises
</name>
</product>
</products>
';
$xml = new \SimpleXMLElement($xmlString);
foreach($xml->product as $product)
{
foreach ($product->name as $name)
{
$attributes = $name->attributes("xml", true);
// print_r($attributes);
foreach ($attributes as $attributeName => $attributeValue)
{
// echo $attributeName . PHP_EOL;
// echo $attributeValue . PHP_EOL;
if ($attributeValue == "eng" && $attributeName == "lang") {
echo "English: " . trim(((string) $name)) . PHP_EOL;
}
if ($attributeValue == "fr" && $attributeName == "lang") {
echo "French: " . trim(((string) $name)) . PHP_EOL;
}
}
}
}
Online demo: https://repl.it/repls/LovingFineDaemon
<?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>