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

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>

Related

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();

HTML DOM Merge paged XML file into single file with a loop

Hey I'm working on an import for a list of elements. The code works for now, but it is not futureproof if there are more items added. The XML uses an unique key and pagination (every 100 items a new key).
Below is my PHP code for the function I've build.
<?php
$feedUrl = '[url of the feed]';
$doc1 = new DOMDocument();
$doc1->load($feedUrl);
$doc1_token = $doc1->getElementsByTagName('resumptionToken')[0]->nodeValue;
$doc2 = new DOMDocument();
$doc2->load($feedUrl . '&resumptionToken=' . $doc1_token);
$doc2_token = $doc2->getElementsByTagName('resumptionToken')[0]->nodeValue;
$doc3 = new DOMDocument();
$doc3->load($feedUrl . '&resumptionToken=' . $doc2_token);
$doc3_token = $doc3->getElementsByTagName('resumptionToken')[0]->nodeValue;
$doc4 = new DOMDocument();
$doc4->load($feedUrl . '&resumptionToken=' . $doc3_token);
$doc4_token = $doc4->getElementsByTagName('resumptionToken')[0]->nodeValue;
$doc5 = new DOMDocument();
$doc5->load($feedUrl . '&resumptionToken=' . $doc4_token);
$doc5_token = $doc5->getElementsByTagName('resumptionToken')[0]->nodeValue;
// get 'ListRecordes' element of document 1
$list_records = $doc1->getElementsByTagName('ListRecords')->item(0); //edited res - items
// iterate over 'item' elements of document 2
$items2 = $doc2->getElementsByTagName('record');
for ($i = 0; $i < $items2->length; $i ++) {
$item2 = $items2->item($i);
// import/copy item from document 2 to document 1
$item1 = $doc1->importNode($item2, true);
// append imported item to document 1 'res' element
$list_records->appendChild($item1);
}
// iterate over 'item' elements of document 3
$items3 = $doc3->getElementsByTagName('record');
for ($i = 0; $i < $items3->length; $i ++) {
$item3 = $items3->item($i);
// import/copy item from document 3 to document 1
$item1 = $doc1->importNode($item3, true);
// append imported item to document 1 'res' element
$list_records->appendChild($item1);
}
// iterate over 'item' elements of document 4
$items4 = $doc4->getElementsByTagName('record');
for ($i = 0; $i < $items4->length; $i ++) {
$item4 = $items4->item($i);
// import/copy item from document 4 to document 1
$item1 = $doc1->importNode($item4, true);
// append imported item to document 1 'res' element
$list_records->appendChild($item1);
}
// iterate over 'item' elements of document 5
$items5 = $doc5->getElementsByTagName('record');
for ($i = 0; $i < $items5->length; $i ++) {
$item5 = $$items5->item($i);
// import/copy item from document 5 to document 1
$item1 = $doc1->importNode($item5, true);
// append imported item to document 1 'res' element
$list_records->appendChild($item1);
}
$doc1->save('merged.xml'); //edited -added saving into xml file
I think the code is not perfect, because if the we add more records than 600, the latest one's are not imported in the merged xml.
Besides this there is also an other issue. We have nested "" nodes. We need to merge the "" direct childs only.
<ListRecords>
<record>
<header>
...
</header>
<metadata>
<record xmlns="http://www.openarchives.org/OAI/2.0/" priref="100000002">
...
</record>
</metadata>
</record>
</ListRecords>
You can use Xpath expressions to address specific elements. On your snippet that would be /ListRecords/record. However I think it misses the document element node with the namespace declaration for the Open Archives Initiative Protocol for Metadata Harvesting. It should be something like:
<OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/">
<ListRecords>
<record>
<header>
<identifier>oai:arXiv.org:hep-th/9901001</identifier>
</header>
</record>
</ListRecords>
</OAI-PMH>
To address the namespace with Xpath you need to register a prefix for it. Then put the feed urls in an array and iterate them:
$mergeDocument = new DOMDocument();
$mergeDocument->loadXML(
'<OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/"><ListRecords/></OAI-PMH>'
);
$mergeTarget = $mergeDocument->documentElement->firstChild;
foreach ($feedUrls as $feedUrl) {
$document = new DOMDocument();
$document->load($feedUrl);
$xpath = new DOMXpath($document);
$xpath->registerNamespace('oai', 'http://www.openarchives.org/OAI/2.0/');
foreach ($xpath->evaluate('/oai:OAI-PMH/oai:ListRecords/oai:record') as $record) {
$mergeTarget->appendChild($mergeDocument->importNode($record, TRUE));
}
}
$mergeDocument->formatOutput = TRUE;
echo $mergeDocument->saveXML();

What is the difference in PHP between DOM nodes and XMLreader->expand() Nodes?

I've rewritten a script that used the PHP DOM functions to iterate through an XML file with a structure like this:
<file>
<record>
<Source>
<SourcePlace>
<Country>Germany</Country>
</SourcePlace>
</Source>
<Person>
<Name>
<firstname>John</firstname>
<lastname>Doe<lastname>
</Name>
</Person>
</record>
<record>
..
</record>
</file>
I've replaced it with a script that uses XMLreader to find each separate record and turn that into a DOMdocument after which it is iterated through. Iteration was done by checking if the node had a child:
function findLeaves($node) {
echo "nodeType: ".$node->nodeType.", nodeName:". $node->nodeName."\n";
if($node->hasChildNodes() ) {
foreach($node->childNodes as $element) {
findLeaves($element)
}
}
ELSE { <do something with leave> }
}
The problem is that the behaviour of the findLeaves() function has changed between the two. Under DOM a node without a value (like Source) had no #text childnodes. Output of above would be:
nodeType:1, nodeName:Source
nodeType:1, nodeName:SourcePlace
nodeType:1, nodeName:Country
nodeType:3, nodeName:#text ```
Under XMLreader this becomes:
nodeType: 1, nodeName:Source
nodeType: 3, nodeName:#text
nodeType: 1, nodeName:SourcePlace
nodeType: 3, nodeName:#text
nodeType: 1, nodeName:Country
I've checked the saveXML() result of the data before entering this function but it seems identical, barring some extra spaces. What could be the reason for the difference?
Code loading the file before the findleaves() function under DOM:
$xmlDoc = new DOMDocument();
$xmlDoc->preserveWhiteSpace = false;
$xmlDoc->load($file);
$xpath = new DOMXPath($xmlDoc);
$records = $xpath->query('//record');
foreach($records as $record) {
foreach ($xpath->query('.//Source', $record) as $source_record) {
findleaves($source_record);
}
}
Code loading the file before the findleaves() function under XMLreader:
$xmlDoc = new XMLReader()
$xmlDoc->open($file)
while ($xmlDoc->read() ) {
if ($xmlDoc->nodeType == XMLReader::ELEMENT && $xmlDoc->name == 'record') {
$record_node = $xmlDoc->expand();
$recordDOM = new DomDocument();
$n = $recordDOM->importNode($record_node,true);
$recordDOM->appendChild($n);document
$recordDOM->preserveWhiteSpace = false;
$xpath = new DOMXPath($recordDOM);
$records = $xpath->query('//record');
foreach($records as $record) {
foreach ($xpath->query('.//Source', $record) as $source_record) {
findleaves($source_record);
}
}
The property DOMDocument::$preserveWhiteSpace affects the load/parse functions. So if you use XMLReader::expand() the property of the document has no effect - you do not load a XML string into it.
You're using Xpath already. .//*[not(*) and normalize-space(.) !== ""] will select element nodes without element children and without any text content (expect white spaces).
Here is an example (including other optimizations):
$xml = <<<'XML'
<file>
<record>
<Source>
<SourcePlace>
<Country>Germany</Country>
</SourcePlace>
</Source>
<Person>
<Name>
<firstname>John</firstname>
<lastname>Doe</lastname>
</Name>
</Person>
</record>
</file>
XML;
$reader = new XMLReader();
$reader->open('data://text/plain;base64,'.base64_encode($xml));
$document = new DOMDocument();
$xpath = new DOMXpath($document);
// find first record
while ($reader->read() && $reader->localName !== 'record') {
continue;
}
while ($reader->localName === 'record') {
// expand node into prepared document
$record = $reader->expand($document);
// match elements without child elements and empty text content
// ignore text nodes with only white space
$expression = './Source//*[not(*) and normalize-space() != ""]';
foreach ($xpath->evaluate($expression, $record) as $leaf) {
var_dump($leaf->localName, $leaf->textContent);
}
// move to the next record sibling
$reader->next('record');
}
$reader->close();
Output:
string(7) "Country"
string(7) "Germany"

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

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);
}

How to add spaces between values in xml?

I have to create an xml out of other xml. I already have the answer to that, but then I'm facing another problem. The output of the xml is quite messy in the tag.
The xml is this:
<rss>
<item id="12907">
<g:productname>Black Bag</g:productname>
<g:detailed_images>
<g:detailed_image>Image1.jpg</g:detailed_image>
<g:detailed_image>Image2.jpg</g:detailed_image>
<g:detailed_image>Image3.jpg</g:detailed_image>
<g:detailed_image>Image4.jpg</g:detailed_image>
<g:detailed_image>Image5.jpg</g:detailed_image>
<g:detailed_image>Image6.jpg</g:detailed_image>
<g:detailed_image>Image7.jpg</g:detailed_image>
<g:detailed_image>Image8.jpg</g:detailed_image>
<g:detailed_image>Image9.jpg</g:detailed_image>
<g:detailed_image>Image10.jpg</g:detailed_image>
<g:detailed_image>Image11.jpg</g:detailed_image>
<g:detailed_image>Image12.jpg</g:detailed_image>
</g:detailed_images>
</item>
<item id="12906">
<g:productname>Yellow Bag</g:productname>
<g:detailed_images>
<g:detailed_image>Image1.jpg</g:detailed_image>
<g:detailed_image>Image2.jpg</g:detailed_image>
<g:detailed_image>Image3.jpg</g:detailed_image>
<g:detailed_image>Image4.jpg</g:detailed_image>
<g:detailed_image>Image5.jpg</g:detailed_image>
<g:detailed_image>Image6.jpg</g:detailed_image>
<g:detailed_image>Image7.jpg</g:detailed_image>
<g:detailed_image>Image8.jpg</g:detailed_image>
<g:detailed_image>Image9.jpg</g:detailed_image>
<g:detailed_image>Image10.jpg</g:detailed_image>
<g:detailed_image>Image11.jpg</g:detailed_image>
<g:detailed_image>Image12.jpg</g:detailed_image>
</g:detailed_images>
</item>
</rss>
The php code that I'm using to create another xml file is this
<?php
$document = new DOMDocument;
$document->formatOutput = true;
$document->preserveWhiteSpace = false;
$document->load('xml_feeds.xml');
$xpath = new DOMXPath($document);
$fields = [
'productname' => 'string(g:productname)',
'detailed_images' => 'string(g:detailed_images)'
];
$xml = new DOMDocument;
$xml->formatOutput = true;
$xml->preserveWhiteSpace = false;
$rss = $xml->appendChild($xml->createElement('rss'));
foreach ($xpath->evaluate('//item') as $item) {
//create tag item
$createItem = $rss->appendChild($xml->createElement('item'));
//getting item's attribute value
$valueID = $item->getAttribute('id');
//create attribute
$itemAttribute = $xml->createAttribute('id');
$itemAttribute->value = $valueID;
$createItem->appendChild($itemAttribute);
foreach ($fields as $caption => $expression) {
$value = $xpath->evaluate($expression, $item);
$createItem->appendChild($xml->createElement($caption, $value));
}
}
$xml->save('new_createxml2.xml');
?>
The result of the new_createxml2.xml is this
<?xml version="1.0"?>
<rss>
<item id="12907">
<productname>Black Bag</productname>
<detailed_images>Image1.jpgImage2.jpgImage3.jpgImage4.jpgImage3.jpgImage4.jpgImage5.jpgImage6.jpgImage7.jpgImage8.jpgImage9.jpgImage10.jpgImage11.jpgImage12.jpg</detailed_images>
</item>
<item id="12906">
<productname>Yellow Bag</productname>
<detailed_images>Image1.jpgImage2.jpgImage3.jpgImage4.jpgImage3.jpgImage4.jpgImage5.jpgImage6.jpgImage7.jpgImage8.jpgImage9.jpgImage10.jpgImage11.jpgImage12.jpg</detailed_images>
</item>
</rss>
I really wonder how can I create the xml tidier than what I've made. I want it to display like this actually:
<?xml version="1.0"?>
<rss>
<item id="12907">
<productname>Black Bag</productname>
<detailed_images>Image1.jpg, Image2.jpg, Image3.jpg, Image4.jpg, Image3.jpg, Image4.jpg, Image5.jpg, Image6.jpg, Image7.jpg, Image8.jpg, Image9.jpg, Image10.jpg, Image11.jpg, Image12.jpg</detailed_images>
</item>
<item id="12906">
<productname>Yellow Bag</productname>
<detailed_images>Image1.jpg, Image2.jpg, Image3.jpg, Image4.jpg, Image3.jpg, Image4.jpg, Image5.jpg, Image6.jpg, Image7.jpg, Image8.jpg, Image9.jpg, Image10.jpg, Image11.jpg, Image12.jpg</detailed_images>
</item>
</rss>
Thank you for your help
The way you've tried it is quite simple for some things, but as you've found out that this method fails when you want processing for each individual piece of data. When you use 'string(g:detailed_images)' - this is the text content of all of the subnodes in your document, which is why you have all of the values stuck together. You could then process this string with some form of regular expression - but as you have no control of the content it's difficult to know what you are going to find.
Changing it to a more traditional - get values using a specific XPath call and processing the values allows you more control over the result.
foreach ($xpath->evaluate('//item') as $item) {
//create tag item
$createItem = $rss->appendChild($xml->createElement('item'));
//getting item's attribute value
$valueID = $item->getAttribute('id');
//create attribute
$itemAttribute = $xml->createAttribute('id');
$itemAttribute->value = $valueID;
$createItem->appendChild($itemAttribute);
$prodName = $xpath->evaluate("string(g:productname)", $item);
$createItem->appendChild($xml->createElement('productname', $prodName));
$images = [];
foreach ( $xpath->query("descendant::g:detailed_image", $item) as $image ) {
$images[] = $image->nodeValue;
}
$createItem->appendChild($xml->createElement('detailed_images',
implode(",", $images)));
}

Categories