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%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 .= '';