diff --git a/php-export-data.class.php b/php-export-data.class.php
index d983cf7..7a8808a 100755
--- a/php-export-data.class.php
+++ b/php-export-data.class.php
@@ -20,11 +20,14 @@ 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()) {
+ ob_end_clean();
+ }
$this->sendHttpHeaders();
break;
case 'string':
@@ -35,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();
@@ -61,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':
@@ -81,37 +84,29 @@ 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 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 {
+ const DELIMITER = "\t";
+
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));
}
}
@@ -119,75 +114,102 @@ 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) {
+ $res = '';
+
+ if ( $this->msExcelSaveHack ) {
+ //hack is to add $separator in first cell of first row
+ $this->msExcelSaveHack = false;
+ $res .= $this->generateRow([static::DELIMITER]);
+ }
+
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";
+ $res .= implode(static::DELIMITER, $row) . "\n";
+
+ return $res;
}
-
+
function sendHttpHeaders() {
header("Content-type: text/csv");
header("Content-Disposition: attachment; filename=".basename($this->filename));
}
}
+/**
+ * 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 {
+ const DELIMITER = ';';
+
+ 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";
@@ -197,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';
@@ -211,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
@@ -224,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) . "\"");
}
-
-}
\ No newline at end of file
+
+}
+
+/**
+ * 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];
+ }
+}
|