Compare commits

..
15 Commits
Author SHA1 Message Date
Adrien Loison b4ace972e7 Update README.md 2015-08-22 19:39:43 -07:00
Adrien Loison c428daff5a Update README.md 2015-08-22 18:14:33 -07:00
Adrien Loison 5908443583 Update README.md 2015-08-22 18:13:36 -07:00
Adrien Loison 050672755d Update README.md 2015-08-22 12:37:24 -07:00
Adrien Loison e1928eb68a Merge pull request #93 from box/font_color
Add support for font color
2015-08-21 21:04:32 -07:00
Adrien Loison 9467b5a810 Add support for font color 2015-08-21 20:58:21 -07:00
Adrien Loison 2699ffbcae Merge pull request #92 from box/invalid_sheet_names_contd
Detection of invalid sheet name - continued
2015-08-21 16:51:02 -07:00
Adrien Loison 3559bc8834 Detection of invalid sheet name - continued
Invalid names can also be triggered by:
- character ":"
- single quote at the beginning of the name
- single quote at the end of the name

Introduced a StringHelper, wrapping multibyte strings functions
2015-08-21 16:44:13 -07:00
Adrien Loison 506d682e74 Merge pull request #91 from box/invalid_sheet_name_detection
Detection of invalid sheet name
2015-08-21 15:36:25 -07:00
Adrien Loison 7efab5576d Detection of invalid sheet name
Based on Excel requirements:
 - it should not be blank
 - it should not exceed 31 characters
 - it should not contain these characters: \ / ? * [ or ]
 - it should be unique
2015-08-21 15:21:36 -07:00
Adrien Loison 366f121eb0 Update README.md 2015-08-21 10:58:11 -07:00
Adrien Loison f8d0ac2682 Merge pull request #89 from box/fix_ios_issue
Fix OfficeImportErrorDomain issue on iOS
2015-08-20 20:32:41 -07:00
Adrien Loison 82377403ff Fix OfficeImportErrorDomain issue on iOS 2015-08-20 20:28:34 -07:00
Adrien Loison e0c84f77b1 Merge pull request #87 from box/use_travis_cache
Use Travis cache
2015-08-18 21:56:51 -07:00
Adrien Loison b78227c370 Use Travis cache
This is to avoid re-downloading all dependencies for every build.
2015-08-18 21:52:25 -07:00
15 changed files with 599 additions and 65 deletions

No files matched your search

+4
View File
@@ -7,6 +7,10 @@ php:
- 7.0
- hhvm
cache:
directories:
- $HOME/.composer/cache
install:
- composer install --no-interaction --prefer-source
+21
View File
@@ -10,6 +10,7 @@
Spout is a PHP library to read and write CSV and XLSX files, in a fast and scalable way.
Contrary to other file readers or writers, it is capable of processing very large files while keeping the memory usage really low (less than 10MB).
Join the community and come discuss about Spout: [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
## Installation
@@ -153,10 +154,12 @@ It is possible to apply some formatting options to a row. Spout supports fonts a
use Box\Spout\Common\Type;
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Writer\Style\StyleBuilder;
use Box\Spout\Writer\Style\Color;
$style = (new StyleBuilder())
->setFontBold()
->setFontSize(15)
->setFontColor(Color::BLUE)
->setShouldWrapText()
->build();
@@ -180,6 +183,7 @@ Font | Bold | `StyleBuilder::setFontBold()`
| Strikethrough | `StyleBuilder::setFontStrikethrough()`
| Font name | `StyleBuilder::setFontName('Arial')`
| Font size | `StyleBuilder::setFontSize(14)`
| Font color | `StyleBuilder::setFontSize(Color::BLUE)`<br>`StyleBuilder::setFontSize(Color::rgb(0, 128, 255))`
Alignment | Wrap text | `StyleBuilder::setShouldWrapText()`
@@ -199,6 +203,11 @@ $writer->setShouldUseInlineStrings(true); // default (and recommended) value
$writer->setShouldUseInlineStrings(false); // will use shared strings
```
> ##### Note on Apple Numbers and iOS support
>
> Apple's products (Numbers and the iOS previewer) don't support inline strings and display empty cells instead. Therefore, if these platforms need to be supported, make sure to use shared strings!
#### New sheet creation
It is also possible to change the behavior of the writer when the maximum number of rows (1,048,576) have been written in the current sheet:
@@ -258,6 +267,16 @@ $sheet = $writer->getCurrentSheet();
$sheetName = $sheet->setName('My custom name');
```
> Please note that Excel has some restrictions on the sheet's name:
> * it must not be blank
> * it must not exceed 31 characters
> * it must not contain these characters: \ / ? * : [ or ]
> * it must not start or end with a single quote
> * it must be unique
>
> Handling these restrictions is the developer's responsibility. Spout does not try to automatically change the sheet's name, as one may rely on this name to be exactly what was passed in.
### Fluent interface
Because fluent interfaces are great, you can use them with Spout:
@@ -319,6 +338,8 @@ So the larger the file would be, the more memory would be consumed, preventing y
Need to contact us directly? Email oss@box.com and be sure to include the name of this project in the subject.
You can also ask questions, submit new features ideas or discuss about Spout in the chat room:<br>
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
## Copyright and License
Generated
+49 -49
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "3c0d36250693792d14bc19f14250fec6",
"hash": "0866be323931eaa6e9431b3bbf0a817a",
"packages": [],
"packages-dev": [
{
@@ -585,16 +585,16 @@
},
{
"name": "phpspec/prophecy",
"version": "v1.4.1",
"version": "v1.5.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373"
"reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
"reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7",
"reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7",
"shasum": ""
},
"require": {
@@ -641,20 +641,20 @@
"spy",
"stub"
],
"time": "2015-04-27 22:15:08"
"time": "2015-08-13 10:07:40"
},
{
"name": "phpunit/php-code-coverage",
"version": "2.1.9",
"version": "2.2.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "5bd48b86cd282da411bb80baac1398ce3fefac41"
"reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5bd48b86cd282da411bb80baac1398ce3fefac41",
"reference": "5bd48b86cd282da411bb80baac1398ce3fefac41",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2d7c03c0e4e080901b8f33b2897b0577be18a13c",
"reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c",
"shasum": ""
},
"require": {
@@ -662,7 +662,7 @@
"phpunit/php-file-iterator": "~1.3",
"phpunit/php-text-template": "~1.2",
"phpunit/php-token-stream": "~1.3",
"sebastian/environment": "~1.0",
"sebastian/environment": "^1.3.2",
"sebastian/version": "~1.0"
},
"require-dev": {
@@ -677,7 +677,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
"dev-master": "2.2.x-dev"
}
},
"autoload": {
@@ -703,7 +703,7 @@
"testing",
"xunit"
],
"time": "2015-07-26 12:54:47"
"time": "2015-08-04 03:42:39"
},
{
"name": "phpunit/php-file-iterator",
@@ -836,16 +836,16 @@
},
{
"name": "phpunit/php-token-stream",
"version": "1.4.3",
"version": "1.4.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9"
"reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/7a9b0969488c3c54fd62b4d504b3ec758fd005d9",
"reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3ab72c62e550370a6cd5dc873e1a04ab57562f5b",
"reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b",
"shasum": ""
},
"require": {
@@ -881,20 +881,20 @@
"keywords": [
"tokenizer"
],
"time": "2015-06-19 03:43:16"
"time": "2015-08-16 08:51:00"
},
{
"name": "phpunit/phpunit",
"version": "4.7.7",
"version": "4.8.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "9b97f9d807b862c2de2a36e86690000801c85724"
"reference": "9b7417edaf28059ea63d86be941e6004dbfcc0cc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9b97f9d807b862c2de2a36e86690000801c85724",
"reference": "9b97f9d807b862c2de2a36e86690000801c85724",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9b7417edaf28059ea63d86be941e6004dbfcc0cc",
"reference": "9b7417edaf28059ea63d86be941e6004dbfcc0cc",
"shasum": ""
},
"require": {
@@ -904,7 +904,7 @@
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=5.3.3",
"phpspec/prophecy": "~1.3,>=1.3.1",
"phpspec/prophecy": "^1.3.1",
"phpunit/php-code-coverage": "~2.1",
"phpunit/php-file-iterator": "~1.4",
"phpunit/php-text-template": "~1.2",
@@ -912,7 +912,7 @@
"phpunit/phpunit-mock-objects": "~2.3",
"sebastian/comparator": "~1.1",
"sebastian/diff": "~1.2",
"sebastian/environment": "~1.2",
"sebastian/environment": "~1.3",
"sebastian/exporter": "~1.2",
"sebastian/global-state": "~1.0",
"sebastian/version": "~1.0",
@@ -927,7 +927,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.7.x-dev"
"dev-master": "4.8.x-dev"
}
},
"autoload": {
@@ -953,24 +953,24 @@
"testing",
"xunit"
],
"time": "2015-07-13 11:28:34"
"time": "2015-08-19 09:20:57"
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "2.3.6",
"version": "2.3.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42"
"reference": "5e2645ad49d196e020b85598d7c97e482725786a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/18dfbcb81d05e2296c0bcddd4db96cade75e6f42",
"reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5e2645ad49d196e020b85598d7c97e482725786a",
"reference": "5e2645ad49d196e020b85598d7c97e482725786a",
"shasum": ""
},
"require": {
"doctrine/instantiator": "~1.0,>=1.0.2",
"doctrine/instantiator": "^1.0.2",
"php": ">=5.3.3",
"phpunit/php-text-template": "~1.2",
"sebastian/exporter": "~1.2"
@@ -1009,7 +1009,7 @@
"mock",
"xunit"
],
"time": "2015-07-10 06:54:24"
"time": "2015-08-19 09:14:08"
},
{
"name": "scrutinizer/ocular",
@@ -1165,16 +1165,16 @@
},
{
"name": "sebastian/environment",
"version": "1.3.0",
"version": "1.3.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "4fe0a44cddd8cc19583a024bdc7374eb2fef0b87"
"reference": "6324c907ce7a52478eeeaede764f48733ef5ae44"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4fe0a44cddd8cc19583a024bdc7374eb2fef0b87",
"reference": "4fe0a44cddd8cc19583a024bdc7374eb2fef0b87",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44",
"reference": "6324c907ce7a52478eeeaede764f48733ef5ae44",
"shasum": ""
},
"require": {
@@ -1211,7 +1211,7 @@
"environment",
"hhvm"
],
"time": "2015-07-26 06:42:57"
"time": "2015-08-03 06:14:51"
},
{
"name": "sebastian/exporter",
@@ -1420,16 +1420,16 @@
},
{
"name": "symfony/console",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "8cf484449130cabfd98dcb4694ca9945802a21ed"
"reference": "d6cf02fe73634c96677e428f840704bfbcaec29e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/8cf484449130cabfd98dcb4694ca9945802a21ed",
"reference": "8cf484449130cabfd98dcb4694ca9945802a21ed",
"url": "https://api.github.com/repos/symfony/Console/zipball/d6cf02fe73634c96677e428f840704bfbcaec29e",
"reference": "d6cf02fe73634c96677e428f840704bfbcaec29e",
"shasum": ""
},
"require": {
@@ -1473,11 +1473,11 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2015-07-09 16:07:40"
"time": "2015-07-28 15:18:12"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
@@ -1535,7 +1535,7 @@
},
{
"name": "symfony/process",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
@@ -1584,16 +1584,16 @@
},
{
"name": "symfony/yaml",
"version": "v2.7.2",
"version": "v2.7.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "4bfbe0ed3909bfddd75b70c094391ec1f142f860"
"reference": "71340e996171474a53f3d29111d046be4ad8a0ff"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/4bfbe0ed3909bfddd75b70c094391ec1f142f860",
"reference": "4bfbe0ed3909bfddd75b70c094391ec1f142f860",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/71340e996171474a53f3d29111d046be4ad8a0ff",
"reference": "71340e996171474a53f3d29111d046be4ad8a0ff",
"shasum": ""
},
"require": {
@@ -1629,7 +1629,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-07-01 11:25:50"
"time": "2015-07-28 14:07:07"
}
],
"aliases": [],
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace Box\Spout\Common\Helper;
/**
* Class StringHelper
* This class provides helper functions to work with strings and multibyte strings.
*
* @codeCoverageIgnore
*
* @package Box\Spout\Common\Helper
*/
class StringHelper
{
/** @var bool Whether the mbstring extension is loaded */
protected $hasMbstringSupport;
/**
*
*/
public function __construct()
{
$this->hasMbstringSupport = extension_loaded('mbstring');
}
/**
* Returns the length of the given string.
* It uses the multi-bytes function is available.
* @see strlen
* @see mb_strlen
*
* @param string $string
* @return int
*/
public function getStringLength($string)
{
return $this->hasMbstringSupport ? mb_strlen($string) : strlen($string);
}
/**
* Returns the position of the first occurrence of the given character/substring within the given string.
* It uses the multi-bytes function is available.
* @see strpos
* @see mb_strpos
*
* @param string $char Needle
* @param string $string Haystack
* @return int Char/substring's first occurrence position within the string if found (starts at 0) or -1 if not found
*/
public function getCharFirstOccurrencePosition($char, $string)
{
$position = $this->hasMbstringSupport ? mb_strpos($string, $char) : strpos($string, $char);
return ($position !== false) ? $position : -1;
}
/**
* Returns the position of the last occurrence of the given character/substring within the given string.
* It uses the multi-bytes function is available.
* @see strrpos
* @see mb_strrpos
*
* @param string $char Needle
* @param string $string Haystack
* @return int Char/substring's last occurrence position within the string if found (starts at 0) or -1 if not found
*/
public function getCharLastOccurrencePosition($char, $string)
{
$position = $this->hasMbstringSupport ? mb_strrpos($string, $char) : strrpos($string, $char);
return ($position !== false) ? $position : -1;
}
}
@@ -0,0 +1,12 @@
<?php
namespace Box\Spout\Writer\Exception;
/**
* Class InvalidColorException
*
* @package Box\Spout\Writer\Exception
*/
class InvalidColorException extends WriterException
{
}
@@ -0,0 +1,12 @@
<?php
namespace Box\Spout\Writer\Exception;
/**
* Class InvalidSheetNameException
*
* @package Box\Spout\Writer\Exception
*/
class InvalidSheetNameException extends WriterException
{
}
+76
View File
@@ -0,0 +1,76 @@
<?php
namespace Box\Spout\Writer\Style;
use Box\Spout\Writer\Exception\InvalidColorException;
/**
* Class Color
* This class provides constants and functions to work with colors
*
* @package Box\Spout\Writer\Style
*/
class Color
{
/** Standard colors - based on Office Online */
const BLACK = 'FF000000';
const WHITE = 'FFFFFFFF';
const RED = 'FFFF0000';
const DARK_RED = 'FFC00000';
const ORANGE = 'FFFFC000';
const YELLOW = 'FFFFFF00';
const LIGHT_GREEN = 'FF92D040';
const GREEN = 'FF00B050';
const LIGHT_BLUE = 'FF00B0E0';
const BLUE = 'FF0070C0';
const DARK_BLUE = 'FF002060';
const PURPLE = 'FF7030A0';
/**
* Returns an ARGB color from R, G and B values
* Alpha is assumed to always be 1
*
* @param int $red Red component, 0 - 255
* @param int $green Green component, 0 - 255
* @param int $blue Blue component, 0 - 255
* @return string ARGB color
*/
public static function rgb($red, $green, $blue)
{
self::throwIfInvalidColorComponentValue($red);
self::throwIfInvalidColorComponentValue($green);
self::throwIfInvalidColorComponentValue($blue);
return strtoupper(
'FF' .
self::convertColorComponentToHex($red) .
self::convertColorComponentToHex($green) .
self::convertColorComponentToHex($blue)
);
}
/**
* Throws an exception is the color component value is outside of bounds (0 - 255)
*
* @param int $colorComponent
* @return void
* @throws \Box\Spout\Writer\Exception\InvalidColorException
*/
protected static function throwIfInvalidColorComponentValue($colorComponent)
{
if (!is_int($colorComponent) || $colorComponent < 0 || $colorComponent > 255) {
throw new InvalidColorException("The RGB components must be between 0 and 255. Received: $colorComponent");
}
}
/**
* Converts the color component to its corresponding hexadecimal value
*
* @param int $colorComponent Color component, 0 - 255
* @return string Corresponding hexadecimal value, with a leading 0 if needed. E.g "0f", "2d"
*/
protected static function convertColorComponentToHex($colorComponent)
{
return str_pad(dechex($colorComponent), 2, '0', 0);
}
}
+31
View File
@@ -12,6 +12,7 @@ class Style
{
/** Default font values */
const DEFAULT_FONT_SIZE = 11;
const DEFAULT_FONT_COLOR = Color::BLACK;
const DEFAULT_FONT_NAME = 'Arial';
/** @var int|null Style ID */
@@ -42,6 +43,11 @@ class Style
/** @var bool Whether the font size property was set */
protected $hasSetFontSize = false;
/** @var string Font color */
protected $fontColor = self::DEFAULT_FONT_COLOR;
/** @var bool Whether the font color property was set */
protected $hasSetFontColor = false;
/** @var string Font name */
protected $fontName = self::DEFAULT_FONT_NAME;
/** @var bool Whether the font name property was set */
@@ -169,6 +175,28 @@ class Style
return $this;
}
/**
* @return string
*/
public function getFontColor()
{
return $this->fontColor;
}
/**
* Sets the font color.
*
* @param string $fontColor ARGB color (@see Color)
* @return Style
*/
public function setFontColor($fontColor)
{
$this->fontColor = $fontColor;
$this->hasSetFontColor = true;
$this->shouldApplyFont = true;
return $this;
}
/**
* @return string
*/
@@ -265,6 +293,9 @@ class Style
if (!$this->hasSetFontSize && $baseStyle->getFontSize() !== self::DEFAULT_FONT_SIZE) {
$mergedStyle->setFontSize($baseStyle->getFontSize());
}
if (!$this->hasSetFontSize && $baseStyle->getFontColor() !== self::DEFAULT_FONT_COLOR) {
$mergedStyle->setFontColor($baseStyle->getFontColor());
}
if (!$this->hasSetFontName && $baseStyle->getFontName() !== self::DEFAULT_FONT_NAME) {
$mergedStyle->setFontName($baseStyle->getFontName());
}
+12
View File
@@ -77,6 +77,18 @@ class StyleBuilder
return $this;
}
/**
* Sets the font color.
*
* @param string $fontColor ARGB color (@see Color)
* @return StyleBuilder
*/
public function setFontColor($fontColor)
{
$this->style->setFontColor($fontColor);
return $this;
}
/**
* Sets the font name.
*
@@ -180,10 +180,10 @@ EOD;
*/
protected function createCoreXmlFile()
{
$createdDate = (new \DateTime())->format('c');
$createdDate = (new \DateTime())->format(\DateTime::W3C);
$coreXmlFileContents = <<<EOD
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dcterms:created xsi:type="dcterms:W3CDTF">$createdDate</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">$createdDate</dcterms:modified>
<cp:revision>0</cp:revision>
+5 -2
View File
@@ -150,9 +150,14 @@ EOD;
{
$content = ' <fonts count="' . count($this->styleIdToStyleMappingTable) . '">' . PHP_EOL;
/** @var \Box\Spout\Writer\Style\Style $style */
foreach ($this->styleIdToStyleMappingTable as $style) {
$content .= ' <font>' . PHP_EOL;
$content .= ' <sz val="' . $style->getFontSize() . '"/>' . PHP_EOL;
$content .= ' <color rgb="' . $style->getFontColor() . '"/>' . PHP_EOL;
$content .= ' <name val="' . $style->getFontName() . '"/>' . PHP_EOL;
if ($style->isFontBold()) {
$content .= ' <b/>' . PHP_EOL;
}
@@ -166,8 +171,6 @@ EOD;
$content .= ' <strike/>' . PHP_EOL;
}
$content .= ' <sz val="' . $style->getFontSize() . '"/>' . PHP_EOL;
$content .= ' <name val="' . $style->getFontName() . '"/>' . PHP_EOL;
$content .= ' </font>' . PHP_EOL;
}
+103 -2
View File
@@ -2,9 +2,12 @@
namespace Box\Spout\Writer\XLSX;
use Box\Spout\Common\Helper\StringHelper;
use Box\Spout\Writer\Exception\InvalidSheetNameException;
/**
* Class Sheet
* Represents a worksheet within a XLSX file
* External representation of a worksheet within a XLSX file
*
* @package Box\Spout\Writer\XLSX
*/
@@ -12,19 +15,32 @@ class Sheet
{
const DEFAULT_SHEET_NAME_PREFIX = 'Sheet';
/** Sheet name should not exceed 31 characters */
const MAX_LENGTH_SHEET_NAME = 31;
/** @var array Invalid characters that cannot be contained in the sheet name */
private static $INVALID_CHARACTERS_IN_SHEET_NAME = ['\\', '/', '?', '*', ':', '[', ']'];
/** @var array Associative array [SHEET_INDEX] => [SHEET_NAME] keeping track of sheets' name to enforce uniqueness */
protected static $SHEETS_NAME_USED = [];
/** @var int Index of the sheet, based on order of creation (zero-based) */
protected $index;
/** @var string Name of the sheet */
protected $name;
/** @var \Box\Spout\Common\Helper\StringHelper */
protected $stringHelper;
/**
* @param int $sheetIndex Index of the sheet, based on order of creation (zero-based)
*/
public function __construct($sheetIndex)
{
$this->index = $sheetIndex;
$this->name = self::DEFAULT_SHEET_NAME_PREFIX . ($sheetIndex + 1);
$this->stringHelper = new StringHelper();
$this->setName(self::DEFAULT_SHEET_NAME_PREFIX . ($sheetIndex + 1));
}
/**
@@ -44,12 +60,97 @@ class Sheet
}
/**
* Sets the name of the sheet. Note that Excel has some restrictions on the name:
* - it should not be blank
* - it should not exceed 31 characters
* - it should not contain these characters: \ / ? * : [ or ]
* - it should be unique
*
* @param string $name Name of the sheet
* @return Sheet
* @throws \Box\Spout\Writer\Exception\InvalidSheetNameException If the sheet's name is invalid.
*/
public function setName($name)
{
if (!$this->isNameValid($name)) {
$errorMessage = "The sheet's name is invalid. It did not meet at least one of these requirements:\n";
$errorMessage .= " - It should not be blank\n";
$errorMessage .= " - It should not exceed 31 characters\n";
$errorMessage .= " - It should not contain these characters: \\ / ? * : [ or ]\n";
$errorMessage .= " - It should be unique";
throw new InvalidSheetNameException($errorMessage);
}
$this->name = $name;
self::$SHEETS_NAME_USED[$this->index] = $name;
return $this;
}
/**
* Returns whether the given sheet's name is valid.
* @see Sheet::setName for validity rules.
*
* @param string $name
* @return bool TRUE if the name is valid, FALSE otherwise.
*/
protected function isNameValid($name)
{
if (!is_string($name)) {
return false;
}
$nameLength = $this->stringHelper->getStringLength($name);
return (
$nameLength > 0 &&
$nameLength <= self::MAX_LENGTH_SHEET_NAME &&
!$this->doesContainInvalidCharacters($name) &&
$this->isNameUnique($name) &&
!$this->doesStartOrEndWithSingleQuote($name)
);
}
/**
* Returns whether the given name contains at least one invalid character.
* @see Sheet::$INVALID_CHARACTERS_IN_SHEET_NAME for the full list.
*
* @param string $name
* @return bool TRUE if the name contains invalid characters, FALSE otherwise.
*/
protected function doesContainInvalidCharacters($name)
{
return (str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name);
}
/**
* Returns whether the given name starts or ends with a single quote
*
* @param string $name
* @return bool TRUE if the name starts or ends with a single quote, FALSE otherwise.
*/
protected function doesStartOrEndWithSingleQuote($name)
{
$startsWithSingleQuote = ($this->stringHelper->getCharFirstOccurrencePosition('\'', $name) === 0);
$endsWithSingleQuote = ($this->stringHelper->getCharLastOccurrencePosition('\'', $name) === ($this->stringHelper->getStringLength($name) - 1));
return ($startsWithSingleQuote || $endsWithSingleQuote);
}
/**
* Returns whether the given name is unique.
*
* @param string $name
* @return bool TRUE if the name is unique, FALSE otherwise.
*/
protected function isNameUnique($name)
{
foreach (self::$SHEETS_NAME_USED as $sheetIndex => $sheetName) {
if ($sheetIndex !== $this->index && $sheetName === $name) {
return false;
}
}
return true;
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
namespace Box\Spout\Writer\Style;
/**
* Class ColorTest
*
* @package Box\Spout\Writer\Style
*/
class ColorTest extends \PHPUnit_Framework_TestCase
{
/**
* @return array
*/
public function dataProviderForTestRGB()
{
return [
[0, 0, 0, Color::BLACK],
[255, 255, 255, Color::WHITE],
[255, 0, 0, Color::RED],
[192, 0, 0, Color::DARK_RED],
[255, 192, 0, Color::ORANGE],
[255, 255, 0, Color::YELLOW],
[146, 208, 64, Color::LIGHT_GREEN],
[0, 176, 80, Color::GREEN],
[0, 176, 224, Color::LIGHT_BLUE],
[0, 112, 192, Color::BLUE],
[0, 32, 96, Color::DARK_BLUE],
[112, 48, 160, Color::PURPLE],
[0, 0, 0, 'FF000000'],
[255, 255, 255, 'FFFFFFFF'],
[255, 0, 0, 'FFFF0000'],
[0, 128, 0, 'FF008000'],
[0, 255, 0, 'FF00FF00'],
[0, 0, 255, 'FF0000FF'],
[128, 22, 43, 'FF80162B'],
];
}
/**
* @dataProvider dataProviderForTestRGB
*
* @param int $red
* @param int $green
* @param int $blue
* @param string $expectedColor
* @return void
*/
public function testRGB($red, $green, $blue, $expectedColor)
{
$color = Color::rgb($red, $green, $blue);
$this->assertEquals($expectedColor, $color);
}
/**
* @return array
*/
public function dataProviderForTestRGBAInvalidColorComponents()
{
return [
[-1, 0, 0],
[0, -1, 0],
[0, 0, -1],
[999, 0, 0],
[0, 999, 0],
[0, 0, 999],
[null, 0, 0],
[0, null, 0],
[0, 0, null],
['1', 0, 0],
[0, '1', 0],
[0, 0, '1'],
[true, 0, 0],
[0, true, 0],
[0, 0, true],
];
}
/**
* @dataProvider dataProviderForTestRGBAInvalidColorComponents
* @expectedException \Box\Spout\Writer\Exception\InvalidColorException
*
* @param int $red
* @param int $green
* @param int $blue
* @return void
*/
public function testRGBInvalidColorComponents($red, $green, $blue)
{
Color::rgb($red, $green, $blue);
}
}
+99 -7
View File
@@ -20,7 +20,7 @@ class SheetTest extends \PHPUnit_Framework_TestCase
*/
public function testGetSheetIndex()
{
$sheets = $this->writeDataAndReturnSheets('test_get_sheet_index.xlsx');
$sheets = $this->writeDataToMulitpleSheetsAndReturnSheets('test_get_sheet_index.xlsx');
$this->assertEquals(2, count($sheets), '2 sheets should have been created');
$this->assertEquals(0, $sheets[0]->getIndex(), 'The first sheet should be index 0');
@@ -32,7 +32,7 @@ class SheetTest extends \PHPUnit_Framework_TestCase
*/
public function testGetSheetName()
{
$sheets = $this->writeDataAndReturnSheets('test_get_sheet_name.xlsx');
$sheets = $this->writeDataToMulitpleSheetsAndReturnSheets('test_get_sheet_name.xlsx');
$this->assertEquals(2, count($sheets), '2 sheets should have been created');
$this->assertEquals('Sheet1', $sheets[0]->getName(), 'Invalid name for the first sheet');
@@ -45,27 +45,118 @@ class SheetTest extends \PHPUnit_Framework_TestCase
public function testSetSheetNameShouldCreateSheetWithCustomName()
{
$fileName = 'test_set_name_should_create_sheet_with_custom_name.xlsx';
$customSheetName = 'CustomName';
$this->writeDataAndReturnSheetWithCustomName($fileName, $customSheetName);
$this->assertSheetNameEquals($customSheetName, $fileName, "The sheet name should have been changed to '$customSheetName'");
}
/**
* @return array
*/
public function dataProviderForInvalidSheetNames()
{
return [
[null],
[21],
[''],
['this title exceeds the 31 characters limit'],
['Illegal \\'],
['Illegal /'],
['Illegal ?'],
['Illegal *'],
['Illegal :'],
['Illegal ['],
['Illegal ]'],
['\'Illegal start'],
['Illegal end\''],
];
}
/**
* @dataProvider dataProviderForInvalidSheetNames
* @expectedException \Box\Spout\Writer\Exception\InvalidSheetNameException
*
* @param string $customSheetName
* @return void
*/
public function testSetSheetNameShouldThrowOnInvalidName($customSheetName)
{
$fileName = 'test_set_name_with_invalid_name_should_throw_exception.xlsx';
$this->writeDataAndReturnSheetWithCustomName($fileName, $customSheetName);
}
/**
* @return void
*/
public function testSetSheetNameShouldNotThrowWhenSettingSameNameAsCurrentOne()
{
$fileName = 'test_set_name_with_same_as_current.xlsx';
$this->createGeneratedFolderIfNeeded($fileName);
$resourcePath = $this->getGeneratedResourcePath($fileName);
$writer = WriterFactory::create(Type::XLSX);
$writer->openToFile($resourcePath);
$customSheetName = 'CustomName';
$customSheetName = 'Sheet name';
$sheet = $writer->getCurrentSheet();
$sheet->setName($customSheetName);
$sheet->setName($customSheetName);
$writer->addRow(['xlsx--11', 'xlsx--12']);
$writer->close();
$this->assertSheetNameEquals($customSheetName, $resourcePath, "The sheet name should have been changed to '$customSheetName'");
$this->assertSheetNameEquals($customSheetName, $fileName, "The sheet name should have been changed to '$customSheetName'");
}
/**
* @expectedException \Box\Spout\Writer\Exception\InvalidSheetNameException
* @return void
*/
public function testSetSheetNameShouldThrowWhenNameIsAlreadyUsed()
{
$fileName = 'test_set_name_with_non_unique_name.xlsx';
$this->createGeneratedFolderIfNeeded($fileName);
$resourcePath = $this->getGeneratedResourcePath($fileName);
$writer = WriterFactory::create(Type::XLSX);
$writer->openToFile($resourcePath);
$customSheetName = 'Sheet name';
$sheet = $writer->getCurrentSheet();
$sheet->setName($customSheetName);
$writer->addNewSheetAndMakeItCurrent();
$sheet = $writer->getCurrentSheet();
$sheet->setName($customSheetName);
}
/**
* @param string $fileName
* @param string $sheetName
* @return Sheet
*/
private function writeDataAndReturnSheetWithCustomName($fileName, $sheetName)
{
$this->createGeneratedFolderIfNeeded($fileName);
$resourcePath = $this->getGeneratedResourcePath($fileName);
$writer = WriterFactory::create(Type::XLSX);
$writer->openToFile($resourcePath);
$sheet = $writer->getCurrentSheet();
$sheet->setName($sheetName);
$writer->addRow(['xlsx--11', 'xlsx--12']);
$writer->close();
}
/**
* @param string $fileName
* @return Sheet[]
*/
private function writeDataAndReturnSheets($fileName)
private function writeDataToMulitpleSheetsAndReturnSheets($fileName)
{
$this->createGeneratedFolderIfNeeded($fileName);
$resourcePath = $this->getGeneratedResourcePath($fileName);
@@ -85,12 +176,13 @@ class SheetTest extends \PHPUnit_Framework_TestCase
/**
* @param string $expectedName
* @param string $resourcePath
* @param string $fileName
* @param string $message
* @return void
*/
private function assertSheetNameEquals($expectedName, $resourcePath, $message = '')
private function assertSheetNameEquals($expectedName, $fileName, $message = '')
{
$resourcePath = $this->getGeneratedResourcePath($fileName);
$pathToWorkbookFile = $resourcePath . '#xl/workbook.xml';
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
@@ -4,6 +4,8 @@ namespace Box\Spout\Writer\XLSX;
use Box\Spout\Common\Type;
use Box\Spout\TestUsingResource;
use Box\Spout\Writer\Style\Color;
use Box\Spout\Writer\Style\Style;
use Box\Spout\Writer\Style\StyleBuilder;
use Box\Spout\Writer\WriterFactory;
@@ -110,6 +112,7 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase
->build();
$style2 = (new StyleBuilder())
->setFontSize(15)
->setFontColor(Color::RED)
->setFontName('Arial')
->build();
@@ -123,24 +126,27 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase
// First font should be the default one
$defaultFontElement = $fontElements->item(0);
$this->assertChildrenNumEquals(2, $defaultFontElement, 'The default font should only have 2 properties.');
$this->assertChildrenNumEquals(3, $defaultFontElement, 'The default font should only have 3 properties.');
$this->assertFirstChildHasAttributeEquals((string) Writer::DEFAULT_FONT_SIZE, $defaultFontElement, 'sz', 'val');
$this->assertFirstChildHasAttributeEquals(Style::DEFAULT_FONT_COLOR, $defaultFontElement, 'color', 'rgb');
$this->assertFirstChildHasAttributeEquals(Writer::DEFAULT_FONT_NAME, $defaultFontElement, 'name', 'val');
// Second font should contain data from the first created style
$secondFontElement = $fontElements->item(1);
$this->assertChildrenNumEquals(6, $secondFontElement, 'The font should only have 6 properties (4 custom styles + 2 default styles).');
$this->assertChildrenNumEquals(7, $secondFontElement, 'The font should only have 7 properties (4 custom styles + 3 default styles).');
$this->assertChildExists($secondFontElement, 'b');
$this->assertChildExists($secondFontElement, 'i');
$this->assertChildExists($secondFontElement, 'u');
$this->assertChildExists($secondFontElement, 'strike');
$this->assertFirstChildHasAttributeEquals((string) Writer::DEFAULT_FONT_SIZE, $secondFontElement, 'sz', 'val');
$this->assertFirstChildHasAttributeEquals(Style::DEFAULT_FONT_COLOR, $secondFontElement, 'color', 'rgb');
$this->assertFirstChildHasAttributeEquals(Writer::DEFAULT_FONT_NAME, $secondFontElement, 'name', 'val');
// Third font should contain data from the second created style
$thirdFontElement = $fontElements->item(2);
$this->assertChildrenNumEquals(2, $thirdFontElement, 'The font should only have 2 properties.');
$this->assertChildrenNumEquals(3, $thirdFontElement, 'The font should only have 3 properties.');
$this->assertFirstChildHasAttributeEquals('15', $thirdFontElement, 'sz', 'val');
$this->assertFirstChildHasAttributeEquals(Color::RED, $thirdFontElement, 'color', 'rgb');
$this->assertFirstChildHasAttributeEquals('Arial', $thirdFontElement, 'name', 'val');
}