XMLWriter using with loop in php - php

i want to create multiple XML file by using php. i used following code which is working fine for single XML file generation but when i try to same code in loop for generation multiple XML file with different name its throw an error but file is save in destination driver can anyone help me to solve this issue
$fileName = date('YmdHis').rand('0000','999999')."output.xml";
working fine for single calling
xmlGenerater($pimcoArr ,$fileName);
error throw when i call it in loop
for($=0;$i<2;$i++){
xmlGenerater($pimcoArr ,$fileName);
}
error message screen
XML file genartion method
function xmlGenerater($data ,$fileName){
if(!empty($data)){
$writer = new XMLWriter();
//lets store our XML into the memory so we can output it later
$writer->openMemory();
//lets also set the indent so its a very clean and formatted XML
$writer->setIndent(true);
//now we need to define our Indent string,which is basically how many blank spaces we want to have for the indent
$writer->setIndentString(" ");
//Lets start our document,setting the version of the XML and also the encoding we gonna use
//XMLWriter->startDocument($string version, $string encoding);
$writer->startDocument("1.0", "UTF-8");
//lets start our main element,lets call it “ersvpresponse”
$writer->startElement('ersvpresponse');
$loop = 1;
foreach($data as $dataRow){
$pimco_id = $dataRow['pimco_id'];
$event_id = $dataRow['event_id'];
$contact_id = $dataRow['contact_id'];
$status = $dataRow['status'];
$writer->startElement("contact");
if($loop==1){
$dateTime = date('Y-m-d');
$writer->writeAttribute("updated",$dateTime);
}
//now we create pimcoeventid node
$writer->startElement("pimcoeventid");
$writer->text("$pimco_id");
$writer->endElement();
//now we create pimcocontactid node
$writer->startElement("pimcocontactid");
$writer->text("$contact_id");
$writer->endElement();
//now we create pimcocontactid node
$writer->startElement("ersvpstatus");
$writer->text("$status");
$writer->endElement();
$writer->endElement();
$loop++;
}
//Now lets close our main element
$writer->endElement();
//close our document
$writer->endDocument();
/*Lets output what we have so far,first we need to set a header so we can display the XML in the
browser,otherwise you will need to look at the source output. */
header('Content-type: text/xml');
//lets then echo our XML;
//echo $writer->outputMemory();
/* that is enough to display our dynamic XML in the browser,now if you wanna create a XML file we need to do this */
$filename = "exportFiles/$fileName";
//lets output the memory to our file variable,and we gonna put that variable inside the file we gonna create
$file = $writer->outputMemory();
//lets create our file
file_put_contents($filename,$file);
}
}

You are putting the documents in separate files on the server but you send them to the client as one single repsonse document, hence the
Line Number 14
<?xml ...
and that doesn't work, you can't just concatenate xml documents and expect the client-side parser to accept that.
But you could e.g. zip those files and send the archive.

Related

How to get specific values on an xml using php?

I'm creating a simple web app using html and php, this app reads an XML file provided by a user on a webpage and then prints some values content on it.
The files are sent to the server by POST and then are read by a php script.
This is a fragment of my xml test file:
<cfdi:Impuestos totalImpuestosTrasladados="72.07">
<cfdi:Traslados>
<cfdi:Traslado importe="50.00" impuesto="IEPS" tasa="16.00"/>
<cfdi:Traslado importe="22.07" impuesto="IVA" tasa="16.00"/>
</cfdi:Traslados>
</cfdi:Impuestos>
And this is my code made on php:
<?php
foreach ($_FILES['archivos']['name'] as $f => $name) {
$tempPath = $_FILES["archivos"]["tmp_name"][$f];
//Load xml file directly on the temp path
$xml = simplexml_load_file($tempPath, null, true);
$namespaces = $xml->getDocNamespaces();
//Creating and loading DOM object
$dom = new DOMDocument('1.0','utf-8');
$dom->load($tempPath);
//Getting IVAs' values
foreach ($xml->xpath('//cfdi:Comprobante//cfdi:Impuestos//cfdi:Traslados//cfdi:Traslado') as $impuestos){
$IVA = $impuestos['importe'];
echo("IVA = ".$IVA."<br>");
}
}
?>
That prints:
IVA = 50.00
IVA = 22.07
Print's screenshot
On this case I want to print only the IVA's value but it takes the IEPS' value too because IEPS is on the same path as IVA. These values are only on this part of the file and I don't know if there's a way to separate them.
I just want to select only the IVA's value. Would you help me please?:(
The complete XML file is: Here
Try:
foreach ($xml->xpath("//cfdi:Comprobante//cfdi:Impuestos//cfdi:Traslados//cfdi:Traslado[#impuesto='IVA']") as $impuestos){

PHP - Create multiple XML files from Mysql data

I'm pulling data from my mysql database and using it to create an XML file. the idea is for each row of data pulled, it be appended to its own XML file
Here's my sample code:
//sql statement here
$results = $fetch_data->fetchAll(PDO::FETCH_ASSOC);
foreach($results as $data)
{
$invoice_no = $data["invoice_no"];
$doc = new DOMDocument();
$doc->formatOutput = true;
$Order = $doc->appendChild($doc->createElement('Order'));
$OrderHead = $Order->appendChild($doc->createElement('OrderHead'));
$Schema = $doc->createElement('Schema');
$Schema->appendChild($doc->createElement('Version', '3.05'));
$OrderHead->appendChild($Schema);
$CrossReference = $doc->createElement('CrossReference', $invoice_no);
$OrderHead->appendChild($CrossReference);
//more xml code...
$doc->save($invoice_no.".xml", LIBXML_NOEMPTYTAG);
}
As shown above, i'm looping through the data from the database to create the XML file then want to save each file(s) prefixed by the $invoice_no. But only one XML file is created and to be more specific, only the last record pulled from the database is used to create the XML file. this is happening though i'm creating the file inside my loop
where could i be going wrong?

PHP script to convert csv to XML not working for large file (around 1GB)

We have written the following PHP script to convert CSV file to XML file. But It got stuck and didn't come out of the while loop to saveXML.
The size of the CSV file is around 1GB, The number of rows in the CSV file is around 1,00,000.
Due to the large number of rows, It is not working.
My question is: How can we modify this following code in such a way that, It works for a large file ?
<?php
$delimit = "," ;
$row_count = 0 ;
$inputFilename = "feed.csv" ;
$outputFilename = 'output.xml';
$inputFile = fopen($inputFilename, 'rt');
$headers = fgetcsv($inputFile);
$doc = new DomDocument();
$doc->formatOutput = true;
$root = $doc->createElement('rows');
$root = $doc->appendChild($root);
while (($row = fgetcsv($inputFile)) !== FALSE)
{
$container = $doc->createElement('row');
foreach ($headers as $i => $header)
{
$arr = explode($delimit, $header);
foreach ($arr as $j => $ar)
{
$child = $doc->createElement(preg_replace("/[^A-Za-z0-9]/","",$ar));
$child = $container->appendChild($child);
$whole = explode($delimit, $row[$i]);
$value = $doc->createTextNode(ltrim( rtrim($whole[$j], '"') ,'"'));
$value = $child->appendChild($value);
}
}
$root->appendChild($container);
echo "." ;
}
echo "Saving the XML now" ;
$result = $doc->saveXML();
echo "Writing to XML file now" ;
$handle = fopen($outputFilename, "w");
fwrite($handle, $result);
fclose($handle);
return $outputFilename;
?>
Edited:
In php.ini the memory_limit and execution time is set for unlimited & maximum. I am executing using command line.
as you noticed, you run into resource problems with such big in/output.
The input handling you use, fgetcsv() is already quite effective as it reads one line at a time.
The output is the problem in this case. You store the whole 1GB raw text into a DOMDocument Object, which adds considerable overhead to the needed memory.
But according to your code, you only write the xml back to a file, so you don't really need it as a DOMDocument at runtime.
The simplest solution would be to build the xml string as a string and write it to the output file for each line of the csv: open the handle for the outputfile with 'a' (fopen($outputfilename, "a");, write the xml header before the loop, fwrite every csv-to-xml-ified elment per loop run, write the xml footer after the loop
It's most probably the (mis)usage of the DomDocument that causes your memory issues (as already answered by #cypherabe).
But instead of the proposed string concatenation solution, I would urge you to take a look at the XmlWriter http://php.net/manual/en/book.xmlwriter.php
The XmlWriter extension represents a writer that provides a non-cached, forward-only means of generating streams or files containing XML data.
This extension can be used in an object oriented style or a procedural one.
It's already bundled with PHP from version 5.2.1
http://www.prestatraining.com/12-tips-to-optimise-your-php-ini-file-for-prestashop/
Look at the section Memory and Size Limits (ignore the fact it's about prestashop)
It sounds like your PHP settings on the server are timing out on execution. If you are trying to process a file that is 1GB I wouldn't be surprised if it fails if you have standard PHP.ini settings.

Format txt file

I need to import csv file with php getcsv function but the file is not well formated and cannot import it. After uploading the file, I'd like to delete all " inside the file to have a proper file with ; for each field.
This is an example of my file:
"DENO;NAME;SURNAME;""BIRTH"";""ZIP"";CITY;E-MAIL;TELEPHONE"
"M;DAVID;BON;""1959-02-12 00:00:00"";75009;PARIS;email#gmail.com;010000000"
"M;DOE;JHON;""1947-02-02 00:00:00"";75008;PARIS;email#gmail.com;060000000"
"M;DAVE;Philippe;""1950-01-01 00:00:00"";75002;""PARIS"";email#gmail.com;070000000"
I think I would need to read each line of the file,and maybe use str_replace but I don't know how to write the new file...
Assuming you can get your code into a text string, you can just replace/remove the double-quotes. Then parse the text and get it into a format that will support fputcsv().
When writing the folder, be sure to check that your user has permissions to write to this folder (for example if the parent folder has permissions drwxr-xr-x, you can fix it with chmod g+w folder_name).
<?php
// sample text to parse
$some_text = '"DENO;NAME;SURNAME;""BIRTH"";""ZIP"";CITY;E-MAIL;TELEPHONE"
"M;DAVID;BON;""1959-02-12 00:00:00"";75009;PARIS;email#gmail.com;010000000"
"M;DOE;JHON;""1947-02-02 00:00:00"";75008;PARIS;email#gmail.com;060000000"
"M;DAVE;Philippe;""1950-01-01 00:00:00"";75002;""PARIS"";email#gmail.com;070000000"';
// remove quotes
$final_text = str_replace('"', '', $some_text);
// create array of rows by searching for new lines
$data = str_getcsv($final_text, "\n");
// create an empty array to save our final csv data
$final_csv = array();
// loop thru the array and save to the final csv data
foreach ($data as $value) {
// before saving to final csv data, split row into individual column items
$value_array = explode(";", $value);
$final_csv[] = $value_array;
}
// helper debugger to show data before writing it
echo "<pre>";
print_r($final_csv);
echo "</pre>";
// create a new file for writing or open and truncate to 0
$fp = fopen('file.csv', 'w');
// write to file from final csv data
foreach ($final_csv as $fields) {
fputcsv($fp, $fields);
}
// close file
fclose($fp);
?>

Convert multiple XML files to one CSV with SimpleXML

I have some xml files, which have the same elements but only with different information.
First file test.xml
<?xml version="1.0" encoding="UTF-8"?>
<phones>
<phone>
<title>"Apple iPhone 5S"</title>
<price>
<regularprice>500</regularprice>
<saleprice>480</saleprice>
</price>
<color>black</color>
</phone>
</phones>
Second file test1.xml
<?xml version="1.0" encoding="UTF-8"?>
<phones>
<phone>
<title>Nokia Lumia 830</title>
<price>
<regularprice>400</regularprice>
<saleprice>370</saleprice>
</price>
<color>black</color>
</phone>
</phones>
I need to convert some values from these xml files into 1 test.csv file
So I am using this php code
<?php
$filexml1='test.xml';
$filexml2='test1.xml';
//File 1
if (file_exists($filexml1)) {
$xml = simplexml_load_file($filexml1);
$f = fopen('test.csv', 'w');
$headers = array('title', 'color');
$converted_array = array_map("strtoupper", $headers);
fputcsv($f, $converted_array, ',', '"');
foreach ($xml->phone as $phone) {
//$phone->title = trim($phone->title, " ");
// Array of just the components you need...
$values = array(
"title" => (string)$phone->title = trim(str_replace ( "\"", """, $phone->title ), " "),
"color" => (string)$phone->color
);
fputcsv($f, $values,',','"');
}
fclose($f);
echo "<p>File 1 coverted to .csv sucessfully</p>";
} else {
exit('Failed to open test.xml.');
}
//File 2
if (file_exists($filexml2)) {
$xml = simplexml_load_file($filexml2);
$f = fopen('test.csv', 'a');
//the same code for second file like for the first file
echo "<p>File 2 coverted to .csv sucessfully</p>";
} else {
exit('Failed to open test1.xml.');
}
?>
The output of the test.csv looks this way
TITLE COLOR
Apple iPhone 5S black
Nokia Lumia 830 black
As you can see I only managed to load each file into a variable and for each file I have to write if statement which makes the script too big, so I am wondering if it is possible to load all files into array, process them with one code block because xml elements are the same and output to one .csv file? Essentially I need the same test.csv output only with less php code.
Thanks in advance.
Next to using an array, there is more in PHP which can make it even more simple. Like an array could represent a list of your files, other constructs in PHP can that, too.
For example, as the XML files you have most likely are inside a specific directory and follow some pattern with their filename, those could be easily represented with a GlobIterator:
$inputFiles = new GlobIterator(__DIR__ . '/*.xml');
You could then foreach over them which I'll show in a moment with another example.
Such a list allows you to streamline your processing. That is important because there is some kind of a generic formular for many programs: Input, Process, Output. This is also called IPO or IPO+S Model. The S stands for storing. In your case while you process the input data, you also store into a new file CSV file which is also the output (after processing is fully done).
When you follow such a generic model, it's easier to structure your code and with a better structure you most often have less code. Even if not, each part of your code is more self-contained and smaller which is most often what you're looking for.
Next to the said list of XML-files I showed at the beginning of the answer with the GlobIterator there are other Iterators that can help to process the XML data.
For example, you've got 1-n XML files that contain 0-n <phone> elements. You know that you want to process any of these <phone> elements, you already exactly know what you want to do with them (extract some data from it). So wouldn't it be great to have a list of all <phone> elements within all XML-files first?
This can be easily done in PHP with the help of a Generator. That is a function that can return values multiple times while it's still "running". This is a simplification, better show some code to illustrate that. Let's say we've got the list of XML files as input and we want all <phone> elements out of it. For sure, you could create an array of all these <phone> elements and process that array later. However, a Generator is able to offer all these <phone> elements directly to be used within a foreach loop:
function extract_phones(Traversable $files) {
foreach ($files as $file) {
$xml = simplexml_load_file($file);
if ($xml === false) {
continue;
}
foreach ($xml->phone as $phone) {
yield $phone;
}
}
}
As this exemplary Generator function shows, it goes over all $files, tries to load them as a SimpleXMLElement and if successfull, iterates over all <phone> elements and yields them.
That means, if the function extract_phones is called within a foreach, that loop will have every <phone> element as SimpleXMLElement:
foreach(extract_phones($inputFiles) as $phone) {
# $phone is a SimpleXMLElement here
}
So now your question asks about creating the CSV file as output. This could be done creating an SplFileObject to pass the output around and access it while processing. It basically works the same like passing the file-handle around like you do in your question but it has better semantics that do allow to change the code more easily later on (you could replace it with another object that behaves the same).
Additionally I've seen a little detail in your code that is worth for some discussion first. You're encoding the quotes as HTML entities:
trim(str_replace( "\"", """, $phone->title ), " ")
You most likely do that because you want to have HTML-Entities inside the CSV file. However, the CSV file does not need such. You also want to have the data in the CSV file as generic as possible. Whether the CSV file is used inside a HTML context later on or within a spreadsheet application should not be your concern when you convert the file-format. My suggestion is here to leave that out and deal at another place with it. A place this more belongs to, and that is later on, e.g. if you use the data from the CSV creating some HTML.
That keeps your conversion and the data clean and it also removes detailed places in your processing which not only make the code more complicate but are very often a place where we introduce flaws into our programs.
I for myself will just remove it from my example.
So let's put this all together: Get all phones from all XML files and store the fields interested in into the output CSV file:
$files = new GlobIterator(__DIR__ . '/*.xml');
$phones = extract_phones($files);
$output = new SplFileObject('file.csv', 'w');
$output->fputcsv($header = ["title", "color"]);
foreach ($phones as $phone) {
$output->fputcsv(
[
$phone->title,
$phone->color,
]
);
}
This then creates the output file you're looking for (without the HTML-entities):
title,color
"""Apple iPhone 5S""",black
"Nokia Lumia 830",black
All this needs is the generator-function I've showed above already that in itself has also straight-forward code. Everything else ships with PHP already. Here is the example code in full:
<?php
/**
* #link http://stackoverflow.com/questions/26074850/convert-multiple-xml-files-to-csv-with-simplexml
*/
function extract_phones(Traversable $files)
{
foreach ($files as $file) {
$xml = simplexml_load_file($file);
if ($xml === false) {
continue;
}
foreach ($xml->phone as $phone) {
yield $phone;
}
}
}
$files = new GlobIterator(__DIR__ . '/*.xml');
$phones = extract_phones($files);
$output = new SplFileObject('file.csv', 'w');
$output->fputcsv($header = ["title", "color"]);
foreach ($phones as $phone) {
$output->fputcsv(
[
$phone->title,
$phone->color,
]
);
}
echo file_get_contents($output->getFilename());
Thanks #Ghost for pointing me to the right direction. So here is my solution.
<?php
$filexml = array ('test.xml', 'test1.xml');
//Headers
$fp = fopen('file.csv', 'w');
$headers = array('title', 'color');
$converted_array = array_map("strtoupper", $headers);
fputcsv($fp, $converted_array, ',', '"');
//XML
foreach ($filexml as $file) {
if (file_exists($file)) {
$xml = simplexml_load_file($file);
foreach ($xml->phone as $phone) {
$values = array(
"title" => (string)$phone->title = trim(str_replace ( "\"", """, $phone->title ), " "),
"color" => (string)$phone->color
);
fputcsv($fp, $values, ',', '"');
}
echo $file . ' converted to .csv sucessfully' . '<br>';
} else {
echo $file . ' was not found' . '<br>';
}
}
fclose($fp);
?>

Categories