You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
8.1 KiB
326 lines
8.1 KiB
<?php |
|
|
|
namespace PhpOffice\PhpSpreadsheet\Writer; |
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; |
|
use PhpOffice\PhpSpreadsheet\Spreadsheet; |
|
|
|
class Csv extends BaseWriter |
|
{ |
|
/** |
|
* PhpSpreadsheet object. |
|
* |
|
* @var Spreadsheet |
|
*/ |
|
private $spreadsheet; |
|
|
|
/** |
|
* Delimiter. |
|
* |
|
* @var string |
|
*/ |
|
private $delimiter = ','; |
|
|
|
/** |
|
* Enclosure. |
|
* |
|
* @var string |
|
*/ |
|
private $enclosure = '"'; |
|
|
|
/** |
|
* Line ending. |
|
* |
|
* @var string |
|
*/ |
|
private $lineEnding = PHP_EOL; |
|
|
|
/** |
|
* Sheet index to write. |
|
* |
|
* @var int |
|
*/ |
|
private $sheetIndex = 0; |
|
|
|
/** |
|
* Whether to write a UTF8 BOM. |
|
* |
|
* @var bool |
|
*/ |
|
private $useBOM = false; |
|
|
|
/** |
|
* Whether to write a Separator line as the first line of the file |
|
* sep=x. |
|
* |
|
* @var bool |
|
*/ |
|
private $includeSeparatorLine = false; |
|
|
|
/** |
|
* Whether to write a fully Excel compatible CSV file. |
|
* |
|
* @var bool |
|
*/ |
|
private $excelCompatibility = false; |
|
|
|
/** |
|
* Output encoding. |
|
* |
|
* @var string |
|
*/ |
|
private $outputEncoding = ''; |
|
|
|
/** |
|
* Create a new CSV. |
|
*/ |
|
public function __construct(Spreadsheet $spreadsheet) |
|
{ |
|
$this->spreadsheet = $spreadsheet; |
|
} |
|
|
|
/** |
|
* Save PhpSpreadsheet to file. |
|
* |
|
* @param resource|string $filename |
|
*/ |
|
public function save($filename, int $flags = 0): void |
|
{ |
|
$this->processFlags($flags); |
|
|
|
// Fetch sheet |
|
$sheet = $this->spreadsheet->getSheet($this->sheetIndex); |
|
|
|
$saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog(); |
|
Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false); |
|
$saveArrayReturnType = Calculation::getArrayReturnType(); |
|
Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE); |
|
|
|
// Open file |
|
$this->openFileHandle($filename); |
|
|
|
if ($this->excelCompatibility) { |
|
$this->setUseBOM(true); // Enforce UTF-8 BOM Header |
|
$this->setIncludeSeparatorLine(true); // Set separator line |
|
$this->setEnclosure('"'); // Set enclosure to " |
|
$this->setDelimiter(';'); // Set delimiter to a semi-colon |
|
$this->setLineEnding("\r\n"); |
|
} |
|
|
|
if ($this->useBOM) { |
|
// Write the UTF-8 BOM code if required |
|
fwrite($this->fileHandle, "\xEF\xBB\xBF"); |
|
} |
|
|
|
if ($this->includeSeparatorLine) { |
|
// Write the separator line if required |
|
fwrite($this->fileHandle, 'sep=' . $this->getDelimiter() . $this->lineEnding); |
|
} |
|
|
|
// Identify the range that we need to extract from the worksheet |
|
$maxCol = $sheet->getHighestDataColumn(); |
|
$maxRow = $sheet->getHighestDataRow(); |
|
|
|
// Write rows to file |
|
for ($row = 1; $row <= $maxRow; ++$row) { |
|
// Convert the row to an array... |
|
$cellsArray = $sheet->rangeToArray('A' . $row . ':' . $maxCol . $row, '', $this->preCalculateFormulas); |
|
// ... and write to the file |
|
$this->writeLine($this->fileHandle, $cellsArray[0]); |
|
} |
|
|
|
$this->maybeCloseFileHandle(); |
|
Calculation::setArrayReturnType($saveArrayReturnType); |
|
Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); |
|
} |
|
|
|
public function getDelimiter(): string |
|
{ |
|
return $this->delimiter; |
|
} |
|
|
|
public function setDelimiter(string $delimiter): self |
|
{ |
|
$this->delimiter = $delimiter; |
|
|
|
return $this; |
|
} |
|
|
|
public function getEnclosure(): string |
|
{ |
|
return $this->enclosure; |
|
} |
|
|
|
public function setEnclosure(string $enclosure = '"'): self |
|
{ |
|
$this->enclosure = $enclosure; |
|
|
|
return $this; |
|
} |
|
|
|
public function getLineEnding(): string |
|
{ |
|
return $this->lineEnding; |
|
} |
|
|
|
public function setLineEnding(string $lineEnding): self |
|
{ |
|
$this->lineEnding = $lineEnding; |
|
|
|
return $this; |
|
} |
|
|
|
/** |
|
* Get whether BOM should be used. |
|
*/ |
|
public function getUseBOM(): bool |
|
{ |
|
return $this->useBOM; |
|
} |
|
|
|
/** |
|
* Set whether BOM should be used, typically when non-ASCII characters are used. |
|
*/ |
|
public function setUseBOM(bool $useBOM): self |
|
{ |
|
$this->useBOM = $useBOM; |
|
|
|
return $this; |
|
} |
|
|
|
/** |
|
* Get whether a separator line should be included. |
|
*/ |
|
public function getIncludeSeparatorLine(): bool |
|
{ |
|
return $this->includeSeparatorLine; |
|
} |
|
|
|
/** |
|
* Set whether a separator line should be included as the first line of the file. |
|
*/ |
|
public function setIncludeSeparatorLine(bool $includeSeparatorLine): self |
|
{ |
|
$this->includeSeparatorLine = $includeSeparatorLine; |
|
|
|
return $this; |
|
} |
|
|
|
/** |
|
* Get whether the file should be saved with full Excel Compatibility. |
|
*/ |
|
public function getExcelCompatibility(): bool |
|
{ |
|
return $this->excelCompatibility; |
|
} |
|
|
|
/** |
|
* Set whether the file should be saved with full Excel Compatibility. |
|
* |
|
* @param bool $excelCompatibility Set the file to be written as a fully Excel compatible csv file |
|
* Note that this overrides other settings such as useBOM, enclosure and delimiter |
|
*/ |
|
public function setExcelCompatibility(bool $excelCompatibility): self |
|
{ |
|
$this->excelCompatibility = $excelCompatibility; |
|
|
|
return $this; |
|
} |
|
|
|
public function getSheetIndex(): int |
|
{ |
|
return $this->sheetIndex; |
|
} |
|
|
|
public function setSheetIndex(int $sheetIndex): self |
|
{ |
|
$this->sheetIndex = $sheetIndex; |
|
|
|
return $this; |
|
} |
|
|
|
public function getOutputEncoding(): string |
|
{ |
|
return $this->outputEncoding; |
|
} |
|
|
|
public function setOutputEncoding(string $outputEnconding): self |
|
{ |
|
$this->outputEncoding = $outputEnconding; |
|
|
|
return $this; |
|
} |
|
|
|
/** @var bool */ |
|
private $enclosureRequired = true; |
|
|
|
public function setEnclosureRequired(bool $value): self |
|
{ |
|
$this->enclosureRequired = $value; |
|
|
|
return $this; |
|
} |
|
|
|
public function getEnclosureRequired(): bool |
|
{ |
|
return $this->enclosureRequired; |
|
} |
|
|
|
/** |
|
* Convert boolean to TRUE/FALSE; otherwise return element cast to string. |
|
* |
|
* @param mixed $element |
|
*/ |
|
private static function elementToString($element): string |
|
{ |
|
if (is_bool($element)) { |
|
return $element ? 'TRUE' : 'FALSE'; |
|
} |
|
|
|
return (string) $element; |
|
} |
|
|
|
/** |
|
* Write line to CSV file. |
|
* |
|
* @param resource $fileHandle PHP filehandle |
|
* @param array $values Array containing values in a row |
|
*/ |
|
private function writeLine($fileHandle, array $values): void |
|
{ |
|
// No leading delimiter |
|
$delimiter = ''; |
|
|
|
// Build the line |
|
$line = ''; |
|
|
|
foreach ($values as $element) { |
|
$element = self::elementToString($element); |
|
// Add delimiter |
|
$line .= $delimiter; |
|
$delimiter = $this->delimiter; |
|
// Escape enclosures |
|
$enclosure = $this->enclosure; |
|
if ($enclosure) { |
|
// If enclosure is not required, use enclosure only if |
|
// element contains newline, delimiter, or enclosure. |
|
if (!$this->enclosureRequired && strpbrk($element, "$delimiter$enclosure\n") === false) { |
|
$enclosure = ''; |
|
} else { |
|
$element = str_replace($enclosure, $enclosure . $enclosure, $element); |
|
} |
|
} |
|
// Add enclosed string |
|
$line .= $enclosure . $element . $enclosure; |
|
} |
|
|
|
// Add line ending |
|
$line .= $this->lineEnding; |
|
|
|
// Write to file |
|
if ($this->outputEncoding != '') { |
|
$line = mb_convert_encoding($line, $this->outputEncoding); |
|
} |
|
fwrite($fileHandle, /** @scrutinizer ignore-type */ $line); |
|
} |
|
}
|
|
|