Merge branch 'master' of github.com:box/spout into add-phpstan
Conflicts: .travis.yml CONTRIBUTING.md composer.json src/Spout/Common/Entity/Cell.php tests/Spout/Writer/ODS/WriterTest.php
This commit is contained in:
commit
540667870a
49 files changed
+529
-152
No files matched your search
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Writer\Common\Manager;
|
||||
|
||||
use Box\Spout\Common\Entity\Style\Style;
|
||||
|
||||
/**
|
||||
* Class RegisteredStyle
|
||||
* Allow to know if this style must replace actual row style.
|
||||
*/
|
||||
class RegisteredStyle
|
||||
{
|
||||
/**
|
||||
* @var Style
|
||||
*/
|
||||
private $style;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isMatchingRowStyle;
|
||||
|
||||
public function __construct(Style $style, bool $isMatchingRowStyle)
|
||||
{
|
||||
$this->style = $style;
|
||||
$this->isMatchingRowStyle = $isMatchingRowStyle;
|
||||
}
|
||||
|
||||
public function getStyle() : Style
|
||||
{
|
||||
return $this->style;
|
||||
}
|
||||
|
||||
public function isMatchingRowStyle() : bool
|
||||
{
|
||||
return $this->isMatchingRowStyle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Writer\Common\Manager\Style;
|
||||
|
||||
use Box\Spout\Common\Entity\Style\Style;
|
||||
|
||||
/**
|
||||
* Class PossiblyUpdatedStyle
|
||||
* Indicates if style is updated.
|
||||
* It allow to know if style registration must be done.
|
||||
*/
|
||||
class PossiblyUpdatedStyle
|
||||
{
|
||||
private $style;
|
||||
private $isUpdated;
|
||||
|
||||
public function __construct(Style $style, bool $isUpdated)
|
||||
{
|
||||
$this->style = $style;
|
||||
$this->isUpdated = $isUpdated;
|
||||
}
|
||||
|
||||
public function getStyle() : Style
|
||||
{
|
||||
return $this->style;
|
||||
}
|
||||
|
||||
public function isUpdated() : bool
|
||||
{
|
||||
return $this->isUpdated;
|
||||
}
|
||||
}
|
||||
@@ -50,13 +50,11 @@ class StyleManager implements StyleManagerInterface
|
||||
* Typically, set "wrap text" if a cell contains a new line.
|
||||
*
|
||||
* @param Cell $cell
|
||||
* @return Style
|
||||
* @return PossiblyUpdatedStyle The eventually updated style
|
||||
*/
|
||||
public function applyExtraStylesIfNeeded(Cell $cell)
|
||||
public function applyExtraStylesIfNeeded(Cell $cell) : PossiblyUpdatedStyle
|
||||
{
|
||||
$updatedStyle = $this->applyWrapTextIfCellContainsNewLine($cell);
|
||||
|
||||
return $updatedStyle;
|
||||
return $this->applyWrapTextIfCellContainsNewLine($cell);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,21 +67,19 @@ class StyleManager implements StyleManagerInterface
|
||||
* on the Windows version of Excel...
|
||||
*
|
||||
* @param Cell $cell The cell the style should be applied to
|
||||
* @return \Box\Spout\Common\Entity\Style\Style The eventually updated style
|
||||
* @return PossiblyUpdatedStyle The eventually updated style
|
||||
*/
|
||||
protected function applyWrapTextIfCellContainsNewLine(Cell $cell)
|
||||
protected function applyWrapTextIfCellContainsNewLine(Cell $cell) : PossiblyUpdatedStyle
|
||||
{
|
||||
$cellStyle = $cell->getStyle();
|
||||
|
||||
// if the "wrap text" option is already set, no-op
|
||||
if ($cellStyle->hasSetWrapText()) {
|
||||
return $cellStyle;
|
||||
}
|
||||
|
||||
if ($cell->isString() && \strpos($cell->getValue(), "\n") !== false) {
|
||||
if (!$cellStyle->hasSetWrapText() && $cell->isString() && \strpos($cell->getValue(), "\n") !== false) {
|
||||
$cellStyle->setShouldWrapText();
|
||||
|
||||
return new PossiblyUpdatedStyle($cellStyle, true);
|
||||
}
|
||||
|
||||
return $cellStyle;
|
||||
return new PossiblyUpdatedStyle($cellStyle, false);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ interface StyleManagerInterface
|
||||
* Typically, set "wrap text" if a cell contains a new line.
|
||||
*
|
||||
* @param Cell $cell
|
||||
* @return Style The updated style
|
||||
* @return PossiblyUpdatedStyle The eventually updated style
|
||||
*/
|
||||
public function applyExtraStylesIfNeeded(Cell $cell);
|
||||
public function applyExtraStylesIfNeeded(Cell $cell) : PossiblyUpdatedStyle;
|
||||
}
|
||||
@@ -36,9 +36,9 @@ class StyleRegistry
|
||||
{
|
||||
$serializedStyle = $this->serialize($style);
|
||||
|
||||
if (!$this->hasStyleAlreadyBeenRegistered($style)) {
|
||||
if (!$this->hasSerializedStyleAlreadyBeenRegistered($serializedStyle)) {
|
||||
$nextStyleId = \count($this->serializedStyleToStyleIdMappingTable);
|
||||
$style->setId($nextStyleId);
|
||||
$style->markAsRegistered($nextStyleId);
|
||||
|
||||
$this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId;
|
||||
$this->styleIdToStyleMappingTable[$nextStyleId] = $style;
|
||||
@@ -48,15 +48,13 @@ class StyleRegistry
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given style has already been registered.
|
||||
* Returns whether the serialized style has already been registered.
|
||||
*
|
||||
* @param Style $style
|
||||
* @param string $serializedStyle The serialized style
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasStyleAlreadyBeenRegistered(Style $style)
|
||||
protected function hasSerializedStyleAlreadyBeenRegistered(string $serializedStyle)
|
||||
{
|
||||
$serializedStyle = $this->serialize($style);
|
||||
|
||||
// Using isset here because it is way faster than array_key_exists...
|
||||
return isset($this->serializedStyleToStyleIdMappingTable[$serializedStyle]);
|
||||
}
|
||||
@@ -101,13 +99,13 @@ class StyleRegistry
|
||||
*/
|
||||
public function serialize(Style $style)
|
||||
{
|
||||
// In order to be able to properly compare style, set static ID value
|
||||
// In order to be able to properly compare style, set static ID value and reset registration
|
||||
$currentId = $style->getId();
|
||||
$style->setId(0);
|
||||
$style->unmarkAsRegistered();
|
||||
|
||||
$serializedStyle = \serialize($style);
|
||||
|
||||
$style->setId($currentId);
|
||||
$style->markAsRegistered($currentId);
|
||||
|
||||
return $serializedStyle;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
|
||||
*/
|
||||
public function registerStyle(Style $style)
|
||||
{
|
||||
if ($style->isRegistered()) {
|
||||
return $style;
|
||||
}
|
||||
|
||||
$registeredStyle = parent::registerStyle($style);
|
||||
$this->usedFontsSet[$style->getFontName()] = true;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use Box\Spout\Common\Exception\IOException;
|
||||
use Box\Spout\Common\Helper\Escaper\ODS as ODSEscaper;
|
||||
use Box\Spout\Common\Helper\StringHelper;
|
||||
use Box\Spout\Writer\Common\Entity\Worksheet;
|
||||
use Box\Spout\Writer\Common\Manager\RegisteredStyle;
|
||||
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
|
||||
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
|
||||
use Box\Spout\Writer\ODS\Manager\Style\StyleManager;
|
||||
@@ -94,7 +95,7 @@ class WorksheetManager implements WorksheetManagerInterface
|
||||
$escapedSheetName = $this->stringsEscaper->escape($externalSheet->getName());
|
||||
$tableStyleName = 'ta' . ($externalSheet->getIndex() + 1);
|
||||
|
||||
$tableElement = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
|
||||
$tableElement = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
|
||||
$tableElement .= '<table:table-column table:default-cell-style-name="ce1" table:style-name="co1" table:number-columns-repeated="' . $worksheet->getMaxNumColumns() . '"/>';
|
||||
|
||||
return $tableElement;
|
||||
@@ -105,8 +106,8 @@ class WorksheetManager implements WorksheetManagerInterface
|
||||
*
|
||||
* @param Worksheet $worksheet The worksheet to add the row to
|
||||
* @param Row $row The row to be added
|
||||
* @throws IOException If the data cannot be written
|
||||
* @throws InvalidArgumentException If a cell value's type is not supported
|
||||
* @throws IOException If the data cannot be written
|
||||
* @return void
|
||||
*/
|
||||
public function addRow(Worksheet $worksheet, Row $row)
|
||||
@@ -126,7 +127,13 @@ class WorksheetManager implements WorksheetManagerInterface
|
||||
$nextCell = isset($cells[$nextCellIndex]) ? $cells[$nextCellIndex] : null;
|
||||
|
||||
if ($nextCell === null || $cell->getValue() !== $nextCell->getValue()) {
|
||||
$data .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $currentCellIndex, $nextCellIndex);
|
||||
$registeredStyle = $this->applyStyleAndRegister($cell, $rowStyle);
|
||||
$cellStyle = $registeredStyle->getStyle();
|
||||
if ($registeredStyle->isMatchingRowStyle()) {
|
||||
$rowStyle = $cellStyle; // Replace actual rowStyle (possibly with null id) by registered style (with id)
|
||||
}
|
||||
|
||||
$data .= $this->getCellXMLWithStyle($cell, $cellStyle, $currentCellIndex, $nextCellIndex);
|
||||
$currentCellIndex = $nextCellIndex;
|
||||
}
|
||||
|
||||
@@ -147,24 +154,46 @@ class WorksheetManager implements WorksheetManagerInterface
|
||||
|
||||
/**
|
||||
* Applies styles to the given style, merging the cell's style with its row's style
|
||||
* Then builds and returns xml for the cell.
|
||||
*
|
||||
* @param Cell $cell
|
||||
* @param Style $rowStyle
|
||||
* @param int $currentCellIndex
|
||||
* @param int $nextCellIndex
|
||||
* @throws InvalidArgumentException If a cell value's type is not supported
|
||||
* @return string
|
||||
* @return RegisteredStyle
|
||||
*/
|
||||
private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $currentCellIndex, $nextCellIndex)
|
||||
private function applyStyleAndRegister(Cell $cell, Style $rowStyle) : RegisteredStyle
|
||||
{
|
||||
// Apply row and extra styles
|
||||
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
|
||||
$cell->setStyle($mergedCellAndRowStyle);
|
||||
$newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
|
||||
$isMatchingRowStyle = false;
|
||||
if ($cell->getStyle()->isEmpty()) {
|
||||
$cell->setStyle($rowStyle);
|
||||
|
||||
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
|
||||
$styleIndex = $registeredStyle->getId() + 1; // 1-based
|
||||
$possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
|
||||
|
||||
if ($possiblyUpdatedStyle->isUpdated()) {
|
||||
$registeredStyle = $this->styleManager->registerStyle($possiblyUpdatedStyle->getStyle());
|
||||
} else {
|
||||
$registeredStyle = $this->styleManager->registerStyle($rowStyle);
|
||||
$isMatchingRowStyle = true;
|
||||
}
|
||||
} else {
|
||||
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
|
||||
$cell->setStyle($mergedCellAndRowStyle);
|
||||
|
||||
$possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
|
||||
if ($possiblyUpdatedStyle->isUpdated()) {
|
||||
$newCellStyle = $possiblyUpdatedStyle->getStyle();
|
||||
} else {
|
||||
$newCellStyle = $mergedCellAndRowStyle;
|
||||
}
|
||||
|
||||
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
|
||||
}
|
||||
|
||||
return new RegisteredStyle($registeredStyle, $isMatchingRowStyle);
|
||||
}
|
||||
|
||||
private function getCellXMLWithStyle(Cell $cell, Style $style, int $currentCellIndex, int $nextCellIndex) : string
|
||||
{
|
||||
$styleIndex = $style->getId() + 1; // 1-based
|
||||
|
||||
$numTimesValueRepeated = ($nextCellIndex - $currentCellIndex);
|
||||
|
||||
@@ -198,12 +227,14 @@ class WorksheetManager implements WorksheetManagerInterface
|
||||
|
||||
$data .= '</table:table-cell>';
|
||||
} elseif ($cell->isBoolean()) {
|
||||
$data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cell->getValue() . '">';
|
||||
$value = $cell->getValue() ? 'true' : 'false'; // boolean-value spec: http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#datatype-boolean
|
||||
$data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $value . '">';
|
||||
$data .= '<text:p>' . $cell->getValue() . '</text:p>';
|
||||
$data .= '</table:table-cell>';
|
||||
} elseif ($cell->isNumeric()) {
|
||||
$data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">';
|
||||
$data .= '<text:p>' . $cell->getValue() . '</text:p>';
|
||||
$cellValue = $this->stringHelper->formatNumericValue($cell->getValue());
|
||||
$data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cellValue . '">';
|
||||
$data .= '<text:p>' . $cellValue . '</text:p>';
|
||||
$data .= '</table:table-cell>';
|
||||
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
|
||||
// only writes the error value if it's a string
|
||||
|
||||
@@ -128,9 +128,26 @@ abstract class WriterAbstract implements WriterInterface
|
||||
// @see https://github.com/box/spout/issues/241
|
||||
$this->globalFunctionsHelper->ob_end_clean();
|
||||
|
||||
// Set headers
|
||||
/*
|
||||
* Set headers
|
||||
*
|
||||
* For newer browsers such as Firefox, Chrome, Opera, Safari, etc., they all support and use `filename*`
|
||||
* specified by the new standard, even if they do not automatically decode filename; it does not matter;
|
||||
* and for older versions of Internet Explorer, they are not recognized `filename*`, will automatically
|
||||
* ignore it and use the old `filename` (the only minor flaw is that there must be an English suffix name).
|
||||
* In this way, the multi-browser multi-language compatibility problem is perfectly solved, which does not
|
||||
* require UA judgment and is more in line with the standard.
|
||||
*
|
||||
* @see https://github.com/box/spout/issues/745
|
||||
* @see https://tools.ietf.org/html/rfc6266
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
|
||||
*/
|
||||
$this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType);
|
||||
$this->globalFunctionsHelper->header('Content-Disposition: attachment; filename="' . $this->outputFilePath . '"');
|
||||
$this->globalFunctionsHelper->header(
|
||||
'Content-Disposition: attachment; ' .
|
||||
'filename="' . rawurldecode($this->outputFilePath) . '"; ' .
|
||||
'filename*=UTF-8\'\'' . rawurldecode($this->outputFilePath)
|
||||
);
|
||||
|
||||
/*
|
||||
* When forcing the download of a file over SSL,IE8 and lower browsers fail
|
||||
|
||||
@@ -119,6 +119,10 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
|
||||
*/
|
||||
public function registerStyle(Style $style)
|
||||
{
|
||||
if ($style->isRegistered()) {
|
||||
return $style;
|
||||
}
|
||||
|
||||
$registeredStyle = parent::registerStyle($style);
|
||||
$this->registerFill($registeredStyle);
|
||||
$this->registerFormat($registeredStyle);
|
||||
|
||||
@@ -14,6 +14,7 @@ use Box\Spout\Writer\Common\Creator\InternalEntityFactory;
|
||||
use Box\Spout\Writer\Common\Entity\Options;
|
||||
use Box\Spout\Writer\Common\Entity\Worksheet;
|
||||
use Box\Spout\Writer\Common\Helper\CellHelper;
|
||||
use Box\Spout\Writer\Common\Manager\RegisteredStyle;
|
||||
use Box\Spout\Writer\Common\Manager\RowManager;
|
||||
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
|
||||
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
|
||||
@@ -161,7 +162,12 @@ EOD;
|
||||
$rowXML = '<row r="' . $rowIndexOneBased . '" spans="1:' . $numCells . '">';
|
||||
|
||||
foreach ($row->getCells() as $columnIndexZeroBased => $cell) {
|
||||
$rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndexOneBased, $columnIndexZeroBased);
|
||||
$registeredStyle = $this->applyStyleAndRegister($cell, $rowStyle);
|
||||
$cellStyle = $registeredStyle->getStyle();
|
||||
if ($registeredStyle->isMatchingRowStyle()) {
|
||||
$rowStyle = $cellStyle; // Replace actual rowStyle (possibly with null id) by registered style (with id)
|
||||
}
|
||||
$rowXML .= $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $cellStyle->getId());
|
||||
}
|
||||
|
||||
$rowXML .= '</row>';
|
||||
@@ -174,26 +180,43 @@ EOD;
|
||||
|
||||
/**
|
||||
* Applies styles to the given style, merging the cell's style with its row's style
|
||||
* Then builds and returns xml for the cell.
|
||||
*
|
||||
* @param Cell $cell
|
||||
* @param Style $rowStyle
|
||||
* @param int $rowIndexOneBased
|
||||
* @param int $columnIndexZeroBased
|
||||
*
|
||||
* @throws InvalidArgumentException If the given value cannot be processed
|
||||
* @return string
|
||||
* @return RegisteredStyle
|
||||
*/
|
||||
private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $rowIndexOneBased, $columnIndexZeroBased)
|
||||
private function applyStyleAndRegister(Cell $cell, Style $rowStyle) : RegisteredStyle
|
||||
{
|
||||
// Apply row and extra styles
|
||||
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
|
||||
$cell->setStyle($mergedCellAndRowStyle);
|
||||
$newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
|
||||
$isMatchingRowStyle = false;
|
||||
if ($cell->getStyle()->isEmpty()) {
|
||||
$cell->setStyle($rowStyle);
|
||||
|
||||
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
|
||||
$possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
|
||||
|
||||
return $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $registeredStyle->getId());
|
||||
if ($possiblyUpdatedStyle->isUpdated()) {
|
||||
$registeredStyle = $this->styleManager->registerStyle($possiblyUpdatedStyle->getStyle());
|
||||
} else {
|
||||
$registeredStyle = $this->styleManager->registerStyle($rowStyle);
|
||||
$isMatchingRowStyle = true;
|
||||
}
|
||||
} else {
|
||||
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
|
||||
$cell->setStyle($mergedCellAndRowStyle);
|
||||
|
||||
$possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
|
||||
|
||||
if ($possiblyUpdatedStyle->isUpdated()) {
|
||||
$newCellStyle = $possiblyUpdatedStyle->getStyle();
|
||||
} else {
|
||||
$newCellStyle = $mergedCellAndRowStyle;
|
||||
}
|
||||
|
||||
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
|
||||
}
|
||||
|
||||
return new RegisteredStyle($registeredStyle, $isMatchingRowStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,7 +241,7 @@ EOD;
|
||||
} elseif ($cell->isBoolean()) {
|
||||
$cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
|
||||
} elseif ($cell->isNumeric()) {
|
||||
$cellXML .= '><v>' . $cell->getValue() . '</v></c>';
|
||||
$cellXML .= '><v>' . $this->stringHelper->formatNumericValue($cell->getValue()) . '</v></c>';
|
||||
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
|
||||
// only writes the error value if it's a string
|
||||
$cellXML .= ' t="e"><v>' . $cell->getValueEvenIfError() . '</v></c>';
|
||||
|
||||
Reference in new issue
Block a user