XML to CSV with PHP converter [problem with images grabing] - php

I really need your help who works with XML and PHP. Looked for many other questions, but still nothing was found about my situation when in xml there is deeper fields and I can't grab them to csv output (code below).
<product>
<images>
<image>...</image>
<image>...</image>
</images>
</product>
My XML file looks like this:
<root>
<product>
<url>
<![CDATA[
https://
]]>
</url>
<id>185</id>
<barcode>284</barcode>
<categories>
<category>14</category>
<category>2</category>
</categories>
<title>
<![CDATA[ Product1 ]]>
</title>
<description>
<![CDATA[
<p>description</p>
]]>
</description>
<price>10</price>
<sec_costs>13.000000</sec_costs>
<quantity>10</quantity>
<warranty/>
<weight>0.000000</weight>
<delivery_text>
<![CDATA[ 1 - 2 d. ]]>
</delivery_text>
<manufacturer>
<![CDATA[ ]]>
</manufacturer>
<images>
<image>
<![CDATA[
https://test.eu/r.jpg
]]>
</image>
<image>
<![CDATA[
https://test.eu/er.jpg
]]>
</image>
<image>
<![CDATA[
https://test.eu/eer.jpg
]]>
</image>
</images>
<product_with_gift>
<![CDATA[ False ]]>
</product_with_gift>
<barcode_format>
<![CDATA[ EAN ]]>
</barcode_format>
</product>
I am using this code to convert it from XML to CSV (used it from other member), the problem is the code works fine, but it doesn't grab images (tried replacing image with images, added extra images columns, but nothing worked out, it just doesn't grab links to image files:
<?
$filexml = 'imp2.xml';
$xml = simplexml_load_file($filexml);
$xml->registerXPathNamespace('g', 'http://base.google.com/ns/1.0');
if (file_exists($filexml)) {
$xml = simplexml_load_file($filexml);
$i = 1; // Position counter
$values = []; // PHP array
// Writing column headers
$columns = array('id', 'barcode', 'title', 'description', 'price', 'sec_costs', 'quantity', 'warranty', 'weight', 'delivery_text', 'manufacturer', 'image', 'product_with_gift', 'barcode_format');
$fs = fopen('csv.csv', 'w');
fputcsv($fs, $columns);
fclose($fs);
// Iterate through each <product> node
$node = $xml->xpath('//product');
foreach ($node as $n) {
// Iterate through each child of <item> node
foreach ($columns as $col) {
if (count($xml->xpath('//product['.$i.']/'.$col)) > 0) {
$values[] = trim($xml->xpath('//product['.$i.']/'.$col)[0]);
} else {
$values[] = '';
}
}
// Write to CSV files (appending to column headers)
$fs = fopen('csv.csv', 'a');
fputcsv($fs, $values);
fclose($fs);
$values = []; // Clean out array for next <item> (i.e., row)
$i++; // Move to next <item> (i.e., node position)
}
}
?>
Any solutions from mid, premium xml,php?

The problem is that you are trying to fetch a list of nodes using just the images tag as the start point, as the subnodes have their own content, they will not appear in the higher level nodes text.
I've made a few changes to the code, but also I now use the <image> element to fetch the data. This code doesn't assume it's just one node for each item, so when it uses the XPath, it always loops through all items and build them into a single string before adding them to the CSV.
$filexml = 'imp2.xml';
if (file_exists($filexml)) {
// Only open file once you know it exists
$xml = simplexml_load_file($filexml);
$i = 1; // Position counter
$values = []; // PHP array
// Writing column headers
$columns = array('id', 'barcode', 'title', 'description', 'price', 'sec_costs', 'quantity', 'warranty', 'weight', 'delivery_text', 'manufacturer', 'image', 'product_with_gift', 'barcode_format');
// Open output file at start
$fs = fopen('csv.csv', 'w');
fputcsv($fs, $columns);
// Iterate through each <product> node
$node = $xml->xpath('//product');
foreach ($node as $n) {
// Iterate through each child of <item> node
foreach ($columns as $col) {
// Use //'.$col so node doesn't have to be directly under product
$dataMatch = $xml->xpath('//product['.$i.']//'.$col);
if (count($dataMatch) > 0) {
// Build list of all matches
$newData = '';
foreach ( $dataMatch as $data) {
$newData .= trim((string)$data).",";
}
// Remove last comma before adding it in
$values[] = rtrim($newData, ",");
} else {
$values[] = '';
}
}
fputcsv($fs, $values);
$values = []; // Clean out array for next <item> (i.e., row)
$i++; // Move to next <item> (i.e., node position)
}
// Close file only at end
fclose($fs);
}

Related

PHP XML find "searching" node and create new node after "searching node"

i need some help from you :
loading XML from a file.
I'm also loading data from a CSV file.
I have 2 columns in CSv:
ID
Number
I'm looking for the ID from the CSV in the XML file.
I can find you, everything works.
However, I need to edit the XML as follows:
If you find an element in the XML whose ID is contains an ID value from a CSV file, copy this element as many times as the value is in the CSV (column number).
Here is my code.
<?php
$file = 'doc.xml';
if (!copy($file, $newfile)) {
echo "failed to copy";
}
$fh = fopen("data.csv", "r");
$csvData = array();
//Loop through the rows in our CSV file and add them to
//the PHP array that we created above.
while (($row = fgetcsv($fh, 0, ";")) !== FALSE) {
$csvData[] = $row;
}
$length = count($csvData);
if (file_exists('doc.xml')) {
$xml = simplexml_load_file('doc.xml');
for ($i=0; $i < $length; $i++) {
$searchedNode = $csvData[$i+1][0];
$searchingMedia = $xml->xpath("/node/media/image[contains(#id,'$searchedNode')]");
foreach ($searchingMedia as $node) {
$update = $node->addAttribute('count',$csvData[$i+1][1]);
}
}
}
$xml->asXml('doc_new.xml');
?>
CSV data :
Can someone help me please ?
SimplXML abstracts the XML nodes, so it is not the best API for direct node manipulations - use DOM.
You did not provide an example but from your code I would expect something like this:
$data = [
[
'42', '6'
]
];
$xml = <<<'XML'
<root>
<node>
<media>
<image id="42"/>
</media>
</node>
</root>
XML;
Then it is fairly straightforward:
// bootstrap the DOM
$document = new DOMDocument();
// let the parser ignore whitespace nodes (indents)
$document->preserveWhiteSpace = FALSE;
$document->loadXML($xml);
// DOM has a spearate object for Xpath
$xpath = new DOMXpath($document);
// iterate the CSV data
foreach ($data as $row) {
// looking for "image" elements with a specific id attribute
$expression = sprintf(
'//root/node/media/image[#id="%s"]',
$row[0]
);
// iterate the found image nodes
foreach ($xpath->evaluate($expression) as $imageNode) {
// "amount" times
for ($i = 0, $c = (int)$row[1]; $i < $c; $i++) {
// clone the "image" element and insert clone after it
$imageNode->after(
$newNode = $imageNode->cloneNode(TRUE)
);
// modify the clone
$newNode->textContent = 'Inserted Node #'.$i;
}
}
}
$document->formatOutput = TRUE;
echo $document->saveXML();
Output:
<?xml version="1.0"?>
<root>
<node>
<media>
<image id="42"/>
<image id="42">Inserted Node #5</image>
<image id="42">Inserted Node #4</image>
<image id="42">Inserted Node #3</image>
<image id="42">Inserted Node #2</image>
<image id="42">Inserted Node #1</image>
<image id="42">Inserted Node #0</image>
</media>
</node>
</root>

PHP XML Trying to add stock_quantity by item id into main feed

I would like merge two feeds the one has all product data and has an product identifier ITEM_ID in every , the second XML feed has same value as ITEM_ID in <item id=""> and inside this <item> has stock_quantity tag but I can't figure it out how to merge these values.. The three dots in XML content means that there are more item tags
The first feed (items.xml) looks like:
<SHOP>
<SHOPITEM>
<DESCRIPTION>
<![CDATA[ <p><span>Just an description. </span></p> ]]>
</DESCRIPTION>
<URL>https://www.korkmaz.cz/tombik-cajova-konvice-2l/</URL>
<IMGURL>https://cdn.myshoptet.com/usr/www.korkmaz.cz/user/shop/orig/52_konvice-tombik-1l.jpg?5f4fcd7d</IMGURL>
<IMGURL_ALTERNATIVE>https://cdn.myshoptet.com/usr/www.korkmaz.cz/user/shop/orig/52-1_bez-trouby.jpg?5f4fcd7d</IMGURL_ALTERNATIVE>
<PURCHASE_PRICE>487,99</PURCHASE_PRICE>
<PRICE_VAT>797,00</PRICE_VAT>
<VAT>21%</VAT>
<CATEGORYTEXT>KUCHYŇSKÉ DOPLŇKY | Příprava čaje a kávy</CATEGORYTEXT>
<DELIVERY_DATE>0</DELIVERY_DATE>
<ITEM_ID>A093</ITEM_ID>
...
</SHOPITEM>
</SHOP>
The second feed (stock.xml) loks like:
<item_list>
<item id="A093">
<delivery_time orderDeadline="2021-09-14 12:00">2021-09-16 12:00</delivery_time>
<stock_quantity>32</stock_quantity>
...
</item>
</item_list>
So I trying something like this (similar method like the $item->ITEM_ID was in separate tag in stock.xml) but doesn't work for me..
<?php
$catalog_name = 'items.xml';
$catalog_url = 'https://admin.srovnej-ceny.cz/export/ca1b20bb6415b2d93ff36c9e3df3f96c.xml';
file_put_contents($catalog_name, fopen($catalog_url, 'r'));
$stock_name = 'stock.xml';
$stock_url = 'https://www.korkmaz.cz/heureka/export/availability.xml';
file_put_contents($stock_name, fopen($stock_url, 'r'));
$stocks=simplexml_load_file("stock.xml") or die("Error: Cannot create object");
foreach($stocks->children() as $item) {
$_stocks["" . $item['id'] . ""] = $item->stock_quantity;
}
$xml=simplexml_load_file("items.xml") or die("Error: Cannot create object");
$dom = new DOMDocument();
$dom->encoding = 'utf-8';
$dom->xmlVersion = '1.0';
$dom->formatOutput = true;
$xml_file_name = 'products.xml';
$root = $dom->createElement('SHOP');
$i=0;
foreach($xml->children() as $item) {
$item_node = $dom->createElement('SHOPITEM');
//$track = $xml->addChild('item');
$item_node->appendChild($dom->createElement('ITEM_ID', $item->ITEM_ID ));
$item_node->appendChild($dom->createElement('PRODUCTNAME', htmlspecialchars($item->PRODUCTNAME) ));
$item_node->appendChild($dom->createElement('DESCRIPTION', htmlspecialchars($item->DESCRIPTION)));
$item_node->appendChild($dom->createElement('MANUFACTURER', $item->MANUFACTURER));
$item_node->appendChild($dom->createElement('EAN', strval($item->EAN) ));
$item_node->appendChild($dom->createElement('IMGURL', strval($item->IMGURL)));
$item_node->appendChild($dom->createElement('PRICE_VAT', strval($item->PRICE_VAT)));
$item_node->appendChild( $dom->createElement('STOCK', $_stocks["" . $item['id'] . ""] ) );
$root->appendChild($item_node);
$i++;
}
$dom->appendChild($root);
$dom->save($xml_file_name);
echo "$i items to $xml_file_name has been successfully created";
?>
Without simplexml you can quite easily "merge" the two documents using the standard DOMDocument and DOMXPath functions.
Given input files as follows:
items.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<SHOP>
<SHOPITEM>
<DESCRIPTION>
<![CDATA[ <p><span>Just an description. </span></p> ]]>
</DESCRIPTION>
<URL>https://www.korkmaz.cz/tombik-cajova-konvice-2l/</URL>
<IMGURL>https://cdn.myshoptet.com/usr/www.korkmaz.cz/user/shop/orig/52_konvice-tombik-1l.jpg?5f4fcd7d</IMGURL>
<IMGURL_ALTERNATIVE>https://cdn.myshoptet.com/usr/www.korkmaz.cz/user/shop/orig/52-1_bez-trouby.jpg?5f4fcd7d</IMGURL_ALTERNATIVE>
<PURCHASE_PRICE>487,99</PURCHASE_PRICE>
<PRICE_VAT>797,00</PRICE_VAT>
<VAT>21%</VAT>
<CATEGORYTEXT>KUCHYŇSKÉ DOPLŇKY | Příprava čaje a kávy</CATEGORYTEXT>
<DELIVERY_DATE>0</DELIVERY_DATE>
<ITEM_ID>A093</ITEM_ID>
</SHOPITEM>
<SHOPITEM>
<DESCRIPTION>
<![CDATA[ <p><span>Just an description. </span></p> ]]>
</DESCRIPTION>
<URL>https://www.korkmaz.cz/tombik-cajova-konvice-2l/</URL>
<IMGURL>https://cdn.myshoptet.com/usr/www.korkmaz.cz/user/shop/orig/52_konvice-tombik-1l.jpg?5f4fcd7d</IMGURL>
<IMGURL_ALTERNATIVE>https://cdn.myshoptet.com/usr/www.korkmaz.cz/user/shop/orig/52-1_bez-trouby.jpg?5f4fcd7d</IMGURL_ALTERNATIVE>
<PURCHASE_PRICE>1850,99</PURCHASE_PRICE>
<PRICE_VAT>2598,00</PRICE_VAT>
<VAT>21%</VAT>
<CATEGORYTEXT>KUCHYŇSKÉ DOPLŇKY | Příprava čaje a kávy</CATEGORYTEXT>
<DELIVERY_DATE>0</DELIVERY_DATE>
<ITEM_ID>A094</ITEM_ID>
</SHOPITEM>
<SHOPITEM>
<DESCRIPTION>
<![CDATA[ <p><span>Just an description. </span></p> ]]>
</DESCRIPTION>
<URL>https://www.korkmaz.cz/tombik-cajova-konvice-2l/</URL>
<IMGURL>https://cdn.myshoptet.com/usr/www.korkmaz.cz/user/shop/orig/52_konvice-tombik-1l.jpg?5f4fcd7d</IMGURL>
<IMGURL_ALTERNATIVE>https://cdn.myshoptet.com/usr/www.korkmaz.cz/user/shop/orig/52-1_bez-trouby.jpg?5f4fcd7d</IMGURL_ALTERNATIVE>
<PURCHASE_PRICE>200,99</PURCHASE_PRICE>
<PRICE_VAT>300,00</PRICE_VAT>
<VAT>21%</VAT>
<CATEGORYTEXT>KUCHYŇSKÉ DOPLŇKY | Příprava čaje a kávy</CATEGORYTEXT>
<DELIVERY_DATE>0</DELIVERY_DATE>
<ITEM_ID>A095</ITEM_ID>
</SHOPITEM>
stock.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<item_list>
<item id="A093">
<delivery_time orderDeadline="2021-09-14 12:00">2021-09-16 12:00</delivery_time>
<stock_quantity>32</stock_quantity>
</item>
<item id="A094">
<delivery_time orderDeadline="2021-09-14 12:00">2021-09-16 12:00</delivery_time>
<stock_quantity>8366</stock_quantity>
</item>
<item id="A095">
<delivery_time orderDeadline="2021-09-14 12:00">2021-09-16 12:00</delivery_time>
<stock_quantity>6732</stock_quantity>
</item>
</item_list>
To generate the "combined xml" type output based upon matching item IDS:
<?php
/*
#
# XML file merge
#
Read "stock.xml" and find matching elements in "items.xml"
- update "items.xml" with nodes cloned from "stock.xml"
*/
function getdom($file){
libxml_use_internal_errors( true );
$dom=new DOMDocument;
$dom->validateOnParse=true;
$dom->recover=true;
$dom->strictErrorChecking=true;
$dom->preserveWhiteSpace=true;
$dom->formatOutput=true;
$dom->load($file);
libxml_clear_errors();
return $dom;
}
$items=getdom('items.xml');
$xpi=new DOMXPath($items);
$stock=getdom('stock.xml');
$xps=new DOMXPath($stock);
/*
If ALL nodes from "stock.xml" are to be merged per ID then set `$merge_only_selected=false`
`$merge_nodes` is an array of nodes from "stock.xml" that will be merged if `$merge_only_selected` is true
*/
$merge_only_selected=true;
$merge_nodes=array('stock_quantity','stock_supplier');
#Find all items in the "stock.xml" file to get the item ID
$col=$xps->query( '//item[#id]' );
foreach( $col as $node ){
#The ID from the "item"
$id=$node->getAttribute('id');
# nodelist of items within "items.xml" that have the same ID.
$item=$xpi->query( sprintf( '//SHOPITEM/ITEM_ID[ text()="%s" ]', $id ) );
# only proceed if we have found a matching node in "items.xml"
if( $item && $item->length > 0 ){
# Find the matched element
$obj=$item->item(0);
# Find the children from the "item"
$children=$node->childNodes;
# for each child found, clone it and import to the "items.xml" file
foreach( $children as $child ){
if( $child->nodeType==XML_ELEMENT_NODE && $id==$obj->nodeValue ){
if( $merge_only_selected==true && !in_array( $child->tagName, $merge_nodes ) ){
continue;
}
$clone=$child->cloneNode(true);
$obj->parentNode->appendChild( $items->importNode( $clone, true ) );
}
}
}
}
#To actually save the modified "items.xml" file:
#$items->save('items.xml');
#To simply view the changes:
printf('<textarea cols=150 rows=50>%s</textarea>',$items->saveXML() );
?>
This is a lot easier with DOM+Xpath. You can use DOMXpath::evaluate() to fetch node lists and scalar values from the XML.
An Xpath expression like /SHOP/SHOP_ITEM returns a DOMNodeList which implements Traversable to support foreach().
But Xpath expression can return scalar values as well. A boolean if they are a condition or a string/number if they contain a type cast or function call. string(/item_list/item[#id="A093"]/stock_quantity) will return the text content of the first matching node or an empty string.
DOMDocument::importNode() allows you to copy a node from another document. But in this case I would suggest creating a new node with a name matching the existing elements.
// bootstrap the XML
$shopDocument = new DOMDocument();
// ignoring pure whitespace nodes (indentation)
$shopDocument->preserveWhiteSpace = FALSE;
$shopDocument->loadXML(getShopXML());
$shopXpath = new DOMXpath($shopDocument);
$stocksDocument = new DOMDocument();
$stocksDocument->loadXML(getStocksXML());
$stocksXpath = new DOMXpath($stocksDocument);
// iterate the shop items
foreach ($shopXpath->evaluate('/SHOP/SHOPITEM') as $shopItem) {
// get the item ID
$itemID = $shopXpath->evaluate('string(ITEM_ID)', $shopItem);
$stockQuantity = 0;
if ($itemID !== '') {
// fetch the stock quantity using the item id
$stockQuantity = (int)$stocksXpath->evaluate(
"string(/item_list/item[#id = '$itemID']/stock_quantity)"
);
// check if here is a "STOCK_QUANTITY" element in the item
if ($shopXpath->evaluate('count(STOCK_QUANTITY) > 0', $shopItem)) {
// update it
foreach ($shopXpath->evaluate('STOCK_QUANTITY', $shopItem) as $quantity) {
$quantity->textContent = (string)$stockQuantity;
}
} else {
// add one
$shopItem
->appendChild($shopDocument->createElement('STOCK_QUANTITY'))
->textContent = (string)$stockQuantity;
}
}
}
$shopDocument->formatOutput = TRUE;
echo $shopDocument->saveXML();

create one in xml with php multiple inputs with same name

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

PHP DOM XML to arrray conversion

I have an xml document below:
<?xml version="1.0" encoding="UTF-8" ?>
<books>
<book>
<name>Title One</name>
<year>2014</year>
<authors>
<author>
<name>Author One</name>
</author>
</authors>
</book>
<book serie="yes">
<name>Title Two</name>
<year>2015</year>
<authors>
<author>
<name>Author two</name>
</author>
<author>
<name>Author three</name>
</author>
</authors>
</book>
<book serie="no">
<name>Title Three</name>
<year>2015</year>
<authors>
<author>
<name>Author four</name>
</author>
</authors>
</book>
</books>
I want to convert it into an array bellow.
array(
array('Tittle one', 2014, 'Author One'),
array('Tittle two', 2015, 'Author two, Author three'),
array('Tittle three', 2015, 'Author four'),
);
My code below is failing to produce the array structure that I would want:
function arrayRepresentation(){
$xmldoc = new DOMDocument();
$xmldoc->load("data/data.xml");
$parentArray = array();
foreach ($xmldoc->getElementsByTagName('book') as $item) {
$parentArray[] = array_generate($item);
}
var_dump($parentArray);
}
function array_generate($item){
$movieArray = array();
$childMovieArray = array();
for ($i = 0; $i < $item->childNodes->length; ++$i) {
$child = $item->childNodes->item($i);
if ($child->nodeType == XML_ELEMENT_NODE) {
if(hasChild($child)){
$childMovieArray = array_generate($child);
}
}
$movieArray[] = trim($child->nodeValue);
}
if(!empty($childMovieArray)){
$movieArray = array_merge($movieArray,$childMovieArray);
}
return $movieArray;
}
function hasChild($p)
{
if ($p->hasChildNodes()) {
foreach ($p->childNodes as $c) {
if ($c->nodeType == XML_ELEMENT_NODE)
return true;
}
}
}
arrayRepresentation();
Basically I am looping through the the nodes to get xml element values. I am then checking if I Node has more child nodes and if it has I loop through it again to get the values. I am failling to deduce a way that: (i) will not give me some empty array elements (ii) Check for any child nodes and put all the xml sibling elements in a single string
PHPs DOM supports Xpath expressions for fetching specific nodes and values. That drastically reduces the amount of loops and conditions you will need.
Here is a demo:
// bootstrap the XML document
$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);
$data = [];
// iterate the node element nodes
foreach ($xpath->evaluate('/books/book') as $book) {
$authors = array_map(
fn ($node) => $node->textContent,
// fetch author name nodes as an array
iterator_to_array($xpath->evaluate('authors/author/name', $book))
);
$data[] = [
// cast first name element child to string
$xpath->evaluate('string(name)', $book),
// cast first year element child to string
$xpath->evaluate('string(year)', $book),
implode(', ', $authors)
];
}
var_dump($data);

Converting XML to CSV using PHP

I need to convert an XML file to CSV.
I have a script but I am unsure of how to use it to my needs.
Here is the script
$filexml='141.xml';
if (file_exists($filexml)) {
$xml = simplexml_load_file($filexml);
$f = fopen('141.csv', 'w');
foreach ($xml->item as $item) {
fputcsv($f, get_object_vars($item),',','"');
}
fclose($f);
}
The file is called 141.xml and here is some of the code in the XML which I need to convert.
<?xml version="1.0" encoding="UTF-8" ?>
<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0">
<channel>
<item>
<title><![CDATA[//title name]]></title>
<link><![CDATA[https://www.someurl.co.uk]]></link>
<description><![CDATA[<p><span>Demo Description</span></p>]]></description>
<g:id><![CDATA[4796]]></g:id>
<g:condition><![CDATA[new]]></g:condition>
<g:price><![CDATA[0.89 GBP]]></g:price>
<g:availability><![CDATA[in stock]]></g:availability>
<g:image_link><![CDATA[https://image-location.png]]></g:image_link>
<g:service><![CDATA[Free Shipping]]></g:service>
<g:price><![CDATA[0 GBP]]></g:price>
</item>
I am running the script from SSH using:
php /var/www/vhosts/mywebsite.co.uk/httpdocs/xml/convert.php
If you can help me, it would be really appreciated :)
Thanks
Consider passing XML data into an array $values and exporting array by row to csv.
Specifically, using the xpath() function for the XML extraction, iterate through each <item> and extract all its children's values (/*). By the way, I add headers in the CSV file.
$filexml='141.xml';
if (file_exists($filexml)) {
$xml = simplexml_load_file($filexml);
$i = 1; // Position counter
$values = []; // PHP array
// Writing column headers
$columns = array('title', 'link', 'description', 'id', 'condition',
'price', 'availability', 'image_link', 'service', 'price');
$fs = fopen('141.csv', 'w');
fputcsv($fs, $columns);
fclose($fs);
// Iterate through each <item> node
$node = $xml->xpath('//item');
foreach ($node as $n) {
// Iterate through each child of <item> node
$child = $xml->xpath('//item['.$i.']/*');
foreach ($child as $value) {
$values[] = $value;
}
// Write to CSV files (appending to column headers)
$fs = fopen('141.csv', 'a');
fputcsv($fs, $values);
fclose($fs);
$values = []; // Clean out array for next <item> (i.e., row)
$i++; // Move to next <item> (i.e., node position)
}
}
Try out below code. And the XML file is having syntax error, the closing tag for rss and channel is missing.
$filexml='141.xml';
if (file_exists($filexml))
{
$xml = simplexml_load_file($filexml);
$f = fopen('141.csv', 'w');
createCsv($xml, $f);
fclose($f);
}
function createCsv($xml,$f)
{
foreach ($xml->children() as $item)
{
$hasChild = (count($item->children()) > 0)?true:false;
if( ! $hasChild)
{
$put_arr = array($item->getName(),$item);
fputcsv($f, $put_arr ,',','"');
}
else
{
createCsv($item, $f);
}
}
}

Categories