From 623b785a4d399d0512ed63ab2c2ef9e928b25389 Mon Sep 17 00:00:00 2001 From: Chris Graham Date: Sun, 22 Mar 2015 18:50:19 +0000 Subject: [PATCH 1/8] Fixed typo in CSV writer --- src/Spout/Reader/CSV.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Spout/Reader/CSV.php b/src/Spout/Reader/CSV.php index bd48248..2da160f 100644 --- a/src/Spout/Reader/CSV.php +++ b/src/Spout/Reader/CSV.php @@ -52,7 +52,7 @@ class CSV extends AbstractReader * The file must be UTF-8 encoded. * @TODO add encoding detection/conversion * - * @param string $filePath Path of the XLSX file to be read + * @param string $filePath Path of the CSV file to be read * @return void * @throws \Box\Spout\Common\Exception\IOException */ From d2029a084fe45e9902e948fe5f1b6ef0cceec6a8 Mon Sep 17 00:00:00 2001 From: Chris Graham Date: Sun, 22 Mar 2015 19:03:32 +0000 Subject: [PATCH 2/8] Fix XLSX bug with empty rows --- src/Spout/Writer/Internal/XLSX/Worksheet.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Spout/Writer/Internal/XLSX/Worksheet.php b/src/Spout/Writer/Internal/XLSX/Worksheet.php index 326c41b..89c3f33 100644 --- a/src/Spout/Writer/Internal/XLSX/Worksheet.php +++ b/src/Spout/Writer/Internal/XLSX/Worksheet.php @@ -121,6 +121,11 @@ EOD; */ public function addRow($dataRow) { + if (count($dataRow) == 0) { + // Without this fix, we get a repair issue in regular Microsoft Excel + $dataRow=array(''); + } + $cellNumber = 0; $rowIndex = $this->lastWrittenRowIndex + 1; $numCells = count($dataRow); From 3dd4c4ecce52a4b58ea5003c05fe107ebbe5c6ac Mon Sep 17 00:00:00 2001 From: Chris Graham Date: Sun, 22 Mar 2015 19:20:53 +0000 Subject: [PATCH 3/8] Fixed numeric check bug for XLSX --- src/Spout/Writer/Internal/XLSX/Worksheet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Spout/Writer/Internal/XLSX/Worksheet.php b/src/Spout/Writer/Internal/XLSX/Worksheet.php index 89c3f33..5f919f6 100644 --- a/src/Spout/Writer/Internal/XLSX/Worksheet.php +++ b/src/Spout/Writer/Internal/XLSX/Worksheet.php @@ -139,7 +139,7 @@ EOD; if (empty($cellValue)) { $data .= '/>' . PHP_EOL; } else { - if (is_numeric($cellValue)) { + if (trim($cellValue,'0123456789.') == '' /*similar to is_numeric without having PHPs regular quirkiness*/) { $data .= '>' . $cellValue . '' . PHP_EOL; } else { if ($this->shouldUseInlineStrings) { From de1d1c8397347cc441705b308692e42ed78a9840 Mon Sep 17 00:00:00 2001 From: Chris Graham Date: Sun, 22 Mar 2015 21:41:33 +0000 Subject: [PATCH 4/8] XLS (BIFF5) and HTM support --- src/Spout/Common/Type.php | 2 + src/Spout/Writer/HTM.php | 103 +++++++++++++++ src/Spout/Writer/WriterFactory.php | 6 + src/Spout/Writer/XLS.php | 193 +++++++++++++++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 src/Spout/Writer/HTM.php create mode 100644 src/Spout/Writer/XLS.php diff --git a/src/Spout/Common/Type.php b/src/Spout/Common/Type.php index 6af7f35..35ca3c9 100644 --- a/src/Spout/Common/Type.php +++ b/src/Spout/Common/Type.php @@ -10,4 +10,6 @@ abstract class Type { const CSV = "csv"; const XLSX = "xlsx"; + const XLS = "xls"; + const HTM = "htm"; } diff --git a/src/Spout/Writer/HTM.php b/src/Spout/Writer/HTM.php new file mode 100644 index 0000000..dfd20d1 --- /dev/null +++ b/src/Spout/Writer/HTM.php @@ -0,0 +1,103 @@ +filePointer, "\n"); + fwrite($this->filePointer, "\n"); + fwrite($this->filePointer, "" . htmlentities(basename(basename($this->outputFilePath, '.html'), '.htm')) . "\n"); + fwrite($this->filePointer, "\n"); + fwrite($this->filePointer, "\n"); + fwrite($this->filePointer, "\n"); + } + + /** + * Adds data to the currently opened writer. + * + * @param array $dataRow Array containing data to be written. + * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @return void + * @throws \Box\Spout\Common\Exception\IOException If unable to write data + */ + protected function addRowToWriter(array $dataRow) + { + $wasWriteSuccessful = true; + if ($this->lastWrittenRowIndex == 0) { + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, "\n"); + } + if ($this->lastWrittenRowIndex == 1) { + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, "\n"); + } + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, "\n"); + foreach ($dataRow as $cell) { + $cell = nl2br(htmlentities($cell)); + + if ($this->lastWrittenRowIndex == 0) { + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, "\t\n"); + } else + { + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, "\t\n"); + } + } + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, "\n"); + if ($this->lastWrittenRowIndex == 0) { + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, "\n"); + } + + if ($wasWriteSuccessful === false) { + throw new IOException('Unable to write data'); + } + + $this->lastWrittenRowIndex++; + if ($this->lastWrittenRowIndex % self::FLUSH_THRESHOLD === 0) { + $this->globalFunctionsHelper->fflush($this->filePointer); + } + } + + /** + * Closes the HTM streamer, preventing any additional writing. + * If set, sets the headers and redirects output to the browser. + * + * @return void + */ + protected function closeWriter() + { + if ($this->filePointer) { + if ($this->lastWrittenRowIndex >= 1) { + fwrite($this->filePointer, "\n"); + } + fwrite($this->filePointer, "
{$cell}{$cell}
\n"); + fwrite($this->filePointer, "\n"); + fwrite($this->filePointer, "\n"); + + $this->globalFunctionsHelper->fclose($this->filePointer); + } + + $this->lastWrittenRowIndex = 0; + } +} diff --git a/src/Spout/Writer/WriterFactory.php b/src/Spout/Writer/WriterFactory.php index 321f0f1..5635ff4 100644 --- a/src/Spout/Writer/WriterFactory.php +++ b/src/Spout/Writer/WriterFactory.php @@ -33,6 +33,12 @@ class WriterFactory case Type::XLSX: $writer = new XLSX(); break; + case Type::XLS: + $writer = new XLS(); + break; + case Type::HTM: + $writer = new HTM(); + break; default: throw new UnsupportedTypeException('No writers supporting the given type: ' . $writerType); } diff --git a/src/Spout/Writer/XLS.php b/src/Spout/Writer/XLS.php new file mode 100644 index 0000000..dc3a295 --- /dev/null +++ b/src/Spout/Writer/XLS.php @@ -0,0 +1,193 @@ +col = 0; + $this->row = 0; + $this->bofMarker(); + } + + /** + * Writes the Excel Beginning of File marker + * + * @see pack() + * @return nothing + */ + private function bofMarker() + { + fwrite($this->filePointer, pack("ssssss", 0x809, 0x8, 0x0, 0x10, 0x0, 0x0)); + } + + /** + * Moves internal cursor all the way left, col = 0 + * + * @return nothing + */ + function home() + { + $this->col = 0; + } + + /** + * Moves internal cursor right by the amount specified + * + * @param optional integer $amount The amount to move right by, defaults to 1 + * @return integer The current column after the move + */ + function right($amount = 1) + { + $this->col += $amount; + return $this->col; + } + + /** + * Moves internal cursor down by amount + * + * @param optional integer $amount The amount to move down by, defaults to 1 + * @return integer The current row after the move + */ + function down($amount = 1) + { + $this->row += $amount; + return $this->row; + } + + /** + * Writes a number to the Excel Spreadsheet + * + * @see pack() + * @param integer $value The value to write out + * @return boolean Success status + */ + function number($value) + { + $wasWriteSuccessful = true; + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, pack("sssss", 0x203, 14, $this->row, $this->col, 0x0)); + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, pack("d", $value)); + return $wasWriteSuccessful; + } + + /** + * Writes a string (or label) to the Excel Spreadsheet + * + * @see pack() + * @param string $value The value to write out + * @return boolean Success status + */ + function label($value) + { + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + // We're doing BIFF5, not BIFF8 - meaning a 255 char limit. If you want something good, use XLSX, else XLS for compatibility. + if (strlen($value) >= 255) { + $value = substr($value, 0, 255); + } + + $length = strlen($value); + $wasWriteSuccessful = true; + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, pack("ssssss", 0x204, 8 + $length, $this->row, $this->col, 0x0, $length)); + if ($value !='') { + $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, $value); + } + return $wasWriteSuccessful; + } + + /** + * Adds data to the currently opened writer. + * + * @param array $dataRow Array containing data to be written. + * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @return void + * @throws \Box\Spout\Common\Exception\IOException If unable to write data + */ + protected function addRowToWriter(array $dataRow) + { + if ($this->row == 65536) { + // Hit the limit. You should have chosen XLSX + return; + } + + $wasWriteSuccessful = true; + + $this->home(); + foreach ($dataRow as $cell) { + if (is_integer($cell)) { + $wasWriteSuccessful = $wasWriteSuccessful && $this->number($cell); + } else + { + $wasWriteSuccessful = $wasWriteSuccessful && $this->label($cell); + } + $this->right(); + } + $this->down(); + + if ($wasWriteSuccessful === false) { + throw new IOException('Unable to write data'); + } + + if ($this->row % self::FLUSH_THRESHOLD === 0) { + $this->globalFunctionsHelper->fflush($this->filePointer); + } + } + + /** + * Writes the Excel End of File marker + * + * @see pack() + * @return nothing + */ + private function eofMarker() + { + fwrite($this->filePointer, pack("ss", 0x0A, 0x00)); + } + + /** + * Closes the XLS streamer, preventing any additional writing. + * If set, sets the headers and redirects output to the browser. + * + * @return void + */ + protected function closeWriter() + { + if ($this->filePointer) { + $this->eofMarker(); + + $this->globalFunctionsHelper->fclose($this->filePointer); + } + + $this->row = 0; + $this->col = 0; + } +} From 6eef69634bc0dcd988359b67ee685b2022f1626c Mon Sep 17 00:00:00 2001 From: Chris Graham Date: Sun, 22 Mar 2015 22:58:20 +0000 Subject: [PATCH 5/8] Normalised numeric handling between XLS and XLSX, plus PSR2 --- src/Spout/Writer/Internal/XLSX/Worksheet.php | 2 +- src/Spout/Writer/XLS.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Spout/Writer/Internal/XLSX/Worksheet.php b/src/Spout/Writer/Internal/XLSX/Worksheet.php index 5f919f6..8007d3d 100644 --- a/src/Spout/Writer/Internal/XLSX/Worksheet.php +++ b/src/Spout/Writer/Internal/XLSX/Worksheet.php @@ -139,7 +139,7 @@ EOD; if (empty($cellValue)) { $data .= '/>' . PHP_EOL; } else { - if (trim($cellValue,'0123456789.') == '' /*similar to is_numeric without having PHPs regular quirkiness*/) { + if (trim($cellValue, '0123456789.') == '' /*similar to is_numeric without having PHPs regular quirkiness*/) { $data .= '>' . $cellValue . '' . PHP_EOL; } else { if ($this->shouldUseInlineStrings) { diff --git a/src/Spout/Writer/XLS.php b/src/Spout/Writer/XLS.php index dc3a295..e84b0db 100644 --- a/src/Spout/Writer/XLS.php +++ b/src/Spout/Writer/XLS.php @@ -143,7 +143,7 @@ class XLS extends AbstractWriter $this->home(); foreach ($dataRow as $cell) { - if (is_integer($cell)) { + if (trim($cell, '0123456789.') == '' /*similar to is_numeric without having PHPs regular quirkiness*/) { $wasWriteSuccessful = $wasWriteSuccessful && $this->number($cell); } else { From 31482fc622e53cc4e40f0235b225065adbd04d7d Mon Sep 17 00:00:00 2001 From: Chris Graham Date: Sun, 22 Mar 2015 22:58:33 +0000 Subject: [PATCH 6/8] Need to support PHP5.3 for a client unfortunately --- src/Spout/Common/Escaper/XLSX.php | 4 ++-- src/Spout/Reader/Helper/XLSX/SharedStringsHelper.php | 4 ++-- src/Spout/Reader/Helper/XLSX/WorksheetHelper.php | 2 +- src/Spout/Reader/XLSX.php | 4 ++-- src/Spout/Writer/Helper/XLSX/FileSystemHelper.php | 3 ++- src/Spout/Writer/Internal/XLSX/Workbook.php | 2 +- src/Spout/Writer/XLSX.php | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Spout/Common/Escaper/XLSX.php b/src/Spout/Common/Escaper/XLSX.php index fcb626a..8d6c255 100644 --- a/src/Spout/Common/Escaper/XLSX.php +++ b/src/Spout/Common/Escaper/XLSX.php @@ -58,8 +58,8 @@ class XLSX implements EscaperInterface */ protected function getControlCharactersEscapingMap() { - $controlCharactersEscapingMap = []; - $whitelistedControlCharacters = ["\t", "\r", "\n"]; + $controlCharactersEscapingMap = array(); + $whitelistedControlCharacters = array("\t", "\r", "\n"); // control characters values are from 0 to 1F (hex values) in the ASCII table for ($charValue = 0x0; $charValue <= 0x1F; $charValue++) { diff --git a/src/Spout/Reader/Helper/XLSX/SharedStringsHelper.php b/src/Spout/Reader/Helper/XLSX/SharedStringsHelper.php index b1a239c..04f49c9 100644 --- a/src/Spout/Reader/Helper/XLSX/SharedStringsHelper.php +++ b/src/Spout/Reader/Helper/XLSX/SharedStringsHelper.php @@ -145,9 +145,9 @@ class SharedStringsHelper */ protected function removeSuperfluousTextNodes($parentNode) { - $tagsToRemove = [ + $tagsToRemove = array( 'rPh', // Pronunciation of the text - ]; + ); foreach ($tagsToRemove as $tagToRemove) { $xpath = '//ns:' . $tagToRemove; diff --git a/src/Spout/Reader/Helper/XLSX/WorksheetHelper.php b/src/Spout/Reader/Helper/XLSX/WorksheetHelper.php index a105e01..14e3b22 100644 --- a/src/Spout/Reader/Helper/XLSX/WorksheetHelper.php +++ b/src/Spout/Reader/Helper/XLSX/WorksheetHelper.php @@ -40,7 +40,7 @@ class WorksheetHelper */ public function getWorksheets() { - $worksheets = []; + $worksheets = array(); $xmlContents = file_get_contents('zip://' . $this->filePath . '#' . self::CONTENT_TYPES_XML_FILE_PATH); diff --git a/src/Spout/Reader/XLSX.php b/src/Spout/Reader/XLSX.php index db46707..f285e89 100644 --- a/src/Spout/Reader/XLSX.php +++ b/src/Spout/Reader/XLSX.php @@ -185,7 +185,7 @@ class XLSX extends AbstractReader } $isInsideRowTag = false; - $rowData = []; + $rowData = array(); while ($this->xmlReader->read()) { if ($this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === 'dimension') { @@ -230,7 +230,7 @@ class XLSX extends AbstractReader } // no data means "end of file" - return ($rowData !== []) ? $rowData : null; + return ($rowData !== array()) ? $rowData : null; } /** diff --git a/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php b/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php index fddbb25..d6a5692 100644 --- a/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php +++ b/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php @@ -179,7 +179,8 @@ EOD; */ protected function createCoreXmlFile() { - $createdDate = (new \DateTime())->format('c'); + $dt = new \DateTime(); + $createdDate = $dt->format('c'); $coreXmlFileContents = << diff --git a/src/Spout/Writer/Internal/XLSX/Workbook.php b/src/Spout/Writer/Internal/XLSX/Workbook.php index 3f8cca4..3df50b4 100644 --- a/src/Spout/Writer/Internal/XLSX/Workbook.php +++ b/src/Spout/Writer/Internal/XLSX/Workbook.php @@ -36,7 +36,7 @@ class Workbook protected $sharedStringsHelper; /** @var Worksheet[] Array containing the workbook's sheets */ - protected $worksheets = []; + protected $worksheets = array(); /** @var Worksheet The worksheet where data will be written to */ protected $currentWorksheet; diff --git a/src/Spout/Writer/XLSX.php b/src/Spout/Writer/XLSX.php index 62e98f1..b25d4a7 100644 --- a/src/Spout/Writer/XLSX.php +++ b/src/Spout/Writer/XLSX.php @@ -88,7 +88,7 @@ class XLSX extends AbstractWriter { $this->throwIfBookIsNotAvailable(); - $externalSheets = []; + $externalSheets = array(); $worksheets = $this->book->getWorksheets(); /** @var Internal\XLSX\Worksheet $worksheet */ From 86d78dbc095dfafaf1b32b07d0d8559977694453 Mon Sep 17 00:00:00 2001 From: Chris Graham Date: Tue, 24 Mar 2015 13:30:35 +0000 Subject: [PATCH 7/8] Made first XLSX row bold, opinionated but reasonable I think (HTML already was, and adding full font/XF support to XLS proved too hard) --- .../Writer/Helper/XLSX/FileSystemHelper.php | 48 ++++++++++++++++++- src/Spout/Writer/Internal/XLSX/Worksheet.php | 2 +- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php b/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php index d6a5692..39854b1 100644 --- a/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php +++ b/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php @@ -20,6 +20,7 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper const XL_FOLDER_NAME = 'xl'; const WORKSHEETS_FOLDER_NAME = 'worksheets'; + const STYLES_FILE_NAME = 'styles.xml'; const RELS_FILE_NAME = '.rels'; const APP_XML_FILE_NAME = 'app.xml'; const CORE_XML_FILE_NAME = 'core.xml'; @@ -81,7 +82,8 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper ->createRootFolder() ->createRelsFolderAndFile() ->createDocPropsFolderAndFiles() - ->createXlFolderAndSubFolders(); + ->createXlFolderAndSubFolders() + ->createStylesFile(); } /** @@ -234,6 +236,48 @@ EOD; return $this; } + /** + * Creates the "styles.xml" file under the "xl" folder + * + * @return FileSystemHelper + * @throws \Box\Spout\Common\Exception\IOException If unable to create the file + */ + protected function createStylesFile() + { + $stylesFileContents = << + + + + + + + + + + + + + + + + + + + + + + + + + +EOD; + + $this->createFileWithContents($this->xlFolder, self::STYLES_FILE_NAME, $stylesFileContents); + + return $this; + } + /** * Creates the "[Content_Types].xml" file under the root folder * @@ -248,6 +292,7 @@ EOD; + EOD; @@ -314,6 +359,7 @@ EOD; + EOD; diff --git a/src/Spout/Writer/Internal/XLSX/Worksheet.php b/src/Spout/Writer/Internal/XLSX/Worksheet.php index 8007d3d..7aa69df 100644 --- a/src/Spout/Writer/Internal/XLSX/Worksheet.php +++ b/src/Spout/Writer/Internal/XLSX/Worksheet.php @@ -134,7 +134,7 @@ EOD; foreach($dataRow as $cellValue) { $columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber); - $data .= ' Date: Tue, 24 Mar 2015 14:44:54 +0000 Subject: [PATCH 8/8] Framework for cell meta-data, implemented hyperlink support on it (for core formats - XLSX, HTM) --- src/Spout/Writer/AbstractWriter.php | 10 ++-- src/Spout/Writer/CSV.php | 3 +- src/Spout/Writer/HTM.php | 10 +++- .../Writer/Helper/XLSX/FileSystemHelper.php | 13 ++++++ src/Spout/Writer/Internal/XLSX/Workbook.php | 7 +-- src/Spout/Writer/Internal/XLSX/Worksheet.php | 46 ++++++++++++++++++- src/Spout/Writer/WriterInterface.php | 3 +- src/Spout/Writer/XLS.php | 3 +- src/Spout/Writer/XLSX.php | 5 +- 9 files changed, 84 insertions(+), 16 deletions(-) diff --git a/src/Spout/Writer/AbstractWriter.php b/src/Spout/Writer/AbstractWriter.php index 0e9e883..b9b207e 100644 --- a/src/Spout/Writer/AbstractWriter.php +++ b/src/Spout/Writer/AbstractWriter.php @@ -38,13 +38,14 @@ abstract class AbstractWriter implements WriterInterface abstract protected function openWriter(); /** - * Adds data to the currently openned writer. + * Adds data to the currently opened writer. * * @param array $dataRow Array containing data to be streamed. * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @param array $metaData Array containing meta-data maps for individual cells, such as 'url' * @return void */ - abstract protected function addRowToWriter(array $dataRow); + abstract protected function addRowToWriter(array $dataRow, array $metaData); /** * Closes the streamer, preventing any additional writing. @@ -138,15 +139,16 @@ abstract class AbstractWriter implements WriterInterface * * @param array $dataRow Array containing data to be streamed. * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @param array $metaData Array containing meta-data maps for individual cells, such as 'url' * * @return \Box\Spout\Writer\AbstractWriter * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer * @throws \Box\Spout\Common\Exception\IOException If unable to write data */ - public function addRow(array $dataRow) + public function addRow(array $dataRow, array $metaData = array()) { if ($this->isWriterOpened) { - $this->addRowToWriter($dataRow); + $this->addRowToWriter($dataRow, $metaData); } else { throw new WriterNotOpenedException('The writer needs to be opened before adding row.'); } diff --git a/src/Spout/Writer/CSV.php b/src/Spout/Writer/CSV.php index 0ac8d91..1c7ae7e 100644 --- a/src/Spout/Writer/CSV.php +++ b/src/Spout/Writer/CSV.php @@ -68,10 +68,11 @@ class CSV extends AbstractWriter * * @param array $dataRow Array containing data to be written. * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @param array $metaData Array containing meta-data maps for individual cells, such as 'url' * @return void * @throws \Box\Spout\Common\Exception\IOException If unable to write data */ - protected function addRowToWriter(array $dataRow) + protected function addRowToWriter(array $dataRow, array $metaData) { $wasWriteSuccessful = fputcsv($this->filePointer, $dataRow, $this->fieldDelimiter, $this->fieldEnclosure); if ($wasWriteSuccessful === false) { diff --git a/src/Spout/Writer/HTM.php b/src/Spout/Writer/HTM.php index dfd20d1..dfa7b5b 100644 --- a/src/Spout/Writer/HTM.php +++ b/src/Spout/Writer/HTM.php @@ -41,10 +41,11 @@ class HTM extends AbstractWriter * * @param array $dataRow Array containing data to be written. * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @param array $metaData Array containing meta-data maps for individual cells, such as 'url' * @return void * @throws \Box\Spout\Common\Exception\IOException If unable to write data */ - protected function addRowToWriter(array $dataRow) + protected function addRowToWriter(array $dataRow, array $metaData) { $wasWriteSuccessful = true; if ($this->lastWrittenRowIndex == 0) { @@ -54,9 +55,14 @@ class HTM extends AbstractWriter $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, "\n"); } $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, "\n"); - foreach ($dataRow as $cell) { + foreach ($dataRow as $i => $cell) { $cell = nl2br(htmlentities($cell)); + if (isset($metaData[$i]['url'])) + { + $cell = '' . $cell . ''; + } + if ($this->lastWrittenRowIndex == 0) { $wasWriteSuccessful = $wasWriteSuccessful && fwrite($this->filePointer, "\t{$cell}\n"); } else diff --git a/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php b/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php index 39854b1..6b84a12 100644 --- a/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php +++ b/src/Spout/Writer/Helper/XLSX/FileSystemHelper.php @@ -208,6 +208,7 @@ EOD; $this->xlFolder = $this->createFolder($this->rootFolder, self::XL_FOLDER_NAME); $this->createXlRelsFolder(); $this->createXlWorksheetsFolder(); + $this->createXlWorksheetsRelsFolder(); return $this; } @@ -236,6 +237,18 @@ EOD; return $this; } + /** + * Creates the "_rels" folder under the "worksheets" folder + * + * @return FileSystemHelper + * @throws \Box\Spout\Common\Exception\IOException If unable to create the folder + */ + protected function createXlWorksheetsRelsFolder() + { + $this->createFolder($this->xlWorksheetsFolder, self::RELS_FOLDER_NAME); + return $this; + } + /** * Creates the "styles.xml" file under the "xl" folder * diff --git a/src/Spout/Writer/Internal/XLSX/Workbook.php b/src/Spout/Writer/Internal/XLSX/Workbook.php index 3df50b4..3261f16 100644 --- a/src/Spout/Writer/Internal/XLSX/Workbook.php +++ b/src/Spout/Writer/Internal/XLSX/Workbook.php @@ -165,11 +165,12 @@ class Workbook * * @param array $dataRow Array containing data to be written. * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @param array $metaData Array containing meta-data maps for individual cells, such as 'url' * @return void * @throws \Box\Spout\Common\Exception\IOException If trying to create a new sheet and unable to open the sheet for writing * @throws \Box\Spout\Writer\Exception\WriterException If unable to write data */ - public function addRowToCurrentWorksheet($dataRow) + public function addRowToCurrentWorksheet($dataRow, $metaData) { $currentWorksheet = $this->getCurrentWorksheet(); $hasReachedMaxRows = $this->hasCurrentWorkseetReachedMaxRows(); @@ -179,12 +180,12 @@ class Workbook // ... continue writing in a new sheet if option set if ($this->shouldCreateNewSheetsAutomatically) { $currentWorksheet = $this->addNewSheetAndMakeItCurrent(); - $currentWorksheet->addRow($dataRow); + $currentWorksheet->addRow($dataRow, $metaData); } else { // otherwise, do nothing as the data won't be read anyways } } else { - $currentWorksheet->addRow($dataRow); + $currentWorksheet->addRow($dataRow, $metaData); } } diff --git a/src/Spout/Writer/Internal/XLSX/Worksheet.php b/src/Spout/Writer/Internal/XLSX/Worksheet.php index 7aa69df..d49f065 100644 --- a/src/Spout/Writer/Internal/XLSX/Worksheet.php +++ b/src/Spout/Writer/Internal/XLSX/Worksheet.php @@ -25,6 +25,9 @@ EOD; /** @var string Path to the XML file that will contain the sheet data */ protected $worksheetFilePath; + /** @var string Path to the XML file that will contain the sheet rels data */ + protected $worksheetRelsFilePath; + /** @var \Box\Spout\Writer\Helper\XLSX\SharedStringsHelper Helper to write shared strings */ protected $sharedStringsHelper; @@ -40,6 +43,9 @@ EOD; /** @var int */ protected $lastWrittenRowIndex = 0; + /** @var array */ + protected $urls = array(); + /** * @param \Box\Spout\Writer\Sheet $externalSheet The associated "external" sheet * @param string $tempFolder Temporary folder where the files to create the XLSX will be stored @@ -55,6 +61,7 @@ EOD; $this->stringsEscaper = new \Box\Spout\Common\Escaper\XLSX(); $this->worksheetFilePath = $worksheetFilesFolder . DIRECTORY_SEPARATOR . strtolower($this->externalSheet->getName()) . '.xml'; + $this->worksheetRelsFilePath = $worksheetFilesFolder . DIRECTORY_SEPARATOR . '_rels' . DIRECTORY_SEPARATOR . strtolower($this->externalSheet->getName()) . '.xml.rels'; $this->startSheet(); } @@ -116,10 +123,11 @@ EOD; * * @param array $dataRow Array containing data to be written. * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @param array $metaData Array containing meta-data maps for individual cells, such as 'url' * @return void * @throws \Box\Spout\Common\Exception\IOException If the data cannot be written */ - public function addRow($dataRow) + public function addRow($dataRow, array $metaData = array()) { if (count($dataRow) == 0) { // Without this fix, we get a repair issue in regular Microsoft Excel @@ -134,7 +142,9 @@ EOD; foreach($dataRow as $cellValue) { $columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber); - $data .= ' urls[$cellPath] = $metaData[$cellNumber]['url']; + } + $cellNumber++; } @@ -173,6 +187,34 @@ EOD; public function close() { fwrite($this->sheetFilePointer, ' ' . PHP_EOL); + + // Write out any hyperlinks + if (count($this->urls) != 0) { + fwrite($this->sheetFilePointer, ' ' . PHP_EOL); + $i = 0; + foreach ($this->urls as $cellPath => $url) { + $refID = 'rId' . ($i + 1); + fwrite($this->sheetFilePointer, ' ' . PHP_EOL); + $i++; + } + fwrite($this->sheetFilePointer, ' ' . PHP_EOL); + } + + // Write rels file + $sheetRelsFilePointer = fopen($this->worksheetRelsFilePath, 'w'); + if (!$sheetRelsFilePointer) throw new IOException('Unable to open rels sheet for writing.'); + fwrite($sheetRelsFilePointer, '' . PHP_EOL); + fwrite($sheetRelsFilePointer, '' . PHP_EOL); + $i = 0; + foreach ($this->urls as $url) { + $refID = 'rId' . ($i + 1); + fwrite($sheetRelsFilePointer, '' . PHP_EOL); + $i++; + } + fwrite($sheetRelsFilePointer, ''); + fclose($sheetRelsFilePointer); + + // Finish file fwrite($this->sheetFilePointer, ''); fclose($this->sheetFilePointer); } diff --git a/src/Spout/Writer/WriterInterface.php b/src/Spout/Writer/WriterInterface.php index 1324053..847c95a 100644 --- a/src/Spout/Writer/WriterInterface.php +++ b/src/Spout/Writer/WriterInterface.php @@ -34,11 +34,12 @@ interface WriterInterface * * @param array $dataRow Array containing data to be streamed. * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @param array $metaData Array containing meta-data maps for individual cells, such as 'url' * @return \Box\Spout\Writer\WriterInterface * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yetthe writer * @throws \Box\Spout\Common\Exception\IOException If unable to write data */ - public function addRow(array $dataRow); + public function addRow(array $dataRow, array $metaData = array()); /** * Write given data to the output. New data will be appended to end of stream. diff --git a/src/Spout/Writer/XLS.php b/src/Spout/Writer/XLS.php index e84b0db..cf91d2c 100644 --- a/src/Spout/Writer/XLS.php +++ b/src/Spout/Writer/XLS.php @@ -129,10 +129,11 @@ class XLS extends AbstractWriter * * @param array $dataRow Array containing data to be written. * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @param array $metaData Array containing meta-data maps for individual cells, such as 'url' * @return void * @throws \Box\Spout\Common\Exception\IOException If unable to write data */ - protected function addRowToWriter(array $dataRow) + protected function addRowToWriter(array $dataRow, array $metaData) { if ($this->row == 65536) { // Hit the limit. You should have chosen XLSX diff --git a/src/Spout/Writer/XLSX.php b/src/Spout/Writer/XLSX.php index b25d4a7..2b470c8 100644 --- a/src/Spout/Writer/XLSX.php +++ b/src/Spout/Writer/XLSX.php @@ -160,14 +160,15 @@ class XLSX extends AbstractWriter * * @param array $dataRow Array containing data to be written. * Example $dataRow = ['data1', 1234, null, '', 'data5']; + * @param array $metaData Array containing meta-data maps for individual cells, such as 'url' * @return void * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the book is not created yet * @throws \Box\Spout\Common\Exception\IOException If unable to write data */ - protected function addRowToWriter(array $dataRow) + protected function addRowToWriter(array $dataRow, array $metaData) { $this->throwIfBookIsNotAvailable(); - $this->book->addRowToCurrentWorksheet($dataRow); + $this->book->addRowToCurrentWorksheet($dataRow, $metaData); } /**