From 9cf41376ab70acc611a981cdcf3bfbcefd17ca36 Mon Sep 17 00:00:00 2001 From: elbaz michael Date: Wed, 22 Jul 2015 18:36:45 +0200 Subject: [PATCH 1/5] csv comma separted values ==> ";" At 129 the separator is wrong so i added a parameter with default values --- php-export-data.class.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/php-export-data.class.php b/php-export-data.class.php index d983cf7..818f38d 100755 --- a/php-export-data.class.php +++ b/php-export-data.class.php @@ -120,13 +120,13 @@ function sendHttpHeaders() { */ class ExportDataCSV extends ExportData { - function generateRow($row) { + function generateRow($row, $separator = ";") { foreach ($row as $key => $value) { // Escape inner quotes and wrap all contents in new quotes. // Note that we are using \" to escape double quote not "" $row[$key] = '"'. str_replace('"', '\"', $value) .'"'; } - return implode(",", $row) . "\n"; + return implode($separator, $row) . "\n"; } function sendHttpHeaders() { @@ -239,4 +239,4 @@ function sendHttpHeaders() { header("Content-Disposition: inline; filename=\"" . basename($this->filename) . "\""); } -} \ No newline at end of file +} From 302ac4065e0b8635d55553063cbcab35bb2c2c9e Mon Sep 17 00:00:00 2001 From: elbaz michael Date: Thu, 23 Jul 2015 18:57:26 +0200 Subject: [PATCH 2/5] changing default value for the separator generateRow($row, $separator = ",") --- php-export-data.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php-export-data.class.php b/php-export-data.class.php index 818f38d..981ab07 100755 --- a/php-export-data.class.php +++ b/php-export-data.class.php @@ -120,7 +120,7 @@ function sendHttpHeaders() { */ class ExportDataCSV extends ExportData { - function generateRow($row, $separator = ";") { + function generateRow($row, $separator = ",") { foreach ($row as $key => $value) { // Escape inner quotes and wrap all contents in new quotes. // Note that we are using \" to escape double quote not "" From dbbf442c76af02d43081554d7b406630bf412708 Mon Sep 17 00:00:00 2001 From: egorrishe Date: Tue, 19 Dec 2017 17:01:08 +0200 Subject: [PATCH 3/5] fix [ExportData::initialize()] if (exportTo == 'browser') {clean the output buffer} --- php-export-data.class.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/php-export-data.class.php b/php-export-data.class.php index 981ab07..5e20faf 100755 --- a/php-export-data.class.php +++ b/php-export-data.class.php @@ -25,6 +25,9 @@ public function initialize() { switch($this->exportTo) { case 'browser': + while (ob_get_level()) { + ob_end_clean(); + } $this->sendHttpHeaders(); break; case 'string': From c533a83bfb0ba5069f96cd273f40d5cf0e0cfbec Mon Sep 17 00:00:00 2001 From: egorrishe Date: Wed, 20 Dec 2017 09:57:55 +0200 Subject: [PATCH 4/5] new [ExportDataSCSV] useful for Windows MS Excel --- php-export-data.class.php | 46 +++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/php-export-data.class.php b/php-export-data.class.php index 5e20faf..ffa01ca 100755 --- a/php-export-data.class.php +++ b/php-export-data.class.php @@ -101,20 +101,15 @@ abstract protected function generateRow($row); /** * ExportDataTSV - Exports to TSV (tab separated value) format. */ -class ExportDataTSV extends ExportData { - - function generateRow($row) { - foreach ($row as $key => $value) { - // Escape inner quotes and wrap all contents in new quotes. - // Note that we are using \" to escape double quote not "" - $row[$key] = '"'. str_replace('"', '\"', $value) .'"'; - } - return implode("\t", $row) . "\n"; +class ExportDataTSV extends ExportDataCSV { + + function generateRow($row, $separator = "\t") { + return parent::generateRow($row, $separator); } function sendHttpHeaders() { header("Content-type: text/tab-separated-values"); - header("Content-Disposition: attachment; filename=".basename($this->filename)); + header("Content-Disposition: attachment; filename=".basename($this->filename)); } } @@ -122,14 +117,29 @@ function sendHttpHeaders() { * ExportDataCSV - Exports to CSV (comma separated value) format. */ class ExportDataCSV extends ExportData { - + /** + * @var bool MS Excel badly work with CSV - even read. + * But if you edit, save and close file in MS Excel, then MS Excel cannot open file again. + */ + public $msExcelSaveHack = false; + function generateRow($row, $separator = ",") { + $res = ''; + + if ( $this->msExcelSaveHack ) { + //hack is to add $separator in first cell of first row + $this->msExcelSaveHack = false; + $res .= $this->generateRow([$separator]); + } + foreach ($row as $key => $value) { // Escape inner quotes and wrap all contents in new quotes. // Note that we are using \" to escape double quote not "" $row[$key] = '"'. str_replace('"', '\"', $value) .'"'; } - return implode($separator, $row) . "\n"; + $res .= implode($separator, $row) . "\n"; + + return $res; } function sendHttpHeaders() { @@ -138,6 +148,18 @@ function sendHttpHeaders() { } } +/** + * ExportDataSCSV - Exports to CSV (semicolon separated value) format. + * Useful if you want to open CSV file on Windows MS Excel without additional actions. + */ +class ExportDataSCSV extends ExportDataCSV { + public $msExcelSaveHack = true; + + function generateRow($row, $separator = ';') { + return parent::generateRow($row, $separator); + } +} + /** * ExportDataExcel exports data into an XML format (spreadsheetML) that can be From 8674d0263b96e3c2bbc6aa25f219db083249abc5 Mon Sep 17 00:00:00 2001 From: egorrishe Date: Wed, 20 Dec 2017 15:20:26 +0200 Subject: [PATCH 5/5] new [ImportDataCSV] parse CSV file line by line --- php-export-data.class.php | 206 +++++++++++++++++++++++++++----------- 1 file changed, 149 insertions(+), 57 deletions(-) diff --git a/php-export-data.class.php b/php-export-data.class.php index ffa01ca..7a8808a 100755 --- a/php-export-data.class.php +++ b/php-export-data.class.php @@ -20,9 +20,9 @@ public function __construct($exportTo = "browser", $filename = "exportdata") { $this->exportTo = $exportTo; $this->filename = $filename; } - + public function initialize() { - + switch($this->exportTo) { case 'browser': while (ob_get_level()) { @@ -38,18 +38,18 @@ public function initialize() { $this->tempFile = fopen($this->tempFilename, "w"); break; } - + $this->write($this->generateHeader()); } - + public function addRow($row) { $this->write($this->generateRow($row)); } - + public function finalize() { - + $this->write($this->generateFooter()); - + switch($this->exportTo) { case 'browser': flush(); @@ -64,13 +64,13 @@ public function finalize() { break; } } - + public function getString() { return $this->stringData; } - + abstract public function sendHttpHeaders(); - + protected function write($data) { switch($this->exportTo) { case 'browser': @@ -84,29 +84,26 @@ protected function write($data) { break; } } - + protected function generateHeader() { // can be overridden by subclass to return any data that goes at the top of the exported file } - + protected function generateFooter() { - // can be overridden by subclass to return any data that goes at the bottom of the exported file + // can be overridden by subclass to return any data that goes at the bottom of the exported file } - + // In subclasses generateRow will take $row array and return string of it formatted for export type abstract protected function generateRow($row); - + } /** * ExportDataTSV - Exports to TSV (tab separated value) format. */ class ExportDataTSV extends ExportDataCSV { + const DELIMITER = "\t"; - function generateRow($row, $separator = "\t") { - return parent::generateRow($row, $separator); - } - function sendHttpHeaders() { header("Content-type: text/tab-separated-values"); header("Content-Disposition: attachment; filename=".basename($this->filename)); @@ -117,19 +114,21 @@ function sendHttpHeaders() { * ExportDataCSV - Exports to CSV (comma separated value) format. */ class ExportDataCSV extends ExportData { + const DELIMITER = ','; + /** * @var bool MS Excel badly work with CSV - even read. * But if you edit, save and close file in MS Excel, then MS Excel cannot open file again. */ public $msExcelSaveHack = false; - function generateRow($row, $separator = ",") { + function generateRow($row) { $res = ''; if ( $this->msExcelSaveHack ) { //hack is to add $separator in first cell of first row $this->msExcelSaveHack = false; - $res .= $this->generateRow([$separator]); + $res .= $this->generateRow([static::DELIMITER]); } foreach ($row as $key => $value) { @@ -137,11 +136,11 @@ function generateRow($row, $separator = ",") { // Note that we are using \" to escape double quote not "" $row[$key] = '"'. str_replace('"', '\"', $value) .'"'; } - $res .= implode($separator, $row) . "\n"; + $res .= implode(static::DELIMITER, $row) . "\n"; return $res; } - + function sendHttpHeaders() { header("Content-type: text/csv"); header("Content-Disposition: attachment; filename=".basename($this->filename)); @@ -153,66 +152,64 @@ function sendHttpHeaders() { * Useful if you want to open CSV file on Windows MS Excel without additional actions. */ class ExportDataSCSV extends ExportDataCSV { - public $msExcelSaveHack = true; + const DELIMITER = ';'; - function generateRow($row, $separator = ';') { - return parent::generateRow($row, $separator); - } + public $msExcelSaveHack = true; } /** - * ExportDataExcel exports data into an XML format (spreadsheetML) that can be + * ExportDataExcel exports data into an XML format (spreadsheetML) that can be * read by MS Excel 2003 and newer as well as OpenOffice - * + * * Creates a workbook with a single worksheet (title specified by * $title). - * + * * Note that using .XML is the "correct" file extension for these files, but it * generally isn't associated with Excel. Using .XLS is tempting, but Excel 2007 will * throw a scary warning that the extension doesn't match the file type. - * + * * Based on Excel XML code from Excel_XML (http://github.com/oliverschwarz/php-excel) * by Oliver Schwarz */ class ExportDataExcel extends ExportData { - + const XmlHeader = "\n"; const XmlFooter = ""; - - public $encoding = 'UTF-8'; // encoding type to specify in file. + + public $encoding = 'UTF-8'; // encoding type to specify in file. // Note that you're on your own for making sure your data is actually encoded to this encoding - - public $title = 'Sheet1'; // title for Worksheet - + + public $title = 'Sheet1'; // title for Worksheet + function generateHeader() { - + // workbook header $output = stripslashes(sprintf(self::XmlHeader, $this->encoding)) . "\n"; - + // Set up styles $output .= "\n"; $output .= "\n"; $output .= "\n"; - + // worksheet header $output .= sprintf("\n \n", htmlentities($this->title)); - + return $output; } - + function generateFooter() { $output = ''; - + // worksheet footer $output .= "
\n
\n"; - + // workbook footer $output .= self::XmlFooter; - + return $output; } - + function generateRow($row) { $output = ''; $output .= " \n"; @@ -222,12 +219,12 @@ function generateRow($row) { $output .= " \n"; return $output; } - + private function generateCell($item) { $output = ''; $style = ''; - - // Tell Excel to treat as a number. Note that Excel only stores roughly 15 digits, so keep + + // Tell Excel to treat as a number. Note that Excel only stores roughly 15 digits, so keep // as text if number is longer than that. if(preg_match("/^-?\d+(?:[.,]\d+)?$/",$item) && (strlen($item) < 15)) { $type = 'Number'; @@ -236,12 +233,12 @@ private function generateCell($item) { // also have an optional time after the date. // // Note we want to be very strict in what we consider a date. There is the possibility - // of really screwing up the data if we try to reformat a string that was not actually + // of really screwing up the data if we try to reformat a string that was not actually // intended to represent a date. elseif(preg_match("/^(\d{1,2}|\d{4})[\/\-]\d{1,2}[\/\-](\d{1,2}|\d{4})([^\d].+)?$/",$item) && - ($timestamp = strtotime($item)) && - ($timestamp > 0) && - ($timestamp < strtotime('+500 years'))) { + ($timestamp = strtotime($item)) && + ($timestamp > 0) && + ($timestamp < strtotime('+500 years'))) { $type = 'DateTime'; $item = strftime("%Y-%m-%dT%H:%M:%S",$timestamp); $style = 'sDT'; // defined in header; tells excel to format date for display @@ -249,19 +246,114 @@ private function generateCell($item) { else { $type = 'String'; } - + $item = str_replace(''', ''', htmlspecialchars($item, ENT_QUOTES)); $output .= " "; $output .= $style ? "" : ""; $output .= sprintf("%s", $type, $item); $output .= "\n"; - + return $output; } - + function sendHttpHeaders() { header("Content-Type: application/vnd.ms-excel; charset=" . $this->encoding); header("Content-Disposition: inline; filename=\"" . basename($this->filename) . "\""); } - + +} + +/** + * Usage example: + * + * $filePath = '/path/to/any/csv/file'; + * $importer = new ImportDataCSV($filePath); + * + * while (($row = $importer->getLine()) !== FALSE) { + * print_r($row); + * } + * + * unset($importer); //do it always to close file handler + */ +class ImportDataCSV { + private $fileHandler; + private $delimiter; + + function __construct($filePath) { + $this->fileHandler = fopen($filePath, 'r'); + } + + function __destruct() { + $this->closeFile(); + } + + function __unset($name) { + $this->closeFile(); + } + + public function getLine() { + if ( !$this->delimiter ) { + return $this->detectDelimiter(); + } + + return fgetcsv($this->fileHandler, null, $this->delimiter); + } + + private function closeFile() { + if ( $this->fileHandler ) { + fclose($this->fileHandler); + $this->fileHandler = null; + } + } + + private function detectDelimiter() { + $i=50; + while (--$i) { + $line = fgets($this->fileHandler); + if ( !$line ) { + continue; + } + + foreach ($this->getDelimiters() as $delimiter) { + $resCsv = str_getcsv($line, $delimiter); + if ( empty($resCsv) || ($resCsv[0] != $delimiter && 1 == count($resCsv)) ) + continue; + + $this->delimiter = $delimiter; + if ( $this->isMsExcelHack($resCsv) ) { + // This line has no useful data. We can skip this line. And return next. + return $this->getLine(); + } else { + return $resCsv; + } + } + + return false; + } + + return false; + } + + /** + * If ExportDataCSV::$msExcelSaveHack was enabled - then first element == DELIMITER, and all other are empty + * @param $resCsv + * @return bool + */ + private function isMsExcelHack($resCsv) { + if ($resCsv[0] != $this->delimiter) + return false; + + unset($resCsv[0]); + foreach ($resCsv as $v) { + if ( !empty($v) ) { + return false; + } + } + + return true; + } + + private function getDelimiters() { + return [ExportDataTSV::DELIMITER, ExportDataCSV::DELIMITER, ExportDataSCSV::DELIMITER]; + } }