532 lines
18 KiB
PHP
532 lines
18 KiB
PHP
<?php
|
|
|
|
namespace Box\Spout\Writer\ODS;
|
|
|
|
use Box\Spout\Common\Type;
|
|
use Box\Spout\Reader\Wrapper\XMLReader;
|
|
use Box\Spout\TestUsingResource;
|
|
use Box\Spout\Writer\Common\Creator\EntityFactory;
|
|
use Box\Spout\Writer\Common\Creator\Style\BorderBuilder;
|
|
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
|
|
use Box\Spout\Writer\Common\Entity\Cell;
|
|
use Box\Spout\Writer\Common\Entity\Style\Border;
|
|
use Box\Spout\Writer\Common\Entity\Style\Color;
|
|
use Box\Spout\Writer\Common\Entity\Style\Style;
|
|
use Box\Spout\Writer\WriterFactory;
|
|
|
|
/**
|
|
* Class WriterWithStyleTest
|
|
*/
|
|
class WriterWithStyleTest extends \PHPUnit_Framework_TestCase
|
|
{
|
|
use TestUsingResource;
|
|
|
|
/** @var Style */
|
|
private $defaultStyle;
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function setUp()
|
|
{
|
|
$this->defaultStyle = (new StyleBuilder())->build();
|
|
}
|
|
|
|
/**
|
|
* @expectedException \Box\Spout\Writer\Exception\WriterNotOpenedException
|
|
*/
|
|
public function testAddRowWithStyleShouldThrowExceptionIfCallAddRowBeforeOpeningWriter()
|
|
{
|
|
$writer = WriterFactory::create(Type::ODS);
|
|
$row = EntityFactory::createRow([
|
|
new Cell('ods--11'),
|
|
new Cell('ods--12'),
|
|
], $this->defaultStyle);
|
|
$writer->addRow($row);
|
|
}
|
|
|
|
/**
|
|
* @expectedException \Box\Spout\Writer\Exception\WriterNotOpenedException
|
|
*/
|
|
public function testAddRowsWithStyleShouldThrowExceptionIfCalledBeforeOpeningWriter()
|
|
{
|
|
$writer = WriterFactory::create(Type::ODS);
|
|
$row = EntityFactory::createRow([
|
|
new Cell('ods--11'),
|
|
new Cell('ods--12'),
|
|
], $this->defaultStyle);
|
|
$writer->addRows([$row]);
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function dataProviderForInvalidStyle()
|
|
{
|
|
return [
|
|
['style'],
|
|
[new \stdClass()],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider dataProviderForInvalidStyle
|
|
*
|
|
* @param \Box\Spout\Writer\Common\Entity\Style\Style $style
|
|
*/
|
|
public function testAddRowWithStyleShouldThrowExceptionIfInvalidStyleGiven($style)
|
|
{
|
|
if (version_compare(PHP_VERSION, '7.0.0') >= 0) {
|
|
$this->expectException(\TypeError::class);
|
|
} else {
|
|
$this->markTestSkipped('PHP > 7.0 only');
|
|
}
|
|
|
|
$fileName = 'test_add_row_with_style_should_throw_exception.ods';
|
|
$this->createGeneratedFolderIfNeeded($fileName);
|
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
|
|
|
$writer = WriterFactory::create(Type::ODS);
|
|
$writer->openToFile($resourcePath);
|
|
$row = EntityFactory::createRow([
|
|
new Cell('ods--11'),
|
|
new Cell('ods--12'),
|
|
], $style);
|
|
$writer->addRow($row);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider dataProviderForInvalidStyle
|
|
*
|
|
* @param \Box\Spout\Writer\Common\Entity\Style\Style $style
|
|
*/
|
|
public function testAddRowsWithStyleShouldThrowExceptionIfInvalidStyleGiven($style)
|
|
{
|
|
if (version_compare(PHP_VERSION, '7.0.0') >= 0) {
|
|
$this->expectException(\TypeError::class);
|
|
} else {
|
|
$this->markTestSkipped('PHP > 7.0 only');
|
|
}
|
|
|
|
$fileName = 'test_add_row_with_style_should_throw_exception.ods';
|
|
$this->createGeneratedFolderIfNeeded($fileName);
|
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
|
|
|
$writer = WriterFactory::create(Type::ODS);
|
|
$writer->openToFile($resourcePath);
|
|
$row = EntityFactory::createRow([
|
|
new Cell('ods--11'),
|
|
new Cell('ods--12'),
|
|
], $style);
|
|
$writer->addRows([[$row]]);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testAddRowWithStyleShouldListAllUsedStylesInCreatedContentXmlFile()
|
|
{
|
|
$fileName = 'test_add_row_with_style_should_list_all_used_fonts.ods';
|
|
$dataRows = [
|
|
['ods--11', 'ods--12'],
|
|
['ods--21', 'ods--22'],
|
|
];
|
|
|
|
$style = (new StyleBuilder())
|
|
->setFontBold()
|
|
->setFontItalic()
|
|
->setFontUnderline()
|
|
->setFontStrikethrough()
|
|
->build();
|
|
$style2 = (new StyleBuilder())
|
|
->setFontSize(15)
|
|
->setFontColor(Color::RED)
|
|
->setFontName('Cambria')
|
|
->setBackgroundColor(Color::GREEN)
|
|
->build();
|
|
|
|
$this->writeToODSFileWithMultipleStyles($dataRows, $fileName, [$style, $style2]);
|
|
|
|
$cellStyleElements = $this->getCellStyleElementsFromContentXmlFile($fileName);
|
|
$this->assertEquals(3, count($cellStyleElements), 'There should be 3 separate cell styles, including the default one.');
|
|
|
|
// Second font should contain data from the first created style
|
|
$customFont1Element = $cellStyleElements[1];
|
|
$this->assertFirstChildHasAttributeEquals('bold', $customFont1Element, 'text-properties', 'fo:font-weight');
|
|
$this->assertFirstChildHasAttributeEquals('italic', $customFont1Element, 'text-properties', 'fo:font-style');
|
|
$this->assertFirstChildHasAttributeEquals('solid', $customFont1Element, 'text-properties', 'style:text-underline-style');
|
|
$this->assertFirstChildHasAttributeEquals('solid', $customFont1Element, 'text-properties', 'style:text-line-through-style');
|
|
|
|
// Third font should contain data from the second created style
|
|
$customFont2Element = $cellStyleElements[2];
|
|
$this->assertFirstChildHasAttributeEquals('15pt', $customFont2Element, 'text-properties', 'fo:font-size');
|
|
$this->assertFirstChildHasAttributeEquals('#' . Color::RED, $customFont2Element, 'text-properties', 'fo:color');
|
|
$this->assertFirstChildHasAttributeEquals('Cambria', $customFont2Element, 'text-properties', 'style:font-name');
|
|
$this->assertFirstChildHasAttributeEquals('#' . Color::GREEN, $customFont2Element, 'table-cell-properties', 'fo:background-color');
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testAddRowWithStyleShouldWriteDefaultStyleSettings()
|
|
{
|
|
$fileName = 'test_add_row_with_style_should_write_default_style_settings.ods';
|
|
$dataRow = ['ods--11', 'ods--12'];
|
|
|
|
$this->writeToODSFile([$dataRow], $fileName, $this->defaultStyle);
|
|
|
|
$textPropertiesElement = $this->getXmlSectionFromStylesXmlFile($fileName, 'style:text-properties');
|
|
$this->assertEquals(Style::DEFAULT_FONT_SIZE . 'pt', $textPropertiesElement->getAttribute('fo:font-size'));
|
|
$this->assertEquals('#' . Style::DEFAULT_FONT_COLOR, $textPropertiesElement->getAttribute('fo:color'));
|
|
$this->assertEquals(Style::DEFAULT_FONT_NAME, $textPropertiesElement->getAttribute('style:font-name'));
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testAddRowWithStyleShouldApplyStyleToCells()
|
|
{
|
|
$fileName = 'test_add_row_with_style_should_apply_style_to_cells.ods';
|
|
$dataRows = [
|
|
['ods--11'],
|
|
['ods--21'],
|
|
['ods--31'],
|
|
];
|
|
$style = (new StyleBuilder())->setFontBold()->build();
|
|
$style2 = (new StyleBuilder())->setFontSize(15)->build();
|
|
|
|
$this->writeToODSFileWithMultipleStyles($dataRows, $fileName, [$style, $style2, null]);
|
|
|
|
$cellDomElements = $this->getCellElementsFromContentXmlFile($fileName);
|
|
$this->assertEquals(3, count($cellDomElements), 'There should be 3 cells with content');
|
|
|
|
$this->assertEquals('ce2', $cellDomElements[0]->getAttribute('table:style-name'));
|
|
$this->assertEquals('ce3', $cellDomElements[1]->getAttribute('table:style-name'));
|
|
$this->assertEquals('ce1', $cellDomElements[2]->getAttribute('table:style-name'));
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testAddRowWithStyleShouldReuseDuplicateStyles()
|
|
{
|
|
$fileName = 'test_add_row_with_style_should_reuse_duplicate_styles.ods';
|
|
$dataRows = [
|
|
['ods--11'],
|
|
['ods--21'],
|
|
];
|
|
$style = (new StyleBuilder())->setFontBold()->build();
|
|
|
|
$this->writeToODSFile($dataRows, $fileName, $style);
|
|
|
|
$cellDomElements = $this->getCellElementsFromContentXmlFile($fileName);
|
|
$this->assertEquals(2, count($cellDomElements), 'There should be 2 cells with content');
|
|
|
|
$this->assertEquals('ce2', $cellDomElements[0]->getAttribute('table:style-name'));
|
|
$this->assertEquals('ce2', $cellDomElements[1]->getAttribute('table:style-name'));
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testAddRowWithStyleShouldAddWrapTextAlignmentInfoInStylesXmlFileIfSpecified()
|
|
{
|
|
$fileName = 'test_add_row_with_style_should_add_wrap_text_alignment.ods';
|
|
$dataRows = [
|
|
['ods--11', 'ods--12'],
|
|
];
|
|
$style = (new StyleBuilder())->setShouldWrapText()->build();
|
|
|
|
$this->writeToODSFile($dataRows, $fileName, $style);
|
|
|
|
$styleElements = $this->getCellStyleElementsFromContentXmlFile($fileName);
|
|
$this->assertEquals(2, count($styleElements), 'There should be 2 styles (default and custom)');
|
|
|
|
$customStyleElement = $styleElements[1];
|
|
$this->assertFirstChildHasAttributeEquals('wrap', $customStyleElement, 'table-cell-properties', 'fo:wrap-option');
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testAddRowWithStyleShouldApplyWrapTextIfCellContainsNewLine()
|
|
{
|
|
$fileName = 'test_add_row_with_style_should_apply_wrap_text_if_new_lines.ods';
|
|
$dataRows = [
|
|
["ods--11\nods--11"],
|
|
];
|
|
|
|
$this->writeToODSFile($dataRows, $fileName, $this->defaultStyle);
|
|
|
|
$styleElements = $this->getCellStyleElementsFromContentXmlFile($fileName);
|
|
$this->assertEquals(2, count($styleElements), 'There should be 2 styles (default and custom)');
|
|
|
|
$customStyleElement = $styleElements[1];
|
|
$this->assertFirstChildHasAttributeEquals('wrap', $customStyleElement, 'table-cell-properties', 'fo:wrap-option');
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testAddBackgroundColor()
|
|
{
|
|
$fileName = 'test_default_background_style.ods';
|
|
$dataRows = [
|
|
['defaultBgColor'],
|
|
];
|
|
|
|
$style = (new StyleBuilder())->setBackgroundColor(Color::WHITE)->build();
|
|
$this->writeToODSFile($dataRows, $fileName, $style);
|
|
|
|
$styleElements = $this->getCellStyleElementsFromContentXmlFile($fileName);
|
|
$this->assertEquals(2, count($styleElements), 'There should be 2 styles (default and custom)');
|
|
|
|
$customStyleElement = $styleElements[1];
|
|
$this->assertFirstChildHasAttributeEquals('#' . Color::WHITE, $customStyleElement, 'table-cell-properties', 'fo:background-color');
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testBorders()
|
|
{
|
|
$fileName = 'test_borders.ods';
|
|
|
|
$dataRows = [
|
|
['row-with-border-bottom-green-thick-solid'],
|
|
['row-without-border'],
|
|
['row-with-border-top-red-thin-dashed'],
|
|
];
|
|
|
|
$borderBottomGreenThickSolid = (new BorderBuilder())
|
|
->setBorderBottom(Color::GREEN, Border::WIDTH_THICK, Border::STYLE_SOLID)->build();
|
|
|
|
$borderTopRedThinDashed = (new BorderBuilder())
|
|
->setBorderTop(Color::RED, Border::WIDTH_THIN, Border::STYLE_DASHED)->build();
|
|
|
|
$styles = [
|
|
(new StyleBuilder())->setBorder($borderBottomGreenThickSolid)->build(),
|
|
(new StyleBuilder())->build(),
|
|
(new StyleBuilder())->setBorder($borderTopRedThinDashed)->build(),
|
|
];
|
|
|
|
$this->writeToODSFileWithMultipleStyles($dataRows, $fileName, $styles);
|
|
|
|
$styleElements = $this->getCellStyleElementsFromContentXmlFile($fileName);
|
|
|
|
$this->assertEquals(3, count($styleElements), 'There should be 3 styles)');
|
|
|
|
// Use reflection for protected members here
|
|
$widthMap = \ReflectionHelper::getStaticValue('Box\Spout\Writer\ODS\Helper\BorderHelper', 'widthMap');
|
|
$styleMap = \ReflectionHelper::getStaticValue('Box\Spout\Writer\ODS\Helper\BorderHelper', 'styleMap');
|
|
|
|
$expectedFirst = sprintf(
|
|
'%s %s #%s',
|
|
$widthMap[Border::WIDTH_THICK],
|
|
$styleMap[Border::STYLE_SOLID],
|
|
Color::GREEN
|
|
);
|
|
|
|
$actualFirst = $styleElements[1]
|
|
->getElementsByTagName('table-cell-properties')
|
|
->item(0)
|
|
->getAttribute('fo:border-bottom');
|
|
|
|
$this->assertEquals($expectedFirst, $actualFirst);
|
|
|
|
$expectedThird = sprintf(
|
|
'%s %s #%s',
|
|
$widthMap[Border::WIDTH_THIN],
|
|
$styleMap[Border::STYLE_DASHED],
|
|
Color::RED
|
|
);
|
|
|
|
$actualThird = $styleElements[2]
|
|
->getElementsByTagName('table-cell-properties')
|
|
->item(0)
|
|
->getAttribute('fo:border-top');
|
|
|
|
$this->assertEquals($expectedThird, $actualThird);
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testSetDefaultRowStyle()
|
|
{
|
|
$fileName = 'test_set_default_row_style.ods';
|
|
$row = EntityFactory::createRow([
|
|
new Cell('ods--11'),
|
|
]);
|
|
$dataRows = [$row];
|
|
|
|
$defaultFontSize = 50;
|
|
$defaultStyle = (new StyleBuilder())->setFontSize($defaultFontSize)->build();
|
|
|
|
$this->writeToODSFileWithDefaultStyle($dataRows, $fileName, $defaultStyle);
|
|
|
|
$textPropertiesElement = $this->getXmlSectionFromStylesXmlFile($fileName, 'style:text-properties');
|
|
$this->assertEquals($defaultFontSize . 'pt', $textPropertiesElement->getAttribute('fo:font-size'));
|
|
}
|
|
|
|
/**
|
|
* @param array $allRows
|
|
* @param string $fileName
|
|
* @param \Box\Spout\Writer\Common\Entity\Style\Style $style
|
|
* @return Writer
|
|
*/
|
|
private function writeToODSFile($allRows, $fileName, $style)
|
|
{
|
|
$arrayToRows = function (array $allRows) use ($style) {
|
|
return array_map(function ($oneRow) use ($style) {
|
|
$row = EntityFactory::createRow(array_map(function ($value) {
|
|
return new Cell($value);
|
|
}, $oneRow), $style);
|
|
|
|
return $row;
|
|
}, $allRows);
|
|
};
|
|
|
|
$this->createGeneratedFolderIfNeeded($fileName);
|
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
|
|
|
/** @var \Box\Spout\Writer\ODS\Writer $writer */
|
|
$writer = WriterFactory::create(Type::ODS);
|
|
|
|
$writer->openToFile($resourcePath);
|
|
$writer->addRows($arrayToRows($allRows));
|
|
$writer->close();
|
|
|
|
return $writer;
|
|
}
|
|
|
|
/**
|
|
* @param array $allRows
|
|
* @param string $fileName
|
|
* @param \Box\Spout\Writer\Common\Entity\Style\Style|null $defaultStyle
|
|
* @return Writer
|
|
*/
|
|
private function writeToODSFileWithDefaultStyle($allRows, $fileName, $defaultStyle)
|
|
{
|
|
$this->createGeneratedFolderIfNeeded($fileName);
|
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
|
|
|
/** @var \Box\Spout\Writer\ODS\Writer $writer */
|
|
$writer = WriterFactory::create(Type::ODS);
|
|
$writer->setDefaultRowStyle($defaultStyle);
|
|
|
|
$writer->openToFile($resourcePath);
|
|
$writer->addRows($allRows);
|
|
$writer->close();
|
|
|
|
return $writer;
|
|
}
|
|
|
|
/**
|
|
* @param array $allRows
|
|
* @param string $fileName
|
|
* @param \Box\Spout\Writer\Common\Entity\Style\Style|null[] $styles
|
|
* @return Writer
|
|
*/
|
|
private function writeToODSFileWithMultipleStyles($allRows, $fileName, $styles)
|
|
{
|
|
// there should be as many rows as there are styles passed in
|
|
$this->assertEquals(count($allRows), count($styles));
|
|
|
|
$this->createGeneratedFolderIfNeeded($fileName);
|
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
|
|
|
/** @var \Box\Spout\Writer\ODS\Writer $writer */
|
|
$writer = WriterFactory::create(Type::ODS);
|
|
|
|
$writer->openToFile($resourcePath);
|
|
for ($i = 0; $i < count($allRows); $i++) {
|
|
$currentRow = $allRows[$i];
|
|
$currentStyle = $styles[$i];
|
|
$row = EntityFactory::createRow(array_map(function ($value) {
|
|
return new Cell($value);
|
|
}, $currentRow), $currentStyle);
|
|
$writer->addRow($row);
|
|
}
|
|
$writer->close();
|
|
|
|
return $writer;
|
|
}
|
|
|
|
/**
|
|
* @param string $fileName
|
|
* @return \DOMNode[]
|
|
*/
|
|
private function getCellElementsFromContentXmlFile($fileName)
|
|
{
|
|
$cellElements = [];
|
|
|
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
|
|
|
$xmlReader = new XMLReader();
|
|
$xmlReader->openFileInZip($resourcePath, 'content.xml');
|
|
|
|
while ($xmlReader->read()) {
|
|
if ($xmlReader->isPositionedOnStartingNode('table:table-cell') && $xmlReader->getAttribute('office:value-type') !== null) {
|
|
$cellElements[] = $xmlReader->expand();
|
|
}
|
|
}
|
|
|
|
$xmlReader->close();
|
|
|
|
return $cellElements;
|
|
}
|
|
|
|
/**
|
|
* @param string $fileName
|
|
* @return \DOMNode[]
|
|
*/
|
|
private function getCellStyleElementsFromContentXmlFile($fileName)
|
|
{
|
|
$cellStyleElements = [];
|
|
|
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
|
|
|
$xmlReader = new XMLReader();
|
|
$xmlReader->openFileInZip($resourcePath, 'content.xml');
|
|
|
|
while ($xmlReader->read()) {
|
|
if ($xmlReader->isPositionedOnStartingNode('style:style') && $xmlReader->getAttribute('style:family') === 'table-cell') {
|
|
$cellStyleElements[] = $xmlReader->expand();
|
|
}
|
|
}
|
|
|
|
$xmlReader->close();
|
|
|
|
return $cellStyleElements;
|
|
}
|
|
|
|
/**
|
|
* @param string $fileName
|
|
* @param string $section
|
|
* @return \DomNode
|
|
*/
|
|
private function getXmlSectionFromStylesXmlFile($fileName, $section)
|
|
{
|
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
|
|
|
$xmlReader = new XMLReader();
|
|
$xmlReader->openFileInZip($resourcePath, 'styles.xml');
|
|
$xmlReader->readUntilNodeFound($section);
|
|
|
|
return $xmlReader->expand();
|
|
}
|
|
|
|
/**
|
|
* @param string $expectedValue
|
|
* @param \DOMNode $parentElement
|
|
* @param string $childTagName
|
|
* @param string $attributeName
|
|
* @return void
|
|
*/
|
|
private function assertFirstChildHasAttributeEquals($expectedValue, $parentElement, $childTagName, $attributeName)
|
|
{
|
|
$this->assertEquals($expectedValue, $parentElement->getElementsByTagName($childTagName)->item(0)->getAttribute($attributeName));
|
|
}
|
|
}
|