diff --git a/CHANGELOG.md b/CHANGELOG.md index 290474a1..a7082cf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Changed +- Better enforcement of value modification to match specified datatype when using setValueExplicit() +- Relax validation of merge cells to allow merge for a single cell reference [Issue #2776](https://github.com/PHPOffice/PhpSpreadsheet/issues/2776) - Memory and speed improvements, particularly for the Cell Collection, and the Writers. See [the Discussion section on github](https://github.com/PHPOffice/PhpSpreadsheet/discussions/2821) for details of performance across versions diff --git a/composer.lock b/composer.lock index 7ad1d7eb..ee0d79d1 100644 --- a/composer.lock +++ b/composer.lock @@ -2142,16 +2142,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.6.3", + "version": "1.7.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "6128620b98292e0b69ea6d799871d77163681c8e" + "reference": "cadad14ac63d8a432e01ae89ac5d0ff6fc3b16ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6128620b98292e0b69ea6d799871d77163681c8e", - "reference": "6128620b98292e0b69ea6d799871d77163681c8e", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cadad14ac63d8a432e01ae89ac5d0ff6fc3b16ab", + "reference": "cadad14ac63d8a432e01ae89ac5d0ff6fc3b16ab", "shasum": "" }, "require": { @@ -2177,7 +2177,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.6.3" + "source": "https://github.com/phpstan/phpstan/tree/1.7.7" }, "funding": [ { @@ -2197,7 +2197,7 @@ "type": "tidelift" } ], - "time": "2022-04-28T11:27:53+00:00" + "time": "2022-05-31T13:58:21+00:00" }, { "name": "phpstan/phpstan-phpunit", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 200dd26b..974bb3d0 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -95,11 +95,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Calculation/Calculation.php - - - message: "#^Offset 'type' does not exist on array\\|null\\.$#" - count: 3 - path: src/PhpSpreadsheet/Calculation/Calculation.php - - message: "#^Offset 'value' does not exist on array\\|null\\.$#" count: 5 @@ -765,11 +760,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php - - - message: "#^PHPDoc tag @var for constant PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Subtotal\\:\\:CALL_FUNCTIONS with type array\\ is not subtype of value array\\{1\\: array\\{'PhpOffice…', 'average'\\}, 2\\: array\\{'PhpOffice…', 'COUNT'\\}, 3\\: array\\{'PhpOffice…', 'COUNTA'\\}, 4\\: array\\{'PhpOffice…', 'max'\\}, 5\\: array\\{'PhpOffice…', 'min'\\}, 6\\: array\\{'PhpOffice…', 'product'\\}, 7\\: array\\{'PhpOffice…', 'STDEV'\\}, 8\\: array\\{'PhpOffice…', 'STDEVP'\\}, \\.\\.\\.\\}\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Statistical\\:\\:MAXIFS\\(\\) should return float but returns float\\|string\\|null\\.$#" count: 1 @@ -1120,31 +1110,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Cell/Coordinate.php - - - message: "#^Parameter \\#1 \\$textValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:substring\\(\\) expects string, string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Cell/DataType.php - - - - message: "#^Parameter \\#1 \\$angle of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowAngle\\(\\) expects int, int\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/Axis.php - - - - message: "#^Parameter \\#1 \\$blur of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowBlur\\(\\) expects float, float\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/Axis.php - - - - message: "#^Parameter \\#1 \\$distance of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowDistance\\(\\) expects float, float\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/Axis.php - - - - message: "#^Parameter \\#2 \\$alpha of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Axis\\:\\:setShadowColor\\(\\) expects int, int\\|string given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/Axis.php - - message: "#^Call to an undefined method object\\:\\:render\\(\\)\\.$#" count: 1 @@ -1205,21 +1170,11 @@ parameters: count: 2 path: src/PhpSpreadsheet/Chart/DataSeries.php - - - message: "#^Parameter \\#1 \\$angle of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setShadowAngle\\(\\) expects int, int\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/GridLines.php - - message: "#^Parameter \\#1 \\$color of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setGlowColor\\(\\) expects string, string\\|null given\\.$#" count: 1 path: src/PhpSpreadsheet/Chart/GridLines.php - - - message: "#^Parameter \\#1 \\$distance of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setShadowDistance\\(\\) expects float, float\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/GridLines.php - - message: "#^Parameter \\#2 \\$alpha of method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\GridLines\\:\\:setGlowColor\\(\\) expects int, int\\|null given\\.$#" count: 1 @@ -1310,36 +1265,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Chart/Properties.php - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getTrueAlpha\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:getTrueAlpha\\(\\) has parameter \\$alpha with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has no return type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has parameter \\$alpha with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has parameter \\$color with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Properties\\:\\:setColorProperties\\(\\) has parameter \\$colorType with no type specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Chart/Properties.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Chart\\\\Renderer\\\\JpGraph\\:\\:formatDataSetLabels\\(\\) has no return type specified\\.$#" count: 1 @@ -4285,21 +4210,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Writer/Xls/Parser.php - - - message: "#^Offset 'left' does not exist on non\\-empty\\-array\\|string\\.$#" - count: 6 - path: src/PhpSpreadsheet/Writer/Xls/Parser.php - - - - message: "#^Offset 'right' does not exist on non\\-empty\\-array\\|string\\.$#" - count: 5 - path: src/PhpSpreadsheet/Writer/Xls/Parser.php - - - - message: "#^Offset 'value' does not exist on non\\-empty\\-array\\|string\\.$#" - count: 7 - path: src/PhpSpreadsheet/Writer/Xls/Parser.php - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Writer\\\\Xls\\\\Parser\\:\\:\\$spreadsheet has no type specified\\.$#" count: 1 @@ -4512,12 +4422,12 @@ parameters: - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, array\\|int\\|string given\\.$#" - count: 8 + count: 2 path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, array\\|int\\|string\\|null given\\.$#" - count: 2 + count: 1 path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php - @@ -4560,11 +4470,6 @@ parameters: count: 2 path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php - - - message: "#^Part \\$xAxis\\-\\>getShadowProperty\\('effect'\\) \\(array\\|int\\|string\\|null\\) of encapsed string cannot be cast to string\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php - - message: "#^Part \\$xAxis\\-\\>getShadowProperty\\(\\['color', 'type'\\]\\) \\(array\\|int\\|string\\|null\\) of encapsed string cannot be cast to string\\.$#" count: 1 @@ -4800,11 +4705,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - message: "#^Negated boolean expression is always false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - message: "#^Parameter \\#2 \\$content of method XMLWriter\\:\\:writeElement\\(\\) expects string\\|null, int\\|string given\\.$#" count: 1 @@ -4845,8 +4745,3 @@ parameters: count: 1 path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - message: "#^Strict comparison using \\=\\=\\= between false and true will always evaluate to false\\.$#" - count: 2 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e97e1ce8..0979eaed 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -24,3 +24,8 @@ parameters: - '~^Instantiated class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot) not found\.$~' - '~^Call to method .*\(\) on an unknown class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot)\.$~' - '~^Access to property .* on an unknown class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot)\.$~' + + # Some issues in Xls/Parser between 1.6.3 and 1.7.7 + - + message: "#^Offset '(left|right|value)' does not exist on (non-empty-array\\|string|array\\|null)\\.$#" + path: src/PhpSpreadsheet/Writer/Xls/Parser.php diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 65105f8e..2bba56f6 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -4021,6 +4021,7 @@ class Calculation // Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand $isOperandOrFunction = (bool) preg_match($regexpMatchString, substr($formula, $index), $match); + $expectingOperatorCopy = $expectingOperator; if ($opCharacter == '-' && !$expectingOperator) { // Is it a negation instead of a minus? // Put a negation on the stack $stack->push('Unary Operator', '~'); @@ -4173,7 +4174,7 @@ class Calculation $this->branchPruner->incrementDepth(); $stack->push('Brace', '(', null); ++$index; - } elseif ($isOperandOrFunction && !$expectingOperator) { // do we now have a function/variable/number? + } elseif ($isOperandOrFunction && !$expectingOperatorCopy) { // do we now have a function/variable/number? $expectingOperator = true; $expectingOperand = false; $val = $match[1]; @@ -4398,13 +4399,17 @@ class Calculation // If we're expecting an operator, but only have a space between the previous and next operands (and both are // Cell References) then we have an INTERSECTION operator + $countOutputMinus1 = count($output) - 1; if ( ($expectingOperator) && + array_key_exists($countOutputMinus1, $output) && + is_array($output[$countOutputMinus1]) && + array_key_exists('type', $output[$countOutputMinus1]) && ( (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) && - ($output[count($output) - 1]['type'] === 'Cell Reference') || + ($output[$countOutputMinus1]['type'] === 'Cell Reference') || (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', substr($formula, $index), $match)) && - ($output[count($output) - 1]['type'] === 'Defined Name' || $output[count($output) - 1]['type'] === 'Value') + ($output[$countOutputMinus1]['type'] === 'Defined Name' || $output[$countOutputMinus1]['type'] === 'Value') ) ) { while ( diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php b/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php index 74f95dec..336bc690 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php @@ -54,7 +54,6 @@ class Subtotal ); } - /** @var callable[] */ private const CALL_FUNCTIONS = [ 1 => [Statistical\Averages::class, 'average'], // 1 and 101 [Statistical\Counts::class, 'COUNT'], // 2 and 102 diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index 76d5e86d..2005694d 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -202,6 +202,11 @@ class Cell * * @param mixed $value Value * @param string $dataType Explicit data type, see DataType::TYPE_* + * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this + * method, then it is your responsibility as an end-user developer to validate that the value and + * the datatype match. + * If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype + * that you specify. * * @return Cell */ @@ -210,7 +215,7 @@ class Cell // set the value according to data type switch ($dataType) { case DataType::TYPE_NULL: - $this->value = $value; + $this->value = null; break; case DataType::TYPE_STRING2: diff --git a/src/PhpSpreadsheet/Cell/DataType.php b/src/PhpSpreadsheet/Cell/DataType.php index 0f7efe2a..390dcde5 100644 --- a/src/PhpSpreadsheet/Cell/DataType.php +++ b/src/PhpSpreadsheet/Cell/DataType.php @@ -48,7 +48,7 @@ class DataType * * @param null|RichText|string $textValue Value to sanitize to an Excel string * - * @return null|RichText|string Sanitized value + * @return RichText|string Sanitized value */ public static function checkString($textValue) { @@ -58,7 +58,7 @@ class DataType } // string must never be longer than 32,767 characters, truncate if necessary - $textValue = StringHelper::substring($textValue, 0, 32767); + $textValue = StringHelper::substring((string) $textValue, 0, 32767); // we require that newline is represented as "\n" in core, not as "\r\n" or "\r" $textValue = str_replace(["\r\n", "\r"], "\n", $textValue); diff --git a/src/PhpSpreadsheet/Chart/Axis.php b/src/PhpSpreadsheet/Chart/Axis.php index 089ebdb9..69607216 100644 --- a/src/PhpSpreadsheet/Chart/Axis.php +++ b/src/PhpSpreadsheet/Chart/Axis.php @@ -89,25 +89,7 @@ class Axis extends Properties * * @var mixed[] */ - private $shadowProperties = [ - 'presets' => self::SHADOW_PRESETS_NOSHADOW, - 'effect' => null, - 'color' => [ - 'type' => self::EXCEL_COLOR_TYPE_STANDARD, - 'value' => 'black', - 'alpha' => 40, - ], - 'size' => [ - 'sx' => null, - 'sy' => null, - 'kx' => null, - ], - 'blur' => null, - 'direction' => null, - 'distance' => null, - 'algn' => null, - 'rotWithShape' => null, - ]; + private $shadowProperties = Properties::PRESETS_OPTIONS[0]; /** * Glow Properties. @@ -340,23 +322,37 @@ class Axis extends Properties return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrow]['size'], 'len'); } + /** + * @param mixed $value + */ + public function setShadowProperty(string $propertyName, $value): self + { + if ($propertyName === 'color' && is_array($value)) { + $this->setShadowColor($value['value'], $value['alpha'], $value['type']); + } else { + $this->shadowProperties[$propertyName] = $value; + } + + return $this; + } + /** * Set Shadow Properties. * * @param int $shadowPresets * @param string $colorValue * @param string $colorType - * @param string $colorAlpha - * @param float $blur - * @param int $angle - * @param float $distance + * @param null|int|string $colorAlpha + * @param null|float $blur + * @param null|int $angle + * @param null|float $distance */ public function setShadowProperties($shadowPresets, $colorValue = null, $colorType = null, $colorAlpha = null, $blur = null, $angle = null, $distance = null): void { $this->setShadowPresetsProperties((int) $shadowPresets) ->setShadowColor( $colorValue ?? $this->shadowProperties['color']['value'], - $colorAlpha ?? (int) $this->shadowProperties['color']['alpha'], + (int) ($colorAlpha ?? $this->shadowProperties['color']['alpha']), $colorType ?? $this->shadowProperties['color']['type'] ) ->setShadowBlur($blur) @@ -379,6 +375,8 @@ class Axis extends Properties return $this; } + private const SHADOW_ARRAY_KEYS = ['size', 'color']; + /** * Set Shadow Properties from Mapped Values. * @@ -391,12 +389,10 @@ class Axis extends Properties $base_reference = $reference; foreach ($propertiesMap as $property_key => $property_val) { if (is_array($property_val)) { - if ($reference === null) { + if (in_array($property_key, self::SHADOW_ARRAY_KEYS, true)) { $reference = &$this->shadowProperties[$property_key]; - } else { - $reference = &$reference[$property_key]; + $this->setShadowPropertiesMapValues($property_val, $reference); } - $this->setShadowPropertiesMapValues($property_val, $reference); } else { if ($base_reference === null) { $this->shadowProperties[$property_key] = $property_val; @@ -412,9 +408,9 @@ class Axis extends Properties /** * Set Shadow Color. * - * @param string $color - * @param int $alpha - * @param string $alphaType + * @param null|string $color + * @param null|int $alpha + * @param null|string $alphaType * * @return $this */ @@ -428,14 +424,14 @@ class Axis extends Properties /** * Set Shadow Blur. * - * @param float $blur + * @param null|float $blur * * @return $this */ private function setShadowBlur($blur) { if ($blur !== null) { - $this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur); + $this->shadowProperties['blur'] = $blur; } return $this; @@ -444,14 +440,14 @@ class Axis extends Properties /** * Set Shadow Angle. * - * @param int $angle + * @param null|float|int $angle * * @return $this */ private function setShadowAngle($angle) { - if ($angle !== null) { - $this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle); + if (is_numeric($angle)) { + $this->shadowProperties['direction'] = $angle; } return $this; @@ -460,14 +456,14 @@ class Axis extends Properties /** * Set Shadow Distance. * - * @param float $distance + * @param null|float $distance * * @return $this */ private function setShadowDistance($distance) { if ($distance !== null) { - $this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance); + $this->shadowProperties['distance'] = $distance; } return $this; @@ -489,9 +485,9 @@ class Axis extends Properties * Set Glow Properties. * * @param float $size - * @param string $colorValue - * @param int $colorAlpha - * @param string $colorType + * @param null|string $colorValue + * @param null|int $colorAlpha + * @param null|string $colorType */ public function setGlowProperties($size, $colorValue = null, $colorAlpha = null, $colorType = null): void { @@ -508,7 +504,7 @@ class Axis extends Properties * * @param array|string $property * - * @return string + * @return null|string */ public function getGlowProperty($property) { @@ -525,7 +521,7 @@ class Axis extends Properties private function setGlowSize($size) { if ($size !== null) { - $this->glowProperties['size'] = $this->getExcelPointsWidth($size); + $this->glowProperties['size'] = $size; } return $this; @@ -555,7 +551,7 @@ class Axis extends Properties public function setSoftEdges($size): void { if ($size !== null) { - $softEdges['size'] = (string) $this->getExcelPointsWidth($size); + $this->softEdges['size'] = $size; } } diff --git a/src/PhpSpreadsheet/Chart/GridLines.php b/src/PhpSpreadsheet/Chart/GridLines.php index 84af3ada..ad254c8c 100644 --- a/src/PhpSpreadsheet/Chart/GridLines.php +++ b/src/PhpSpreadsheet/Chart/GridLines.php @@ -45,25 +45,7 @@ class GridLines extends Properties ], ]; - private $shadowProperties = [ - 'presets' => self::SHADOW_PRESETS_NOSHADOW, - 'effect' => null, - 'color' => [ - 'type' => self::EXCEL_COLOR_TYPE_STANDARD, - 'value' => 'black', - 'alpha' => 85, - ], - 'size' => [ - 'sx' => null, - 'sy' => null, - 'kx' => null, - ], - 'blur' => null, - 'direction' => null, - 'distance' => null, - 'algn' => null, - 'rotWithShape' => null, - ]; + private $shadowProperties = Properties::PRESETS_OPTIONS[0]; private $glowProperties = [ 'size' => null, @@ -202,6 +184,18 @@ class GridLines extends Properties ->setGlowColor($colorValue, $colorAlpha, $colorType); } + /** + * Get Glow Property. + * + * @param array|string $property + * + * @return null|string + */ + public function getGlowProperty($property) + { + return $this->getArrayElementsValue($this->glowProperties, $property); + } + /** * Get Glow Color Property. * @@ -233,7 +227,7 @@ class GridLines extends Properties */ private function setGlowSize($size) { - $this->glowProperties['size'] = $this->getExcelPointsWidth((float) $size); + $this->glowProperties['size'] = $size; return $this; } @@ -253,7 +247,7 @@ class GridLines extends Properties $this->glowProperties['color']['value'] = (string) $color; } if ($alpha !== null) { - $this->glowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha); + $this->glowProperties['color']['alpha'] = (int) $alpha; } if ($colorType !== null) { $this->glowProperties['color']['type'] = (string) $colorType; @@ -275,16 +269,27 @@ class GridLines extends Properties return $this->getLineStyleArrowSize($this->lineProperties['style']['arrow'][$arrowSelector]['size'], $propertySelector); } + /** + * @param mixed $value + */ + public function setShadowProperty(string $propertyName, $value): self + { + $this->activateObject(); + $this->shadowProperties[$propertyName] = $value; + + return $this; + } + /** * Set Shadow Properties. * * @param int $presets * @param string $colorValue * @param string $colorType - * @param string $colorAlpha - * @param string $blur - * @param int $angle - * @param float $distance + * @param null|float|int|string $colorAlpha + * @param null|float $blur + * @param null|int $angle + * @param null|float $distance */ public function setShadowProperties($presets, $colorValue = null, $colorType = null, $colorAlpha = null, $blur = null, $angle = null, $distance = null): void { @@ -292,10 +297,10 @@ class GridLines extends Properties ->setShadowPresetsProperties((int) $presets) ->setShadowColor( $colorValue ?? $this->shadowProperties['color']['value'], - $colorAlpha === null ? (int) $this->shadowProperties['color']['alpha'] : $this->getTrueAlpha($colorAlpha), + $colorAlpha === null ? (int) $this->shadowProperties['color']['alpha'] : (int) $colorAlpha, $colorType ?? $this->shadowProperties['color']['type'] ) - ->setShadowBlur((float) $blur) + ->setShadowBlur($blur) ->setShadowAngle($angle) ->setShadowDistance($distance); } @@ -315,6 +320,8 @@ class GridLines extends Properties return $this; } + private const SHADOW_ARRAY_KEYS = ['size', 'color']; + /** * Set Shadow Properties Values. * @@ -327,12 +334,10 @@ class GridLines extends Properties $base_reference = $reference; foreach ($propertiesMap as $property_key => $property_val) { if (is_array($property_val)) { - if ($reference === null) { + if (in_array($property_key, self::SHADOW_ARRAY_KEYS, true)) { $reference = &$this->shadowProperties[$property_key]; - } else { - $reference = &$reference[$property_key]; + $this->setShadowPropertiesMapValues($property_val, $reference); } - $this->setShadowPropertiesMapValues($property_val, $reference); } else { if ($base_reference === null) { $this->shadowProperties[$property_key] = $property_val; @@ -360,7 +365,7 @@ class GridLines extends Properties $this->shadowProperties['color']['value'] = (string) $color; } if ($alpha !== null) { - $this->shadowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha); + $this->shadowProperties['color']['alpha'] = (int) $alpha; } if ($colorType !== null) { $this->shadowProperties['color']['type'] = (string) $colorType; @@ -372,14 +377,14 @@ class GridLines extends Properties /** * Set Shadow Blur. * - * @param float $blur + * @param ?float $blur * * @return $this */ private function setShadowBlur($blur) { if ($blur !== null) { - $this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur); + $this->shadowProperties['blur'] = $blur; } return $this; @@ -388,14 +393,14 @@ class GridLines extends Properties /** * Set Shadow Angle. * - * @param int $angle + * @param null|float|int|string $angle * * @return $this */ private function setShadowAngle($angle) { - if ($angle !== null) { - $this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle); + if (is_numeric($angle)) { + $this->shadowProperties['direction'] = $angle; } return $this; @@ -404,14 +409,14 @@ class GridLines extends Properties /** * Set Shadow Distance. * - * @param float $distance + * @param ?float $distance * * @return $this */ private function setShadowDistance($distance) { if ($distance !== null) { - $this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance); + $this->shadowProperties['distance'] = $distance; } return $this; @@ -434,11 +439,11 @@ class GridLines extends Properties * * @param float $size */ - public function setSoftEdgesSize($size): void + public function setSoftEdges($size): void { if ($size !== null) { $this->activateObject(); - $this->softEdges['size'] = (string) $this->getExcelPointsWidth($size); + $this->softEdges['size'] = $size; } } diff --git a/src/PhpSpreadsheet/Chart/Properties.php b/src/PhpSpreadsheet/Chart/Properties.php index 9f1a5ce0..01a83915 100644 --- a/src/PhpSpreadsheet/Chart/Properties.php +++ b/src/PhpSpreadsheet/Chart/Properties.php @@ -111,6 +111,10 @@ abstract class Properties const SHADOW_PRESETS_PERSPECTIVE_LOWER_RIGHT = 22; const SHADOW_PRESETS_PERSPECTIVE_LOWER_LEFT = 23; + const POINTS_WIDTH_MULTIPLIER = 12700; + const ANGLE_MULTIPLIER = 60000; // direction and size-kx size-ky + const PERCENTAGE_MULTIPLIER = 100000; // size sx and sy + /** * @param float $width * @@ -118,30 +122,61 @@ abstract class Properties */ protected function getExcelPointsWidth($width) { - return $width * 12700; + return $width * self::POINTS_WIDTH_MULTIPLIER; + } + + public static function pointsToXml(float $width): string + { + return (string) (int) ($width * self::POINTS_WIDTH_MULTIPLIER); + } + + public static function xmlToPoints(string $width): float + { + return ((float) $width) / self::POINTS_WIDTH_MULTIPLIER; + } + + public static function angleToXml(float $angle): string + { + return (string) (int) ($angle * self::ANGLE_MULTIPLIER); + } + + public static function xmlToAngle(string $angle): float + { + return ((float) $angle) / self::ANGLE_MULTIPLIER; + } + + public static function tenthOfPercentToXml(float $value): string + { + return (string) (int) ($value * self::PERCENTAGE_MULTIPLIER); + } + + public static function xmlToTenthOfPercent(string $value): float + { + return ((float) $value) / self::PERCENTAGE_MULTIPLIER; + } + + public static function alphaToXml(int $alpha): string + { + return (string) (100 - $alpha) . '000'; } /** - * @param float $angle - * - * @return float + * @param float|int|string $alpha */ - protected function getExcelPointsAngle($angle) + public static function alphaFromXml($alpha): int { - return $angle * 60000; + return 100 - ((int) $alpha / 1000); } - protected function getTrueAlpha($alpha) - { - return (string) 100 - $alpha . '000'; - } - - protected function setColorProperties($color, $alpha, $colorType) + /** + * @param null|float|int|string $alpha + */ + protected function setColorProperties(?string $color, $alpha, ?string $colorType): array { return [ - 'type' => (string) $colorType, - 'value' => (string) $color, - 'alpha' => (string) $this->getTrueAlpha($alpha), + 'type' => $colorType, + 'value' => $color, + 'alpha' => (int) $alpha, ]; } @@ -162,196 +197,217 @@ abstract class Properties return $sizes[$arraySelector][$arrayKaySelector]; } + protected const PRESETS_OPTIONS = [ + //NONE + 0 => [ + 'presets' => self::SHADOW_PRESETS_NOSHADOW, + 'effect' => null, + 'color' => [ + 'type' => self::EXCEL_COLOR_TYPE_STANDARD, + 'value' => 'black', + 'alpha' => 40, + ], + 'size' => [ + 'sx' => null, + 'sy' => null, + 'kx' => null, + 'ky' => null, + ], + 'blur' => null, + 'direction' => null, + 'distance' => null, + 'algn' => null, + 'rotWithShape' => null, + ], + //OUTER + 1 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 2700000 / self::ANGLE_MULTIPLIER, + 'algn' => 'tl', + 'rotWithShape' => '0', + ], + 2 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 5400000 / self::ANGLE_MULTIPLIER, + 'algn' => 't', + 'rotWithShape' => '0', + ], + 3 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 8100000 / self::ANGLE_MULTIPLIER, + 'algn' => 'tr', + 'rotWithShape' => '0', + ], + 4 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'algn' => 'l', + 'rotWithShape' => '0', + ], + 5 => [ + 'effect' => 'outerShdw', + 'size' => [ + 'sx' => 102000 / self::PERCENTAGE_MULTIPLIER, + 'sy' => 102000 / self::PERCENTAGE_MULTIPLIER, + ], + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'algn' => 'ctr', + 'rotWithShape' => '0', + ], + 6 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 10800000 / self::ANGLE_MULTIPLIER, + 'algn' => 'r', + 'rotWithShape' => '0', + ], + 7 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 18900000 / self::ANGLE_MULTIPLIER, + 'algn' => 'bl', + 'rotWithShape' => '0', + ], + 8 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 16200000 / self::ANGLE_MULTIPLIER, + 'rotWithShape' => '0', + ], + 9 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 13500000 / self::ANGLE_MULTIPLIER, + 'algn' => 'br', + 'rotWithShape' => '0', + ], + //INNER + 10 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 2700000 / self::ANGLE_MULTIPLIER, + ], + 11 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 5400000 / self::ANGLE_MULTIPLIER, + ], + 12 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 8100000 / self::ANGLE_MULTIPLIER, + ], + 13 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + ], + 14 => [ + 'effect' => 'innerShdw', + 'blur' => 114300 / self::POINTS_WIDTH_MULTIPLIER, + ], + 15 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 10800000 / self::ANGLE_MULTIPLIER, + ], + 16 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 18900000 / self::ANGLE_MULTIPLIER, + ], + 17 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 16200000 / self::ANGLE_MULTIPLIER, + ], + 18 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 13500000 / self::ANGLE_MULTIPLIER, + ], + //perspective + 19 => [ + 'effect' => 'outerShdw', + 'blur' => 152400 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 317500 / self::POINTS_WIDTH_MULTIPLIER, + 'size' => [ + 'sx' => 90000 / self::PERCENTAGE_MULTIPLIER, + 'sy' => -19000 / self::PERCENTAGE_MULTIPLIER, + ], + 'direction' => 5400000 / self::ANGLE_MULTIPLIER, + 'rotWithShape' => '0', + ], + 20 => [ + 'effect' => 'outerShdw', + 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 18900000 / self::ANGLE_MULTIPLIER, + 'size' => [ + 'sy' => 23000 / self::PERCENTAGE_MULTIPLIER, + 'kx' => -1200000 / self::ANGLE_MULTIPLIER, + ], + 'algn' => 'bl', + 'rotWithShape' => '0', + ], + 21 => [ + 'effect' => 'outerShdw', + 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 13500000 / self::ANGLE_MULTIPLIER, + 'size' => [ + 'sy' => 23000 / self::PERCENTAGE_MULTIPLIER, + 'kx' => 1200000 / self::ANGLE_MULTIPLIER, + ], + 'algn' => 'br', + 'rotWithShape' => '0', + ], + 22 => [ + 'effect' => 'outerShdw', + 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 12700 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 2700000 / self::ANGLE_MULTIPLIER, + 'size' => [ + 'sy' => -23000 / self::PERCENTAGE_MULTIPLIER, + 'kx' => -800400 / self::ANGLE_MULTIPLIER, + ], + 'algn' => 'bl', + 'rotWithShape' => '0', + ], + 23 => [ + 'effect' => 'outerShdw', + 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 12700 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 8100000 / self::ANGLE_MULTIPLIER, + 'size' => [ + 'sy' => -23000 / self::PERCENTAGE_MULTIPLIER, + 'kx' => 800400 / self::ANGLE_MULTIPLIER, + ], + 'algn' => 'br', + 'rotWithShape' => '0', + ], + ]; + protected function getShadowPresetsMap($presetsOption) { - $presets_options = [ - //OUTER - 1 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '2700000', - 'algn' => 'tl', - 'rotWithShape' => '0', - ], - 2 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '5400000', - 'algn' => 't', - 'rotWithShape' => '0', - ], - 3 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '8100000', - 'algn' => 'tr', - 'rotWithShape' => '0', - ], - 4 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'algn' => 'l', - 'rotWithShape' => '0', - ], - 5 => [ - 'effect' => 'outerShdw', - 'size' => [ - 'sx' => '102000', - 'sy' => '102000', - ], - 'blur' => '63500', - 'distance' => '38100', - 'algn' => 'ctr', - 'rotWithShape' => '0', - ], - 6 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '10800000', - 'algn' => 'r', - 'rotWithShape' => '0', - ], - 7 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '18900000', - 'algn' => 'bl', - 'rotWithShape' => '0', - ], - 8 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '16200000', - 'rotWithShape' => '0', - ], - 9 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '13500000', - 'algn' => 'br', - 'rotWithShape' => '0', - ], - //INNER - 10 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '2700000', - ], - 11 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '5400000', - ], - 12 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '8100000', - ], - 13 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - ], - 14 => [ - 'effect' => 'innerShdw', - 'blur' => '114300', - ], - 15 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '10800000', - ], - 16 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '18900000', - ], - 17 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '16200000', - ], - 18 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '13500000', - ], - //perspective - 19 => [ - 'effect' => 'outerShdw', - 'blur' => '152400', - 'distance' => '317500', - 'size' => [ - 'sx' => '90000', - 'sy' => '-19000', - ], - 'direction' => '5400000', - 'rotWithShape' => '0', - ], - 20 => [ - 'effect' => 'outerShdw', - 'blur' => '76200', - 'direction' => '18900000', - 'size' => [ - 'sy' => '23000', - 'kx' => '-1200000', - ], - 'algn' => 'bl', - 'rotWithShape' => '0', - ], - 21 => [ - 'effect' => 'outerShdw', - 'blur' => '76200', - 'direction' => '13500000', - 'size' => [ - 'sy' => '23000', - 'kx' => '1200000', - ], - 'algn' => 'br', - 'rotWithShape' => '0', - ], - 22 => [ - 'effect' => 'outerShdw', - 'blur' => '76200', - 'distance' => '12700', - 'direction' => '2700000', - 'size' => [ - 'sy' => '-23000', - 'kx' => '-800400', - ], - 'algn' => 'bl', - 'rotWithShape' => '0', - ], - 23 => [ - 'effect' => 'outerShdw', - 'blur' => '76200', - 'distance' => '12700', - 'direction' => '8100000', - 'size' => [ - 'sy' => '-23000', - 'kx' => '800400', - ], - 'algn' => 'br', - 'rotWithShape' => '0', - ], - ]; - - return $presets_options[$presetsOption]; + return self::PRESETS_OPTIONS[$presetsOption] ?? self::PRESETS_OPTIONS[0]; } protected function getArrayElementsValue($properties, $elements) diff --git a/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php b/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php index 0ab70870..b276707d 100644 --- a/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php +++ b/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php @@ -21,6 +21,14 @@ use ScatterPlot; use Spline; use StockPlot; +/** + * Jpgraph is not maintained in Composer, and the version there + * is extremely out of date. For that reason, all unit test + * requiring Jpgraph are skipped. So, do not measure + * code coverage for this class till that is fixed. + * + * @codeCoverageIgnore + */ class JpGraph implements IRenderer { private static $width = 640; diff --git a/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php b/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php index 5b501e0f..204c7302 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php @@ -22,11 +22,8 @@ class PageSetup public function printInformation(SimpleXMLElement $sheet): self { - if (isset($sheet->PrintInformation)) { + if (isset($sheet->PrintInformation, $sheet->PrintInformation[0])) { $printInformation = $sheet->PrintInformation[0]; - if (!$printInformation) { - return $this; - } $scale = (string) $printInformation->Scale->attributes()['percentage']; $pageOrder = (string) $printInformation->order; diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index 10f12dfe..9f8a3ace 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -2280,13 +2280,15 @@ class Xls extends BaseReader // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false; - if ($diagonalUp == false && $diagonalDown == false) { - $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE); - } elseif ($diagonalUp == true && $diagonalDown == false) { + if ($diagonalUp === false) { + if ($diagonalDown == false) { + $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE); + } else { + $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN); + } + } elseif ($diagonalDown == false) { $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP); - } elseif ($diagonalUp == false && $diagonalDown == true) { - $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN); - } elseif ($diagonalUp == true && $diagonalDown == true) { + } else { $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH); } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index 98507af8..55a150b7 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -3,11 +3,14 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; +use PhpOffice\PhpSpreadsheet\Chart\Axis; use PhpOffice\PhpSpreadsheet\Chart\DataSeries; use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; +use PhpOffice\PhpSpreadsheet\Chart\GridLines; use PhpOffice\PhpSpreadsheet\Chart\Layout; use PhpOffice\PhpSpreadsheet\Chart\Legend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; +use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Chart\Title; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Style\Color; @@ -67,6 +70,9 @@ class Chart $dispBlanksAs = $plotVisOnly = null; $plotArea = null; $rotX = $rotY = $rAngAx = $perspective = null; + $xAxis = new Axis(); + $yAxis = new Axis(); + $majorGridlines = $minorGridlines = null; foreach ($chartElementsC as $chartElementKey => $chartElement) { switch ($chartElementKey) { case 'chart': @@ -93,6 +99,7 @@ class Chart if (isset($chartDetail->title)) { $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); } + $this->readEffects($chartDetail, $xAxis); break; case 'dateAx': @@ -102,10 +109,27 @@ class Chart break; case 'valAx': - if (isset($chartDetail->title, $chartDetail->axPos)) { - $axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); + $whichAxis = null; + $axPos = null; + if (isset($chartDetail->axPos)) { $axPos = self::getAttribute($chartDetail->axPos, 'val', 'string'); + switch ($axPos) { + case 't': + case 'b': + $whichAxis = $xAxis; + + break; + case 'r': + case 'l': + $whichAxis = $yAxis; + + break; + } + } + if (isset($chartDetail->title)) { + $axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); + switch ($axPos) { case 't': case 'b': @@ -119,6 +143,19 @@ class Chart break; } } + $this->readEffects($chartDetail, $whichAxis); + if (isset($chartDetail->majorGridlines)) { + $majorGridlines = new GridLines(); + if (isset($chartDetail->majorGridlines->spPr)) { + $this->readEffects($chartDetail->majorGridlines, $majorGridlines); + } + } + if (isset($chartDetail->minorGridlines)) { + $minorGridlines = new GridLines(); + if (isset($chartDetail->minorGridlines->spPr)) { + $this->readEffects($chartDetail->minorGridlines, $minorGridlines); + } + } break; case 'barChart': @@ -240,7 +277,7 @@ class Chart } } } - $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel); + $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis, $majorGridlines, $minorGridlines); if (is_int($rotX)) { $chart->setRotX($rotX); } @@ -345,9 +382,8 @@ class Chart if (is_countable($ln->noFill) && count($ln->noFill) === 1) { $noFill = true; } - $sf = $children->solidFill->schemeClr; - if ($sf) { - $schemeClr = self::getAttribute($sf, 'val', 'string'); + if (isset($children->solidFill)) { + $this->readColor($children->solidFill, $srgbClr, $schemeClr); } break; @@ -357,8 +393,8 @@ class Chart $pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null; if (count($seriesDetail->spPr) === 1) { $ln = $seriesDetail->spPr->children($this->aNamespace); - if (count($ln->solidFill) === 1) { - $srgbClr = self::getAttribute($ln->solidFill->srgbClr, 'val', 'string'); + if (isset($ln->solidFill)) { + $this->readColor($ln->solidFill, $srgbClr, $schemeClr); } } @@ -603,7 +639,8 @@ class Chart $defaultLatin = null; $defaultEastAsian = null; $defaultComplexScript = null; - $defaultColor = null; + $defaultSrgbColor = ''; + $defaultSchemeColor = ''; if (isset($titleDetailPart->pPr->defRPr)) { /** @var ?int */ $defaultFontSize = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer'); @@ -632,9 +669,8 @@ class Chart /** @var ?string */ $defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string'); } - if (isset($titleDetailPart->pPr->defRPr->solidFill->srgbClr)) { - /** @var ?string */ - $defaultColor = self::getAttribute($titleDetailPart->pPr->defRPr->solidFill->srgbClr, 'val', 'string'); + if (isset($titleDetailPart->pPr->defRPr->solidFill)) { + $this->readColor($titleDetailPart->pPr->defRPr->solidFill, $defaultSrgbColor, $defaultSchemeClr); } } foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) { @@ -660,7 +696,8 @@ class Chart $latinName = null; $eastAsian = null; $complexScript = null; - $fontColor = null; + $fontSrgbClr = ''; + $fontSchemeClr = ''; $uSchemeClr = null; if (isset($titleDetailElement->rPr)) { // not used now, not sure it ever was, grandfathering @@ -686,10 +723,9 @@ class Chart // not used now, not sure it ever was, grandfathering /** @var ?string */ - $fontColor = self::getAttribute($titleDetailElement->rPr, 'color', 'string'); - if (isset($titleDetailElement->rPr->solidFill->srgbClr)) { - /** @var ?string */ - $fontColor = self::getAttribute($titleDetailElement->rPr->solidFill->srgbClr, 'val', 'string'); + $fontSrgbClr = self::getAttribute($titleDetailElement->rPr, 'color', 'string'); + if (isset($titleDetailElement->rPr->solidFill)) { + $this->readColor($titleDetailElement->rPr->solidFill, $fontSrgbClr, $fontSchemeClr); } /** @var ?bool */ @@ -742,11 +778,17 @@ class Chart $fontFound = true; } - $fontColor = $fontColor ?? $defaultColor; - if ($fontColor !== null) { - $objText->getFont()->setColor(new Color($fontColor)); + $fontSrgbClr = $fontSrgbClr ?? $defaultSrgbColor; + if (!empty($fontSrgbClr)) { + $objText->getFont()->setColor(new Color($fontSrgbClr)); $fontFound = true; } + // need to think about what to do here + //$fontSchemeClr = $fontSchemeClr ?? $defaultSchemeColor; + //if (!empty($fontSchemeClr)) { + // $objText->getFont()->setColor(new Color($fontSrgbClr)); + // $fontFound = true; + //} $bold = $bold ?? $defaultBold; if ($bold !== null) { @@ -877,4 +919,124 @@ class Chart } } } + + /** + * @param null|Axis|GridLines $chartObject may be extended to include other types + */ + private function readEffects(SimpleXMLElement $chartDetail, $chartObject): void + { + if (!isset($chartObject, $chartDetail->spPr)) { + return; + } + $sppr = $chartDetail->spPr->children($this->aNamespace); + + if (isset($sppr->effectLst->glow)) { + $axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / Properties::POINTS_WIDTH_MULTIPLIER; + if ($axisGlowSize != 0.0) { + $colorArray = $this->readColor($sppr->effectLst->glow); + $chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']); + } + } + + if (isset($sppr->effectLst->softEdge)) { + /** @var string */ + $softEdgeSize = self::getAttribute($sppr->effectLst->softEdge, 'rad', 'string'); + if (is_numeric($softEdgeSize)) { + $chartObject->setSoftEdges((float) Properties::xmlToPoints($softEdgeSize)); + } + } + + $type = ''; + foreach (self::SHADOW_TYPES as $shadowType) { + if (isset($sppr->effectLst->$shadowType)) { + $type = $shadowType; + + break; + } + } + if ($type !== '') { + /** @var string */ + $blur = self::getAttribute($sppr->effectLst->$type, 'blurRad', 'string'); + $blur = is_numeric($blur) ? Properties::xmlToPoints($blur) : null; + /** @var string */ + $dist = self::getAttribute($sppr->effectLst->$type, 'dist', 'string'); + $dist = is_numeric($dist) ? Properties::xmlToPoints($dist) : null; + /** @var string */ + $direction = self::getAttribute($sppr->effectLst->$type, 'dir', 'string'); + $direction = is_numeric($direction) ? Properties::xmlToAngle($direction) : null; + $algn = self::getAttribute($sppr->effectLst->$type, 'algn', 'string'); + $rot = self::getAttribute($sppr->effectLst->$type, 'rotWithShape', 'string'); + $size = []; + foreach (['sx', 'sy'] as $sizeType) { + $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string'); + if (is_numeric($sizeValue)) { + $size[$sizeType] = Properties::xmlToTenthOfPercent((string) $sizeValue); + } else { + $size[$sizeType] = null; + } + } + foreach (['kx', 'ky'] as $sizeType) { + $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string'); + if (is_numeric($sizeValue)) { + $size[$sizeType] = Properties::xmlToAngle((string) $sizeValue); + } else { + $size[$sizeType] = null; + } + } + $colorArray = $this->readColor($sppr->effectLst->$type); + $chartObject + ->setShadowProperty('effect', $type) + ->setShadowProperty('blur', $blur) + ->setShadowProperty('direction', $direction) + ->setShadowProperty('distance', $dist) + ->setShadowProperty('algn', $algn) + ->setShadowProperty('rotWithShape', $rot) + ->setShadowProperty('size', $size) + ->setShadowProperty('color', $colorArray); + } + } + + private const SHADOW_TYPES = [ + 'outerShdw', + 'innerShdw', + ]; + + private function readColor(SimpleXMLElement $colorXml, ?string &$srgbClr = null, ?string &$schemeClr = null): array + { + $result = [ + 'type' => null, + 'value' => null, + 'alpha' => null, + ]; + if (isset($colorXml->srgbClr)) { + $result['type'] = Properties::EXCEL_COLOR_TYPE_ARGB; + $result['value'] = $srgbClr = self::getAttribute($colorXml->srgbClr, 'val', 'string'); + if (isset($colorXml->srgbClr->alpha)) { + /** @var string */ + $alpha = self::getAttribute($colorXml->srgbClr->alpha, 'val', 'string'); + $alpha = Properties::alphaFromXml($alpha); + $result['alpha'] = $alpha; + } + } elseif (isset($colorXml->schemeClr)) { + $result['type'] = Properties::EXCEL_COLOR_TYPE_SCHEME; + $result['value'] = $schemeClr = self::getAttribute($colorXml->schemeClr, 'val', 'string'); + if (isset($colorXml->schemeClr->alpha)) { + /** @var string */ + $alpha = self::getAttribute($colorXml->schemeClr->alpha, 'val', 'string'); + $alpha = Properties::alphaFromXml($alpha); + $result['alpha'] = $alpha; + } + } elseif (isset($colorXml->prstClr)) { + $result['type'] = Properties::EXCEL_COLOR_TYPE_STANDARD; + $result['value'] = self::getAttribute($colorXml->prstClr, 'val', 'string'); + if (isset($colorXml->prstClr->alpha)) { + /** @var string */ + $alpha = self::getAttribute($colorXml->prstClr->alpha, 'val', 'string'); + $alpha = Properties::alphaFromXml($alpha); + $result['alpha'] = $alpha; + } + } + + return $result; + } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php index 2c15c515..f84aaa68 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -194,12 +194,14 @@ class Styles extends BaseParserClass $diagonalUp = self::boolean($diagonalUp); $diagonalDown = $this->getAttribute($borderStyleXml, 'diagonalDown'); $diagonalDown = self::boolean($diagonalDown); - if (!$diagonalUp && !$diagonalDown) { - $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE); - } elseif ($diagonalUp && !$diagonalDown) { + if ($diagonalUp === false) { + if ($diagonalDown === false) { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE); + } else { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN); + } + } elseif ($diagonalDown === false) { $borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP); - } elseif (!$diagonalUp && $diagonalDown) { - $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN); } else { $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH); } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Theme.php b/src/PhpSpreadsheet/Reader/Xlsx/Theme.php index 1f2b863c..706c4d19 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Theme.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Theme.php @@ -41,9 +41,11 @@ class Theme } /** - * Get Theme Name. + * Not called by Reader, never accessible any other time. * * @return string + * + * @codeCoverageIgnore */ public function getThemeName() { @@ -51,9 +53,11 @@ class Theme } /** - * Get colour Scheme Name. + * Not called by Reader, never accessible any other time. * * @return string + * + * @codeCoverageIgnore */ public function getColourSchemeName() { @@ -69,25 +73,6 @@ class Theme */ public function getColourByIndex($index) { - if (isset($this->colourMap[$index])) { - return $this->colourMap[$index]; - } - - return null; - } - - /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. - */ - public function __clone() - { - $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if ((is_object($value)) && ($key != '_parent')) { - $this->$key = clone $value; - } else { - $this->$key = $value; - } - } + return $this->colourMap[$index] ?? null; } } diff --git a/src/PhpSpreadsheet/RichText/RichText.php b/src/PhpSpreadsheet/RichText/RichText.php index 3a6e1f8e..88e7c792 100644 --- a/src/PhpSpreadsheet/RichText/RichText.php +++ b/src/PhpSpreadsheet/RichText/RichText.php @@ -158,11 +158,14 @@ class RichText implements IComparable { $vars = get_object_vars($this); foreach ($vars as $key => $value) { - if (is_object($value)) { - $this->$key = clone $value; - } else { - $this->$key = $value; + $newValue = is_object($value) ? (clone $value) : $value; + if (is_array($value)) { + $newValue = []; + foreach ($value as $key2 => $value2) { + $newValue[$key2] = is_object($value2) ? (clone $value2) : $value2; + } } + $this->$key = $newValue; } } } diff --git a/src/PhpSpreadsheet/RichText/TextElement.php b/src/PhpSpreadsheet/RichText/TextElement.php index 6bec005b..23733436 100644 --- a/src/PhpSpreadsheet/RichText/TextElement.php +++ b/src/PhpSpreadsheet/RichText/TextElement.php @@ -68,19 +68,4 @@ class TextElement implements ITextElement __CLASS__ ); } - - /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. - */ - public function __clone() - { - $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if (is_object($value)) { - $this->$key = clone $value; - } else { - $this->$key = $value; - } - } - } } diff --git a/src/PhpSpreadsheet/Shared/StringHelper.php b/src/PhpSpreadsheet/Shared/StringHelper.php index 9970a211..7d6a990f 100644 --- a/src/PhpSpreadsheet/Shared/StringHelper.php +++ b/src/PhpSpreadsheet/Shared/StringHelper.php @@ -470,7 +470,7 @@ class StringHelper /** * Get a substring of a UTF-8 encoded string. * - * @param string $textValue UTF-8 encoded string + * @param null|string $textValue UTF-8 encoded string * @param int $offset Start offset * @param int $length Maximum number of characters in substring * @@ -478,7 +478,7 @@ class StringHelper */ public static function substring($textValue, $offset, $length = 0) { - return mb_substr($textValue, $offset, $length, 'UTF-8'); + return mb_substr($textValue ?? '', $offset, $length, 'UTF-8'); } /** diff --git a/src/PhpSpreadsheet/Style/Font.php b/src/PhpSpreadsheet/Style/Font.php index e5b056c9..7b1ced63 100644 --- a/src/PhpSpreadsheet/Style/Font.php +++ b/src/PhpSpreadsheet/Style/Font.php @@ -19,7 +19,7 @@ class Font extends Supervisor protected $name = 'Calibri'; /** - * The following 6 are used only for chart titles, I think. + * The following 7 are used only for chart titles, I think. * *@var string */ @@ -39,6 +39,9 @@ class Font extends Supervisor /** @var string */ private $uSchemeClr = ''; + + /** @var string */ + private $uSrgbClr = ''; // end of chart title items /** @@ -299,11 +302,14 @@ class Font extends Supervisor if ($fontname == '') { $fontname = 'Calibri'; } - if ($this->isSupervisor) { + if (!$this->isSupervisor) { + $this->latin = $fontname; + } else { + // should never be true + // @codeCoverageIgnoreStart $styleArray = $this->getStyleArray(['latin' => $fontname]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); - } else { - $this->latin = $fontname; + // @codeCoverageIgnoreEnd } return $this; @@ -314,11 +320,14 @@ class Font extends Supervisor if ($fontname == '') { $fontname = 'Calibri'; } - if ($this->isSupervisor) { + if (!$this->isSupervisor) { + $this->eastAsian = $fontname; + } else { + // should never be true + // @codeCoverageIgnoreStart $styleArray = $this->getStyleArray(['eastAsian' => $fontname]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); - } else { - $this->eastAsian = $fontname; + // @codeCoverageIgnoreEnd } return $this; @@ -329,11 +338,14 @@ class Font extends Supervisor if ($fontname == '') { $fontname = 'Calibri'; } - if ($this->isSupervisor) { + if (!$this->isSupervisor) { + $this->complexScript = $fontname; + } else { + // should never be true + // @codeCoverageIgnoreStart $styleArray = $this->getStyleArray(['complexScript' => $fontname]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); - } else { - $this->complexScript = $fontname; + // @codeCoverageIgnoreEnd } return $this; @@ -533,11 +545,14 @@ class Font extends Supervisor public function setBaseLine(int $baseLine): self { - if ($this->isSupervisor) { + if (!$this->isSupervisor) { + $this->baseLine = $baseLine; + } else { + // should never be true + // @codeCoverageIgnoreStart $styleArray = $this->getStyleArray(['baseLine' => $baseLine]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); - } else { - $this->baseLine = $baseLine; + // @codeCoverageIgnoreEnd } return $this; @@ -554,11 +569,14 @@ class Font extends Supervisor public function setStrikeType(string $strikeType): self { - if ($this->isSupervisor) { + if (!$this->isSupervisor) { + $this->strikeType = $strikeType; + } else { + // should never be true + // @codeCoverageIgnoreStart $styleArray = $this->getStyleArray(['strikeType' => $strikeType]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); - } else { - $this->strikeType = $strikeType; + // @codeCoverageIgnoreEnd } return $this; @@ -575,11 +593,38 @@ class Font extends Supervisor public function setUSchemeClr(string $uSchemeClr): self { - if ($this->isSupervisor) { + if (!$this->isSupervisor) { + $this->uSchemeClr = $uSchemeClr; + } else { + // should never be true + // @codeCoverageIgnoreStart $styleArray = $this->getStyleArray(['uSchemeClr' => $uSchemeClr]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd + } + + return $this; + } + + public function getUSrgbClr(): string + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getUSrgbClr(); + } + + return $this->uSrgbClr; + } + + public function setUSrgbClr(string $uSrgbClr): self + { + if (!$this->isSupervisor) { + $this->uSrgbClr = $uSrgbClr; } else { - $this->uSchemeClr = $uSchemeClr; + // should never be true + // @codeCoverageIgnoreStart + $styleArray = $this->getStyleArray(['uSrgbClr' => $uSrgbClr]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd } return $this; @@ -713,15 +758,18 @@ class Font extends Supervisor $this->underline . ($this->strikethrough ? 't' : 'f') . $this->color->getHashCode() . - '*' . - $this->latin . - '*' . - $this->eastAsian . - '*' . - $this->complexScript . - $this->strikeType . - $this->uSchemeClr . - (string) $this->baseLine . + implode( + '*', + [ + $this->latin, + $this->eastAsian, + $this->complexScript, + $this->strikeType, + $this->uSchemeClr, + $this->uSrgbClr, + (string) $this->baseLine, + ] + ) . __CLASS__ ); } @@ -743,6 +791,8 @@ class Font extends Supervisor $this->exportArray2($exportedArray, 'subscript', $this->getSubscript()); $this->exportArray2($exportedArray, 'superscript', $this->getSuperscript()); $this->exportArray2($exportedArray, 'underline', $this->getUnderline()); + $this->exportArray2($exportedArray, 'uSchemeClr', $this->getUSchemeClr()); + $this->exportArray2($exportedArray, 'uSrgbClr', $this->getUSrgbClr()); return $exportedArray; } diff --git a/src/PhpSpreadsheet/Style/Style.php b/src/PhpSpreadsheet/Style/Style.php index 78e5ebbf..5ea70597 100644 --- a/src/PhpSpreadsheet/Style/Style.php +++ b/src/PhpSpreadsheet/Style/Style.php @@ -421,8 +421,10 @@ class Style extends Supervisor // Handle bug in PHPStan, see https://github.com/phpstan/phpstan/issues/5805 // $newStyle should always be defined. // This block might not be needed in the future + // @codeCoverageIgnoreStart $newStyle = clone $style; $newStyle->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd } // we don't have such a cell Xf, need to add diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 4a441a93..d047380a 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -1177,6 +1177,11 @@ class Worksheet implements IComparable * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * @param mixed $value Value of the cell * @param string $dataType Explicit data type, see DataType::TYPE_* + * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this + * method, then it is your responsibility as an end-user developer to validate that the value and + * the datatype match. + * If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype + * that you specify. * * @return $this */ @@ -1199,6 +1204,11 @@ class Worksheet implements IComparable * @param int $row Numeric row coordinate of the cell * @param mixed $value Value of the cell * @param string $dataType Explicit data type, see DataType::TYPE_* + * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this + * method, then it is your responsibility as an end-user developer to validate that the value and + * the datatype match. + * If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype + * that you specify. * * @return $this */ @@ -1752,31 +1762,39 @@ class Worksheet implements IComparable { $range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range)); - if (preg_match('/^([A-Z]+)(\\d+):([A-Z]+)(\\d+)$/', $range, $matches) === 1) { - $this->mergeCells[$range] = $range; - $firstRow = (int) $matches[2]; - $lastRow = (int) $matches[4]; - $firstColumn = $matches[1]; - $lastColumn = $matches[3]; - $firstColumnIndex = Coordinate::columnIndexFromString($firstColumn); - $lastColumnIndex = Coordinate::columnIndexFromString($lastColumn); - $numberRows = $lastRow - $firstRow; - $numberColumns = $lastColumnIndex - $firstColumnIndex; + if (strpos($range, ':') === false) { + $range .= ":{$range}"; + } - // create upper left cell if it does not already exist - $upperLeft = "{$firstColumn}{$firstRow}"; - if (!$this->cellExists($upperLeft)) { - $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL); - } + if (preg_match('/^([A-Z]+)(\\d+):([A-Z]+)(\\d+)$/', $range, $matches) !== 1) { + throw new Exception('Merge must be on a valid range of cells.'); + } - // Blank out the rest of the cells in the range (if they exist) - if ($numberRows > $numberColumns) { - $this->clearMergeCellsByColumn($firstColumn, $lastColumn, $firstRow, $lastRow, $upperLeft); - } else { - $this->clearMergeCellsByRow($firstColumn, $lastColumnIndex, $firstRow, $lastRow, $upperLeft); - } + $this->mergeCells[$range] = $range; + $firstRow = (int) $matches[2]; + $lastRow = (int) $matches[4]; + $firstColumn = $matches[1]; + $lastColumn = $matches[3]; + $firstColumnIndex = Coordinate::columnIndexFromString($firstColumn); + $lastColumnIndex = Coordinate::columnIndexFromString($lastColumn); + $numberRows = $lastRow - $firstRow; + $numberColumns = $lastColumnIndex - $firstColumnIndex; + + if ($numberRows === 1 && $numberColumns === 1) { + return $this; + } + + // create upper left cell if it does not already exist + $upperLeft = "{$firstColumn}{$firstRow}"; + if (!$this->cellExists($upperLeft)) { + $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL); + } + + // Blank out the rest of the cells in the range (if they exist) + if ($numberRows > $numberColumns) { + $this->clearMergeCellsByColumn($firstColumn, $lastColumn, $firstRow, $lastRow, $upperLeft); } else { - throw new Exception('Merge must be set on a range of cells.'); + $this->clearMergeCellsByRow($firstColumn, $lastColumnIndex, $firstRow, $lastRow, $upperLeft); } return $this; diff --git a/src/PhpSpreadsheet/Writer/BaseWriter.php b/src/PhpSpreadsheet/Writer/BaseWriter.php index 7a3fa369..2ea0ea8b 100644 --- a/src/PhpSpreadsheet/Writer/BaseWriter.php +++ b/src/PhpSpreadsheet/Writer/BaseWriter.php @@ -118,7 +118,9 @@ abstract class BaseWriter implements IWriter $mode = 'wb'; $scheme = parse_url($filename, PHP_URL_SCHEME); if ($scheme === 's3') { + // @codeCoverageIgnoreStart $mode = 'w'; + // @codeCoverageIgnoreEnd } $fileHandle = $filename ? fopen($filename, $mode) : false; if ($fileHandle === false) { diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index 08d578e6..e24afbac 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -9,6 +9,7 @@ use PhpOffice\PhpSpreadsheet\Chart\GridLines; use PhpOffice\PhpSpreadsheet\Chart\Layout; use PhpOffice\PhpSpreadsheet\Chart\Legend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; +use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Chart\Title; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; @@ -497,6 +498,14 @@ class Chart extends WriterPart $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels')); $objWriter->endElement(); + $objWriter->startElement('c:spPr'); + $objWriter->startElement('a:effectLst'); + $this->writeGlow($objWriter, $yAxis); + $this->writeShadow($objWriter, $yAxis); + $this->writeSoftEdge($objWriter, $yAxis); + $objWriter->endElement(); // effectLst + $objWriter->endElement(); // spPr + if ($id2 !== '0') { $objWriter->startElement('c:crossAx'); $objWriter->writeAttribute('val', $id2); @@ -619,61 +628,9 @@ class Chart extends WriterPart $objWriter->endElement(); //end ln } $objWriter->startElement('a:effectLst'); - - if ($majorGridlines->getGlowSize() !== null) { - $objWriter->startElement('a:glow'); - $objWriter->writeAttribute('rad', $majorGridlines->getGlowSize()); - $objWriter->startElement("a:{$majorGridlines->getGlowColor('type')}"); - $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('value')); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('alpha')); - $objWriter->endElement(); //end alpha - $objWriter->endElement(); //end schemeClr - $objWriter->endElement(); //end glow - } - - if ($majorGridlines->getShadowProperty('presets') !== null) { - $objWriter->startElement("a:{$majorGridlines->getShadowProperty('effect')}"); - if ($majorGridlines->getShadowProperty('blur') !== null) { - $objWriter->writeAttribute('blurRad', $majorGridlines->getShadowProperty('blur')); - } - if ($majorGridlines->getShadowProperty('distance') !== null) { - $objWriter->writeAttribute('dist', $majorGridlines->getShadowProperty('distance')); - } - if ($majorGridlines->getShadowProperty('direction') !== null) { - $objWriter->writeAttribute('dir', $majorGridlines->getShadowProperty('direction')); - } - if ($majorGridlines->getShadowProperty('algn') !== null) { - $objWriter->writeAttribute('algn', $majorGridlines->getShadowProperty('algn')); - } - if ($majorGridlines->getShadowProperty(['size', 'sx']) !== null) { - $objWriter->writeAttribute('sx', $majorGridlines->getShadowProperty(['size', 'sx'])); - } - if ($majorGridlines->getShadowProperty(['size', 'sy']) !== null) { - $objWriter->writeAttribute('sy', $majorGridlines->getShadowProperty(['size', 'sy'])); - } - if ($majorGridlines->getShadowProperty(['size', 'kx']) !== null) { - $objWriter->writeAttribute('kx', $majorGridlines->getShadowProperty(['size', 'kx'])); - } - if ($majorGridlines->getShadowProperty('rotWithShape') !== null) { - $objWriter->writeAttribute('rotWithShape', $majorGridlines->getShadowProperty('rotWithShape')); - } - $objWriter->startElement("a:{$majorGridlines->getShadowProperty(['color', 'type'])}"); - $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'value'])); - - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'alpha'])); - $objWriter->endElement(); //end alpha - - $objWriter->endElement(); //end color:type - $objWriter->endElement(); //end shadow - } - - if ($majorGridlines->getSoftEdgesSize() !== null) { - $objWriter->startElement('a:softEdge'); - $objWriter->writeAttribute('rad', $majorGridlines->getSoftEdgesSize()); - $objWriter->endElement(); //end softEdge - } + $this->writeGlow($objWriter, $majorGridlines); + $this->writeShadow($objWriter, $majorGridlines); + $this->writeSoftEdge($objWriter, $majorGridlines); $objWriter->endElement(); //end effectLst $objWriter->endElement(); //end spPr @@ -727,61 +684,11 @@ class Chart extends WriterPart } $objWriter->startElement('a:effectLst'); - - if ($minorGridlines->getGlowSize() !== null) { - $objWriter->startElement('a:glow'); - $objWriter->writeAttribute('rad', $minorGridlines->getGlowSize()); - $objWriter->startElement("a:{$minorGridlines->getGlowColor('type')}"); - $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('value')); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('alpha')); - $objWriter->endElement(); //end alpha - $objWriter->endElement(); //end schemeClr - $objWriter->endElement(); //end glow - } - - if ($minorGridlines->getShadowProperty('presets') !== null) { - $objWriter->startElement("a:{$minorGridlines->getShadowProperty('effect')}"); - if ($minorGridlines->getShadowProperty('blur') !== null) { - $objWriter->writeAttribute('blurRad', $minorGridlines->getShadowProperty('blur')); - } - if ($minorGridlines->getShadowProperty('distance') !== null) { - $objWriter->writeAttribute('dist', $minorGridlines->getShadowProperty('distance')); - } - if ($minorGridlines->getShadowProperty('direction') !== null) { - $objWriter->writeAttribute('dir', $minorGridlines->getShadowProperty('direction')); - } - if ($minorGridlines->getShadowProperty('algn') !== null) { - $objWriter->writeAttribute('algn', $minorGridlines->getShadowProperty('algn')); - } - if ($minorGridlines->getShadowProperty(['size', 'sx']) !== null) { - $objWriter->writeAttribute('sx', $minorGridlines->getShadowProperty(['size', 'sx'])); - } - if ($minorGridlines->getShadowProperty(['size', 'sy']) !== null) { - $objWriter->writeAttribute('sy', $minorGridlines->getShadowProperty(['size', 'sy'])); - } - if ($minorGridlines->getShadowProperty(['size', 'kx']) !== null) { - $objWriter->writeAttribute('kx', $minorGridlines->getShadowProperty(['size', 'kx'])); - } - if ($minorGridlines->getShadowProperty('rotWithShape') !== null) { - $objWriter->writeAttribute('rotWithShape', $minorGridlines->getShadowProperty('rotWithShape')); - } - $objWriter->startElement("a:{$minorGridlines->getShadowProperty(['color', 'type'])}"); - $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'value'])); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'alpha'])); - $objWriter->endElement(); //end alpha - $objWriter->endElement(); //end color:type - $objWriter->endElement(); //end shadow - } - - if ($minorGridlines->getSoftEdgesSize() !== null) { - $objWriter->startElement('a:softEdge'); - $objWriter->writeAttribute('rad', $minorGridlines->getSoftEdgesSize()); - $objWriter->endElement(); //end softEdge - } - + $this->writeGlow($objWriter, $minorGridlines); + $this->writeShadow($objWriter, $minorGridlines); + $this->writeSoftEdge($objWriter, $minorGridlines); $objWriter->endElement(); //end effectLst + $objWriter->endElement(); //end spPr $objWriter->endElement(); //end minorGridLines } @@ -904,64 +811,11 @@ class Chart extends WriterPart $objWriter->endElement(); $objWriter->startElement('a:effectLst'); - - if ($xAxis->getGlowProperty('size') !== null) { - $objWriter->startElement('a:glow'); - $objWriter->writeAttribute('rad', $xAxis->getGlowProperty('size')); - $objWriter->startElement("a:{$xAxis->getGlowProperty(['color', 'type'])}"); - $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'value'])); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'alpha'])); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); - } - - if ($xAxis->getShadowProperty('presets') !== null) { - $objWriter->startElement("a:{$xAxis->getShadowProperty('effect')}"); - - if ($xAxis->getShadowProperty('blur') !== null) { - $objWriter->writeAttribute('blurRad', $xAxis->getShadowProperty('blur')); - } - if ($xAxis->getShadowProperty('distance') !== null) { - $objWriter->writeAttribute('dist', $xAxis->getShadowProperty('distance')); - } - if ($xAxis->getShadowProperty('direction') !== null) { - $objWriter->writeAttribute('dir', $xAxis->getShadowProperty('direction')); - } - if ($xAxis->getShadowProperty('algn') !== null) { - $objWriter->writeAttribute('algn', $xAxis->getShadowProperty('algn')); - } - if ($xAxis->getShadowProperty(['size', 'sx']) !== null) { - $objWriter->writeAttribute('sx', $xAxis->getShadowProperty(['size', 'sx'])); - } - if ($xAxis->getShadowProperty(['size', 'sy']) !== null) { - $objWriter->writeAttribute('sy', $xAxis->getShadowProperty(['size', 'sy'])); - } - if ($xAxis->getShadowProperty(['size', 'kx']) !== null) { - $objWriter->writeAttribute('kx', $xAxis->getShadowProperty(['size', 'kx'])); - } - if ($xAxis->getShadowProperty('rotWithShape') !== null) { - $objWriter->writeAttribute('rotWithShape', $xAxis->getShadowProperty('rotWithShape')); - } - - $objWriter->startElement("a:{$xAxis->getShadowProperty(['color', 'type'])}"); - $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'value'])); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'alpha'])); - $objWriter->endElement(); - $objWriter->endElement(); - - $objWriter->endElement(); - } - - if ($xAxis->getSoftEdgesSize() !== null) { - $objWriter->startElement('a:softEdge'); - $objWriter->writeAttribute('rad', $xAxis->getSoftEdgesSize()); - $objWriter->endElement(); - } - + $this->writeGlow($objWriter, $xAxis); + $this->writeShadow($objWriter, $xAxis); + $this->writeSoftEdge($objWriter, $xAxis); $objWriter->endElement(); //effectList + $objWriter->endElement(); //end spPr if ($id1 !== '0') { @@ -1637,4 +1491,100 @@ class Chart extends WriterPart $objWriter->endElement(); } + + /** + * Write shadow properties. + * + * @param Axis|GridLines $xAxis + */ + private function writeShadow(XMLWriter $objWriter, $xAxis): void + { + if ($xAxis->getShadowProperty('effect') === null) { + return; + } + /** @var string */ + $effect = $xAxis->getShadowProperty('effect'); + $objWriter->startElement("a:$effect"); + + if (is_numeric($xAxis->getShadowProperty('blur'))) { + $objWriter->writeAttribute('blurRad', Properties::pointsToXml((float) $xAxis->getShadowProperty('blur'))); + } + if (is_numeric($xAxis->getShadowProperty('distance'))) { + $objWriter->writeAttribute('dist', Properties::pointsToXml((float) $xAxis->getShadowProperty('distance'))); + } + if (is_numeric($xAxis->getShadowProperty('direction'))) { + $objWriter->writeAttribute('dir', Properties::angleToXml((float) $xAxis->getShadowProperty('direction'))); + } + if ($xAxis->getShadowProperty('algn') !== null) { + $objWriter->writeAttribute('algn', $xAxis->getShadowProperty('algn')); + } + foreach (['sx', 'sy'] as $sizeType) { + $sizeValue = $xAxis->getShadowProperty(['size', $sizeType]); + if (is_numeric($sizeValue)) { + $objWriter->writeAttribute($sizeType, Properties::tenthOfPercentToXml((float) $sizeValue)); + } + } + foreach (['kx', 'ky'] as $sizeType) { + $sizeValue = $xAxis->getShadowProperty(['size', $sizeType]); + if (is_numeric($sizeValue)) { + $objWriter->writeAttribute($sizeType, Properties::angleToXml((float) $sizeValue)); + } + } + if ($xAxis->getShadowProperty('rotWithShape') !== null) { + $objWriter->writeAttribute('rotWithShape', $xAxis->getShadowProperty('rotWithShape')); + } + + $objWriter->startElement("a:{$xAxis->getShadowProperty(['color', 'type'])}"); + $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'value'])); + $alpha = $xAxis->getShadowProperty(['color', 'alpha']); + if (is_numeric($alpha)) { + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', Properties::alphaToXml((int) $alpha)); + $objWriter->endElement(); + } + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write glow properties. + * + * @param Axis|GridLines $yAxis + */ + private function writeGlow(XMLWriter $objWriter, $yAxis): void + { + $size = $yAxis->getGlowProperty('size'); + if (empty($size)) { + return; + } + $objWriter->startElement('a:glow'); + $objWriter->writeAttribute('rad', Properties::pointsToXml((float) $size)); + $objWriter->startElement("a:{$yAxis->getGlowProperty(['color', 'type'])}"); + $objWriter->writeAttribute('val', (string) $yAxis->getGlowProperty(['color', 'value'])); + $alpha = $yAxis->getGlowProperty(['color', 'alpha']); + if (is_numeric($alpha)) { + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', Properties::alphaToXml((int) $alpha)); + $objWriter->endElement(); // alpha + } + $objWriter->endElement(); // color + $objWriter->endElement(); // glow + } + + /** + * Write soft edge properties. + * + * @param Axis|GridLines $yAxis + */ + private function writeSoftEdge(XMLWriter $objWriter, $yAxis): void + { + $softEdgeSize = $yAxis->getSoftEdgesSize(); + if (empty($softEdgeSize)) { + return; + } + $objWriter->startElement('a:softEdge'); + $objWriter->writeAttribute('rad', Properties::pointsToXml((float) $softEdgeSize)); + $objWriter->endElement(); //end softEdge + } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index be65d163..660f7801 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -323,10 +323,7 @@ class Worksheet extends WriterPart } // Set Zero Height row - if ( - (string) $worksheet->getDefaultRowDimension()->getZeroHeight() === '1' || - strtolower((string) $worksheet->getDefaultRowDimension()->getZeroHeight()) == 'true' - ) { + if ($worksheet->getDefaultRowDimension()->getZeroHeight()) { $objWriter->writeAttribute('zeroHeight', '1'); } @@ -1079,7 +1076,7 @@ class Worksheet extends WriterPart $rowDimension = $worksheet->getRowDimension($currentRow); // Write current row? - $writeCurrentRow = isset($cellsByRow[$currentRow]) || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() == false || $rowDimension->getCollapsed() == true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null; + $writeCurrentRow = isset($cellsByRow[$currentRow]) || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() === false || $rowDimension->getCollapsed() === true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null; if ($writeCurrentRow) { // Start a new row diff --git a/tests/PhpSpreadsheetTests/Calculation/MergedCellTest.php b/tests/PhpSpreadsheetTests/Calculation/MergedCellTest.php index 5e5aff6a..e6737b6d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/MergedCellTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/MergedCellTest.php @@ -100,7 +100,7 @@ class MergedCellTest extends TestCase $sheet->mergeCells($range); self::fail("Expected invalid merge range $range"); } catch (SpreadException $e) { - self::assertSame('Merge must be set on a range of cells.', $e->getMessage()); + self::assertSame('Merge must be on a valid range of cells.', $e->getMessage()); } } @@ -109,7 +109,8 @@ class MergedCellTest extends TestCase $spreadSheet = new Spreadsheet(); $dataSheet = $spreadSheet->getActiveSheet(); - $this->setBadRange($dataSheet, 'B1'); + // TODO - Reinstate full validation and disallow single cell merging for version 2.0 +// $this->setBadRange($dataSheet, 'B1'); $this->setBadRange($dataSheet, 'Invalid'); $this->setBadRange($dataSheet, '1'); $this->setBadRange($dataSheet, 'C'); diff --git a/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php b/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php new file mode 100644 index 00000000..ad7fc776 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/AxisGlowTest.php @@ -0,0 +1,268 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testGlowY(): void + { + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] + ); + + // Set the Labels for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 + ]; + // Set the X-Axis Labels + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 + ]; + // Set the Data values for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), + ]; + + // Build the dataseries + $series = new DataSeries( + DataSeries::TYPE_AREACHART, // plotType + DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues + ); + + // Set the series in the plot area + $plotArea = new PlotArea(null, [$series]); + // Set the chart legend + $legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false); + + $title = new Title('Test %age-Stacked Area Chart'); + $yAxisLabel = new Title('Value ($k)'); + + // Create the chart + $chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel + ); + $yAxis = $chart->getChartAxisY(); + $xAxis = $chart->getChartAxisX(); + $yGlowSize = 10.0; + $yAxis->setGlowProperties($yGlowSize, 'FFFF00', 30, Properties::EXCEL_COLOR_TYPE_ARGB); + $expectedGlowColor = [ + 'type' => 'srgbClr', + 'value' => 'FFFF00', + 'alpha' => 30, + ]; + $softEdgesY = 2.5; + $yAxis->setSoftEdges($softEdgesY); + $softEdgesX = 5; + $xAxis->setSoftEdges($softEdgesX); + self::assertEquals($yGlowSize, $yAxis->getGlowProperty('size')); + self::assertEquals($expectedGlowColor, $yAxis->getGlowProperty('color')); + self::assertEquals($softEdgesY, $yAxis->getSoftEdgesSize()); + self::assertEquals($softEdgesX, $xAxis->getSoftEdgesSize()); + + // Set the position where the chart should appear in the worksheet + $chart->setTopLeftPosition('A7'); + $chart->setBottomRightPosition('H20'); + + // Add the chart to the worksheet + $worksheet->addChart($chart); + + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $sheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + $yAxis2 = $chart2->getChartAxisY(); + self::assertEquals($yGlowSize, $yAxis2->getGlowProperty('size')); + self::assertEquals($expectedGlowColor, $yAxis2->getGlowProperty('color')); + self::assertEquals($softEdgesY, $yAxis2->getSoftEdgesSize()); + $xAxis2 = $chart2->getChartAxisX(); + self::assertNull($xAxis2->getGlowProperty('size')); + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testGlowX(): void + { + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] + ); + + // Set the Labels for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 + ]; + // Set the X-Axis Labels + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 + ]; + // Set the Data values for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), + ]; + + // Build the dataseries + $series = new DataSeries( + DataSeries::TYPE_AREACHART, // plotType + DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues + ); + + // Set the series in the plot area + $plotArea = new PlotArea(null, [$series]); + // Set the chart legend + $legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false); + + $title = new Title('Test %age-Stacked Area Chart'); + $yAxisLabel = new Title('Value ($k)'); + + // Create the chart + $chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel + ); + $yAxis = $chart->getChartAxisX(); // deliberate + $yGlowSize = 20.0; + $yAxis->setGlowProperties($yGlowSize, 'accent1', 20, Properties::EXCEL_COLOR_TYPE_SCHEME); + $expectedGlowColor = [ + 'type' => 'schemeClr', + 'value' => 'accent1', + 'alpha' => 20, + ]; + self::assertEquals($yGlowSize, $yAxis->getGlowProperty('size')); + self::assertEquals($expectedGlowColor, $yAxis->getGlowProperty('color')); + + // Set the position where the chart should appear in the worksheet + $chart->setTopLeftPosition('A7'); + $chart->setBottomRightPosition('H20'); + + // Add the chart to the worksheet + $worksheet->addChart($chart); + + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $sheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + $yAxis2 = $chart2->getChartAxisX(); // deliberate + self::assertEquals($yGlowSize, $yAxis2->getGlowProperty('size')); + self::assertEquals($expectedGlowColor, $yAxis2->getGlowProperty('color')); + $xAxis2 = $chart2->getChartAxisY(); // deliberate + self::assertNull($xAxis2->getGlowProperty('size')); + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/AxisShadowTest.php b/tests/PhpSpreadsheetTests/Chart/AxisShadowTest.php new file mode 100644 index 00000000..d6f122ef --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/AxisShadowTest.php @@ -0,0 +1,184 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testGlowY(): void + { + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] + ); + + // Set the Labels for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 + ]; + // Set the X-Axis Labels + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 + ]; + // Set the Data values for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), + ]; + + // Build the dataseries + $series = new DataSeries( + DataSeries::TYPE_AREACHART, // plotType + DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues + ); + + // Set the series in the plot area + $plotArea = new PlotArea(null, [$series]); + // Set the chart legend + $legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false); + + $title = new Title('Test %age-Stacked Area Chart'); + $yAxisLabel = new Title('Value ($k)'); + + // Create the chart + $chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel + ); + $yAxis = $chart->getChartAxisY(); + $expectedY = [ + 'effect' => 'outerShdw', + 'algn' => 'tl', + 'blur' => 5, + 'direction' => 45, + 'distance' => 3, + 'rotWithShape' => 0, + 'color' => [ + 'type' => Properties::EXCEL_COLOR_TYPE_STANDARD, + 'value' => 'black', + 'alpha' => 40, + ], + ]; + foreach ($expectedY as $key => $value) { + $yAxis->setShadowProperty($key, $value); + } + foreach ($expectedY as $key => $value) { + self::assertEquals($value, $yAxis->getShadowProperty($key), $key); + } + $xAxis = $chart->getChartAxisX(); + $expectedX = [ + 'effect' => 'outerShdw', + 'algn' => 'bl', + 'blur' => 6, + 'direction' => 315, + 'distance' => 3, + 'rotWithShape' => 0, + 'size' => [ + 'sx' => null, + 'sy' => 254, + 'kx' => -94, + 'ky' => null, + ], + 'color' => [ + 'type' => Properties::EXCEL_COLOR_TYPE_ARGB, + 'value' => 'FF0000', + 'alpha' => 20, + ], + ]; + foreach ($expectedX as $key => $value) { + $xAxis->setShadowProperty($key, $value); + } + foreach ($expectedX as $key => $value) { + self::assertEquals($value, $xAxis->getShadowProperty($key), $key); + } + + // Set the position where the chart should appear in the worksheet + $chart->setTopLeftPosition('A7'); + $chart->setBottomRightPosition('H20'); + + // Add the chart to the worksheet + $worksheet->addChart($chart); + + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $sheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + $yAxis2 = $chart2->getChartAxisY(); + foreach ($expectedY as $key => $value) { + self::assertEquals($value, $yAxis2->getShadowProperty($key), $key); + } + $xAxis2 = $chart2->getChartAxisX(); + foreach ($expectedX as $key => $value) { + self::assertEquals($value, $xAxis2->getShadowProperty($key), $key); + } + + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32CatAxValAxTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php similarity index 99% rename from tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32CatAxValAxTest.php rename to tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php index af33baa1..268ee094 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/Charts32CatAxValAxTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32CatAxValAxTest.php @@ -1,6 +1,6 @@ setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testGlowY(): void + { + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] + ); + + // Set the Labels for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 + ]; + // Set the X-Axis Labels + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 + ]; + // Set the Data values for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), + ]; + + // Build the dataseries + $series = new DataSeries( + DataSeries::TYPE_LINECHART, // plotType + DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues + ); + + // Set the series in the plot area + $plotArea = new PlotArea(null, [$series]); + // Set the chart legend + $legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false); + + $title = new Title('Test %age-Stacked Area Chart'); + $yAxisLabel = new Title('Value ($k)'); + $majorGridlines = new GridLines(); + $majorGlowSize = 10.0; + $majorGridlines->setGlowProperties($majorGlowSize, 'FFFF00', 30, Properties::EXCEL_COLOR_TYPE_ARGB); + $softEdgeSize = 2.5; + $majorGridlines->setSoftEdges($softEdgeSize); + $expectedGlowColor = [ + 'type' => 'srgbClr', + 'value' => 'FFFF00', + 'alpha' => 30, + ]; + self::assertEquals($majorGlowSize, $majorGridlines->getGlowProperty('size')); + self::assertEquals($majorGlowSize, $majorGridlines->getGlowSize()); + self::assertEquals($expectedGlowColor['value'], $majorGridlines->getGlowColor('value')); + self::assertEquals($expectedGlowColor, $majorGridlines->getGlowProperty('color')); + self::assertEquals($softEdgeSize, $majorGridlines->getSoftEdgesSize()); + + $minorGridlines = new GridLines(); + $expectedShadow = [ + 'effect' => 'outerShdw', + 'algn' => 'tl', + 'blur' => 4, + 'direction' => 45, + 'distance' => 3, + 'rotWithShape' => 0, + 'color' => [ + 'type' => Properties::EXCEL_COLOR_TYPE_STANDARD, + 'value' => 'black', + 'alpha' => 40, + ], + ]; + foreach ($expectedShadow as $key => $value) { + $minorGridlines->setShadowProperty($key, $value); + } + foreach ($expectedShadow as $key => $value) { + self::assertEquals($value, $minorGridlines->getShadowProperty($key), $key); + } + + // Create the chart + $chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel, // yAxisLabel + null, // xAxis + null, // yAxis + $majorGridlines, + $minorGridlines + ); + $majorGridlines2 = $chart->getMajorGridlines(); + self::assertEquals($majorGlowSize, $majorGridlines2->getGlowProperty('size')); + self::assertEquals($expectedGlowColor, $majorGridlines2->getGlowProperty('color')); + self::assertEquals($softEdgeSize, $majorGridlines2->getSoftEdgesSize()); + $minorGridlines2 = $chart->getMinorGridlines(); + foreach ($expectedShadow as $key => $value) { + self::assertEquals($value, $minorGridlines2->getShadowProperty($key), $key); + } + + // Set the position where the chart should appear in the worksheet + $chart->setTopLeftPosition('A7'); + $chart->setBottomRightPosition('H20'); + + // Add the chart to the worksheet + $worksheet->addChart($chart); + + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $sheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + $majorGridlines3 = $chart2->getMajorGridlines(); + self::assertEquals($majorGlowSize, $majorGridlines3->getGlowProperty('size')); + self::assertEquals($expectedGlowColor, $majorGridlines3->getGlowProperty('color')); + self::assertEquals($softEdgeSize, $majorGridlines3->getSoftEdgesSize()); + $minorGridlines3 = $chart->getMinorGridlines(); + foreach ($expectedShadow as $key => $value) { + self::assertEquals($value, $minorGridlines3->getShadowProperty($key), $key); + } + + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/MultiplierTest.php b/tests/PhpSpreadsheetTests/Chart/MultiplierTest.php new file mode 100644 index 00000000..35161ff7 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/MultiplierTest.php @@ -0,0 +1,157 @@ +getActiveSheet(); + $worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] + ); + + // Set the Labels for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesLabels = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2010 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2012 + ]; + // Set the X-Axis Labels + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $xAxisTickValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 + ]; + // Set the Data values for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesValues = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', null, 4), + ]; + + // Build the dataseries + $series = new DataSeries( + DataSeries::TYPE_AREACHART, // plotType + DataSeries::GROUPING_PERCENT_STACKED, // plotGrouping + range(0, count($dataSeriesValues) - 1), // plotOrder + $dataSeriesLabels, // plotLabel + $xAxisTickValues, // plotCategory + $dataSeriesValues // plotValues + ); + + // Set the series in the plot area + $plotArea = new PlotArea(null, [$series]); + // Set the chart legend + $legend = new ChartLegend(ChartLegend::POSITION_TOPRIGHT, null, false); + + $title = new Title('Test %age-Stacked Area Chart'); + $yAxisLabel = new Title('Value ($k)'); + + // Create the chart + $chart = new Chart( + 'chart1', // name + $title, // title + $legend, // legend + $plotArea, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + $yAxisLabel // yAxisLabel + ); + $xAxis = $chart->getChartAxisX(); + $expectedX = [ + 'effect' => 'outerShdw', + 'algn' => 'bl', + 'blur' => 6, + 'direction' => 315, + 'distance' => 3, + 'rotWithShape' => 0, + 'size' => [ + 'sx' => null, + 'sy' => 254, + 'kx' => -94, + 'ky' => null, + ], + 'color' => [ + 'type' => Properties::EXCEL_COLOR_TYPE_ARGB, + 'value' => 'FF0000', + 'alpha' => 20, + ], + ]; + $expectedXmlX = [ + '', + '', + ]; + $expectedXmlNoX = [ + ' sx=', + ' ky=', + ]; + foreach ($expectedX as $key => $value) { + $xAxis->setShadowProperty($key, $value); + } + // Set the position where the chart should appear in the worksheet + $chart->setTopLeftPosition('A7'); + $chart->setBottomRightPosition('H20'); + + // Add the chart to the worksheet + $worksheet->addChart($chart); + + $writer = new XlsxWriter($spreadsheet); + $writer->setIncludeCharts(true); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); + + // confirm that file contains expected tags + foreach ($expectedXmlX as $expected) { + self::assertSame(1, substr_count($data, $expected), $expected); + } + foreach ($expectedXmlNoX as $expected) { + self::assertSame(0, substr_count($data, $expected), $expected); + } + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php b/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php new file mode 100644 index 00000000..58c024c1 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/ShadowPresetsTest.php @@ -0,0 +1,183 @@ +setShadowProperties(17); + $expectedShadow = [ + 'effect' => 'innerShdw', + 'distance' => 4, + 'direction' => 270, + 'blur' => 5, + ]; + foreach ($expectedShadow as $key => $value) { + self::assertEquals($gridlines->getShadowProperty($key), $value, $key); + } + } + + public function testGridlineShadowPresetsWithArray(): void + { + $gridlines = new GridLines(); + $gridlines->setShadowProperties(20); + $expectedShadow = [ + 'effect' => 'outerShdw', + 'blur' => 6, + 'direction' => 315, + 'size' => [ + 'sx' => null, + 'sy' => 0.23, + 'kx' => -20, + 'ky' => null, + ], + 'algn' => 'bl', + 'rotWithShape' => '0', + ]; + foreach ($expectedShadow as $key => $value) { + self::assertEquals($gridlines->getShadowProperty($key), $value, $key); + } + } + + public function testAxisShadowPresets(): void + { + $axis = new Axis(); + $axis->setShadowProperties(9); + $expectedShadow = [ + 'effect' => 'outerShdw', + 'blur' => 4, + 'distance' => 3, + 'direction' => 225, + 'algn' => 'br', + 'rotWithShape' => '0', + ]; + foreach ($expectedShadow as $key => $value) { + self::assertEquals($axis->getShadowProperty($key), $value, $key); + } + } + + public function testAxisShadowPresetsWithChanges(): void + { + $axis = new Axis(); + $axis->setShadowProperties( + 9, // preset + 'FF0000', // colorValue + 'srgbClr', // colorType + 20, // alpha + 6, // blur + 30, // direction + 4, // distance + ); + $expectedShadow = [ + 'effect' => 'outerShdw', + 'blur' => 6, + 'distance' => 4, + 'direction' => 30, + 'algn' => 'br', + 'rotWithShape' => '0', + 'color' => [ + 'value' => 'FF0000', + 'type' => 'srgbClr', + 'alpha' => 20, + ], + ]; + foreach ($expectedShadow as $key => $value) { + self::assertEquals($axis->getShadowProperty($key), $value, $key); + } + } + + public function testGridlinesShadowPresetsWithChanges(): void + { + $gridline = new GridLines(); + $gridline->setShadowProperties( + 9, // preset + 'FF0000', // colorValue + 'srgbClr', // colorType + 20, // alpha + 6, // blur + 30, // direction + 4, // distance + ); + $expectedShadow = [ + 'effect' => 'outerShdw', + 'blur' => 6, + 'distance' => 4, + 'direction' => 30, + 'algn' => 'br', + 'rotWithShape' => '0', + 'color' => [ + 'value' => 'FF0000', + 'type' => 'srgbClr', + 'alpha' => 20, + ], + ]; + foreach ($expectedShadow as $key => $value) { + self::assertEquals($gridline->getShadowProperty($key), $value, $key); + } + } + + public function testOutOfRangePresets(): void + { + $axis = new Axis(); + $axis->setShadowProperties(99); + $expectedShadow = [ + 'presets' => Properties::SHADOW_PRESETS_NOSHADOW, + 'effect' => null, + 'color' => [ + 'type' => Properties::EXCEL_COLOR_TYPE_STANDARD, + 'value' => 'black', + 'alpha' => 40, + ], + 'size' => [ + 'sx' => null, + 'sy' => null, + 'kx' => null, + 'ky' => null, + ], + 'blur' => null, + 'direction' => null, + 'distance' => null, + 'algn' => null, + 'rotWithShape' => null, + ]; + foreach ($expectedShadow as $key => $value) { + self::assertEquals($value, $axis->getShadowProperty($key), $key); + } + } + + public function testOutOfRangeGridlines(): void + { + $gridline = new GridLines(); + $gridline->setShadowProperties(99); + $expectedShadow = [ + 'presets' => Properties::SHADOW_PRESETS_NOSHADOW, + 'effect' => null, + 'color' => [ + 'type' => Properties::EXCEL_COLOR_TYPE_STANDARD, + 'value' => 'black', + 'alpha' => 40, + ], + 'size' => [ + 'sx' => null, + 'sy' => null, + 'kx' => null, + 'ky' => null, + ], + 'blur' => null, + 'direction' => null, + 'distance' => null, + 'algn' => null, + 'rotWithShape' => null, + ]; + foreach ($expectedShadow as $key => $value) { + self::assertEquals($value, $gridline->getShadowProperty($key), $key); + } + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/BaseNoLoad.php b/tests/PhpSpreadsheetTests/Reader/BaseNoLoad.php new file mode 100644 index 00000000..02298e8d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/BaseNoLoad.php @@ -0,0 +1,18 @@ +loadSpreadsheetFromFile($filename); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/BaseNoLoadTest.php b/tests/PhpSpreadsheetTests/Reader/BaseNoLoadTest.php new file mode 100644 index 00000000..c79bb9e9 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/BaseNoLoadTest.php @@ -0,0 +1,17 @@ +expectException(SpreadsheetException::class); + $this->expectExceptionMessage('Reader classes must implement their own loadSpreadsheetFromFile() method'); + $reader = new BaseNoLoad(); + $reader->loadxxx('unknown.file'); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Html/HtmlBorderTest.php b/tests/PhpSpreadsheetTests/Reader/Html/HtmlBorderTest.php index 58a0b5d7..a7b95bcf 100644 --- a/tests/PhpSpreadsheetTests/Reader/Html/HtmlBorderTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Html/HtmlBorderTest.php @@ -18,6 +18,8 @@ class HtmlBorderTest extends TestCase Border left Border right + + '; $filename = HtmlHelper::createHtml($html); @@ -61,6 +63,17 @@ class HtmlBorderTest extends TestCase foreach ([$borders->getTop(), $borders->getBottom(), $borders->getLeft(), $borders->getRight()] as $border) { self::assertEquals(Border::BORDER_NONE, $border->getBorderStyle()); } + + $style = $firstSheet->getCell('G1')->getStyle(); + $borders = $style->getBorders(); + $border = $borders->getRight(); + self::assertEquals(Border::BORDER_DASHED, $border->getBorderStyle()); + + $style = $firstSheet->getCell('H1')->getStyle(); + $borders = $style->getBorders(); + $border = $borders->getRight(); + self::assertEquals(Border::BORDER_DOTTED, $border->getBorderStyle()); + self::assertEquals('333333', $border->getColor()->getRGB()); } /** diff --git a/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php b/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php index 38a9bc9e..cabac403 100644 --- a/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Html/HtmlTest.php @@ -327,4 +327,35 @@ class HtmlTest extends TestCase self::assertEquals(Border::BORDER_THIN, $border->getBorderStyle()); } } + + public function testBorderWithColspan(): void + { + $html = ' + + + + + + + +
NOT SPANNEDSPANNED
NOT SPANNED
'; + + $reader = new Html(); + $spreadsheet = $reader->loadFromString($html); + $firstSheet = $spreadsheet->getSheet(0); + $style = $firstSheet->getStyle('B1:B2'); + + $borders = $style->getBorders(); + + $totalBorders = [ + $borders->getTop(), + $borders->getLeft(), + $borders->getBottom(), + $borders->getRight(), + ]; + + foreach ($totalBorders as $border) { + self::assertEquals(Border::BORDER_THIN, $border->getBorderStyle()); + } + } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php new file mode 100644 index 00000000..6d6949d8 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php @@ -0,0 +1,106 @@ +getRowDimension($row)->getVisible()) { + $actualVisible[] = $row; + } + } + + return $actualVisible; + } + + public function testReadDateRange(): void + { + $spreadsheet = IOFactory::load(self::TESTBOOK); + $sheet = $spreadsheet->getSheetByName('daterange'); + self::assertNotNull($sheet); + $filter = $sheet->getAutoFilter(); + $maxRow = 30; + self::assertSame("A1:A$maxRow", $filter->getRange()); + $columns = $filter->getColumns(); + self::assertCount(1, $columns); + $column = $columns['A'] ?? null; + self::assertNotNull($column); + $ruleset = $column->getRules(); + self::assertCount(1, $ruleset); + $rule = $ruleset[0]; + self::assertSame(Rule::AUTOFILTER_RULETYPE_DATEGROUP, $rule->getRuleType()); + $value = $rule->getValue(); + self::assertIsArray($value); + self::assertCount(6, $value); + self::assertSame('2002', $value['year']); + self::assertSame('', $value['month']); + self::assertSame('', $value['day']); + self::assertSame('', $value['hour']); + self::assertSame('', $value['minute']); + self::assertSame('', $value['second']); + self::assertSame( + [25, 26, 27, 28, 29, 30], + $this->getVisibleSheet($sheet, $maxRow) + ); + $spreadsheet->disconnectWorksheets(); + } + + public function testReadTopTen(): void + { + $spreadsheet = IOFactory::load(self::TESTBOOK); + $sheet = $spreadsheet->getSheetByName('top10'); + self::assertNotNull($sheet); + $filter = $sheet->getAutoFilter(); + $maxRow = 65; + self::assertSame("A1:A$maxRow", $filter->getRange()); + $columns = $filter->getColumns(); + self::assertCount(1, $columns); + $column = $columns['A'] ?? null; + self::assertNotNull($column); + $ruleset = $column->getRules(); + self::assertCount(1, $ruleset); + $rule = $ruleset[0]; + self::assertSame(Rule::AUTOFILTER_RULETYPE_TOPTENFILTER, $rule->getRuleType()); + $value = $rule->getValue(); + self::assertSame('10', $value); + self::assertSame( + [56, 57, 58, 59, 60, 61, 62, 63, 64, 65], + $this->getVisibleSheet($sheet, $maxRow) + ); + $spreadsheet->disconnectWorksheets(); + } + + public function testReadDynamic(): void + { + $spreadsheet = IOFactory::load(self::TESTBOOK); + $sheet = $spreadsheet->getSheetByName('dynamic'); + self::assertNotNull($sheet); + $filter = $sheet->getAutoFilter(); + $maxRow = 30; + self::assertSame("A1:A$maxRow", $filter->getRange()); + $columns = $filter->getColumns(); + self::assertCount(1, $columns); + $column = $columns['A'] ?? null; + self::assertNotNull($column); + $ruleset = $column->getRules(); + self::assertCount(1, $ruleset); + $rule = $ruleset[0]; + self::assertSame(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER, $rule->getRuleType()); + self::assertSame('M4', $rule->getGrouping()); + self::assertSame( + [5, 17, 28], + $this->getVisibleSheet($sheet, $maxRow) + ); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetup2Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetup2Test.php new file mode 100644 index 00000000..1bbc88ac --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetup2Test.php @@ -0,0 +1,41 @@ +getAllSheets() as $worksheet) { + ++$sheets; + $hf = $worksheet->getHeaderFooter(); + self::assertTrue($hf->getDifferentOddEven()); + self::assertTrue($hf->getDifferentFirst()); + } + self::assertSame(4, $sheets); + + $spreadsheet->disconnectWorksheets(); + } + + public function testColumnBreak(): void + { + $spreadsheet = IOFactory::load(self::TESTBOOK); + $sheet = $spreadsheet->getSheetByName('colbreak'); + self::assertNotNull($sheet); + $breaks = $sheet->getBreaks(); + self::assertCount(1, $breaks); + $break = $breaks['D1'] ?? null; + self::assertNotNull($break); + self::assertSame($break, Worksheet::BREAK_COLUMN); + + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/RichTextTest.php b/tests/PhpSpreadsheetTests/RichTextTest.php new file mode 100644 index 00000000..e2dfcce7 --- /dev/null +++ b/tests/PhpSpreadsheetTests/RichTextTest.php @@ -0,0 +1,41 @@ +getActiveSheet(); + $cell = $sheet->getCell('A1'); + $cell->setValue(2); + self::assertSame(2, $cell->getCalculatedValue()); + $cell->getStyle()->getFont()->setName('whatever'); + $richText = new RichText($cell); + self::assertSame('whatever', $sheet->getCell('A1')->getStyle()->getFont()->getName()); + self::assertEquals($richText, $cell->getValue()); + self::assertSame('2', $cell->getCalculatedValue()); + + $spreadsheet->disconnectWorksheets(); + } + + public function testTextElements(): void + { + $element1 = new TextElement('A'); + self::assertNull($element1->getFont()); + $element2 = new TextElement('B'); + $element3 = new TextElement('C'); + $richText = new RichText(); + $richText->setRichTextElements([$element1, $element2, $element3]); + self::assertSame('ABC', $richText->getPlainText()); + $cloneText = clone $richText; + self::assertEquals($richText, $cloneText); + self::assertNotSame($richText, $cloneText); + } +} diff --git a/tests/PhpSpreadsheetTests/Style/FontTest.php b/tests/PhpSpreadsheetTests/Style/FontTest.php index a6843c10..02814afa 100644 --- a/tests/PhpSpreadsheetTests/Style/FontTest.php +++ b/tests/PhpSpreadsheetTests/Style/FontTest.php @@ -38,6 +38,7 @@ class FontTest extends TestCase $font->setSuperscript(true); self::assertTrue($font->getSuperscript()); self::assertFalse($font->getSubscript(), 'False remains unchanged'); + $spreadsheet->disconnectWorksheets(); } public function testSize(): void @@ -70,5 +71,21 @@ class FontTest extends TestCase $font->setSize($invalidFontSizeValue); self::assertEquals(10, $font->getSize(), 'Set to 10 after trying to set an invalid value.'); } + $spreadsheet->disconnectWorksheets(); + } + + public function testName(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $cell = $sheet->getCell('A1'); + $cell->setValue('Cell A1'); + $font = $cell->getStyle()->getFont(); + self::assertEquals('Calibri', $font->getName(), 'The default is Calibri'); + $font->setName('whatever'); + self::assertEquals('whatever', $font->getName(), 'The default is Calibri'); + $font->setName(''); + self::assertEquals('Calibri', $font->getName(), 'Null string changed to default'); + $spreadsheet->disconnectWorksheets(); } } diff --git a/tests/data/Reader/XLSX/autofilter2.xlsx b/tests/data/Reader/XLSX/autofilter2.xlsx new file mode 100644 index 00000000..70c9839c Binary files /dev/null and b/tests/data/Reader/XLSX/autofilter2.xlsx differ diff --git a/tests/data/Style/Color/ColorGetBlue.php b/tests/data/Style/Color/ColorGetBlue.php index d12a98f0..ff4d9fb7 100644 --- a/tests/data/Style/Color/ColorGetBlue.php +++ b/tests/data/Style/Color/ColorGetBlue.php @@ -32,4 +32,5 @@ return [ 'FFFF00', false, ], + 'invalid hex' => ['00', 'AABBDX'], ];