Improve ODS Writer
Remove num-columns-repeated and num-rows-repeated as it does not seem to be required (LibreOffice does not add them). This greatly simplifies the writer and the XML output. Added some optional attributes to help LibreOffice with cell values caching ("calcext")
This commit is contained in:
parent
ef171910b9
commit
156fd29a44
@ -145,7 +145,7 @@ EOD;
|
|||||||
|
|
||||||
$metaXmlFileContents = <<<EOD
|
$metaXmlFileContents = <<<EOD
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<office:document-meta office:version="1.1" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<office:document-meta office:version="1.2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<office:meta>
|
<office:meta>
|
||||||
<dc:creator>$appName</dc:creator>
|
<dc:creator>$appName</dc:creator>
|
||||||
<meta:creation-date>$createdDate</meta:creation-date>
|
<meta:creation-date>$createdDate</meta:creation-date>
|
||||||
@ -182,7 +182,7 @@ EOD;
|
|||||||
{
|
{
|
||||||
$contentXmlFileContents = <<<EOD
|
$contentXmlFileContents = <<<EOD
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<office:document-content xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:msoxl="http://schemas.microsoft.com/office/excel/formula" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<office:document-content office:version="1.2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:msoxl="http://schemas.microsoft.com/office/excel/formula" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
EOD;
|
EOD;
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ EOD;
|
|||||||
|
|
||||||
foreach ($worksheets as $worksheet) {
|
foreach ($worksheets as $worksheet) {
|
||||||
// write the "<table:table>" node, with the final sheet's name
|
// write the "<table:table>" node, with the final sheet's name
|
||||||
fwrite($contentXmlHandle, $worksheet->getTableRootNodeAsString() . PHP_EOL);
|
fwrite($contentXmlHandle, $worksheet->getTableElementStartAsString() . PHP_EOL);
|
||||||
|
|
||||||
$worksheetFilePath = $worksheet->getWorksheetFilePath();
|
$worksheetFilePath = $worksheet->getWorksheetFilePath();
|
||||||
$this->copyFileContentsToTarget($worksheetFilePath, $contentXmlHandle);
|
$this->copyFileContentsToTarget($worksheetFilePath, $contentXmlHandle);
|
||||||
|
@ -46,7 +46,7 @@ class StyleHelper extends AbstractStyleHelper
|
|||||||
{
|
{
|
||||||
$content = <<<EOD
|
$content = <<<EOD
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<office:document-styles xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:msoxl="http://schemas.microsoft.com/office/excel/formula" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<office:document-styles office:version="1.2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:msoxl="http://schemas.microsoft.com/office/excel/formula" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
EOD;
|
EOD;
|
||||||
|
|
||||||
|
@ -18,13 +18,6 @@ use Box\Spout\Writer\Common\Sheet;
|
|||||||
*/
|
*/
|
||||||
class Worksheet implements WorksheetInterface
|
class Worksheet implements WorksheetInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @see https://wiki.openoffice.org/wiki/Documentation/FAQ/Calc/Miscellaneous/What's_the_maximum_number_of_rows_and_cells_for_a_spreadsheet_file%3f
|
|
||||||
* @see https://bz.apache.org/ooo/show_bug.cgi?id=30215
|
|
||||||
*/
|
|
||||||
const MAX_NUM_ROWS_REPEATED = 1048576;
|
|
||||||
const MAX_NUM_COLUMNS_REPEATED = 1024;
|
|
||||||
|
|
||||||
/** @var \Box\Spout\Writer\Common\Sheet The "external" sheet */
|
/** @var \Box\Spout\Writer\Common\Sheet The "external" sheet */
|
||||||
protected $externalSheet;
|
protected $externalSheet;
|
||||||
|
|
||||||
@ -37,9 +30,12 @@ class Worksheet implements WorksheetInterface
|
|||||||
/** @var \Box\Spout\Common\Helper\StringHelper To help with string manipulation */
|
/** @var \Box\Spout\Common\Helper\StringHelper To help with string manipulation */
|
||||||
protected $stringHelper;
|
protected $stringHelper;
|
||||||
|
|
||||||
/** @var Resource Pointer to the sheet data file (e.g. xl/worksheets/sheet1.xml) */
|
/** @var Resource Pointer to the temporary sheet data file (e.g. worksheets-temp/sheet1.xml) */
|
||||||
protected $sheetFilePointer;
|
protected $sheetFilePointer;
|
||||||
|
|
||||||
|
/** @var int Maximum number of columns among all the written rows */
|
||||||
|
protected $maxNumColumns = 1;
|
||||||
|
|
||||||
/** @var int Index of the last written row */
|
/** @var int Index of the last written row */
|
||||||
protected $lastWrittenRowIndex = 0;
|
protected $lastWrittenRowIndex = 0;
|
||||||
|
|
||||||
@ -62,6 +58,8 @@ class Worksheet implements WorksheetInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the worksheet to accept data
|
* Prepares the worksheet to accept data
|
||||||
|
* The XML file does not contain the "<table:table>" node as it contains the sheet's name
|
||||||
|
* which may change during the execution of the program. It will be added at the end.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
|
||||||
@ -70,11 +68,6 @@ class Worksheet implements WorksheetInterface
|
|||||||
{
|
{
|
||||||
$this->sheetFilePointer = fopen($this->worksheetFilePath, 'w');
|
$this->sheetFilePointer = fopen($this->worksheetFilePath, 'w');
|
||||||
$this->throwIfSheetFilePointerIsNotAvailable();
|
$this->throwIfSheetFilePointerIsNotAvailable();
|
||||||
|
|
||||||
// The XML file does not contain the "<table:table>" node as it contains the sheet's name
|
|
||||||
// which may change during the execution of the program. It will be added at the end.
|
|
||||||
$content = ' <table:table-column table:default-cell-style-name="ce1" table:number-columns-repeated="' . self::MAX_NUM_COLUMNS_REPEATED . '" table:style-name="co1"/>' . PHP_EOL;
|
|
||||||
fwrite($this->sheetFilePointer, $content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,12 +96,15 @@ class Worksheet implements WorksheetInterface
|
|||||||
*
|
*
|
||||||
* @return string <table> node as string
|
* @return string <table> node as string
|
||||||
*/
|
*/
|
||||||
public function getTableRootNodeAsString()
|
public function getTableElementStartAsString()
|
||||||
{
|
{
|
||||||
$escapedSheetName = $this->stringsEscaper->escape($this->externalSheet->getName());
|
$escapedSheetName = $this->stringsEscaper->escape($this->externalSheet->getName());
|
||||||
$tableStyleName = 'ta' . ($this->externalSheet->getIndex() + 1);
|
$tableStyleName = 'ta' . ($this->externalSheet->getIndex() + 1);
|
||||||
|
|
||||||
return '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
|
$tableElement = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">' . PHP_EOL;
|
||||||
|
$tableElement .= ' <table:table-column table:default-cell-style-name="ce1" table:style-name="co1" table:number-columns-repeated="' . $this->maxNumColumns . '"/>';
|
||||||
|
|
||||||
|
return $tableElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -139,7 +135,7 @@ class Worksheet implements WorksheetInterface
|
|||||||
*/
|
*/
|
||||||
public function addRow($dataRow, $style)
|
public function addRow($dataRow, $style)
|
||||||
{
|
{
|
||||||
$numColumnsRepeated = self::MAX_NUM_COLUMNS_REPEATED;
|
$this->maxNumColumns = max($this->maxNumColumns, count($dataRow));
|
||||||
$styleIndex = ($style->getId() + 1); // 1-based
|
$styleIndex = ($style->getId() + 1); // 1-based
|
||||||
|
|
||||||
$data = ' <table:table-row table:style-name="ro1">' . PHP_EOL;
|
$data = ' <table:table-row table:style-name="ro1">' . PHP_EOL;
|
||||||
@ -148,7 +144,7 @@ class Worksheet implements WorksheetInterface
|
|||||||
$data .= ' <table:table-cell table:style-name="ce' . $styleIndex . '"';
|
$data .= ' <table:table-cell table:style-name="ce' . $styleIndex . '"';
|
||||||
|
|
||||||
if (CellHelper::isNonEmptyString($cellValue)) {
|
if (CellHelper::isNonEmptyString($cellValue)) {
|
||||||
$data .= ' office:value-type="string">' . PHP_EOL;
|
$data .= ' office:value-type="string" calcext:value-type="string">' . PHP_EOL;
|
||||||
|
|
||||||
$cellValueLines = explode("\n", $cellValue);
|
$cellValueLines = explode("\n", $cellValue);
|
||||||
foreach ($cellValueLines as $cellValueLine) {
|
foreach ($cellValueLines as $cellValueLine) {
|
||||||
@ -157,11 +153,11 @@ class Worksheet implements WorksheetInterface
|
|||||||
|
|
||||||
$data .= ' </table:table-cell>' . PHP_EOL;
|
$data .= ' </table:table-cell>' . PHP_EOL;
|
||||||
} else if (CellHelper::isBoolean($cellValue)) {
|
} else if (CellHelper::isBoolean($cellValue)) {
|
||||||
$data .= ' office:value-type="boolean" office:value="' . $cellValue . '">' . PHP_EOL;
|
$data .= ' office:value-type="boolean" calcext:value-type="boolean" office:value="' . $cellValue . '">' . PHP_EOL;
|
||||||
$data .= ' <text:p>' . $cellValue . '</text:p>' . PHP_EOL;
|
$data .= ' <text:p>' . $cellValue . '</text:p>' . PHP_EOL;
|
||||||
$data .= ' </table:table-cell>' . PHP_EOL;
|
$data .= ' </table:table-cell>' . PHP_EOL;
|
||||||
} else if (CellHelper::isNumeric($cellValue)) {
|
} else if (CellHelper::isNumeric($cellValue)) {
|
||||||
$data .= ' office:value-type="float" office:value="' . $cellValue . '">' . PHP_EOL;
|
$data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cellValue . '">' . PHP_EOL;
|
||||||
$data .= ' <text:p>' . $cellValue . '</text:p>' . PHP_EOL;
|
$data .= ' <text:p>' . $cellValue . '</text:p>' . PHP_EOL;
|
||||||
$data .= ' </table:table-cell>' . PHP_EOL;
|
$data .= ' </table:table-cell>' . PHP_EOL;
|
||||||
} else if (empty($cellValue)) {
|
} else if (empty($cellValue)) {
|
||||||
@ -169,12 +165,6 @@ class Worksheet implements WorksheetInterface
|
|||||||
} else {
|
} else {
|
||||||
throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue));
|
throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
$numColumnsRepeated--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($numColumnsRepeated > 0) {
|
|
||||||
$data .= ' <table:table-cell table:number-columns-repeated="' . $numColumnsRepeated . '"/>' . PHP_EOL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$data .= ' </table:table-row>' . PHP_EOL;
|
$data .= ' </table:table-row>' . PHP_EOL;
|
||||||
@ -195,16 +185,6 @@ class Worksheet implements WorksheetInterface
|
|||||||
*/
|
*/
|
||||||
public function close()
|
public function close()
|
||||||
{
|
{
|
||||||
$remainingRepeatedRows = self::MAX_NUM_ROWS_REPEATED - $this->lastWrittenRowIndex;
|
|
||||||
|
|
||||||
if ($remainingRepeatedRows > 0) {
|
|
||||||
$data = ' <table:table-row table:style-name="ro1" table:number-rows-repeated="' . $remainingRepeatedRows . '">' . PHP_EOL;
|
|
||||||
$data .= ' <table:table-cell table:number-columns-repeated="' . self::MAX_NUM_COLUMNS_REPEATED . '"/>' . PHP_EOL;
|
|
||||||
$data .= ' </table:table-row>' . PHP_EOL;
|
|
||||||
|
|
||||||
fwrite($this->sheetFilePointer, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose($this->sheetFilePointer);
|
fclose($this->sheetFilePointer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase
|
|||||||
$style2 = (new StyleBuilder())
|
$style2 = (new StyleBuilder())
|
||||||
->setFontSize(15)
|
->setFontSize(15)
|
||||||
->setFontColor(Color::RED)
|
->setFontColor(Color::RED)
|
||||||
->setFontName('Font')
|
->setFontName('Cambria')
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
$this->writeToODSFileWithMultipleStyles($dataRows, $fileName, [$style, $style2]);
|
$this->writeToODSFileWithMultipleStyles($dataRows, $fileName, [$style, $style2]);
|
||||||
@ -133,7 +133,7 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase
|
|||||||
$customFont2Element = $cellStyleElements[2];
|
$customFont2Element = $cellStyleElements[2];
|
||||||
$this->assertFirstChildHasAttributeEquals('15pt', $customFont2Element, 'text-properties', 'fo:font-size');
|
$this->assertFirstChildHasAttributeEquals('15pt', $customFont2Element, 'text-properties', 'fo:font-size');
|
||||||
$this->assertFirstChildHasAttributeEquals('#' . Color::RED, $customFont2Element, 'text-properties', 'fo:color');
|
$this->assertFirstChildHasAttributeEquals('#' . Color::RED, $customFont2Element, 'text-properties', 'fo:color');
|
||||||
$this->assertFirstChildHasAttributeEquals('Font', $customFont2Element, 'text-properties', 'style:font-name');
|
$this->assertFirstChildHasAttributeEquals('Cambria', $customFont2Element, 'text-properties', 'style:font-name');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -114,7 +114,7 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase
|
|||||||
$style2 = (new StyleBuilder())
|
$style2 = (new StyleBuilder())
|
||||||
->setFontSize(15)
|
->setFontSize(15)
|
||||||
->setFontColor(Color::RED)
|
->setFontColor(Color::RED)
|
||||||
->setFontName('Font')
|
->setFontName('Cambria')
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
$this->writeToXLSXFileWithMultipleStyles($dataRows, $fileName, [$style, $style2]);
|
$this->writeToXLSXFileWithMultipleStyles($dataRows, $fileName, [$style, $style2]);
|
||||||
@ -148,7 +148,7 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertChildrenNumEquals(3, $thirdFontElement, 'The font should only have 3 properties.');
|
$this->assertChildrenNumEquals(3, $thirdFontElement, 'The font should only have 3 properties.');
|
||||||
$this->assertFirstChildHasAttributeEquals('15', $thirdFontElement, 'sz', 'val');
|
$this->assertFirstChildHasAttributeEquals('15', $thirdFontElement, 'sz', 'val');
|
||||||
$this->assertFirstChildHasAttributeEquals(Color::toARGB(Color::RED), $thirdFontElement, 'color', 'rgb');
|
$this->assertFirstChildHasAttributeEquals(Color::toARGB(Color::RED), $thirdFontElement, 'color', 'rgb');
|
||||||
$this->assertFirstChildHasAttributeEquals('Font', $thirdFontElement, 'name', 'val');
|
$this->assertFirstChildHasAttributeEquals('Cambria', $thirdFontElement, 'name', 'val');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user