diff --git a/README.md b/README.md index a2054be..b961305 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ The writer always generate CSV files encoded in UTF-8, with a BOM. #### Row styling -It is possible to apply some formatting options to a row. Spout supports fonts as well as alignment styles. +It is possible to apply some formatting options to a row. Spout supports fonts, borders as well as alignment styles. ```php use Box\Spout\Common\Type; @@ -150,6 +150,31 @@ $writer->addRowsWithStyle($multipleRows, $style); // style will be applied to al $writer->close(); ``` +Adding borders to a row requires a ```Border``` object. + +```php +use Box\Spout\Common\Type; +use Box\Spout\Writer\WriterFactory; +use Box\Spout\Writer\Style\StyleBuilder; +use Box\Spout\Writer\Style\Border; +use Box\Spout\Writer\Style\BorderPart; + +$border = new Border(); +$border + ->addPart(new BorderPart(Border::BOTTOM, Border::STYLE_SOLID, Border::WIDTH_THICK, Color::ORANGE)) + ->addPart(new BorderPart(Border::RIGHT, Border::STYLE_DASHED, BOrder::WIDTH_THIN, Color::GREEN)); + +$style = (new StyleBuilder()) + ->setBorder($border) + ->build(); + +$writerXlsx = WriterFactory::create(Type::XLSX); +$writerXlsx->openToFile('borders.xlsx'); +$writerXlsx->addRowsWithStyle($multipleRows, $style); +$writerXlsx->close(); + +``` + Unfortunately, Spout does not support all the possible formatting options yet. But you can find the most important ones: Category | Property | API diff --git a/src/Spout/Writer/Exception/InvalidBorderNameException.php b/src/Spout/Writer/Exception/InvalidBorderNameException.php new file mode 100644 index 0000000..989141b --- /dev/null +++ b/src/Spout/Writer/Exception/InvalidBorderNameException.php @@ -0,0 +1,18 @@ + + */ +class BorderHelper +{ + + /** + * ODS border attributes + * + * @var array + */ + public static $odsStyleMap = [ + Border::STYLE_SOLID.'%'.Border::WIDTH_THIN => '0.75pt solid', + Border::STYLE_SOLID.'%'.Border::WIDTH_MEDIUM => '1.75pt solid', + Border::STYLE_SOLID.'%'.Border::WIDTH_THICK => '2.5pt solid', + Border::STYLE_DOTTED.'%'.Border::WIDTH_THIN => '0.75pt dotted', + Border::STYLE_DOTTED.'%'.Border::WIDTH_MEDIUM => '1.75pt dotted', + Border::STYLE_DOTTED.'%'.Border::WIDTH_THICK => '2.5pt dotted', + Border::STYLE_DASHED.'%'.Border::WIDTH_THIN => '0.75pt dashed', + Border::STYLE_DASHED.'%'.Border::WIDTH_MEDIUM => '1.75pt dashed', + Border::STYLE_DASHED.'%'.Border::WIDTH_THICK => '2.5pt dashed', + Border::STYLE_DOUBLE.'%'.Border::WIDTH_THIN => '0.75pt double', + Border::STYLE_DOUBLE.'%'.Border::WIDTH_MEDIUM => '1.75pt double', + Border::STYLE_DOUBLE.'%'.Border::WIDTH_THICK => '2.5pt double', + Border::STYLE_NONE.'%'.Border::WIDTH_THIN => 'none', + Border::STYLE_NONE.'%'.Border::WIDTH_MEDIUM => 'none', + Border::STYLE_NONE.'%'.Border::WIDTH_THICK => 'none', + ]; + + /** + * @param BorderPart $borderPart + * @return string + */ + public static function serializeBorderPart(BorderPart $borderPart) + { + $styleDef = $borderPart->getStyle() .'%' . $borderPart->getWidth(); + $borderStyle = self::$odsStyleMap[$styleDef]; + $colorEl = ($borderPart->getColor() && $borderPart->getStyle() !== Border::STYLE_NONE) + ? '#' . $borderPart->getColor() : ''; + $partEl = sprintf( + 'fo:border-%s="%s"', + $borderPart->getName(), + $borderStyle . ' ' .$colorEl + ); + return $partEl; + } +} diff --git a/src/Spout/Writer/ODS/Helper/StyleHelper.php b/src/Spout/Writer/ODS/Helper/StyleHelper.php index f8b0c4d..54d266b 100644 --- a/src/Spout/Writer/ODS/Helper/StyleHelper.php +++ b/src/Spout/Writer/ODS/Helper/StyleHelper.php @@ -3,6 +3,7 @@ namespace Box\Spout\Writer\ODS\Helper; use Box\Spout\Writer\Common\Helper\AbstractStyleHelper; +use Box\Spout\Writer\Style\BorderPart; /** * Class StyleHelper @@ -256,9 +257,17 @@ EOD; $content .= ''; } + if ($style->shouldApplyBorder()) { + $el = ''; + $borders = array_map(function (BorderPart $borderPart) { + return BorderHelper::serializeBorderPart($borderPart); + }, $style->getBorder()->getParts()); + $content .= sprintf($el, implode(' ', $borders)); + } + $content .= ''; + return $content; } - } diff --git a/src/Spout/Writer/Style/Border.php b/src/Spout/Writer/Style/Border.php new file mode 100644 index 0000000..bfee320 --- /dev/null +++ b/src/Spout/Writer/Style/Border.php @@ -0,0 +1,64 @@ +setParts($borderParts); + } + + /** + * @return array + */ + public function getParts() + { + return $this->parts; + } + + /** + * Set BorderParts + * @param array $parts + */ + public function setParts($parts) + { + unset($this->parts); + foreach ($parts as $part) { + $this->addPart($part); + } + } + + /** + * @param BorderPart $borderPart + * @return self + */ + public function addPart(BorderPart $borderPart) + { + $this->parts[$borderPart->getName()] = $borderPart; + return $this; + } +} diff --git a/src/Spout/Writer/Style/BorderPart.php b/src/Spout/Writer/Style/BorderPart.php new file mode 100644 index 0000000..f2cd9ff --- /dev/null +++ b/src/Spout/Writer/Style/BorderPart.php @@ -0,0 +1,174 @@ +setName($name); + $this->setStyle($style); + $this->setWidth($width); + $this->setColor($color); + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + if (!in_array($name, self::$allowedNames)) { + throw new InvalidBorderNameException($name); + } + $this->name = $name; + } + + /** + * @return string + */ + public function getStyle() + { + return $this->style; + } + + /** + * @param string $style + */ + public function setStyle($style) + { + if (!in_array($style, self::$allowedStyles)) { + throw new InvalidBorderStyleException($style); + } + $this->style = $style; + } + + /** + * @return string + */ + public function getColor() + { + return $this->color; + } + + /** + * @param string $color + */ + public function setColor($color) + { + $this->color = $color; + } + + /** + * @return string + */ + public function getWidth() + { + return $this->width; + } + + /** + * @param string $width + */ + public function setWidth($width) + { + if(!in_array($width, self::$allowedWidth)) { + throw new InvalidBorderWidthException($width); + } + $this->width = $width; + } + + /** + * @return array + */ + public static function getAllowedStyles() + { + return self::$allowedStyles; + } + + /** + * @return array + */ + public static function getAllowedNames() + { + return self::$allowedNames; + } + + /** + * @return array + */ + public static function getAllowedWidth() + { + return self::$allowedWidth; + } +} diff --git a/src/Spout/Writer/Style/Style.php b/src/Spout/Writer/Style/Style.php index 91e9475..b595b3a 100644 --- a/src/Spout/Writer/Style/Style.php +++ b/src/Spout/Writer/Style/Style.php @@ -61,6 +61,16 @@ class Style /** @var bool Whether the wrap text property was set */ protected $hasSetWrapText = false; + /** + * @var Border + */ + protected $border = null; + + /** + * @var bool Whether border properties should be applied + */ + protected $shouldApplyBorder = false; + /** * @return int|null */ @@ -79,6 +89,32 @@ class Style return $this; } + /** + * @return Border + */ + public function getBorder() + { + return $this->border; + } + + /** + * @param Border $border + */ + public function setBorder(Border $border) + { + $this->shouldApplyBorder = true; + $this->border = $border; + return $this; + } + + /** + * @return boolean + */ + public function shouldApplyBorder() + { + return $this->shouldApplyBorder; + } + /** * @return boolean */ @@ -302,6 +338,9 @@ class Style if (!$this->hasSetWrapText && $baseStyle->shouldWrapText()) { $mergedStyle->setShouldWrapText(); } + if (!$this->getBorder() && $baseStyle->shouldApplyBorder()) { + $mergedStyle->setBorder($baseStyle->getBorder()); + } return $mergedStyle; } diff --git a/src/Spout/Writer/Style/StyleBuilder.php b/src/Spout/Writer/Style/StyleBuilder.php index 4619f43..6d6239c 100644 --- a/src/Spout/Writer/Style/StyleBuilder.php +++ b/src/Spout/Writer/Style/StyleBuilder.php @@ -121,6 +121,18 @@ class StyleBuilder return $this; } + /** + * Set a border + * + * @param Border $border + * @return $this + */ + public function setBorder(Border $border) + { + $this->style->setBorder($border); + return $this; + } + /** * Returns the configured style. The style is cached and can be reused. * diff --git a/src/Spout/Writer/XLSX/Helper/BorderHelper.php b/src/Spout/Writer/XLSX/Helper/BorderHelper.php new file mode 100644 index 0000000..be28b12 --- /dev/null +++ b/src/Spout/Writer/XLSX/Helper/BorderHelper.php @@ -0,0 +1,43 @@ + 'thin', + Border::STYLE_SOLID.'%'.Border::WIDTH_MEDIUM => 'medium', + Border::STYLE_SOLID.'%'.Border::WIDTH_THICK => 'thick', + Border::STYLE_DOTTED.'%'.Border::WIDTH_THIN => 'dotted', + Border::STYLE_DOTTED.'%'.Border::WIDTH_MEDIUM => 'dotted', + Border::STYLE_DOTTED.'%'.Border::WIDTH_THICK => 'dotted', + Border::STYLE_DASHED.'%'.Border::WIDTH_THIN => 'dashed', + Border::STYLE_DASHED.'%'.Border::WIDTH_MEDIUM => 'mediumDashed', + Border::STYLE_DASHED.'%'.Border::WIDTH_THICK => 'mediumDashed', + Border::STYLE_DOUBLE.'%'.Border::WIDTH_THIN => 'double', + Border::STYLE_DOUBLE.'%'.Border::WIDTH_MEDIUM => 'double', + Border::STYLE_DOUBLE.'%'.Border::WIDTH_THICK => 'double', + Border::STYLE_NONE.'%'.Border::WIDTH_THIN => 'none', + Border::STYLE_NONE.'%'.Border::WIDTH_MEDIUM => 'none', + Border::STYLE_NONE.'%'.Border::WIDTH_THICK => 'none', + ]; + + /** + * @param BorderPart $borderPart + * @return string + */ + public static function serializeBorderPart(BorderPart $borderPart) + { + $styleDef = $borderPart->getStyle() .'%' . $borderPart->getWidth(); + $borderStyle = self::$xlsxStyleMap[$styleDef]; + + $colorEl = $borderPart->getColor() ? sprintf('', $borderPart->getColor()) : ''; + $partEl = sprintf( + '<%s style="%s">%s', $borderPart->getName(), $borderStyle, $colorEl, $borderPart->getName() + ); + return $partEl.PHP_EOL; + } +} \ No newline at end of file diff --git a/src/Spout/Writer/XLSX/Helper/StyleHelper.php b/src/Spout/Writer/XLSX/Helper/StyleHelper.php index f3da2b5..ed09ef8 100644 --- a/src/Spout/Writer/XLSX/Helper/StyleHelper.php +++ b/src/Spout/Writer/XLSX/Helper/StyleHelper.php @@ -100,17 +100,23 @@ EOD; */ protected function getBordersSectionContent() { - return << - - - - - - - - -EOD; + $content = ''; + /** @var \Box\Spout\Writer\Style\Style $style */ + foreach ($this->getRegisteredStyles() as $style) { + $border = $style->getBorder(); + if ($border) { + $content .= ''; + foreach ($border->getParts() as $part) { + /** @var $part \Box\Spout\Writer\Style\BorderPart */ + $content .= BorderHelper::serializeBorderPart($part); + } + $content .= ''; + } else { + $content .= ''; + } + } + $content .= ''; + return $content; } /** @@ -139,12 +145,16 @@ EOD; $content = ''; foreach ($registeredStyles as $style) { - $content .= 'getId() . '" fillId="0" borderId="' . $style->getId() . '" xfId="0"'; if ($style->shouldApplyFont()) { $content .= ' applyFont="1"'; } + if ($style->shouldApplyBorder()) { + $content .= ' applyBorder="1"'; + } + if ($style->shouldWrapText()) { $content .= ' applyAlignment="1">'; $content .= '';