Update to phpspreadsheet 1.8.2

This commit is contained in:
Laurent Destailleur 2021-04-08 18:32:02 +02:00
parent 04536f9ec8
commit 76c3229c97
61 changed files with 2557 additions and 1126 deletions

View File

@ -23,7 +23,7 @@ PEAR Mail_MIME 1.8.9 BSD Yes
ParseDown 1.6 MIT License Yes Markdown parser ParseDown 1.6 MIT License Yes Markdown parser
PCLZip 2.8.4 LGPL-3+ Yes Library to zip/unzip files PCLZip 2.8.4 LGPL-3+ Yes Library to zip/unzip files
PHPDebugBar 1.15.1 MIT License Yes Used only by the module "debugbar" for developers PHPDebugBar 1.15.1 MIT License Yes Used only by the module "debugbar" for developers
PHPSpreadSheet ? LGPL-2.1+ Yes Read/Write XLS files, read ODS files PHPSpreadSheet 1.8.2 LGPL-2.1+ Yes Read/Write XLS files, read ODS files
php-iban 1.4.7 LGPL-3+ Yes Parse and validate IBAN (and IIBAN) bank account information in PHP php-iban 1.4.7 LGPL-3+ Yes Parse and validate IBAN (and IIBAN) bank account information in PHP
PHPoAuthLib 0.8.2 MIT License Yes Library to provide oauth1 and oauth2 to different service PHPoAuthLib 0.8.2 MIT License Yes Library to provide oauth1 and oauth2 to different service
PHPPrintIPP 1.3 GPL-2+ Yes Library to send print IPP requests PHPPrintIPP 1.3 GPL-2+ Yes Library to send print IPP requests

57
composer.lock generated
View File

@ -327,6 +327,63 @@
"source": "https://github.com/php-fig/log/tree/1.1.3" "source": "https://github.com/php-fig/log/tree/1.1.3"
}, },
"time": "2020-03-23T09:12:05+00:00" "time": "2020-03-23T09:12:05+00:00"
},
{
"name": "phpoffice/phpexcel",
"version": "1.8.2",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PHPExcel.git",
"reference": "1441011fb7ecdd8cc689878f54f8b58a6805f870"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PHPExcel/zipball/1441011fb7ecdd8cc689878f54f8b58a6805f870",
"reference": "1441011fb7ecdd8cc689878f54f8b58a6805f870",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
"php": "^5.2|^7.0"
},
"require-dev": {
"squizlabs/php_codesniffer": "2.*"
},
"type": "library",
"autoload": {
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "http://blog.maartenballiauw.be"
},
{
"name": "Erik Tilt"
},
{
"name": "Franck Lefevre",
"homepage": "http://rootslabs.net"
},
{
"name": "Mark Baker",
"homepage": "http://markbakeruk.net"
}
],
"description": "PHPExcel - OpenXML - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PHPExcel",
"keywords": [
"OpenXML",
"excel",
"xlsx"
],
"abandoned": "phpoffice/phpspreadsheet",
"time": "2018-11-22T23:07:24+00:00"
}, },
{ {
"name": "restler/framework", "name": "restler/framework",

View File

@ -117,7 +117,7 @@ class Calculation
/** /**
* An array of the nested cell references accessed by the calculation engine, used for the debug log. * An array of the nested cell references accessed by the calculation engine, used for the debug log.
* *
* @var array of string * @var CyclicReferenceStack
*/ */
private $cyclicReferenceStack; private $cyclicReferenceStack;
@ -1945,6 +1945,11 @@ class Calculation
'functionCall' => [MathTrig::class, 'SUMXMY2'], 'functionCall' => [MathTrig::class, 'SUMXMY2'],
'argumentCount' => '2', 'argumentCount' => '2',
], ],
'SWITCH' => [
'category' => Category::CATEGORY_LOGICAL,
'functionCall' => [Logical::class, 'statementSwitch'],
'argumentCount' => '3+',
],
'SYD' => [ 'SYD' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'SYD'], 'functionCall' => [Financial::class, 'SYD'],
@ -2207,8 +2212,8 @@ class Calculation
private static function loadLocales() private static function loadLocales()
{ {
$localeFileDirectory = __DIR__ . '/locale/'; $localeFileDirectory = __DIR__ . '/locale/';
foreach (glob($localeFileDirectory . '/*', GLOB_ONLYDIR) as $filename) { foreach (glob($localeFileDirectory . '*', GLOB_ONLYDIR) as $filename) {
$filename = substr($filename, strlen($localeFileDirectory) + 1); $filename = substr($filename, strlen($localeFileDirectory));
if ($filename != 'en') { if ($filename != 'en') {
self::$validLocaleLanguages[] = $filename; self::$validLocaleLanguages[] = $filename;
} }
@ -2413,7 +2418,6 @@ class Calculation
if (strpos($locale, '_') !== false) { if (strpos($locale, '_') !== false) {
list($language) = explode('_', $locale); list($language) = explode('_', $locale);
} }
if (count(self::$validLocaleLanguages) == 1) { if (count(self::$validLocaleLanguages) == 1) {
self::loadLocales(); self::loadLocales();
} }
@ -2704,7 +2708,7 @@ class Calculation
* @param Cell $pCell Cell to calculate * @param Cell $pCell Cell to calculate
* @param bool $resetLog Flag indicating whether the debug log should be reset or not * @param bool $resetLog Flag indicating whether the debug log should be reset or not
* *
* @throws Exception * @throws \PhpOffice\PhpSpreadsheet\Exception
* *
* @return mixed * @return mixed
*/ */
@ -2808,7 +2812,7 @@ class Calculation
* @param string $cellID Address of the cell to calculate * @param string $cellID Address of the cell to calculate
* @param Cell $pCell Cell to calculate * @param Cell $pCell Cell to calculate
* *
* @throws Exception * @throws \PhpOffice\PhpSpreadsheet\Exception
* *
* @return mixed * @return mixed
*/ */
@ -2892,6 +2896,15 @@ class Calculation
{ {
$cellValue = null; $cellValue = null;
// Quote-Prefixed cell values cannot be formulae, but are treated as strings
if ($pCell !== null && $pCell->getStyle()->getQuotePrefix() === true) {
return self::wrapResult((string) $formula);
}
if (preg_match('/^=\s*cmd\s*\|/miu', $formula) !== 0) {
return self::wrapResult($formula);
}
// Basic validation that this is indeed a formula // Basic validation that this is indeed a formula
// We simply return the cell value if not // We simply return the cell value if not
$formula = trim($formula); $formula = trim($formula);

View File

@ -273,6 +273,60 @@ class Logical
return ($condition) ? $returnIfTrue : $returnIfFalse; return ($condition) ? $returnIfTrue : $returnIfFalse;
} }
/**
* STATEMENT_SWITCH.
*
* Returns corresponding with first match (any data type such as a string, numeric, date, etc).
*
* Excel Function:
* =SWITCH (expression, value1, result1, value2, result2, ... value_n, result_n [, default])
*
* Expression
* The expression to compare to a list of values.
* value1, value2, ... value_n
* A list of values that are compared to expression. The SWITCH function is looking for the first value that matches the expression.
* result1, result2, ... result_n
* A list of results. The SWITCH function returns the corresponding result when a value matches expression.
* default
* Optional. It is the default to return if expression does not match any of the values (value1, value2, ... value_n).
*
* @category Logical Functions
*
* @param mixed $arguments Statement arguments
*
* @return mixed The value of matched expression
*/
public static function statementSwitch(...$arguments)
{
$result = Functions::VALUE();
if (count($arguments) > 0) {
$targetValue = Functions::flattenSingleValue($arguments[0]);
$argc = count($arguments) - 1;
$switchCount = floor($argc / 2);
$switchSatisfied = false;
$hasDefaultClause = $argc % 2 !== 0;
$defaultClause = $argc % 2 === 0 ? null : $arguments[count($arguments) - 1];
if ($switchCount) {
for ($index = 0; $index < $switchCount; ++$index) {
if ($targetValue == $arguments[$index * 2 + 1]) {
$result = $arguments[$index * 2 + 2];
$switchSatisfied = true;
break;
}
}
}
if (!$switchSatisfied) {
$result = $hasDefaultClause ? $defaultClause : Functions::NA();
}
}
return $result;
}
/** /**
* IFERROR. * IFERROR.
* *

View File

@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class LookupRef class LookupRef
@ -473,8 +474,9 @@ class LookupRef
$lookupValue = Functions::flattenSingleValue($lookupValue); $lookupValue = Functions::flattenSingleValue($lookupValue);
$matchType = ($matchType === null) ? 1 : (int) Functions::flattenSingleValue($matchType); $matchType = ($matchType === null) ? 1 : (int) Functions::flattenSingleValue($matchType);
$initialLookupValue = $lookupValue;
// MATCH is not case sensitive // MATCH is not case sensitive
$lookupValue = strtolower($lookupValue); $lookupValue = StringHelper::strToLower($lookupValue);
// Lookup_value type has to be number, text, or logical values // Lookup_value type has to be number, text, or logical values
if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) { if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) {
@ -502,7 +504,7 @@ class LookupRef
} }
// Convert strings to lowercase for case-insensitive testing // Convert strings to lowercase for case-insensitive testing
if (is_string($lookupArrayValue)) { if (is_string($lookupArrayValue)) {
$lookupArray[$i] = strtolower($lookupArrayValue); $lookupArray[$i] = StringHelper::strToLower($lookupArrayValue);
} }
if (($lookupArrayValue === null) && (($matchType == 1) || ($matchType == -1))) { if (($lookupArrayValue === null) && (($matchType == 1) || ($matchType == -1))) {
$lookupArray = array_slice($lookupArray, 0, $i - 1); $lookupArray = array_slice($lookupArray, 0, $i - 1);
@ -522,9 +524,13 @@ class LookupRef
if ($matchType == 0 || $matchType == 1) { if ($matchType == 0 || $matchType == 1) {
foreach ($lookupArray as $i => $lookupArrayValue) { foreach ($lookupArray as $i => $lookupArrayValue) {
if (($matchType == 0) && ($lookupArrayValue == $lookupValue)) { $onlyNumeric = is_numeric($lookupArrayValue) && is_numeric($lookupValue);
$onlyNumericExactMatch = $onlyNumeric && $lookupArrayValue == $lookupValue;
$nonOnlyNumericExactMatch = !$onlyNumeric && $lookupArrayValue === $lookupValue;
$exactMatch = $onlyNumericExactMatch || $nonOnlyNumericExactMatch;
if (($matchType == 0) && $exactMatch) {
// exact match // exact match
return ++$i; return $i + 1;
} elseif (($matchType == 1) && ($lookupArrayValue <= $lookupValue)) { } elseif (($matchType == 1) && ($lookupArrayValue <= $lookupValue)) {
$i = array_search($i, $keySet); $i = array_search($i, $keySet);
@ -661,7 +667,9 @@ class LookupRef
{ {
reset($a); reset($a);
$firstColumn = key($a); $firstColumn = key($a);
if (($aLower = strtolower($a[$firstColumn])) == ($bLower = strtolower($b[$firstColumn]))) { $aLower = StringHelper::strToLower($a[$firstColumn]);
$bLower = StringHelper::strToLower($b[$firstColumn]);
if ($aLower == $bLower) {
return 0; return 0;
} }
@ -707,11 +715,14 @@ class LookupRef
uasort($lookup_array, ['self', 'vlookupSort']); uasort($lookup_array, ['self', 'vlookupSort']);
} }
$lookupLower = StringHelper::strToLower($lookup_value);
$rowNumber = $rowValue = false; $rowNumber = $rowValue = false;
foreach ($lookup_array as $rowKey => $rowData) { foreach ($lookup_array as $rowKey => $rowData) {
$firstLower = StringHelper::strToLower($rowData[$firstColumn]);
// break if we have passed possible keys // break if we have passed possible keys
if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) || if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) ||
(!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && (strtolower($rowData[$firstColumn]) > strtolower($lookup_value)))) { (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && ($firstLower > $lookupLower))) {
break; break;
} }
// remember the last key, but only if datatypes match // remember the last key, but only if datatypes match
@ -719,17 +730,15 @@ class LookupRef
(!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))) { (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))) {
if ($not_exact_match) { if ($not_exact_match) {
$rowNumber = $rowKey; $rowNumber = $rowKey;
$rowValue = $rowData[$firstColumn];
continue; continue;
} elseif ((strtolower($rowData[$firstColumn]) == strtolower($lookup_value)) } elseif (($firstLower == $lookupLower)
// Spreadsheets software returns first exact match, // Spreadsheets software returns first exact match,
// we have sorted and we might have broken key orders // we have sorted and we might have broken key orders
// we want the first one (by its initial index) // we want the first one (by its initial index)
&& (($rowNumber == false) || ($rowKey < $rowNumber)) && (($rowNumber == false) || ($rowKey < $rowNumber))
) { ) {
$rowNumber = $rowKey; $rowNumber = $rowKey;
$rowValue = $rowData[$firstColumn];
} }
} }
} }
@ -782,8 +791,11 @@ class LookupRef
// break if we have passed possible keys // break if we have passed possible keys
$bothNumeric = is_numeric($lookup_value) && is_numeric($rowData); $bothNumeric = is_numeric($lookup_value) && is_numeric($rowData);
$bothNotNumeric = !is_numeric($lookup_value) && !is_numeric($rowData); $bothNotNumeric = !is_numeric($lookup_value) && !is_numeric($rowData);
$lookupLower = StringHelper::strToLower($lookup_value);
$rowDataLower = StringHelper::strToLower($rowData);
if (($bothNumeric && $rowData > $lookup_value) || if (($bothNumeric && $rowData > $lookup_value) ||
($bothNotNumeric && strtolower($rowData) > strtolower($lookup_value))) { ($bothNotNumeric && $rowDataLower > $lookupLower)) {
break; break;
} }
@ -793,7 +805,7 @@ class LookupRef
$rowNumber = $rowKey; $rowNumber = $rowKey;
continue; continue;
} elseif (strtolower($rowData) === strtolower($lookup_value) } elseif ($rowDataLower === $lookupLower
&& ($rowNumber === null || $rowKey < $rowNumber) && ($rowNumber === null || $rowKey < $rowNumber)
) { ) {
$rowNumber = $rowKey; $rowNumber = $rowKey;

View File

@ -1224,11 +1224,12 @@ class MathTrig
} }
$testCondition = '=' . $arg . $condition; $testCondition = '=' . $arg . $condition;
$sumValue = array_key_exists($key, $sumArgs) ? $sumArgs[$key] : 0;
if (is_numeric($sumArgs[$key]) && if (is_numeric($sumValue) &&
Calculation::getInstance()->_calculateFormulaValue($testCondition)) { Calculation::getInstance()->_calculateFormulaValue($testCondition)) {
// Is it a value within our criteria and only numeric can be added to the result // Is it a value within our criteria and only numeric can be added to the result
$returnValue += $sumArgs[$key]; $returnValue += $sumValue;
} }
} }

View File

@ -337,6 +337,7 @@ SUMSQ
SUMX2MY2 SUMX2MY2
SUMX2PY2 SUMX2PY2
SUMXMY2 SUMXMY2
SWITCH
SYD SYD
T T
TAN TAN

View File

@ -16,6 +16,8 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
* @param Cell $cell Cell to bind value to * @param Cell $cell Cell to bind value to
* @param mixed $value Value to bind in cell * @param mixed $value Value to bind in cell
* *
* @throws \PhpOffice\PhpSpreadsheet\Exception
*
* @return bool * @return bool
*/ */
public function bindValue(Cell $cell, $value = null) public function bindValue(Cell $cell, $value = null)

View File

@ -523,7 +523,7 @@ class Cell
/** /**
* If this cell is in a merge range, then return the range. * If this cell is in a merge range, then return the range.
* *
* @return string * @return false|string
*/ */
public function getMergeRange() public function getMergeRange()
{ {

View File

@ -157,14 +157,12 @@ abstract class Coordinate
} }
// Build range // Build range
$imploded = [];
$counter = count($pRange); $counter = count($pRange);
for ($i = 0; $i < $counter; ++$i) { for ($i = 0; $i < $counter; ++$i) {
$pRange[$i] = implode(':', $pRange[$i]); $pRange[$i] = implode(':', $pRange[$i]);
} }
$imploded = implode(',', $pRange);
return $imploded; return implode(',', $pRange);
} }
/** /**

View File

@ -14,6 +14,8 @@ class DefaultValueBinder implements IValueBinder
* @param Cell $cell Cell to bind value to * @param Cell $cell Cell to bind value to
* @param mixed $value Value to bind in cell * @param mixed $value Value to bind in cell
* *
* @throws \PhpOffice\PhpSpreadsheet\Exception
*
* @return bool * @return bool
*/ */
public function bindValue(Cell $cell, $value) public function bindValue(Cell $cell, $value)

View File

@ -0,0 +1,31 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class StringValueBinder implements IValueBinder
{
/**
* Bind value to a cell.
*
* @param Cell $cell Cell to bind value to
* @param mixed $value Value to bind in cell
*
* @throws \PhpOffice\PhpSpreadsheet\Exception
*
* @return bool
*/
public function bindValue(Cell $cell, $value)
{
// sanitize UTF-8 strings
if (is_string($value)) {
$value = StringHelper::sanitizeUTF8($value);
}
$cell->setValueExplicit((string) $value, DataType::TYPE_STRING);
// Done!
return true;
}
}

View File

@ -365,10 +365,10 @@ class Axis extends Properties
} }
/** /**
* Set Shadow Properties from Maped Values. * Set Shadow Properties from Mapped Values.
* *
* @param array $properties_map * @param array $properties_map
* @param * $reference * @param mixed &$reference
* *
* @return Axis * @return Axis
*/ */

View File

@ -319,7 +319,7 @@ class GridLines extends Properties
* Set Shadow Properties Values. * Set Shadow Properties Values.
* *
* @param array $properties_map * @param array $properties_map
* @param * $reference * @param mixed &$reference
* *
* @return GridLines * @return GridLines
*/ */
@ -439,7 +439,7 @@ class GridLines extends Properties
{ {
if ($size !== null) { if ($size !== null) {
$this->activateObject(); $this->activateObject();
$softEdges['size'] = (string) $this->getExcelPointsWidth($size); $this->softEdges['size'] = (string) $this->getExcelPointsWidth($size);
} }
} }

View File

@ -175,13 +175,13 @@ class Layout
/** /**
* Set X-Mode. * Set X-Mode.
* *
* @param X-Mode $value * @param string $value
* *
* @return Layout * @return Layout
*/ */
public function setXMode($value) public function setXMode($value)
{ {
$this->xMode = $value; $this->xMode = (string) $value;
return $this; return $this;
} }
@ -199,13 +199,13 @@ class Layout
/** /**
* Set Y-Mode. * Set Y-Mode.
* *
* @param Y-Mode $value * @param string $value
* *
* @return Layout * @return Layout
*/ */
public function setYMode($value) public function setYMode($value)
{ {
$this->yMode = $value; $this->yMode = (string) $value;
return $this; return $this;
} }
@ -223,13 +223,13 @@ class Layout
/** /**
* Set X-Position. * Set X-Position.
* *
* @param X-Position $value * @param float $value
* *
* @return Layout * @return Layout
*/ */
public function setXPosition($value) public function setXPosition($value)
{ {
$this->xPos = $value; $this->xPos = (float) $value;
return $this; return $this;
} }
@ -247,13 +247,13 @@ class Layout
/** /**
* Set Y-Position. * Set Y-Position.
* *
* @param Y-Position $value * @param float $value
* *
* @return Layout * @return Layout
*/ */
public function setYPosition($value) public function setYPosition($value)
{ {
$this->yPos = $value; $this->yPos = (float) $value;
return $this; return $this;
} }

View File

@ -241,7 +241,7 @@ class Cells
*/ */
public function getHighestColumn($row = null) public function getHighestColumn($row = null)
{ {
if ($row == null) { if ($row === null) {
$colRow = $this->getHighestRowAndColumn(); $colRow = $this->getHighestRowAndColumn();
return $colRow['column']; return $colRow['column'];
@ -259,7 +259,7 @@ class Cells
$columnList[] = Coordinate::columnIndexFromString($c); $columnList[] = Coordinate::columnIndexFromString($c);
} }
return Coordinate::stringFromColumnIndex(max($columnList) + 1); return Coordinate::stringFromColumnIndex(max($columnList));
} }
/** /**
@ -272,7 +272,7 @@ class Cells
*/ */
public function getHighestRow($column = null) public function getHighestRow($column = null)
{ {
if ($column == null) { if ($column === null) {
$colRow = $this->getHighestRowAndColumn(); $colRow = $this->getHighestRowAndColumn();
return $colRow['row']; return $colRow['row'];

View File

@ -418,7 +418,7 @@ class Properties
* *
* @param string $propertyName * @param string $propertyName
* *
* @return string * @return mixed
*/ */
public function getCustomPropertyValue($propertyName) public function getCustomPropertyValue($propertyName)
{ {

View File

@ -55,27 +55,16 @@ abstract class BaseReader implements IReader
*/ */
protected $securityScanner; protected $securityScanner;
/** public function __construct()
* Read data only? {
* If this is true, then the Reader will only read data values for cells, it will not read any formatting information. $this->readFilter = new DefaultReadFilter();
* If false (the default) it will read data and formatting. }
*
* @return bool
*/
public function getReadDataOnly() public function getReadDataOnly()
{ {
return $this->readDataOnly; return $this->readDataOnly;
} }
/**
* Set read data only
* Set to true, to advise the Reader only to read data values for cells, and to ignore any formatting information.
* Set to false (the default) to advise the Reader to read both data and formatting for cells.
*
* @param bool $pValue
*
* @return IReader
*/
public function setReadDataOnly($pValue) public function setReadDataOnly($pValue)
{ {
$this->readDataOnly = (bool) $pValue; $this->readDataOnly = (bool) $pValue;
@ -83,27 +72,11 @@ abstract class BaseReader implements IReader
return $this; return $this;
} }
/**
* Read empty cells?
* If this is true (the default), then the Reader will read data values for all cells, irrespective of value.
* If false it will not read data for cells containing a null value or an empty string.
*
* @return bool
*/
public function getReadEmptyCells() public function getReadEmptyCells()
{ {
return $this->readEmptyCells; return $this->readEmptyCells;
} }
/**
* Set read empty cells
* Set to true (the default) to advise the Reader read data values for all cells, irrespective of value.
* Set to false to advise the Reader to ignore cells containing a null value or an empty string.
*
* @param bool $pValue
*
* @return IReader
*/
public function setReadEmptyCells($pValue) public function setReadEmptyCells($pValue)
{ {
$this->readEmptyCells = (bool) $pValue; $this->readEmptyCells = (bool) $pValue;
@ -111,29 +84,11 @@ abstract class BaseReader implements IReader
return $this; return $this;
} }
/**
* Read charts in workbook?
* If this is true, then the Reader will include any charts that exist in the workbook.
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
* If false (the default) it will ignore any charts defined in the workbook file.
*
* @return bool
*/
public function getIncludeCharts() public function getIncludeCharts()
{ {
return $this->includeCharts; return $this->includeCharts;
} }
/**
* Set read charts in workbook
* Set to true, to advise the Reader to include any charts that exist in the workbook.
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
* Set to false (the default) to discard charts.
*
* @param bool $pValue
*
* @return IReader
*/
public function setIncludeCharts($pValue) public function setIncludeCharts($pValue)
{ {
$this->includeCharts = (bool) $pValue; $this->includeCharts = (bool) $pValue;
@ -141,27 +96,11 @@ abstract class BaseReader implements IReader
return $this; return $this;
} }
/**
* Get which sheets to load
* Returns either an array of worksheet names (the list of worksheets that should be loaded), or a null
* indicating that all worksheets in the workbook should be loaded.
*
* @return mixed
*/
public function getLoadSheetsOnly() public function getLoadSheetsOnly()
{ {
return $this->loadSheetsOnly; return $this->loadSheetsOnly;
} }
/**
* Set which sheets to load.
*
* @param mixed $value
* This should be either an array of worksheet names to be loaded, or a string containing a single worksheet name.
* If NULL, then it tells the Reader to read all worksheets in the workbook
*
* @return IReader
*/
public function setLoadSheetsOnly($value) public function setLoadSheetsOnly($value)
{ {
if ($value === null) { if ($value === null) {
@ -173,12 +112,6 @@ abstract class BaseReader implements IReader
return $this; return $this;
} }
/**
* Set all sheets to load
* Tells the Reader to load all worksheets from the workbook.
*
* @return IReader
*/
public function setLoadAllSheets() public function setLoadAllSheets()
{ {
$this->loadSheetsOnly = null; $this->loadSheetsOnly = null;
@ -186,23 +119,11 @@ abstract class BaseReader implements IReader
return $this; return $this;
} }
/**
* Read filter.
*
* @return IReadFilter
*/
public function getReadFilter() public function getReadFilter()
{ {
return $this->readFilter; return $this->readFilter;
} }
/**
* Set read filter.
*
* @param IReadFilter $pValue
*
* @return IReader
*/
public function setReadFilter(IReadFilter $pValue) public function setReadFilter(IReadFilter $pValue)
{ {
$this->readFilter = $pValue; $this->readFilter = $pValue;
@ -210,7 +131,7 @@ abstract class BaseReader implements IReader
return $this; return $this;
} }
public function getSecuritySCanner() public function getSecurityScanner()
{ {
if (property_exists($this, 'securityScanner')) { if (property_exists($this, 'securityScanner')) {
return $this->securityScanner; return $this->securityScanner;

View File

@ -62,7 +62,7 @@ class Csv extends BaseReader
*/ */
public function __construct() public function __construct()
{ {
$this->readFilter = new DefaultReadFilter(); parent::__construct();
} }
/** /**
@ -143,7 +143,7 @@ class Csv extends BaseReader
return; return;
} }
return $this->skipBOM(); $this->skipBOM();
} }
/** /**
@ -155,7 +155,7 @@ class Csv extends BaseReader
return; return;
} }
$potentialDelimiters = [',', ';', "\t", '|', ':', ' ']; $potentialDelimiters = [',', ';', "\t", '|', ':', ' ', '~'];
$counts = []; $counts = [];
foreach ($potentialDelimiters as $delimiter) { foreach ($potentialDelimiters as $delimiter) {
$counts[$delimiter] = []; $counts[$delimiter] = [];
@ -184,8 +184,9 @@ class Csv extends BaseReader
// If number of lines is 0, nothing to infer : fall back to the default // If number of lines is 0, nothing to infer : fall back to the default
if ($numberLines === 0) { if ($numberLines === 0) {
$this->delimiter = reset($potentialDelimiters); $this->delimiter = reset($potentialDelimiters);
$this->skipBOM();
return $this->skipBOM(); return;
} }
// Calculate the mean square deviations for each delimiter (ignoring delimiters that haven't been found consistently) // Calculate the mean square deviations for each delimiter (ignoring delimiters that haven't been found consistently)
@ -230,7 +231,7 @@ class Csv extends BaseReader
$this->delimiter = reset($potentialDelimiters); $this->delimiter = reset($potentialDelimiters);
} }
return $this->skipBOM(); $this->skipBOM();
} }
/** /**
@ -254,15 +255,13 @@ class Csv extends BaseReader
$line = $line . $newLine; $line = $line . $newLine;
// Drop everything that is enclosed to avoid counting false positives in enclosures // Drop everything that is enclosed to avoid counting false positives in enclosures
$enclosure = preg_quote($this->enclosure, '/'); $enclosure = '(?<!' . preg_quote($this->escapeCharacter, '/') . ')'
$line = preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/U', '', $line); . preg_quote($this->enclosure, '/');
$line = preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line);
// See if we have any enclosures left in the line // See if we have any enclosures left in the line
$matches = []; // if we still have an enclosure then we need to read the next line as well
preg_match('/(' . $enclosure . ')/', $line, $matches); if (preg_match('/(' . $enclosure . ')/', $line) > 0) {
// if we still have an enclosure then we need to read the next line aswell
if (count($matches) > 0) {
$line = $this->getNextLine($line); $line = $this->getNextLine($line);
} }
@ -547,7 +546,8 @@ class Csv extends BaseReader
fclose($this->fileHandle); fclose($this->fileHandle);
// Trust file extension if any // Trust file extension if any
if (strtolower(pathinfo($pFilename, PATHINFO_EXTENSION)) === 'csv') { $extension = strtolower(pathinfo($pFilename, PATHINFO_EXTENSION));
if (in_array($extension, ['csv', 'tsv'])) {
return true; return true;
} }

View File

@ -36,7 +36,7 @@ class Gnumeric extends BaseReader
*/ */
public function __construct() public function __construct()
{ {
$this->readFilter = new DefaultReadFilter(); parent::__construct();
$this->referenceHelper = ReferenceHelper::getInstance(); $this->referenceHelper = ReferenceHelper::getInstance();
$this->securityScanner = XmlScanner::getInstance($this); $this->securityScanner = XmlScanner::getInstance($this);
} }

View File

@ -12,6 +12,9 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Fill; use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
/** PhpSpreadsheet root directory */ /** PhpSpreadsheet root directory */
@ -96,6 +99,26 @@ class Html extends BaseReader
], ],
], ],
], // Bottom border ], // Bottom border
'strong' => [
'font' => [
'bold' => true,
],
], // Bold
'b' => [
'font' => [
'bold' => true,
],
], // Bold
'i' => [
'font' => [
'italic' => true,
],
], // Italic
'em' => [
'font' => [
'italic' => true,
],
], // Italic
]; ];
protected $rowspan = []; protected $rowspan = [];
@ -105,7 +128,7 @@ class Html extends BaseReader
*/ */
public function __construct() public function __construct()
{ {
$this->readFilter = new DefaultReadFilter(); parent::__construct();
$this->securityScanner = XmlScanner::getInstance($this); $this->securityScanner = XmlScanner::getInstance($this);
} }
@ -295,11 +318,9 @@ class Html extends BaseReader
switch ($child->nodeName) { switch ($child->nodeName) {
case 'meta': case 'meta':
foreach ($attributeArray as $attributeName => $attributeValue) { foreach ($attributeArray as $attributeName => $attributeValue) {
switch ($attributeName) { // Extract character set, so we can convert to UTF-8 if required
case 'content': if ($attributeName === 'charset') {
// TODO $this->setInputEncoding($attributeValue);
// Extract character set, so we can convert to UTF-8 if required
break;
} }
} }
$this->processDomElement($child, $sheet, $row, $column, $cellContent); $this->processDomElement($child, $sheet, $row, $column, $cellContent);
@ -334,6 +355,10 @@ class Html extends BaseReader
$cellContent .= ' '; $cellContent .= ' ';
} }
if (isset($this->formats[$child->nodeName])) {
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
}
break; break;
case 'hr': case 'hr':
$this->flushCell($sheet, $column, $row, $cellContent); $this->flushCell($sheet, $column, $row, $cellContent);
@ -423,6 +448,10 @@ class Html extends BaseReader
$column = 'A'; $column = 'A';
} }
break;
case 'img':
$this->insertImage($sheet, $column, $row, $attributeArray);
break; break;
case 'table': case 'table':
$this->flushCell($sheet, $column, $row, $cellContent); $this->flushCell($sheet, $column, $row, $cellContent);
@ -448,6 +477,11 @@ class Html extends BaseReader
$column = $this->getTableStartColumn(); $column = $this->getTableStartColumn();
$cellContent = ''; $cellContent = '';
$this->processDomElement($child, $sheet, $row, $column, $cellContent); $this->processDomElement($child, $sheet, $row, $column, $cellContent);
if (isset($attributeArray['height'])) {
$sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
}
++$row; ++$row;
break; break;
@ -501,6 +535,27 @@ class Html extends BaseReader
] ]
); );
} }
if (isset($attributeArray['width'])) {
$sheet->getColumnDimension($column)->setWidth($attributeArray['width']);
}
if (isset($attributeArray['height'])) {
$sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
}
if (isset($attributeArray['align'])) {
$sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']);
}
if (isset($attributeArray['valign'])) {
$sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']);
}
if (isset($attributeArray['data-format'])) {
$sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']);
}
++$column; ++$column;
break; break;
@ -608,36 +663,271 @@ class Html extends BaseReader
return; return;
} }
$supported_styles = ['background-color', 'color']; $cellStyle = $sheet->getStyle($column . $row);
// add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color // add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color
$styles = explode(';', $attributeArray['style']); $styles = explode(';', $attributeArray['style']);
foreach ($styles as $st) { foreach ($styles as $st) {
$value = explode(':', $st); $value = explode(':', $st);
$styleName = isset($value[0]) ? trim($value[0]) : null;
$styleValue = isset($value[1]) ? trim($value[1]) : null;
if (empty(trim($value[0])) || !in_array(trim($value[0]), $supported_styles)) { if (!$styleName) {
continue; continue;
} }
//check if has #, so we can get clean hex switch ($styleName) {
if (substr(trim($value[1]), 0, 1) == '#') { case 'background':
$style_color = substr(trim($value[1]), 1);
}
if (empty($style_color)) {
continue;
}
switch (trim($value[0])) {
case 'background-color': case 'background-color':
$sheet->getStyle($column . $row)->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => "{$style_color}"]]]); $styleColor = $this->getStyleColor($styleValue);
if (!$styleColor) {
continue 2;
}
$cellStyle->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $styleColor]]]);
break; break;
case 'color': case 'color':
$sheet->getStyle($column . $row)->applyFromArray(['font' => ['color' => ['rgb' => "{$style_color}"]]]); $styleColor = $this->getStyleColor($styleValue);
if (!$styleColor) {
continue 2;
}
$cellStyle->applyFromArray(['font' => ['color' => ['rgb' => $styleColor]]]);
break;
case 'border':
$this->setBorderStyle($cellStyle, $styleValue, 'allBorders');
break;
case 'border-top':
$this->setBorderStyle($cellStyle, $styleValue, 'top');
break;
case 'border-bottom':
$this->setBorderStyle($cellStyle, $styleValue, 'bottom');
break;
case 'border-left':
$this->setBorderStyle($cellStyle, $styleValue, 'left');
break;
case 'border-right':
$this->setBorderStyle($cellStyle, $styleValue, 'right');
break;
case 'font-size':
$cellStyle->getFont()->setSize(
(float) $styleValue
);
break;
case 'font-weight':
if ($styleValue === 'bold' || $styleValue >= 500) {
$cellStyle->getFont()->setBold(true);
}
break;
case 'font-style':
if ($styleValue === 'italic') {
$cellStyle->getFont()->setItalic(true);
}
break;
case 'font-family':
$cellStyle->getFont()->setName(str_replace('\'', '', $styleValue));
break;
case 'text-decoration':
switch ($styleValue) {
case 'underline':
$cellStyle->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
break;
case 'line-through':
$cellStyle->getFont()->setStrikethrough(true);
break;
}
break;
case 'text-align':
$cellStyle->getAlignment()->setHorizontal($styleValue);
break;
case 'vertical-align':
$cellStyle->getAlignment()->setVertical($styleValue);
break;
case 'width':
$sheet->getColumnDimension($column)->setWidth(
str_replace('px', '', $styleValue)
);
break;
case 'height':
$sheet->getRowDimension($row)->setRowHeight(
str_replace('px', '', $styleValue)
);
break;
case 'word-wrap':
$cellStyle->getAlignment()->setWrapText(
$styleValue === 'break-word'
);
break;
case 'text-indent':
$cellStyle->getAlignment()->setIndent(
(int) str_replace(['px'], '', $styleValue)
);
break; break;
} }
} }
} }
/**
* Check if has #, so we can get clean hex.
*
* @param $value
*
* @return null|string
*/
public function getStyleColor($value)
{
if (strpos($value, '#') === 0) {
return substr($value, 1);
}
return null;
}
/**
* @param Worksheet $sheet
* @param string $column
* @param int $row
* @param array $attributes
*
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
private function insertImage(Worksheet $sheet, $column, $row, array $attributes)
{
if (!isset($attributes['src'])) {
return;
}
$src = urldecode($attributes['src']);
$width = isset($attributes['width']) ? (float) $attributes['width'] : null;
$height = isset($attributes['height']) ? (float) $attributes['height'] : null;
$name = isset($attributes['alt']) ? (float) $attributes['alt'] : null;
$drawing = new Drawing();
$drawing->setPath($src);
$drawing->setWorksheet($sheet);
$drawing->setCoordinates($column . $row);
$drawing->setOffsetX(0);
$drawing->setOffsetY(10);
$drawing->setResizeProportional(true);
if ($name) {
$drawing->setName($name);
}
if ($width) {
$drawing->setWidth((int) $width);
}
if ($height) {
$drawing->setHeight((int) $height);
}
$sheet->getColumnDimension($column)->setWidth(
$drawing->getWidth() / 6
);
$sheet->getRowDimension($row)->setRowHeight(
$drawing->getHeight() * 0.9
);
}
/**
* Map html border style to PhpSpreadsheet border style.
*
* @param string $style
*
* @return null|string
*/
public function getBorderStyle($style)
{
switch ($style) {
case 'solid':
return Border::BORDER_THIN;
case 'dashed':
return Border::BORDER_DASHED;
case 'dotted':
return Border::BORDER_DOTTED;
case 'medium':
return Border::BORDER_MEDIUM;
case 'thick':
return Border::BORDER_THICK;
case 'none':
return Border::BORDER_NONE;
case 'dash-dot':
return Border::BORDER_DASHDOT;
case 'dash-dot-dot':
return Border::BORDER_DASHDOTDOT;
case 'double':
return Border::BORDER_DOUBLE;
case 'hair':
return Border::BORDER_HAIR;
case 'medium-dash-dot':
return Border::BORDER_MEDIUMDASHDOT;
case 'medium-dash-dot-dot':
return Border::BORDER_MEDIUMDASHDOTDOT;
case 'medium-dashed':
return Border::BORDER_MEDIUMDASHED;
case 'slant-dash-dot':
return Border::BORDER_SLANTDASHDOT;
}
return null;
}
/**
* @param Style $cellStyle
* @param string $styleValue
* @param string $type
*/
private function setBorderStyle(Style $cellStyle, $styleValue, $type)
{
list(, $borderStyle, $color) = explode(' ', $styleValue);
$cellStyle->applyFromArray([
'borders' => [
$type => [
'borderStyle' => $this->getBorderStyle($borderStyle),
'color' => ['rgb' => $this->getStyleColor($color)],
],
],
]);
}
} }

View File

@ -18,6 +18,112 @@ interface IReader
*/ */
public function canRead($pFilename); public function canRead($pFilename);
/**
* Read data only?
* If this is true, then the Reader will only read data values for cells, it will not read any formatting information.
* If false (the default) it will read data and formatting.
*
* @return bool
*/
public function getReadDataOnly();
/**
* Set read data only
* Set to true, to advise the Reader only to read data values for cells, and to ignore any formatting information.
* Set to false (the default) to advise the Reader to read both data and formatting for cells.
*
* @param bool $pValue
*
* @return IReader
*/
public function setReadDataOnly($pValue);
/**
* Read empty cells?
* If this is true (the default), then the Reader will read data values for all cells, irrespective of value.
* If false it will not read data for cells containing a null value or an empty string.
*
* @return bool
*/
public function getReadEmptyCells();
/**
* Set read empty cells
* Set to true (the default) to advise the Reader read data values for all cells, irrespective of value.
* Set to false to advise the Reader to ignore cells containing a null value or an empty string.
*
* @param bool $pValue
*
* @return IReader
*/
public function setReadEmptyCells($pValue);
/**
* Read charts in workbook?
* If this is true, then the Reader will include any charts that exist in the workbook.
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
* If false (the default) it will ignore any charts defined in the workbook file.
*
* @return bool
*/
public function getIncludeCharts();
/**
* Set read charts in workbook
* Set to true, to advise the Reader to include any charts that exist in the workbook.
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
* Set to false (the default) to discard charts.
*
* @param bool $pValue
*
* @return IReader
*/
public function setIncludeCharts($pValue);
/**
* Get which sheets to load
* Returns either an array of worksheet names (the list of worksheets that should be loaded), or a null
* indicating that all worksheets in the workbook should be loaded.
*
* @return mixed
*/
public function getLoadSheetsOnly();
/**
* Set which sheets to load.
*
* @param mixed $value
* This should be either an array of worksheet names to be loaded, or a string containing a single worksheet name.
* If NULL, then it tells the Reader to read all worksheets in the workbook
*
* @return IReader
*/
public function setLoadSheetsOnly($value);
/**
* Set all sheets to load
* Tells the Reader to load all worksheets from the workbook.
*
* @return IReader
*/
public function setLoadAllSheets();
/**
* Read filter.
*
* @return IReadFilter
*/
public function getReadFilter();
/**
* Set read filter.
*
* @param IReadFilter $pValue
*
* @return IReader
*/
public function setReadFilter(IReadFilter $pValue);
/** /**
* Loads PhpSpreadsheet from file. * Loads PhpSpreadsheet from file.
* *

View File

@ -7,7 +7,7 @@ use DateTimeZone;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\Reader\Ods\Properties as DocumentProperties;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Settings;
@ -25,7 +25,7 @@ class Ods extends BaseReader
*/ */
public function __construct() public function __construct()
{ {
$this->readFilter = new DefaultReadFilter(); parent::__construct();
$this->securityScanner = XmlScanner::getInstance($this); $this->securityScanner = XmlScanner::getInstance($this);
} }
@ -52,7 +52,7 @@ class Ods extends BaseReader
$stat = $zip->statName('mimetype'); $stat = $zip->statName('mimetype');
if ($stat && ($stat['size'] <= 255)) { if ($stat && ($stat['size'] <= 255)) {
$mimeType = $zip->getFromName($stat['name']); $mimeType = $zip->getFromName($stat['name']);
} elseif ($stat = $zip->statName('META-INF/manifest.xml')) { } elseif ($zip->statName('META-INF/manifest.xml')) {
$xml = simplexml_load_string( $xml = simplexml_load_string(
$this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')), $this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')),
'SimpleXMLElement', 'SimpleXMLElement',
@ -265,7 +265,7 @@ class Ods extends BaseReader
$zip = new ZipArchive(); $zip = new ZipArchive();
if (!$zip->open($pFilename)) { if (!$zip->open($pFilename)) {
throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.'); throw new Exception("Could not open {$pFilename} for reading! Error opening file.");
} }
// Meta // Meta
@ -275,97 +275,13 @@ class Ods extends BaseReader
'SimpleXMLElement', 'SimpleXMLElement',
Settings::getLibXmlLoaderOptions() Settings::getLibXmlLoaderOptions()
); );
if ($xml === false) {
throw new Exception('Unable to read data from {$pFilename}');
}
$namespacesMeta = $xml->getNamespaces(true); $namespacesMeta = $xml->getNamespaces(true);
$docProps = $spreadsheet->getProperties(); (new DocumentProperties($spreadsheet))->load($xml, $namespacesMeta);
$officeProperty = $xml->children($namespacesMeta['office']);
foreach ($officeProperty as $officePropertyData) {
$officePropertyDC = [];
if (isset($namespacesMeta['dc'])) {
$officePropertyDC = $officePropertyData->children($namespacesMeta['dc']);
}
foreach ($officePropertyDC as $propertyName => $propertyValue) {
$propertyValue = (string) $propertyValue;
switch ($propertyName) {
case 'title':
$docProps->setTitle($propertyValue);
break;
case 'subject':
$docProps->setSubject($propertyValue);
break;
case 'creator':
$docProps->setCreator($propertyValue);
$docProps->setLastModifiedBy($propertyValue);
break;
case 'date':
$creationDate = strtotime($propertyValue);
$docProps->setCreated($creationDate);
$docProps->setModified($creationDate);
break;
case 'description':
$docProps->setDescription($propertyValue);
break;
}
}
$officePropertyMeta = [];
if (isset($namespacesMeta['dc'])) {
$officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
}
foreach ($officePropertyMeta as $propertyName => $propertyValue) {
$propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']);
$propertyValue = (string) $propertyValue;
switch ($propertyName) {
case 'initial-creator':
$docProps->setCreator($propertyValue);
break;
case 'keyword':
$docProps->setKeywords($propertyValue);
break;
case 'creation-date':
$creationDate = strtotime($propertyValue);
$docProps->setCreated($creationDate);
break;
case 'user-defined':
$propertyValueType = Properties::PROPERTY_TYPE_STRING;
foreach ($propertyValueAttributes as $key => $value) {
if ($key == 'name') {
$propertyValueName = (string) $value;
} elseif ($key == 'value-type') {
switch ($value) {
case 'date':
$propertyValue = Properties::convertProperty($propertyValue, 'date');
$propertyValueType = Properties::PROPERTY_TYPE_DATE;
break;
case 'boolean':
$propertyValue = Properties::convertProperty($propertyValue, 'bool');
$propertyValueType = Properties::PROPERTY_TYPE_BOOLEAN;
break;
case 'float':
$propertyValue = Properties::convertProperty($propertyValue, 'r4');
$propertyValueType = Properties::PROPERTY_TYPE_FLOAT;
break;
default:
$propertyValueType = Properties::PROPERTY_TYPE_STRING;
}
}
}
$docProps->setCustomProperty($propertyValueName, $propertyValue, $propertyValueType);
break;
}
}
}
// Content // Content
@ -513,7 +429,7 @@ class Ods extends BaseReader
foreach ($paragraphs as $pData) { foreach ($paragraphs as $pData) {
$dataArray[] = $this->scanElementForText($pData); $dataArray[] = $this->scanElementForText($pData);
} }
$allCellDataText = implode($dataArray, "\n"); $allCellDataText = implode("\n", $dataArray);
$type = $cellData->getAttributeNS($officeNs, 'value-type'); $type = $cellData->getAttributeNS($officeNs, 'value-type');
@ -580,12 +496,12 @@ class Ods extends BaseReader
); );
$dataValue = Date::formattedPHPToExcel( $dataValue = Date::formattedPHPToExcel(
$year, (int) $year,
$month, (int) $month,
$day, (int) $day,
$hour, (int) $hour,
$minute, (int) $minute,
$second (int) $second
); );
if ($dataValue != floor($dataValue)) { if ($dataValue != floor($dataValue)) {

View File

@ -0,0 +1,136 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
class Properties
{
private $spreadsheet;
public function __construct(Spreadsheet $spreadsheet)
{
$this->spreadsheet = $spreadsheet;
}
public function load(\SimpleXMLElement $xml, $namespacesMeta)
{
$docProps = $this->spreadsheet->getProperties();
$officeProperty = $xml->children($namespacesMeta['office']);
foreach ($officeProperty as $officePropertyData) {
/** @var \SimpleXMLElement $officePropertyData */
$officePropertiesDC = (object) [];
if (isset($namespacesMeta['dc'])) {
$officePropertiesDC = $officePropertyData->children($namespacesMeta['dc']);
}
$this->setCoreProperties($docProps, $officePropertiesDC);
$officePropertyMeta = (object) [];
if (isset($namespacesMeta['dc'])) {
$officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
}
foreach ($officePropertyMeta as $propertyName => $propertyValue) {
$this->setMetaProperties($namespacesMeta, $propertyValue, $propertyName, $docProps);
}
}
}
private function setCoreProperties(DocumentProperties $docProps, \SimpleXMLElement $officePropertyDC)
{
foreach ($officePropertyDC as $propertyName => $propertyValue) {
$propertyValue = (string) $propertyValue;
switch ($propertyName) {
case 'title':
$docProps->setTitle($propertyValue);
break;
case 'subject':
$docProps->setSubject($propertyValue);
break;
case 'creator':
$docProps->setCreator($propertyValue);
$docProps->setLastModifiedBy($propertyValue);
break;
case 'creation-date':
$creationDate = strtotime($propertyValue);
$docProps->setCreated($creationDate);
$docProps->setModified($creationDate);
break;
case 'keyword':
$docProps->setKeywords($propertyValue);
break;
case 'description':
$docProps->setDescription($propertyValue);
break;
}
}
}
private function setMetaProperties(
$namespacesMeta,
\SimpleXMLElement $propertyValue,
$propertyName,
DocumentProperties $docProps
) {
$propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']);
$propertyValue = (string) $propertyValue;
switch ($propertyName) {
case 'initial-creator':
$docProps->setCreator($propertyValue);
break;
case 'keyword':
$docProps->setKeywords($propertyValue);
break;
case 'creation-date':
$creationDate = strtotime($propertyValue);
$docProps->setCreated($creationDate);
break;
case 'user-defined':
$this->setUserDefinedProperty($propertyValueAttributes, $propertyValue, $docProps);
break;
}
}
private function setUserDefinedProperty($propertyValueAttributes, $propertyValue, DocumentProperties $docProps)
{
$propertyValueName = '';
$propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING;
foreach ($propertyValueAttributes as $key => $value) {
if ($key == 'name') {
$propertyValueName = (string) $value;
} elseif ($key == 'value-type') {
switch ($value) {
case 'date':
$propertyValue = DocumentProperties::convertProperty($propertyValue, 'date');
$propertyValueType = DocumentProperties::PROPERTY_TYPE_DATE;
break;
case 'boolean':
$propertyValue = DocumentProperties::convertProperty($propertyValue, 'bool');
$propertyValueType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
break;
case 'float':
$propertyValue = DocumentProperties::convertProperty($propertyValue, 'r4');
$propertyValueType = DocumentProperties::PROPERTY_TYPE_FLOAT;
break;
default:
$propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING;
}
}
}
$docProps->setCustomProperty($propertyValueName, $propertyValue, $propertyValueType);
}
}

View File

@ -3,16 +3,10 @@
namespace PhpOffice\PhpSpreadsheet\Reader\Security; namespace PhpOffice\PhpSpreadsheet\Reader\Security;
use PhpOffice\PhpSpreadsheet\Reader; use PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Settings;
class XmlScanner class XmlScanner
{ {
/**
* Identifies whether the thread-safe libxmlDisableEntityLoader() function is available.
*
* @var bool
*/
private $libxmlDisableEntityLoader = false;
/** /**
* String used to identify risky xml elements. * String used to identify risky xml elements.
* *
@ -22,10 +16,16 @@ class XmlScanner
private $callback; private $callback;
private function __construct($pattern = '<!DOCTYPE') private static $libxmlDisableEntityLoaderValue;
public function __construct($pattern = '<!DOCTYPE')
{ {
$this->pattern = $pattern; $this->pattern = $pattern;
$this->libxmlDisableEntityLoader = $this->identifyLibxmlDisableEntityLoaderAvailability();
$this->disableEntityLoaderCheck();
// A fatal error will bypass the destructor, so we register a shutdown here
register_shutdown_function([__CLASS__, 'shutdown']);
} }
public static function getInstance(Reader\IReader $reader) public static function getInstance(Reader\IReader $reader)
@ -43,7 +43,7 @@ class XmlScanner
} }
} }
private function identifyLibxmlDisableEntityLoaderAvailability() public static function threadSafeLibxmlDisableEntityLoaderAvailability()
{ {
if (PHP_MAJOR_VERSION == 7) { if (PHP_MAJOR_VERSION == 7) {
switch (PHP_MINOR_VERSION) { switch (PHP_MINOR_VERSION) {
@ -61,11 +61,54 @@ class XmlScanner
return false; return false;
} }
private function disableEntityLoaderCheck()
{
if (Settings::getLibXmlDisableEntityLoader()) {
$libxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true);
if (self::$libxmlDisableEntityLoaderValue === null) {
self::$libxmlDisableEntityLoaderValue = $libxmlDisableEntityLoaderValue;
}
}
}
public static function shutdown()
{
if (self::$libxmlDisableEntityLoaderValue !== null) {
libxml_disable_entity_loader(self::$libxmlDisableEntityLoaderValue);
self::$libxmlDisableEntityLoaderValue = null;
}
}
public function __destruct()
{
self::shutdown();
}
public function setAdditionalCallback(callable $callback) public function setAdditionalCallback(callable $callback)
{ {
$this->callback = $callback; $this->callback = $callback;
} }
private function toUtf8($xml)
{
$pattern = '/encoding="(.*?)"/';
$result = preg_match($pattern, $xml, $matches);
$charset = strtoupper($result ? $matches[1] : 'UTF-8');
if ($charset !== 'UTF-8') {
$xml = mb_convert_encoding($xml, 'UTF-8', $charset);
$result = preg_match($pattern, $xml, $matches);
$charset = strtoupper($result ? $matches[1] : 'UTF-8');
if ($charset !== 'UTF-8') {
throw new Reader\Exception('Suspicious Double-encoded XML, spreadsheet file load() aborted to prevent XXE/XEE attacks');
}
}
return $xml;
}
/** /**
* Scan the XML for use of <!ENTITY to prevent XXE/XEE attacks. * Scan the XML for use of <!ENTITY to prevent XXE/XEE attacks.
* *
@ -77,33 +120,19 @@ class XmlScanner
*/ */
public function scan($xml) public function scan($xml)
{ {
if ($this->libxmlDisableEntityLoader) { $this->disableEntityLoaderCheck();
$previousLibxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true);
}
$pattern = '/encoding="(.*?)"/'; $xml = $this->toUtf8($xml);
$result = preg_match($pattern, $xml, $matches);
$charset = $result ? $matches[1] : 'UTF-8';
if ($charset !== 'UTF-8') {
$xml = mb_convert_encoding($xml, 'UTF-8', $charset);
}
// Don't rely purely on libxml_disable_entity_loader() // Don't rely purely on libxml_disable_entity_loader()
$pattern = '/\\0?' . implode('\\0?', str_split($this->pattern)) . '\\0?/'; $pattern = '/\\0?' . implode('\\0?', str_split($this->pattern)) . '\\0?/';
try { if (preg_match($pattern, $xml)) {
if (preg_match($pattern, $xml)) { throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks');
throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); }
}
if ($this->callback !== null && is_callable($this->callback)) { if ($this->callback !== null && is_callable($this->callback)) {
$xml = call_user_func($this->callback, $xml); $xml = call_user_func($this->callback, $xml);
}
} finally {
if (isset($previousLibxmlDisableEntityLoaderValue)) {
libxml_disable_entity_loader($previousLibxmlDisableEntityLoaderValue);
}
} }
return $xml; return $xml;

View File

@ -43,7 +43,7 @@ class Slk extends BaseReader
*/ */
public function __construct() public function __construct()
{ {
$this->readFilter = new DefaultReadFilter(); parent::__construct();
} }
/** /**

View File

@ -412,7 +412,7 @@ class Xls extends BaseReader
*/ */
public function __construct() public function __construct()
{ {
$this->readFilter = new DefaultReadFilter(); parent::__construct();
} }
/** /**

View File

@ -4,10 +4,19 @@ namespace PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink; use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
use PhpOffice\PhpSpreadsheet\Document\Properties;
use PhpOffice\PhpSpreadsheet\NamedRange; use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ColumnAndRowAttributes;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles;
use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\ReferenceHelper;
use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Settings;
@ -20,11 +29,9 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Borders; use PhpOffice\PhpSpreadsheet\Style\Borders;
use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Style\Protection; use PhpOffice\PhpSpreadsheet\Style\Protection;
use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement; use SimpleXMLElement;
@ -52,7 +59,7 @@ class Xlsx extends BaseReader
*/ */
public function __construct() public function __construct()
{ {
$this->readFilter = new DefaultReadFilter(); parent::__construct();
$this->referenceHelper = ReferenceHelper::getInstance(); $this->referenceHelper = ReferenceHelper::getInstance();
$this->securityScanner = XmlScanner::getInstance($this); $this->securityScanner = XmlScanner::getInstance($this);
} }
@ -323,60 +330,6 @@ class Xlsx extends BaseReader
return $contents; return $contents;
} }
/**
* Set Worksheet column attributes by attributes array passed.
*
* @param Worksheet $docSheet
* @param string $column A, B, ... DX, ...
* @param array $columnAttributes array of attributes (indexes are attribute name, values are value)
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ?
*/
private function setColumnAttributes(Worksheet $docSheet, $column, array $columnAttributes)
{
if (isset($columnAttributes['xfIndex'])) {
$docSheet->getColumnDimension($column)->setXfIndex($columnAttributes['xfIndex']);
}
if (isset($columnAttributes['visible'])) {
$docSheet->getColumnDimension($column)->setVisible($columnAttributes['visible']);
}
if (isset($columnAttributes['collapsed'])) {
$docSheet->getColumnDimension($column)->setCollapsed($columnAttributes['collapsed']);
}
if (isset($columnAttributes['outlineLevel'])) {
$docSheet->getColumnDimension($column)->setOutlineLevel($columnAttributes['outlineLevel']);
}
if (isset($columnAttributes['width'])) {
$docSheet->getColumnDimension($column)->setWidth($columnAttributes['width']);
}
}
/**
* Set Worksheet row attributes by attributes array passed.
*
* @param Worksheet $docSheet
* @param int $row 1, 2, 3, ... 99, ...
* @param array $rowAttributes array of attributes (indexes are attribute name, values are value)
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ?
*/
private function setRowAttributes(Worksheet $docSheet, $row, array $rowAttributes)
{
if (isset($rowAttributes['xfIndex'])) {
$docSheet->getRowDimension($row)->setXfIndex($rowAttributes['xfIndex']);
}
if (isset($rowAttributes['visible'])) {
$docSheet->getRowDimension($row)->setVisible($rowAttributes['visible']);
}
if (isset($rowAttributes['collapsed'])) {
$docSheet->getRowDimension($row)->setCollapsed($rowAttributes['collapsed']);
}
if (isset($rowAttributes['outlineLevel'])) {
$docSheet->getRowDimension($row)->setOutlineLevel($rowAttributes['outlineLevel']);
}
if (isset($rowAttributes['rowHeight'])) {
$docSheet->getRowDimension($row)->setRowHeight($rowAttributes['rowHeight']);
}
}
/** /**
* Loads Spreadsheet from file. * Loads Spreadsheet from file.
* *
@ -456,70 +409,20 @@ class Xlsx extends BaseReader
'SimpleXMLElement', 'SimpleXMLElement',
Settings::getLibXmlLoaderOptions() Settings::getLibXmlLoaderOptions()
); );
$propertyReader = new PropertyReader($this->securityScanner, $excel->getProperties());
foreach ($rels->Relationship as $rel) { foreach ($rels->Relationship as $rel) {
switch ($rel['Type']) { switch ($rel['Type']) {
case 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties': case 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties':
$xmlCore = simplexml_load_string( $propertyReader->readCoreProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
$this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
if (is_object($xmlCore)) {
$xmlCore->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/');
$xmlCore->registerXPathNamespace('dcterms', 'http://purl.org/dc/terms/');
$xmlCore->registerXPathNamespace('cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties');
$docProps = $excel->getProperties();
$docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator')));
$docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy')));
$docProps->setCreated(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:created')))); //! respect xsi:type
$docProps->setModified(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:modified')))); //! respect xsi:type
$docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title')));
$docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description')));
$docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject')));
$docProps->setKeywords((string) self::getArrayItem($xmlCore->xpath('cp:keywords')));
$docProps->setCategory((string) self::getArrayItem($xmlCore->xpath('cp:category')));
}
break; break;
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties': case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties':
$xmlCore = simplexml_load_string( $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
$this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
if (is_object($xmlCore)) {
$docProps = $excel->getProperties();
if (isset($xmlCore->Company)) {
$docProps->setCompany((string) $xmlCore->Company);
}
if (isset($xmlCore->Manager)) {
$docProps->setManager((string) $xmlCore->Manager);
}
}
break; break;
case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties': case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties':
$xmlCore = simplexml_load_string( $propertyReader->readCustomProperties($this->getFromZipArchive($zip, "{$rel['Target']}"));
$this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
if (is_object($xmlCore)) {
$docProps = $excel->getProperties();
/** @var SimpleXMLElement $xmlProperty */
foreach ($xmlCore as $xmlProperty) {
$cellDataOfficeAttributes = $xmlProperty->attributes();
if (isset($cellDataOfficeAttributes['name'])) {
$propertyName = (string) $cellDataOfficeAttributes['name'];
$cellDataOfficeChildren = $xmlProperty->children('http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
$attributeType = $cellDataOfficeChildren->getName();
$attributeValue = (string) $cellDataOfficeChildren->{$attributeType};
$attributeValue = Properties::convertProperty($attributeValue, $attributeType);
$attributeType = Properties::convertPropertyType($attributeType);
$docProps->setCustomProperty($propertyName, $attributeValue, $attributeType);
}
}
}
break; break;
//Ribbon //Ribbon
@ -586,8 +489,7 @@ class Xlsx extends BaseReader
} }
} }
} }
$styles = [];
$cellStyles = [];
$xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']")); $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']"));
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
$xmlStyles = simplexml_load_string( $xmlStyles = simplexml_load_string(
@ -595,6 +497,9 @@ class Xlsx extends BaseReader
'SimpleXMLElement', 'SimpleXMLElement',
Settings::getLibXmlLoaderOptions() Settings::getLibXmlLoaderOptions()
); );
$styles = [];
$cellStyles = [];
$numFmts = null; $numFmts = null;
if ($xmlStyles && $xmlStyles->numFmts[0]) { if ($xmlStyles && $xmlStyles->numFmts[0]) {
$numFmts = $xmlStyles->numFmts[0]; $numFmts = $xmlStyles->numFmts[0];
@ -674,31 +579,10 @@ class Xlsx extends BaseReader
} }
} }
$dxfs = []; $styleReader = new Styles($xmlStyles);
if (!$this->readDataOnly && $xmlStyles) { $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles);
// Conditional Styles $dxfs = $styleReader->dxfs($this->readDataOnly);
if ($xmlStyles->dxfs) { $styles = $styleReader->styles();
foreach ($xmlStyles->dxfs->dxf as $dxf) {
$style = new Style(false, true);
self::readStyle($style, $dxf);
$dxfs[] = $style;
}
}
// Cell Styles
if ($xmlStyles->cellStyles) {
foreach ($xmlStyles->cellStyles->cellStyle as $cellStyle) {
if ((int) ($cellStyle['builtinId']) == 0) {
if (isset($cellStyles[(int) ($cellStyle['xfId'])])) {
// Set default style
$style = new Style();
self::readStyle($style, $cellStyles[(int) ($cellStyle['xfId'])]);
// normal style, currently not using it for anything
}
}
}
}
}
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
$xmlWorkbook = simplexml_load_string( $xmlWorkbook = simplexml_load_string(
@ -765,134 +649,19 @@ class Xlsx extends BaseReader
$docSheet->setSheetState((string) $eleSheet['state']); $docSheet->setSheetState((string) $eleSheet['state']);
} }
if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) { if ($xmlSheet) {
if (isset($xmlSheet->sheetViews->sheetView['zoomScale'])) { if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) {
$zoomScale = (int) ($xmlSheet->sheetViews->sheetView['zoomScale']); $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet);
if ($zoomScale <= 0) { $sheetViews->load();
// setZoomScale will throw an Exception if the scale is less than or equals 0
// that is OK when manually creating documents, but we should be able to read all documents
$zoomScale = 100;
}
$docSheet->getSheetView()->setZoomScale($zoomScale);
}
if (isset($xmlSheet->sheetViews->sheetView['zoomScaleNormal'])) {
$zoomScaleNormal = (int) ($xmlSheet->sheetViews->sheetView['zoomScaleNormal']);
if ($zoomScaleNormal <= 0) {
// setZoomScaleNormal will throw an Exception if the scale is less than or equals 0
// that is OK when manually creating documents, but we should be able to read all documents
$zoomScaleNormal = 100;
}
$docSheet->getSheetView()->setZoomScaleNormal($zoomScaleNormal);
}
if (isset($xmlSheet->sheetViews->sheetView['view'])) {
$docSheet->getSheetView()->setView((string) $xmlSheet->sheetViews->sheetView['view']);
}
if (isset($xmlSheet->sheetViews->sheetView['showGridLines'])) {
$docSheet->setShowGridLines(self::boolean((string) $xmlSheet->sheetViews->sheetView['showGridLines']));
}
if (isset($xmlSheet->sheetViews->sheetView['showRowColHeaders'])) {
$docSheet->setShowRowColHeaders(self::boolean((string) $xmlSheet->sheetViews->sheetView['showRowColHeaders']));
}
if (isset($xmlSheet->sheetViews->sheetView['rightToLeft'])) {
$docSheet->setRightToLeft(self::boolean((string) $xmlSheet->sheetViews->sheetView['rightToLeft']));
}
if (isset($xmlSheet->sheetViews->sheetView->pane)) {
$xSplit = 0;
$ySplit = 0;
$topLeftCell = null;
if (isset($xmlSheet->sheetViews->sheetView->pane['xSplit'])) {
$xSplit = (int) ($xmlSheet->sheetViews->sheetView->pane['xSplit']);
}
if (isset($xmlSheet->sheetViews->sheetView->pane['ySplit'])) {
$ySplit = (int) ($xmlSheet->sheetViews->sheetView->pane['ySplit']);
}
if (isset($xmlSheet->sheetViews->sheetView->pane['topLeftCell'])) {
$topLeftCell = (string) $xmlSheet->sheetViews->sheetView->pane['topLeftCell'];
}
$docSheet->freezePane(Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1), $topLeftCell);
} }
if (isset($xmlSheet->sheetViews->sheetView->selection)) { $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheet);
if (isset($xmlSheet->sheetViews->sheetView->selection['sqref'])) { $sheetViewOptions->load($this->getReadDataOnly());
$sqref = (string) $xmlSheet->sheetViews->sheetView->selection['sqref'];
$sqref = explode(' ', $sqref); (new ColumnAndRowAttributes($docSheet, $xmlSheet))
$sqref = $sqref[0]; ->load($this->getReadFilter(), $this->getReadDataOnly());
$docSheet->setSelectedCells($sqref);
}
}
} }
if (isset($xmlSheet->sheetPr, $xmlSheet->sheetPr->tabColor)) {
if (isset($xmlSheet->sheetPr->tabColor['rgb'])) {
$docSheet->getTabColor()->setARGB((string) $xmlSheet->sheetPr->tabColor['rgb']);
}
}
if (isset($xmlSheet->sheetPr, $xmlSheet->sheetPr['codeName'])) {
$docSheet->setCodeName((string) $xmlSheet->sheetPr['codeName'], false);
}
if (isset($xmlSheet->sheetPr, $xmlSheet->sheetPr->outlinePr)) {
if (isset($xmlSheet->sheetPr->outlinePr['summaryRight']) &&
!self::boolean((string) $xmlSheet->sheetPr->outlinePr['summaryRight'])) {
$docSheet->setShowSummaryRight(false);
} else {
$docSheet->setShowSummaryRight(true);
}
if (isset($xmlSheet->sheetPr->outlinePr['summaryBelow']) &&
!self::boolean((string) $xmlSheet->sheetPr->outlinePr['summaryBelow'])) {
$docSheet->setShowSummaryBelow(false);
} else {
$docSheet->setShowSummaryBelow(true);
}
}
if (isset($xmlSheet->sheetPr, $xmlSheet->sheetPr->pageSetUpPr)) {
if (isset($xmlSheet->sheetPr->pageSetUpPr['fitToPage']) &&
!self::boolean((string) $xmlSheet->sheetPr->pageSetUpPr['fitToPage'])) {
$docSheet->getPageSetup()->setFitToPage(false);
} else {
$docSheet->getPageSetup()->setFitToPage(true);
}
}
if (isset($xmlSheet->sheetFormatPr)) {
if (isset($xmlSheet->sheetFormatPr['customHeight']) &&
self::boolean((string) $xmlSheet->sheetFormatPr['customHeight']) &&
isset($xmlSheet->sheetFormatPr['defaultRowHeight'])) {
$docSheet->getDefaultRowDimension()->setRowHeight((float) $xmlSheet->sheetFormatPr['defaultRowHeight']);
}
if (isset($xmlSheet->sheetFormatPr['defaultColWidth'])) {
$docSheet->getDefaultColumnDimension()->setWidth((float) $xmlSheet->sheetFormatPr['defaultColWidth']);
}
if (isset($xmlSheet->sheetFormatPr['zeroHeight']) &&
((string) $xmlSheet->sheetFormatPr['zeroHeight'] == '1')) {
$docSheet->getDefaultRowDimension()->setZeroHeight(true);
}
}
if (isset($xmlSheet->printOptions) && !$this->readDataOnly) {
if (self::boolean((string) $xmlSheet->printOptions['gridLinesSet'])) {
$docSheet->setShowGridlines(true);
}
if (self::boolean((string) $xmlSheet->printOptions['gridLines'])) {
$docSheet->setPrintGridlines(true);
}
if (self::boolean((string) $xmlSheet->printOptions['horizontalCentered'])) {
$docSheet->getPageSetup()->setHorizontalCentered(true);
}
if (self::boolean((string) $xmlSheet->printOptions['verticalCentered'])) {
$docSheet->getPageSetup()->setVerticalCentered(true);
}
}
$this->readColumnsAndRowsAttributes($xmlSheet, $docSheet);
if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) { if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
$cIndex = 1; // Cell Start from 1 $cIndex = 1; // Cell Start from 1
foreach ($xmlSheet->sheetData->row as $row) { foreach ($xmlSheet->sheetData->row as $row) {
@ -972,38 +741,39 @@ class Xlsx extends BaseReader
break; break;
} }
// Check for numeric values // read empty cells or the cells are not empty
if (is_numeric($value) && $cellDataType != 's') { if ($this->readEmptyCells || ($value !== null && $value !== '')) {
if ($value == (int) $value) { // Check for numeric values
$value = (int) $value; if (is_numeric($value) && $cellDataType != 's') {
} elseif ($value == (float) $value) { if ($value == (int) $value) {
$value = (float) $value; $value = (int) $value;
} elseif ($value == (float) $value) { } elseif ($value == (float) $value) {
$value = (float) $value; $value = (float) $value;
}
} }
}
// Rich text? // Rich text?
if ($value instanceof RichText && $this->readDataOnly) { if ($value instanceof RichText && $this->readDataOnly) {
$value = $value->getPlainText(); $value = $value->getPlainText();
} }
$cell = $docSheet->getCell($r); $cell = $docSheet->getCell($r);
// Assign value // Assign value
if ($cellDataType != '') { if ($cellDataType != '') {
$cell->setValueExplicit($value, $cellDataType); $cell->setValueExplicit($value, $cellDataType);
} else { } else {
$cell->setValue($value); $cell->setValue($value);
} }
if ($calculatedValue !== null) { if ($calculatedValue !== null) {
$cell->setCalculatedValue($calculatedValue); $cell->setCalculatedValue($calculatedValue);
} }
// Style information? // Style information?
if ($c['s'] && !$this->readDataOnly) { if ($c['s'] && !$this->readDataOnly) {
// no style index means 0, it seems // no style index means 0, it seems
$cell->setXfIndex(isset($styles[(int) ($c['s'])]) ? $cell->setXfIndex(isset($styles[(int) ($c['s'])]) ?
(int) ($c['s']) : 0); (int) ($c['s']) : 0);
}
} }
$rowIndex += 1; $rowIndex += 1;
} }
@ -1011,49 +781,8 @@ class Xlsx extends BaseReader
} }
} }
$conditionals = [];
if (!$this->readDataOnly && $xmlSheet && $xmlSheet->conditionalFormatting) { if (!$this->readDataOnly && $xmlSheet && $xmlSheet->conditionalFormatting) {
foreach ($xmlSheet->conditionalFormatting as $conditional) { (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();
foreach ($conditional->cfRule as $cfRule) {
if (((string) $cfRule['type'] == Conditional::CONDITION_NONE || (string) $cfRule['type'] == Conditional::CONDITION_CELLIS || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT || (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION) && isset($dxfs[(int) ($cfRule['dxfId'])])) {
$conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
}
}
}
foreach ($conditionals as $ref => $cfRules) {
ksort($cfRules);
$conditionalStyles = [];
foreach ($cfRules as $cfRule) {
$objConditional = new Conditional();
$objConditional->setConditionType((string) $cfRule['type']);
$objConditional->setOperatorType((string) $cfRule['operator']);
if ((string) $cfRule['text'] != '') {
$objConditional->setText((string) $cfRule['text']);
}
if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) {
$objConditional->setStopIfTrue(true);
}
if (count($cfRule->formula) > 1) {
foreach ($cfRule->formula as $formula) {
$objConditional->addCondition((string) $formula);
}
} else {
$objConditional->addCondition((string) $cfRule->formula);
}
$objConditional->setStyle(clone $dxfs[(int) ($cfRule['dxfId'])]);
$conditionalStyles[] = $objConditional;
}
// Extract all cell references in $ref
$cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref)));
foreach ($cellBlocks as $cellBlock) {
$docSheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles);
}
}
} }
$aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells']; $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells'];
@ -1074,103 +803,7 @@ class Xlsx extends BaseReader
} }
if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) { if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) {
$autoFilterRange = (string) $xmlSheet->autoFilter['ref']; (new AutoFilter($docSheet, $xmlSheet))->load();
if (strpos($autoFilterRange, ':') !== false) {
$autoFilter = $docSheet->getAutoFilter();
$autoFilter->setRange($autoFilterRange);
foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) {
$column = $autoFilter->getColumnByOffset((int) $filterColumn['colId']);
// Check for standard filters
if ($filterColumn->filters) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER);
$filters = $filterColumn->filters;
if ((isset($filters['blank'])) && ($filters['blank'] == 1)) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule(null, '')->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_FILTER);
}
// Standard filters are always an OR join, so no join rule needs to be set
// Entries can be either filter elements
foreach ($filters->filter as $filterRule) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule(null, (string) $filterRule['val'])->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_FILTER);
}
// Or Date Group elements
foreach ($filters->dateGroupItem as $dateGroupItem) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule(
null,
[
'year' => (string) $dateGroupItem['year'],
'month' => (string) $dateGroupItem['month'],
'day' => (string) $dateGroupItem['day'],
'hour' => (string) $dateGroupItem['hour'],
'minute' => (string) $dateGroupItem['minute'],
'second' => (string) $dateGroupItem['second'],
],
(string) $dateGroupItem['dateTimeGrouping']
)
->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP);
}
}
// Check for custom filters
if ($filterColumn->customFilters) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER);
$customFilters = $filterColumn->customFilters;
// Custom filters can an AND or an OR join;
// and there should only ever be one or two entries
if ((isset($customFilters['and'])) && ($customFilters['and'] == 1)) {
$column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND);
}
foreach ($customFilters->customFilter as $filterRule) {
$column->createRule()->setRule(
(string) $filterRule['operator'],
(string) $filterRule['val']
)
->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER);
}
}
// Check for dynamic filters
if ($filterColumn->dynamicFilter) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER);
// We should only ever have one dynamic filter
foreach ($filterColumn->dynamicFilter as $filterRule) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule(
null,
(string) $filterRule['val'],
(string) $filterRule['type']
)
->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER);
if (isset($filterRule['val'])) {
$column->setAttribute('val', (string) $filterRule['val']);
}
if (isset($filterRule['maxVal'])) {
$column->setAttribute('maxVal', (string) $filterRule['maxVal']);
}
}
}
// Check for dynamic filters
if ($filterColumn->top10) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER);
// We should only ever have one top10 filter
foreach ($filterColumn->top10 as $filterRule) {
$column->createRule()->setRule(
(((isset($filterRule['percent'])) && ($filterRule['percent'] == 1))
? Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT
: Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE
),
(string) $filterRule['val'],
(((isset($filterRule['top'])) && ($filterRule['top'] == 1))
? Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP
: Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM
)
)
->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_TOPTENFILTER);
}
}
}
}
} }
if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) { if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) {
@ -1182,124 +815,12 @@ class Xlsx extends BaseReader
} }
} }
if ($xmlSheet && $xmlSheet->pageMargins && !$this->readDataOnly) { if ($xmlSheet && !$this->readDataOnly) {
$docPageMargins = $docSheet->getPageMargins(); $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData);
$docPageMargins->setLeft((float) ($xmlSheet->pageMargins['left']));
$docPageMargins->setRight((float) ($xmlSheet->pageMargins['right']));
$docPageMargins->setTop((float) ($xmlSheet->pageMargins['top']));
$docPageMargins->setBottom((float) ($xmlSheet->pageMargins['bottom']));
$docPageMargins->setHeader((float) ($xmlSheet->pageMargins['header']));
$docPageMargins->setFooter((float) ($xmlSheet->pageMargins['footer']));
}
if ($xmlSheet && $xmlSheet->pageSetup && !$this->readDataOnly) {
$docPageSetup = $docSheet->getPageSetup();
if (isset($xmlSheet->pageSetup['orientation'])) {
$docPageSetup->setOrientation((string) $xmlSheet->pageSetup['orientation']);
}
if (isset($xmlSheet->pageSetup['paperSize'])) {
$docPageSetup->setPaperSize((int) ($xmlSheet->pageSetup['paperSize']));
}
if (isset($xmlSheet->pageSetup['scale'])) {
$docPageSetup->setScale((int) ($xmlSheet->pageSetup['scale']), false);
}
if (isset($xmlSheet->pageSetup['fitToHeight']) && (int) ($xmlSheet->pageSetup['fitToHeight']) >= 0) {
$docPageSetup->setFitToHeight((int) ($xmlSheet->pageSetup['fitToHeight']), false);
}
if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) {
$docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false);
}
if (isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) &&
self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])) {
$docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber']));
}
$relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
if (isset($relAttributes['id'])) {
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id'];
}
}
if ($xmlSheet && $xmlSheet->headerFooter && !$this->readDataOnly) {
$docHeaderFooter = $docSheet->getHeaderFooter();
if (isset($xmlSheet->headerFooter['differentOddEven']) &&
self::boolean((string) $xmlSheet->headerFooter['differentOddEven'])) {
$docHeaderFooter->setDifferentOddEven(true);
} else {
$docHeaderFooter->setDifferentOddEven(false);
}
if (isset($xmlSheet->headerFooter['differentFirst']) &&
self::boolean((string) $xmlSheet->headerFooter['differentFirst'])) {
$docHeaderFooter->setDifferentFirst(true);
} else {
$docHeaderFooter->setDifferentFirst(false);
}
if (isset($xmlSheet->headerFooter['scaleWithDoc']) &&
!self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc'])) {
$docHeaderFooter->setScaleWithDocument(false);
} else {
$docHeaderFooter->setScaleWithDocument(true);
}
if (isset($xmlSheet->headerFooter['alignWithMargins']) &&
!self::boolean((string) $xmlSheet->headerFooter['alignWithMargins'])) {
$docHeaderFooter->setAlignWithMargins(false);
} else {
$docHeaderFooter->setAlignWithMargins(true);
}
$docHeaderFooter->setOddHeader((string) $xmlSheet->headerFooter->oddHeader);
$docHeaderFooter->setOddFooter((string) $xmlSheet->headerFooter->oddFooter);
$docHeaderFooter->setEvenHeader((string) $xmlSheet->headerFooter->evenHeader);
$docHeaderFooter->setEvenFooter((string) $xmlSheet->headerFooter->evenFooter);
$docHeaderFooter->setFirstHeader((string) $xmlSheet->headerFooter->firstHeader);
$docHeaderFooter->setFirstFooter((string) $xmlSheet->headerFooter->firstFooter);
}
if ($xmlSheet && $xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk && !$this->readDataOnly) {
foreach ($xmlSheet->rowBreaks->brk as $brk) {
if ($brk['man']) {
$docSheet->setBreak("A$brk[id]", Worksheet::BREAK_ROW);
}
}
}
if ($xmlSheet && $xmlSheet->colBreaks && $xmlSheet->colBreaks->brk && !$this->readDataOnly) {
foreach ($xmlSheet->colBreaks->brk as $brk) {
if ($brk['man']) {
$docSheet->setBreak(Coordinate::stringFromColumnIndex((string) $brk['id'] + 1) . '1', Worksheet::BREAK_COLUMN);
}
}
} }
if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) { if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) {
foreach ($xmlSheet->dataValidations->dataValidation as $dataValidation) { (new DataValidations($docSheet, $xmlSheet))->load();
// Uppercase coordinate
$range = strtoupper($dataValidation['sqref']);
$rangeSet = explode(' ', $range);
foreach ($rangeSet as $range) {
$stRange = $docSheet->shrinkRangeToFit($range);
// Extract all cell references in $range
foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $reference) {
// Create validation
$docValidation = $docSheet->getCell($reference)->getDataValidation();
$docValidation->setType((string) $dataValidation['type']);
$docValidation->setErrorStyle((string) $dataValidation['errorStyle']);
$docValidation->setOperator((string) $dataValidation['operator']);
$docValidation->setAllowBlank($dataValidation['allowBlank'] != 0);
$docValidation->setShowDropDown($dataValidation['showDropDown'] == 0);
$docValidation->setShowInputMessage($dataValidation['showInputMessage'] != 0);
$docValidation->setShowErrorMessage($dataValidation['showErrorMessage'] != 0);
$docValidation->setErrorTitle((string) $dataValidation['errorTitle']);
$docValidation->setError((string) $dataValidation['error']);
$docValidation->setPromptTitle((string) $dataValidation['promptTitle']);
$docValidation->setPrompt((string) $dataValidation['prompt']);
$docValidation->setFormula1((string) $dataValidation->formula1);
$docValidation->setFormula2((string) $dataValidation->formula2);
}
}
}
} }
// unparsed sheet AlternateContent // unparsed sheet AlternateContent
@ -1313,50 +834,25 @@ class Xlsx extends BaseReader
} }
// Add hyperlinks // Add hyperlinks
$hyperlinks = [];
if (!$this->readDataOnly) { if (!$this->readDataOnly) {
$hyperlinkReader = new Hyperlinks($docSheet);
// Locate hyperlink relations // Locate hyperlink relations
if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
if ($zip->locateName($relationsFileName)) {
//~ http://schemas.openxmlformats.org/package/2006/relationships" //~ http://schemas.openxmlformats.org/package/2006/relationships"
$relsWorksheet = simplexml_load_string( $relsWorksheet = simplexml_load_string(
$this->securityScanner->scan( $this->securityScanner->scan(
$this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') $this->getFromZipArchive($zip, $relationsFileName)
), ),
'SimpleXMLElement', 'SimpleXMLElement',
Settings::getLibXmlLoaderOptions() Settings::getLibXmlLoaderOptions()
); );
foreach ($relsWorksheet->Relationship as $ele) { $hyperlinkReader->readHyperlinks($relsWorksheet);
if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
$hyperlinks[(string) $ele['Id']] = (string) $ele['Target'];
}
}
} }
// Loop through hyperlinks // Loop through hyperlinks
if ($xmlSheet && $xmlSheet->hyperlinks) { if ($xmlSheet && $xmlSheet->hyperlinks) {
/** @var SimpleXMLElement $hyperlink */ $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks);
foreach ($xmlSheet->hyperlinks->hyperlink as $hyperlink) {
// Link url
$linkRel = $hyperlink->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
foreach (Coordinate::extractAllCellReferencesInRange($hyperlink['ref']) as $cellReference) {
$cell = $docSheet->getCell($cellReference);
if (isset($linkRel['id'])) {
$hyperlinkUrl = $hyperlinks[(string) $linkRel['id']];
if (isset($hyperlink['location'])) {
$hyperlinkUrl .= '#' . (string) $hyperlink['location'];
}
$cell->getHyperlink()->setUrl($hyperlinkUrl);
} elseif (isset($hyperlink['location'])) {
$cell->getHyperlink()->setUrl('sheet://' . (string) $hyperlink['location']);
}
// Tooltip
if (isset($hyperlink['tooltip'])) {
$cell->getHyperlink()->setTooltip((string) $hyperlink['tooltip']);
}
}
}
} }
} }
@ -1418,12 +914,18 @@ class Xlsx extends BaseReader
foreach ($vmlComments as $relName => $relPath) { foreach ($vmlComments as $relName => $relPath) {
// Load VML comments file // Load VML comments file
$relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
$vmlCommentsFile = simplexml_load_string(
$this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)), try {
'SimpleXMLElement', $vmlCommentsFile = simplexml_load_string(
Settings::getLibXmlLoaderOptions() $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)),
); 'SimpleXMLElement',
$vmlCommentsFile->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); Settings::getLibXmlLoaderOptions()
);
$vmlCommentsFile->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml');
} catch (\Throwable $ex) {
//Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData
continue;
}
$shapes = $vmlCommentsFile->xpath('//v:shape'); $shapes = $vmlCommentsFile->xpath('//v:shape');
foreach ($shapes as $shape) { foreach ($shapes as $shape) {
@ -1597,8 +1099,10 @@ class Xlsx extends BaseReader
} }
} }
if ($xmlSheet->drawing && !$this->readDataOnly) { if ($xmlSheet->drawing && !$this->readDataOnly) {
$unparsedDrawings = [];
foreach ($xmlSheet->drawing as $drawing) { foreach ($xmlSheet->drawing as $drawing) {
$fileDrawing = $drawings[(string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')]; $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id');
$fileDrawing = $drawings[$drawingRelId];
//~ http://schemas.openxmlformats.org/package/2006/relationships" //~ http://schemas.openxmlformats.org/package/2006/relationships"
$relsDrawing = simplexml_load_string( $relsDrawing = simplexml_load_string(
$this->securityScanner->scan( $this->securityScanner->scan(
@ -1630,10 +1134,11 @@ class Xlsx extends BaseReader
$this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)),
'SimpleXMLElement', 'SimpleXMLElement',
Settings::getLibXmlLoaderOptions() Settings::getLibXmlLoaderOptions()
)->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); );
$xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
if ($xmlDrawing->oneCellAnchor) { if ($xmlDrawingChildren->oneCellAnchor) {
foreach ($xmlDrawing->oneCellAnchor as $oneCellAnchor) { foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) {
if ($oneCellAnchor->pic->blipFill) { if ($oneCellAnchor->pic->blipFill) {
/** @var SimpleXMLElement $blip */ /** @var SimpleXMLElement $blip */
$blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; $blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
@ -1667,12 +1172,13 @@ class Xlsx extends BaseReader
if ($outerShdw) { if ($outerShdw) {
$shadow = $objDrawing->getShadow(); $shadow = $objDrawing->getShadow();
$shadow->setVisible(true); $shadow->setVisible(true);
$shadow->setBlurRadius(Drawing::EMUTopixels(self::getArrayItem($outerShdw->attributes(), 'blurRad'))); $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad')));
$shadow->setDistance(Drawing::EMUTopixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist')));
$shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir')));
$shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn'));
$shadow->getColor()->setRGB(self::getArrayItem($outerShdw->srgbClr->attributes(), 'val')); $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr;
$shadow->setAlpha(self::getArrayItem($outerShdw->srgbClr->alpha->attributes(), 'val') / 1000); $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val'));
$shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000);
} }
$this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks); $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks);
@ -1688,8 +1194,8 @@ class Xlsx extends BaseReader
} }
} }
} }
if ($xmlDrawing->twoCellAnchor) { if ($xmlDrawingChildren->twoCellAnchor) {
foreach ($xmlDrawing->twoCellAnchor as $twoCellAnchor) { foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) {
if ($twoCellAnchor->pic->blipFill) { if ($twoCellAnchor->pic->blipFill) {
$blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; $blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
$xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm; $xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
@ -1719,12 +1225,13 @@ class Xlsx extends BaseReader
if ($outerShdw) { if ($outerShdw) {
$shadow = $objDrawing->getShadow(); $shadow = $objDrawing->getShadow();
$shadow->setVisible(true); $shadow->setVisible(true);
$shadow->setBlurRadius(Drawing::EMUTopixels(self::getArrayItem($outerShdw->attributes(), 'blurRad'))); $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad')));
$shadow->setDistance(Drawing::EMUTopixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist')));
$shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir')));
$shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn'));
$shadow->getColor()->setRGB(self::getArrayItem($outerShdw->srgbClr->attributes(), 'val')); $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr;
$shadow->setAlpha(self::getArrayItem($outerShdw->srgbClr->alpha->attributes(), 'val') / 1000); $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val'));
$shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000);
} }
$this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks); $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks);
@ -1754,13 +1261,21 @@ class Xlsx extends BaseReader
} }
} }
} }
if ($relsDrawing === false && $xmlDrawing->count() == 0) {
// Save Drawing without rels and children as unparsed
$unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML();
}
} }
// store original rId of drawing files // store original rId of drawing files
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = [];
foreach ($relsWorksheet->Relationship as $ele) { foreach ($relsWorksheet->Relationship as $ele) {
if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') { if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') {
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = (string) $ele['Id']; $drawingRelId = (string) $ele['Id'];
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId;
if (isset($unparsedDrawings[$drawingRelId])) {
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['Drawings'][$drawingRelId] = $unparsedDrawings[$drawingRelId];
}
} }
} }
@ -1905,7 +1420,7 @@ class Xlsx extends BaseReader
if (strpos((string) $definedName, '!') !== false) { if (strpos((string) $definedName, '!') !== false) {
// Extract sheet name // Extract sheet name
$extractedSheetName = Worksheet::extractSheetTitle((string) $definedName, true); $extractedSheetName = Worksheet::extractSheetTitle((string) $definedName, true);
$extractedSheetName = $extractedSheetName[0]; $extractedSheetName = trim($extractedSheetName[0], "'");
// Locate sheet // Locate sheet
$locatedSheet = $excel->getSheetByName($extractedSheetName); $locatedSheet = $excel->getSheetByName($extractedSheetName);
@ -2511,94 +2026,4 @@ class Xlsx extends BaseReader
return (bool) $xsdBoolean; return (bool) $xsdBoolean;
} }
/**
* Read columns and rows attributes from XML and set them on the worksheet.
*
* @param SimpleXMLElement $xmlSheet
* @param Worksheet $docSheet
*/
private function readColumnsAndRowsAttributes(SimpleXMLElement $xmlSheet, Worksheet $docSheet)
{
$columnsAttributes = [];
$rowsAttributes = [];
if (isset($xmlSheet->cols) && !$this->readDataOnly) {
foreach ($xmlSheet->cols->col as $col) {
for ($i = (int) ($col['min']); $i <= (int) ($col['max']); ++$i) {
if ($col['style'] && !$this->readDataOnly) {
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['xfIndex'] = (int) $col['style'];
}
if (self::boolean($col['hidden'])) {
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['visible'] = false;
}
if (self::boolean($col['collapsed'])) {
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['collapsed'] = true;
}
if ($col['outlineLevel'] > 0) {
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['outlineLevel'] = (int) $col['outlineLevel'];
}
$columnsAttributes[Coordinate::stringFromColumnIndex($i)]['width'] = (float) $col['width'];
if ((int) ($col['max']) == 16384) {
break;
}
}
}
}
if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) {
foreach ($xmlSheet->sheetData->row as $row) {
if ($row['ht'] && !$this->readDataOnly) {
$rowsAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
}
if (self::boolean($row['hidden']) && !$this->readDataOnly) {
$rowsAttributes[(int) $row['r']]['visible'] = false;
}
if (self::boolean($row['collapsed'])) {
$rowsAttributes[(int) $row['r']]['collapsed'] = true;
}
if ($row['outlineLevel'] > 0) {
$rowsAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
}
if ($row['s'] && !$this->readDataOnly) {
$rowsAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
}
}
}
$readFilter = (\get_class($this->getReadFilter()) !== DefaultReadFilter::class ? $this->getReadFilter() : null);
// set columns/rows attributes
$columnsAttributesSet = [];
$rowsAttributesSet = [];
foreach ($columnsAttributes as $coordColumn => $columnAttributes) {
if ($readFilter !== null) {
foreach ($rowsAttributes as $coordRow => $rowAttributes) {
if (!$readFilter->readCell($coordColumn, $coordRow, $docSheet->getTitle())) {
continue 2;
}
}
}
if (!isset($columnsAttributesSet[$coordColumn])) {
$this->setColumnAttributes($docSheet, $coordColumn, $columnAttributes);
$columnsAttributesSet[$coordColumn] = true;
}
}
foreach ($rowsAttributes as $coordRow => $rowAttributes) {
if ($readFilter !== null) {
foreach ($columnsAttributes as $coordColumn => $columnAttributes) {
if (!$readFilter->readCell($coordColumn, $coordRow, $docSheet->getTitle())) {
continue 2;
}
}
}
if (!isset($rowsAttributesSet[$coordRow])) {
$this->setRowAttributes($docSheet, $coordRow, $rowAttributes);
$rowsAttributesSet[$coordRow] = true;
}
}
}
} }

View File

@ -0,0 +1,144 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class AutoFilter
{
private $worksheet;
private $worksheetXml;
public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml)
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
}
public function load()
{
$autoFilterRange = (string) $this->worksheetXml->autoFilter['ref'];
if (strpos($autoFilterRange, ':') !== false) {
$this->readAutoFilter($autoFilterRange, $this->worksheetXml);
}
}
private function readAutoFilter($autoFilterRange, $xmlSheet)
{
$autoFilter = $this->worksheet->getAutoFilter();
$autoFilter->setRange($autoFilterRange);
foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) {
$column = $autoFilter->getColumnByOffset((int) $filterColumn['colId']);
// Check for standard filters
if ($filterColumn->filters) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER);
$filters = $filterColumn->filters;
if ((isset($filters['blank'])) && ($filters['blank'] == 1)) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule(null, '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
}
// Standard filters are always an OR join, so no join rule needs to be set
// Entries can be either filter elements
foreach ($filters->filter as $filterRule) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule(null, (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
}
// Or Date Group elements
$this->readDateRangeAutoFilter($filters, $column);
}
// Check for custom filters
$this->readCustomAutoFilter($filterColumn, $column);
// Check for dynamic filters
$this->readDynamicAutoFilter($filterColumn, $column);
// Check for dynamic filters
$this->readTopTenAutoFilter($filterColumn, $column);
}
}
private function readDateRangeAutoFilter(\SimpleXMLElement $filters, Column $column)
{
foreach ($filters->dateGroupItem as $dateGroupItem) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule(
null,
[
'year' => (string) $dateGroupItem['year'],
'month' => (string) $dateGroupItem['month'],
'day' => (string) $dateGroupItem['day'],
'hour' => (string) $dateGroupItem['hour'],
'minute' => (string) $dateGroupItem['minute'],
'second' => (string) $dateGroupItem['second'],
],
(string) $dateGroupItem['dateTimeGrouping']
)->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP);
}
}
private function readCustomAutoFilter(\SimpleXMLElement $filterColumn, Column $column)
{
if ($filterColumn->customFilters) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER);
$customFilters = $filterColumn->customFilters;
// Custom filters can an AND or an OR join;
// and there should only ever be one or two entries
if ((isset($customFilters['and'])) && ($customFilters['and'] == 1)) {
$column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND);
}
foreach ($customFilters->customFilter as $filterRule) {
$column->createRule()->setRule(
(string) $filterRule['operator'],
(string) $filterRule['val']
)->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER);
}
}
}
private function readDynamicAutoFilter(\SimpleXMLElement $filterColumn, Column $column)
{
if ($filterColumn->dynamicFilter) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER);
// We should only ever have one dynamic filter
foreach ($filterColumn->dynamicFilter as $filterRule) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule(
null,
(string) $filterRule['val'],
(string) $filterRule['type']
)->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER);
if (isset($filterRule['val'])) {
$column->setAttribute('val', (string) $filterRule['val']);
}
if (isset($filterRule['maxVal'])) {
$column->setAttribute('maxVal', (string) $filterRule['maxVal']);
}
}
}
}
private function readTopTenAutoFilter(\SimpleXMLElement $filterColumn, Column $column)
{
if ($filterColumn->top10) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER);
// We should only ever have one top10 filter
foreach ($filterColumn->top10 as $filterRule) {
$column->createRule()->setRule(
(((isset($filterRule['percent'])) && ($filterRule['percent'] == 1))
? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT
: Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE
),
(string) $filterRule['val'],
(((isset($filterRule['top'])) && ($filterRule['top'] == 1))
? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP
: Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM
)
)->setRuleType(Rule::AUTOFILTER_RULETYPE_TOPTENFILTER);
}
}
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
class BaseParserClass
{
protected static function boolean($value)
{
if (is_object($value)) {
$value = (string) $value;
}
if (is_numeric($value)) {
return (bool) $value;
}
return $value === strtolower('true');
}
}

View File

@ -0,0 +1,204 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class ColumnAndRowAttributes extends BaseParserClass
{
private $worksheet;
private $worksheetXml;
public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml = null)
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
}
/**
* Set Worksheet column attributes by attributes array passed.
*
* @param string $columnAddress A, B, ... DX, ...
* @param array $columnAttributes array of attributes (indexes are attribute name, values are value)
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ?
*/
private function setColumnAttributes($columnAddress, array $columnAttributes)
{
if (isset($columnAttributes['xfIndex'])) {
$this->worksheet->getColumnDimension($columnAddress)->setXfIndex($columnAttributes['xfIndex']);
}
if (isset($columnAttributes['visible'])) {
$this->worksheet->getColumnDimension($columnAddress)->setVisible($columnAttributes['visible']);
}
if (isset($columnAttributes['collapsed'])) {
$this->worksheet->getColumnDimension($columnAddress)->setCollapsed($columnAttributes['collapsed']);
}
if (isset($columnAttributes['outlineLevel'])) {
$this->worksheet->getColumnDimension($columnAddress)->setOutlineLevel($columnAttributes['outlineLevel']);
}
if (isset($columnAttributes['width'])) {
$this->worksheet->getColumnDimension($columnAddress)->setWidth($columnAttributes['width']);
}
}
/**
* Set Worksheet row attributes by attributes array passed.
*
* @param int $rowNumber 1, 2, 3, ... 99, ...
* @param array $rowAttributes array of attributes (indexes are attribute name, values are value)
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ?
*/
private function setRowAttributes($rowNumber, array $rowAttributes)
{
if (isset($rowAttributes['xfIndex'])) {
$this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']);
}
if (isset($rowAttributes['visible'])) {
$this->worksheet->getRowDimension($rowNumber)->setVisible($rowAttributes['visible']);
}
if (isset($rowAttributes['collapsed'])) {
$this->worksheet->getRowDimension($rowNumber)->setCollapsed($rowAttributes['collapsed']);
}
if (isset($rowAttributes['outlineLevel'])) {
$this->worksheet->getRowDimension($rowNumber)->setOutlineLevel($rowAttributes['outlineLevel']);
}
if (isset($rowAttributes['rowHeight'])) {
$this->worksheet->getRowDimension($rowNumber)->setRowHeight($rowAttributes['rowHeight']);
}
}
/**
* @param IReadFilter $readFilter
* @param bool $readDataOnly
*/
public function load(IReadFilter $readFilter = null, $readDataOnly = false)
{
if ($this->worksheetXml === null) {
return;
}
$columnsAttributes = [];
$rowsAttributes = [];
if (isset($this->worksheetXml->cols)) {
$columnsAttributes = $this->readColumnAttributes($this->worksheetXml->cols, $readDataOnly);
}
if ($this->worksheetXml->sheetData && $this->worksheetXml->sheetData->row) {
$rowsAttributes = $this->readRowAttributes($this->worksheetXml->sheetData->row, $readDataOnly);
}
// set columns/rows attributes
$columnsAttributesAreSet = [];
foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
if ($readFilter === null ||
!$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes)) {
if (!isset($columnsAttributesAreSet[$columnCoordinate])) {
$this->setColumnAttributes($columnCoordinate, $columnAttributes);
$columnsAttributesAreSet[$columnCoordinate] = true;
}
}
}
$rowsAttributesAreSet = [];
foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
if ($readFilter === null ||
!$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes)) {
if (!isset($rowsAttributesAreSet[$rowCoordinate])) {
$this->setRowAttributes($rowCoordinate, $rowAttributes);
$rowsAttributesAreSet[$rowCoordinate] = true;
}
}
}
}
private function isFilteredColumn(IReadFilter $readFilter, $columnCoordinate, array $rowsAttributes)
{
foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
return true;
}
}
return false;
}
private function readColumnAttributes(\SimpleXMLElement $worksheetCols, $readDataOnly)
{
$columnAttributes = [];
foreach ($worksheetCols->col as $column) {
$startColumn = Coordinate::stringFromColumnIndex((int) $column['min']);
$endColumn = Coordinate::stringFromColumnIndex((int) $column['max']);
++$endColumn;
for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) {
$columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly);
if ((int) ($column['max']) == 16384) {
break;
}
}
}
return $columnAttributes;
}
private function readColumnRangeAttributes(\SimpleXMLElement $column, $readDataOnly)
{
$columnAttributes = [];
if ($column['style'] && !$readDataOnly) {
$columnAttributes['xfIndex'] = (int) $column['style'];
}
if (self::boolean($column['hidden'])) {
$columnAttributes['visible'] = false;
}
if (self::boolean($column['collapsed'])) {
$columnAttributes['collapsed'] = true;
}
if (((int) $column['outlineLevel']) > 0) {
$columnAttributes['outlineLevel'] = (int) $column['outlineLevel'];
}
$columnAttributes['width'] = (float) $column['width'];
return $columnAttributes;
}
private function isFilteredRow(IReadFilter $readFilter, $rowCoordinate, array $columnsAttributes)
{
foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
return true;
}
}
return false;
}
private function readRowAttributes(\SimpleXMLElement $worksheetRow, $readDataOnly)
{
$rowAttributes = [];
foreach ($worksheetRow as $row) {
if ($row['ht'] && !$readDataOnly) {
$rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
}
if (self::boolean($row['hidden'])) {
$rowAttributes[(int) $row['r']]['visible'] = false;
}
if (self::boolean($row['collapsed'])) {
$rowAttributes[(int) $row['r']]['collapsed'] = true;
}
if ((int) $row['outlineLevel'] > 0) {
$rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
}
if ($row['s'] && !$readDataOnly) {
$rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
}
}
return $rowAttributes;
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class ConditionalStyles
{
private $worksheet;
private $worksheetXml;
private $dxfs;
public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml, array $dxfs = [])
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
$this->dxfs = $dxfs;
}
public function load()
{
$this->setConditionalStyles(
$this->worksheet,
$this->readConditionalStyles($this->worksheetXml)
);
}
private function readConditionalStyles($xmlSheet)
{
$conditionals = [];
foreach ($xmlSheet->conditionalFormatting as $conditional) {
foreach ($conditional->cfRule as $cfRule) {
if (((string) $cfRule['type'] == Conditional::CONDITION_NONE
|| (string) $cfRule['type'] == Conditional::CONDITION_CELLIS
|| (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT
|| (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION)
&& isset($this->dxfs[(int) ($cfRule['dxfId'])])) {
$conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
}
}
}
return $conditionals;
}
private function setConditionalStyles(Worksheet $worksheet, array $conditionals)
{
foreach ($conditionals as $ref => $cfRules) {
ksort($cfRules);
$conditionalStyles = $this->readStyleRules($cfRules);
// Extract all cell references in $ref
$cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref)));
foreach ($cellBlocks as $cellBlock) {
$worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles);
}
}
}
private function readStyleRules($cfRules)
{
$conditionalStyles = [];
foreach ($cfRules as $cfRule) {
$objConditional = new Conditional();
$objConditional->setConditionType((string) $cfRule['type']);
$objConditional->setOperatorType((string) $cfRule['operator']);
if ((string) $cfRule['text'] != '') {
$objConditional->setText((string) $cfRule['text']);
}
if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) {
$objConditional->setStopIfTrue(true);
}
if (count($cfRule->formula) > 1) {
foreach ($cfRule->formula as $formula) {
$objConditional->addCondition((string) $formula);
}
} else {
$objConditional->addCondition((string) $cfRule->formula);
}
$objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]);
$conditionalStyles[] = $objConditional;
}
return $conditionalStyles;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class DataValidations
{
private $worksheet;
private $worksheetXml;
public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml)
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
}
public function load()
{
foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
// Uppercase coordinate
$range = strtoupper($dataValidation['sqref']);
$rangeSet = explode(' ', $range);
foreach ($rangeSet as $range) {
$stRange = $this->worksheet->shrinkRangeToFit($range);
// Extract all cell references in $range
foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $reference) {
// Create validation
$docValidation = $this->worksheet->getCell($reference)->getDataValidation();
$docValidation->setType((string) $dataValidation['type']);
$docValidation->setErrorStyle((string) $dataValidation['errorStyle']);
$docValidation->setOperator((string) $dataValidation['operator']);
$docValidation->setAllowBlank($dataValidation['allowBlank'] != 0);
$docValidation->setShowDropDown($dataValidation['showDropDown'] == 0);
$docValidation->setShowInputMessage($dataValidation['showInputMessage'] != 0);
$docValidation->setShowErrorMessage($dataValidation['showErrorMessage'] != 0);
$docValidation->setErrorTitle((string) $dataValidation['errorTitle']);
$docValidation->setError((string) $dataValidation['error']);
$docValidation->setPromptTitle((string) $dataValidation['promptTitle']);
$docValidation->setPrompt((string) $dataValidation['prompt']);
$docValidation->setFormula1((string) $dataValidation->formula1);
$docValidation->setFormula2((string) $dataValidation->formula2);
}
}
}
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Hyperlinks
{
private $worksheet;
private $hyperlinks = [];
public function __construct(Worksheet $workSheet)
{
$this->worksheet = $workSheet;
}
public function readHyperlinks(\SimpleXMLElement $relsWorksheet)
{
foreach ($relsWorksheet->Relationship as $element) {
if ($element['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
$this->hyperlinks[(string) $element['Id']] = (string) $element['Target'];
}
}
}
public function setHyperlinks(\SimpleXMLElement $worksheetXml)
{
foreach ($worksheetXml->hyperlink as $hyperlink) {
$this->setHyperlink($hyperlink, $this->worksheet);
}
}
private function setHyperlink(\SimpleXMLElement $hyperlink, Worksheet $worksheet)
{
// Link url
$linkRel = $hyperlink->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
foreach (Coordinate::extractAllCellReferencesInRange($hyperlink['ref']) as $cellReference) {
$cell = $worksheet->getCell($cellReference);
if (isset($linkRel['id'])) {
$hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']];
if (isset($hyperlink['location'])) {
$hyperlinkUrl .= '#' . (string) $hyperlink['location'];
}
$cell->getHyperlink()->setUrl($hyperlinkUrl);
} elseif (isset($hyperlink['location'])) {
$cell->getHyperlink()->setUrl('sheet://' . (string) $hyperlink['location']);
}
// Tooltip
if (isset($hyperlink['tooltip'])) {
$cell->getHyperlink()->setTooltip((string) $hyperlink['tooltip']);
}
}
}
}

View File

@ -0,0 +1,150 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class PageSetup extends BaseParserClass
{
private $worksheet;
private $worksheetXml;
public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml = null)
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
}
public function load(array $unparsedLoadedData)
{
if (!$this->worksheetXml) {
return $unparsedLoadedData;
}
$this->margins($this->worksheetXml, $this->worksheet);
$unparsedLoadedData = $this->pageSetup($this->worksheetXml, $this->worksheet, $unparsedLoadedData);
$this->headerFooter($this->worksheetXml, $this->worksheet);
$this->pageBreaks($this->worksheetXml, $this->worksheet);
return $unparsedLoadedData;
}
private function margins(\SimpleXMLElement $xmlSheet, Worksheet $worksheet)
{
if ($xmlSheet->pageMargins) {
$docPageMargins = $worksheet->getPageMargins();
$docPageMargins->setLeft((float) ($xmlSheet->pageMargins['left']));
$docPageMargins->setRight((float) ($xmlSheet->pageMargins['right']));
$docPageMargins->setTop((float) ($xmlSheet->pageMargins['top']));
$docPageMargins->setBottom((float) ($xmlSheet->pageMargins['bottom']));
$docPageMargins->setHeader((float) ($xmlSheet->pageMargins['header']));
$docPageMargins->setFooter((float) ($xmlSheet->pageMargins['footer']));
}
}
private function pageSetup(\SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData)
{
if ($xmlSheet->pageSetup) {
$docPageSetup = $worksheet->getPageSetup();
if (isset($xmlSheet->pageSetup['orientation'])) {
$docPageSetup->setOrientation((string) $xmlSheet->pageSetup['orientation']);
}
if (isset($xmlSheet->pageSetup['paperSize'])) {
$docPageSetup->setPaperSize((int) ($xmlSheet->pageSetup['paperSize']));
}
if (isset($xmlSheet->pageSetup['scale'])) {
$docPageSetup->setScale((int) ($xmlSheet->pageSetup['scale']), false);
}
if (isset($xmlSheet->pageSetup['fitToHeight']) && (int) ($xmlSheet->pageSetup['fitToHeight']) >= 0) {
$docPageSetup->setFitToHeight((int) ($xmlSheet->pageSetup['fitToHeight']), false);
}
if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) {
$docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false);
}
if (isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) &&
self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])) {
$docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber']));
}
$relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
if (isset($relAttributes['id'])) {
$unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id'];
}
}
return $unparsedLoadedData;
}
private function headerFooter(\SimpleXMLElement $xmlSheet, Worksheet $worksheet)
{
if ($xmlSheet->headerFooter) {
$docHeaderFooter = $worksheet->getHeaderFooter();
if (isset($xmlSheet->headerFooter['differentOddEven']) &&
self::boolean((string) $xmlSheet->headerFooter['differentOddEven'])) {
$docHeaderFooter->setDifferentOddEven(true);
} else {
$docHeaderFooter->setDifferentOddEven(false);
}
if (isset($xmlSheet->headerFooter['differentFirst']) &&
self::boolean((string) $xmlSheet->headerFooter['differentFirst'])) {
$docHeaderFooter->setDifferentFirst(true);
} else {
$docHeaderFooter->setDifferentFirst(false);
}
if (isset($xmlSheet->headerFooter['scaleWithDoc']) &&
!self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc'])) {
$docHeaderFooter->setScaleWithDocument(false);
} else {
$docHeaderFooter->setScaleWithDocument(true);
}
if (isset($xmlSheet->headerFooter['alignWithMargins']) &&
!self::boolean((string) $xmlSheet->headerFooter['alignWithMargins'])) {
$docHeaderFooter->setAlignWithMargins(false);
} else {
$docHeaderFooter->setAlignWithMargins(true);
}
$docHeaderFooter->setOddHeader((string) $xmlSheet->headerFooter->oddHeader);
$docHeaderFooter->setOddFooter((string) $xmlSheet->headerFooter->oddFooter);
$docHeaderFooter->setEvenHeader((string) $xmlSheet->headerFooter->evenHeader);
$docHeaderFooter->setEvenFooter((string) $xmlSheet->headerFooter->evenFooter);
$docHeaderFooter->setFirstHeader((string) $xmlSheet->headerFooter->firstHeader);
$docHeaderFooter->setFirstFooter((string) $xmlSheet->headerFooter->firstFooter);
}
}
private function pageBreaks(\SimpleXMLElement $xmlSheet, Worksheet $worksheet)
{
if ($xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk) {
$this->rowBreaks($xmlSheet, $worksheet);
}
if ($xmlSheet->colBreaks && $xmlSheet->colBreaks->brk) {
$this->columnBreaks($xmlSheet, $worksheet);
}
}
private function rowBreaks(\SimpleXMLElement $xmlSheet, Worksheet $worksheet)
{
foreach ($xmlSheet->rowBreaks->brk as $brk) {
if ($brk['man']) {
$worksheet->setBreak("A{$brk['id']}", Worksheet::BREAK_ROW);
}
}
}
private function columnBreaks(\SimpleXMLElement $xmlSheet, Worksheet $worksheet)
{
foreach ($xmlSheet->colBreaks->brk as $brk) {
if ($brk['man']) {
$worksheet->setBreak(
Coordinate::stringFromColumnIndex(((int) $brk['id']) + 1) . '1',
Worksheet::BREAK_COLUMN
);
}
}
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\Settings;
class Properties
{
private $securityScanner;
private $docProps;
public function __construct(XmlScanner $securityScanner, DocumentProperties $docProps)
{
$this->securityScanner = $securityScanner;
$this->docProps = $docProps;
}
private function extractPropertyData($propertyData)
{
return simplexml_load_string(
$this->securityScanner->scan($propertyData),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
}
public function readCoreProperties($propertyData)
{
$xmlCore = $this->extractPropertyData($propertyData);
if (is_object($xmlCore)) {
$xmlCore->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/');
$xmlCore->registerXPathNamespace('dcterms', 'http://purl.org/dc/terms/');
$xmlCore->registerXPathNamespace('cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties');
$this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator')));
$this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy')));
$this->docProps->setCreated(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:created')))); //! respect xsi:type
$this->docProps->setModified(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:modified')))); //! respect xsi:type
$this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title')));
$this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description')));
$this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject')));
$this->docProps->setKeywords((string) self::getArrayItem($xmlCore->xpath('cp:keywords')));
$this->docProps->setCategory((string) self::getArrayItem($xmlCore->xpath('cp:category')));
}
}
public function readExtendedProperties($propertyData)
{
$xmlCore = $this->extractPropertyData($propertyData);
if (is_object($xmlCore)) {
if (isset($xmlCore->Company)) {
$this->docProps->setCompany((string) $xmlCore->Company);
}
if (isset($xmlCore->Manager)) {
$this->docProps->setManager((string) $xmlCore->Manager);
}
}
}
public function readCustomProperties($propertyData)
{
$xmlCore = $this->extractPropertyData($propertyData);
if (is_object($xmlCore)) {
foreach ($xmlCore as $xmlProperty) {
/** @var \SimpleXMLElement $xmlProperty */
$cellDataOfficeAttributes = $xmlProperty->attributes();
if (isset($cellDataOfficeAttributes['name'])) {
$propertyName = (string) $cellDataOfficeAttributes['name'];
$cellDataOfficeChildren = $xmlProperty->children('http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
$attributeType = $cellDataOfficeChildren->getName();
$attributeValue = (string) $cellDataOfficeChildren->{$attributeType};
$attributeValue = DocumentProperties::convertProperty($attributeValue, $attributeType);
$attributeType = DocumentProperties::convertPropertyType($attributeType);
$this->docProps->setCustomProperty($propertyName, $attributeValue, $attributeType);
}
}
}
}
private static function getArrayItem(array $array, $key = 0)
{
return isset($array[$key]) ? $array[$key] : null;
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class SheetViewOptions extends BaseParserClass
{
private $worksheet;
private $worksheetXml;
public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml = null)
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
}
/**
* @param bool $readDataOnly
*/
public function load($readDataOnly = false)
{
if ($this->worksheetXml === null) {
return;
}
if (isset($this->worksheetXml->sheetPr)) {
$this->tabColor($this->worksheetXml->sheetPr);
$this->codeName($this->worksheetXml->sheetPr);
$this->outlines($this->worksheetXml->sheetPr);
$this->pageSetup($this->worksheetXml->sheetPr);
}
if (isset($this->worksheetXml->sheetFormatPr)) {
$this->sheetFormat($this->worksheetXml->sheetFormatPr);
}
if (!$readDataOnly && isset($this->worksheetXml->printOptions)) {
$this->printOptions($this->worksheetXml->printOptions);
}
}
private function tabColor(\SimpleXMLElement $sheetPr)
{
if (isset($sheetPr->tabColor, $sheetPr->tabColor['rgb'])) {
$this->worksheet->getTabColor()->setARGB((string) $sheetPr->tabColor['rgb']);
}
}
private function codeName(\SimpleXMLElement $sheetPr)
{
if (isset($sheetPr['codeName'])) {
$this->worksheet->setCodeName((string) $sheetPr['codeName'], false);
}
}
private function outlines(\SimpleXMLElement $sheetPr)
{
if (isset($sheetPr->outlinePr)) {
if (isset($sheetPr->outlinePr['summaryRight']) &&
!self::boolean((string) $sheetPr->outlinePr['summaryRight'])) {
$this->worksheet->setShowSummaryRight(false);
} else {
$this->worksheet->setShowSummaryRight(true);
}
if (isset($sheetPr->outlinePr['summaryBelow']) &&
!self::boolean((string) $sheetPr->outlinePr['summaryBelow'])) {
$this->worksheet->setShowSummaryBelow(false);
} else {
$this->worksheet->setShowSummaryBelow(true);
}
}
}
private function pageSetup(\SimpleXMLElement $sheetPr)
{
if (isset($sheetPr->pageSetUpPr)) {
if (isset($sheetPr->pageSetUpPr['fitToPage']) &&
!self::boolean((string) $sheetPr->pageSetUpPr['fitToPage'])) {
$this->worksheet->getPageSetup()->setFitToPage(false);
} else {
$this->worksheet->getPageSetup()->setFitToPage(true);
}
}
}
private function sheetFormat(\SimpleXMLElement $sheetFormatPr)
{
if (isset($sheetFormatPr['customHeight']) &&
self::boolean((string) $sheetFormatPr['customHeight']) &&
isset($sheetFormatPr['defaultRowHeight'])) {
$this->worksheet->getDefaultRowDimension()
->setRowHeight((float) $sheetFormatPr['defaultRowHeight']);
}
if (isset($sheetFormatPr['defaultColWidth'])) {
$this->worksheet->getDefaultColumnDimension()
->setWidth((float) $sheetFormatPr['defaultColWidth']);
}
if (isset($sheetFormatPr['zeroHeight']) &&
((string) $sheetFormatPr['zeroHeight'] === '1')) {
$this->worksheet->getDefaultRowDimension()->setZeroHeight(true);
}
}
private function printOptions(\SimpleXMLElement $printOptions)
{
if (self::boolean((string) $printOptions['gridLinesSet'])) {
$this->worksheet->setShowGridlines(true);
}
if (self::boolean((string) $printOptions['gridLines'])) {
$this->worksheet->setPrintGridlines(true);
}
if (self::boolean((string) $printOptions['horizontalCentered'])) {
$this->worksheet->getPageSetup()->setHorizontalCentered(true);
}
if (self::boolean((string) $printOptions['verticalCentered'])) {
$this->worksheet->getPageSetup()->setVerticalCentered(true);
}
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class SheetViews extends BaseParserClass
{
private $sheetViewXml;
private $worksheet;
public function __construct(\SimpleXMLElement $sheetViewXml, Worksheet $workSheet)
{
$this->sheetViewXml = $sheetViewXml;
$this->worksheet = $workSheet;
}
public function load()
{
$this->zoomScale();
$this->view();
$this->gridLines();
$this->headers();
$this->direction();
if (isset($this->sheetViewXml->pane)) {
$this->pane();
}
if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection['sqref'])) {
$this->selection();
}
}
private function zoomScale()
{
if (isset($this->sheetViewXml['zoomScale'])) {
$zoomScale = (int) ($this->sheetViewXml['zoomScale']);
if ($zoomScale <= 0) {
// setZoomScale will throw an Exception if the scale is less than or equals 0
// that is OK when manually creating documents, but we should be able to read all documents
$zoomScale = 100;
}
$this->worksheet->getSheetView()->setZoomScale($zoomScale);
}
if (isset($this->sheetViewXml['zoomScaleNormal'])) {
$zoomScaleNormal = (int) ($this->sheetViewXml['zoomScaleNormal']);
if ($zoomScaleNormal <= 0) {
// setZoomScaleNormal will throw an Exception if the scale is less than or equals 0
// that is OK when manually creating documents, but we should be able to read all documents
$zoomScaleNormal = 100;
}
$this->worksheet->getSheetView()->setZoomScaleNormal($zoomScaleNormal);
}
}
private function view()
{
if (isset($this->sheetViewXml['view'])) {
$this->worksheet->getSheetView()->setView((string) $this->sheetViewXml['view']);
}
}
private function gridLines()
{
if (isset($this->sheetViewXml['showGridLines'])) {
$this->worksheet->setShowGridLines(
self::boolean((string) $this->sheetViewXml['showGridLines'])
);
}
}
private function headers()
{
if (isset($this->sheetViewXml['showRowColHeaders'])) {
$this->worksheet->setShowRowColHeaders(
self::boolean((string) $this->sheetViewXml['showRowColHeaders'])
);
}
}
private function direction()
{
if (isset($this->sheetViewXml['rightToLeft'])) {
$this->worksheet->setRightToLeft(
self::boolean((string) $this->sheetViewXml['rightToLeft'])
);
}
}
private function pane()
{
$xSplit = 0;
$ySplit = 0;
$topLeftCell = null;
if (isset($this->sheetViewXml->pane['xSplit'])) {
$xSplit = (int) ($this->sheetViewXml->pane['xSplit']);
}
if (isset($this->sheetViewXml->pane['ySplit'])) {
$ySplit = (int) ($this->sheetViewXml->pane['ySplit']);
}
if (isset($this->sheetViewXml->pane['topLeftCell'])) {
$topLeftCell = (string) $this->sheetViewXml->pane['topLeftCell'];
}
$this->worksheet->freezePane(
Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1),
$topLeftCell
);
}
private function selection()
{
$sqref = (string) $this->sheetViewXml->selection['sqref'];
$sqref = explode(' ', $sqref);
$sqref = $sqref[0];
$this->worksheet->setSelectedCells($sqref);
}
}

View File

@ -0,0 +1,265 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Borders;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Style\Protection;
use PhpOffice\PhpSpreadsheet\Style\Style;
class Styles extends BaseParserClass
{
/**
* Theme instance.
*
* @var Theme
*/
private static $theme = null;
private $styles = [];
private $cellStyles = [];
private $styleXml;
public function __construct(\SimpleXMLElement $styleXml)
{
$this->styleXml = $styleXml;
}
public function setStyleBaseData(Theme $theme = null, $styles = [], $cellStyles = [])
{
self::$theme = $theme;
$this->styles = $styles;
$this->cellStyles = $cellStyles;
}
private static function readFontStyle(Font $fontStyle, \SimpleXMLElement $fontStyleXml)
{
$fontStyle->setName((string) $fontStyleXml->name['val']);
$fontStyle->setSize((float) $fontStyleXml->sz['val']);
if (isset($fontStyleXml->b)) {
$fontStyle->setBold(!isset($fontStyleXml->b['val']) || self::boolean((string) $fontStyleXml->b['val']));
}
if (isset($fontStyleXml->i)) {
$fontStyle->setItalic(!isset($fontStyleXml->i['val']) || self::boolean((string) $fontStyleXml->i['val']));
}
if (isset($fontStyleXml->strike)) {
$fontStyle->setStrikethrough(!isset($fontStyleXml->strike['val']) || self::boolean((string) $fontStyleXml->strike['val']));
}
$fontStyle->getColor()->setARGB(self::readColor($fontStyleXml->color));
if (isset($fontStyleXml->u) && !isset($fontStyleXml->u['val'])) {
$fontStyle->setUnderline(Font::UNDERLINE_SINGLE);
} elseif (isset($fontStyleXml->u, $fontStyleXml->u['val'])) {
$fontStyle->setUnderline((string) $fontStyleXml->u['val']);
}
if (isset($fontStyleXml->vertAlign, $fontStyleXml->vertAlign['val'])) {
$verticalAlign = strtolower((string) $fontStyleXml->vertAlign['val']);
if ($verticalAlign === 'superscript') {
$fontStyle->setSuperscript(true);
}
if ($verticalAlign === 'subscript') {
$fontStyle->setSubscript(true);
}
}
}
private static function readFillStyle(Fill $fillStyle, \SimpleXMLElement $fillStyleXml)
{
if ($fillStyleXml->gradientFill) {
/** @var \SimpleXMLElement $gradientFill */
$gradientFill = $fillStyleXml->gradientFill[0];
if (!empty($gradientFill['type'])) {
$fillStyle->setFillType((string) $gradientFill['type']);
}
$fillStyle->setRotation((float) ($gradientFill['degree']));
$gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
$fillStyle->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color));
$fillStyle->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color));
} elseif ($fillStyleXml->patternFill) {
$patternType = (string) $fillStyleXml->patternFill['patternType'] != '' ? (string) $fillStyleXml->patternFill['patternType'] : 'solid';
$fillStyle->setFillType($patternType);
if ($fillStyleXml->patternFill->fgColor) {
$fillStyle->getStartColor()->setARGB(self::readColor($fillStyleXml->patternFill->fgColor, true));
} else {
$fillStyle->getStartColor()->setARGB('FF000000');
}
if ($fillStyleXml->patternFill->bgColor) {
$fillStyle->getEndColor()->setARGB(self::readColor($fillStyleXml->patternFill->bgColor, true));
}
}
}
private static function readBorderStyle(Borders $borderStyle, \SimpleXMLElement $borderStyleXml)
{
$diagonalUp = self::boolean((string) $borderStyleXml['diagonalUp']);
$diagonalDown = self::boolean((string) $borderStyleXml['diagonalDown']);
if (!$diagonalUp && !$diagonalDown) {
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE);
} elseif ($diagonalUp && !$diagonalDown) {
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP);
} elseif (!$diagonalUp && $diagonalDown) {
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN);
} else {
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH);
}
self::readBorder($borderStyle->getLeft(), $borderStyleXml->left);
self::readBorder($borderStyle->getRight(), $borderStyleXml->right);
self::readBorder($borderStyle->getTop(), $borderStyleXml->top);
self::readBorder($borderStyle->getBottom(), $borderStyleXml->bottom);
self::readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal);
}
private static function readBorder(Border $border, \SimpleXMLElement $borderXml)
{
if (isset($borderXml['style'])) {
$border->setBorderStyle((string) $borderXml['style']);
}
if (isset($borderXml->color)) {
$border->getColor()->setARGB(self::readColor($borderXml->color));
}
}
private static function readAlignmentStyle(Alignment $alignment, \SimpleXMLElement $alignmentXml)
{
$alignment->setHorizontal((string) $alignmentXml->alignment['horizontal']);
$alignment->setVertical((string) $alignmentXml->alignment['vertical']);
$textRotation = 0;
if ((int) $alignmentXml->alignment['textRotation'] <= 90) {
$textRotation = (int) $alignmentXml->alignment['textRotation'];
} elseif ((int) $alignmentXml->alignment['textRotation'] > 90) {
$textRotation = 90 - (int) $alignmentXml->alignment['textRotation'];
}
$alignment->setTextRotation((int) $textRotation);
$alignment->setWrapText(self::boolean((string) $alignmentXml->alignment['wrapText']));
$alignment->setShrinkToFit(self::boolean((string) $alignmentXml->alignment['shrinkToFit']));
$alignment->setIndent((int) ((string) $alignmentXml->alignment['indent']) > 0 ? (int) ((string) $alignmentXml->alignment['indent']) : 0);
$alignment->setReadOrder((int) ((string) $alignmentXml->alignment['readingOrder']) > 0 ? (int) ((string) $alignmentXml->alignment['readingOrder']) : 0);
}
private function readStyle(Style $docStyle, $style)
{
$docStyle->getNumberFormat()->setFormatCode($style->numFmt);
if (isset($style->font)) {
self::readFontStyle($docStyle->getFont(), $style->font);
}
if (isset($style->fill)) {
self::readFillStyle($docStyle->getFill(), $style->fill);
}
if (isset($style->border)) {
self::readBorderStyle($docStyle->getBorders(), $style->border);
}
if (isset($style->alignment)) {
self::readAlignmentStyle($docStyle->getAlignment(), $style->alignment);
}
// protection
if (isset($style->protection)) {
$this->readProtectionLocked($docStyle, $style);
$this->readProtectionHidden($docStyle, $style);
}
// top-level style settings
if (isset($style->quotePrefix)) {
$docStyle->setQuotePrefix(true);
}
}
private function readProtectionLocked(Style $docStyle, $style)
{
if (isset($style->protection['locked'])) {
if (self::boolean((string) $style->protection['locked'])) {
$docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
} else {
$docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
}
}
}
private function readProtectionHidden(Style $docStyle, $style)
{
if (isset($style->protection['hidden'])) {
if (self::boolean((string) $style->protection['hidden'])) {
$docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
} else {
$docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
}
}
}
private static function readColor($color, $background = false)
{
if (isset($color['rgb'])) {
return (string) $color['rgb'];
} elseif (isset($color['indexed'])) {
return Color::indexedColor($color['indexed'] - 7, $background)->getARGB();
} elseif (isset($color['theme'])) {
if (self::$theme !== null) {
$returnColour = self::$theme->getColourByIndex((int) $color['theme']);
if (isset($color['tint'])) {
$tintAdjust = (float) $color['tint'];
$returnColour = Color::changeBrightness($returnColour, $tintAdjust);
}
return 'FF' . $returnColour;
}
}
return ($background) ? 'FFFFFFFF' : 'FF000000';
}
public function dxfs($readDataOnly = false)
{
$dxfs = [];
if (!$readDataOnly && $this->styleXml) {
// Conditional Styles
if ($this->styleXml->dxfs) {
foreach ($this->styleXml->dxfs->dxf as $dxf) {
$style = new Style(false, true);
$this->readStyle($style, $dxf);
$dxfs[] = $style;
}
}
// Cell Styles
if ($this->styleXml->cellStyles) {
foreach ($this->styleXml->cellStyles->cellStyle as $cellStyle) {
if ((int) ($cellStyle['builtinId']) == 0) {
if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) {
// Set default style
$style = new Style();
$this->readStyle($style, $this->cellStyles[(int) ($cellStyle['xfId'])]);
// normal style, currently not using it for anything
}
}
}
}
}
return $dxfs;
}
public function styles()
{
return $this->styles;
}
private static function getArrayItem($array, $key = 0)
{
return isset($array[$key]) ? $array[$key] : null;
}
}

View File

@ -41,7 +41,7 @@ class Xml extends BaseReader
*/ */
public function __construct() public function __construct()
{ {
$this->readFilter = new DefaultReadFilter(); parent::__construct();
$this->securityScanner = XmlScanner::getInstance($this); $this->securityScanner = XmlScanner::getInstance($this);
} }

View File

@ -82,13 +82,13 @@ class ReferenceHelper
*/ */
public static function cellSort($a, $b) public static function cellSort($a, $b)
{ {
$ac = $bc = ''; // TODO Scrutinizer doesn't like sscanf($a, '%[A-Z]%d', $ac, $ar), and we can't use short list() syntax
$ar = $br = 0; // [$ac, $ar] = sscanf($a, '%[A-Z]%d') while retaining PHP 5.6 support.
// Switch when we drop support for 5.6
list($ac, $ar) = sscanf($a, '%[A-Z]%d');
list($bc, $br) = sscanf($b, '%[A-Z]%d');
sscanf($a, '%[A-Z]%d', $ac, $ar); if ($ar === $br) {
sscanf($b, '%[A-Z]%d', $bc, $br);
if ($ar == $br) {
return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc); return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
} }
@ -106,13 +106,13 @@ class ReferenceHelper
*/ */
public static function cellReverseSort($a, $b) public static function cellReverseSort($a, $b)
{ {
$ac = $bc = ''; // TODO Scrutinizer doesn't like sscanf($a, '%[A-Z]%d', $ac, $ar), and we can't use short list() syntax
$ar = $br = 0; // [$ac, $ar] = sscanf($a, '%[A-Z]%d') while retaining PHP 5.6 support.
// Switch when we drop support for 5.6
list($ac, $ar) = sscanf($a, '%[A-Z]%d');
list($bc, $br) = sscanf($b, '%[A-Z]%d');
sscanf($a, '%[A-Z]%d', $ac, $ar); if ($ar === $br) {
sscanf($b, '%[A-Z]%d', $bc, $br);
if ($ar == $br) {
return 1 - strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc); return 1 - strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
} }
@ -625,7 +625,7 @@ class ReferenceHelper
* Update references within formulas. * Update references within formulas.
* *
* @param string $pFormula Formula to update * @param string $pFormula Formula to update
* @param int $pBefore Insert before this one * @param string $pBefore Insert before this one
* @param int $pNumCols Number of columns to insert * @param int $pNumCols Number of columns to insert
* @param int $pNumRows Number of rows to insert * @param int $pNumRows Number of rows to insert
* @param string $sheetName Worksheet name/title * @param string $sheetName Worksheet name/title

View File

@ -24,6 +24,20 @@ class Settings
*/ */
private static $libXmlLoaderOptions = null; private static $libXmlLoaderOptions = null;
/**
* Allow/disallow libxml_disable_entity_loader() call when not thread safe.
* Default behaviour is to do the check, but if you're running PHP versions
* 7.2 < 7.2.1
* 7.1 < 7.1.13
* 7.0 < 7.0.27
* 5.6 ANY
* then you may need to disable this check to prevent unwanted behaviour in other threads
* SECURITY WARNING: Changing this flag is not recommended.
*
* @var bool
*/
private static $libXmlDisableEntityLoader = true;
/** /**
* The cache implementation to be used for cell collection. * The cache implementation to be used for cell collection.
* *
@ -101,6 +115,34 @@ class Settings
return self::$libXmlLoaderOptions; return self::$libXmlLoaderOptions;
} }
/**
* Enable/Disable the entity loader for libxml loader.
* Allow/disallow libxml_disable_entity_loader() call when not thread safe.
* Default behaviour is to do the check, but if you're running PHP versions
* 7.2 < 7.2.1
* 7.1 < 7.1.13
* 7.0 < 7.0.27
* 5.6 ANY
* then you may need to disable this check to prevent unwanted behaviour in other threads
* SECURITY WARNING: Changing this flag to false is not recommended.
*
* @param bool $state
*/
public static function setLibXmlDisableEntityLoader($state)
{
self::$libXmlDisableEntityLoader = (bool) $state;
}
/**
* Return the state of the entity loader (disabled/enabled) for libxml loader.
*
* @return bool $state
*/
public static function getLibXmlDisableEntityLoader()
{
return self::$libXmlDisableEntityLoader;
}
/** /**
* Sets the implementation of cache that should be used for cell collection. * Sets the implementation of cache that should be used for cell collection.
* *

View File

@ -192,7 +192,7 @@ class Date
$interval = $days . ' days'; $interval = $days . ' days';
return $baseDate->modify($interval) return $baseDate->modify($interval)
->setTime($hours, $minutes, $seconds); ->setTime((int) $hours, (int) $minutes, (int) $seconds);
} }
/** /**
@ -244,12 +244,12 @@ class Date
public static function dateTimeToExcel(DateTimeInterface $dateValue) public static function dateTimeToExcel(DateTimeInterface $dateValue)
{ {
return self::formattedPHPToExcel( return self::formattedPHPToExcel(
$dateValue->format('Y'), (int) $dateValue->format('Y'),
$dateValue->format('m'), (int) $dateValue->format('m'),
$dateValue->format('d'), (int) $dateValue->format('d'),
$dateValue->format('H'), (int) $dateValue->format('H'),
$dateValue->format('i'), (int) $dateValue->format('i'),
$dateValue->format('s') (int) $dateValue->format('s')
); );
} }
@ -325,11 +325,12 @@ class Date
*/ */
public static function isDateTime(Cell $pCell) public static function isDateTime(Cell $pCell)
{ {
return self::isDateTimeFormat( return is_numeric($pCell->getValue()) &&
$pCell->getWorksheet()->getStyle( self::isDateTimeFormat(
$pCell->getCoordinate() $pCell->getWorksheet()->getStyle(
)->getNumberFormat() $pCell->getCoordinate()
); )->getNumberFormat()
);
} }
/** /**

View File

@ -537,31 +537,37 @@ class OLE
/** /**
* Returns a timestamp from an OLE container's date. * Returns a timestamp from an OLE container's date.
* *
* @param int $string A binary string with the encoded date * @param string $oleTimestamp A binary string with the encoded date
* *
* @return string The timestamp corresponding to the string * @throws ReaderException
*
* @return int The Unix timestamp corresponding to the string
*/ */
public static function OLE2LocalDate($string) public static function OLE2LocalDate($oleTimestamp)
{ {
if (strlen($string) != 8) { if (strlen($oleTimestamp) != 8) {
throw new ReaderException('Expecting 8 byte string'); throw new ReaderException('Expecting 8 byte string');
} }
// factor used for separating numbers into 4 bytes parts // convert to units of 100 ns since 1601:
$factor = pow(2, 32); $unpackedTimestamp = unpack('v4', $oleTimestamp);
list(, $high_part) = unpack('V', substr($string, 4, 4)); $timestampHigh = (float) $unpackedTimestamp[4] * 65536 + (float) $unpackedTimestamp[3];
list(, $low_part) = unpack('V', substr($string, 0, 4)); $timestampLow = (float) $unpackedTimestamp[2] * 65536 + (float) $unpackedTimestamp[1];
$big_date = ($high_part * $factor) + $low_part; // translate to seconds since 1601:
// translate to seconds $timestampHigh /= 10000000;
$big_date /= 10000000; $timestampLow /= 10000000;
// days from 1-1-1601 until the beggining of UNIX era // days from 1601 to 1970:
$days = 134774; $days = 134774;
// translate to seconds from beggining of UNIX era // translate to seconds since 1970:
$big_date -= $days * 24 * 3600; $unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5);
return floor($big_date); if ((int) $unixTimestamp == $unixTimestamp) {
return (int) $unixTimestamp;
}
return $unixTimestamp >= 0.0 ? PHP_INT_MAX : PHP_INT_MIN;
} }
} }

View File

@ -113,7 +113,9 @@ class ExponentialBestFit extends BestFit
*/ */
public function __construct($yValues, $xValues = [], $const = true) public function __construct($yValues, $xValues = [], $const = true)
{ {
if (parent::__construct($yValues, $xValues) !== false) { parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->exponentialRegression($yValues, $xValues, $const); $this->exponentialRegression($yValues, $xValues, $const);
} }
} }

View File

@ -72,7 +72,9 @@ class LinearBestFit extends BestFit
*/ */
public function __construct($yValues, $xValues = [], $const = true) public function __construct($yValues, $xValues = [], $const = true)
{ {
if (parent::__construct($yValues, $xValues) !== false) { parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->linearRegression($yValues, $xValues, $const); $this->linearRegression($yValues, $xValues, $const);
} }
} }

View File

@ -81,7 +81,9 @@ class LogarithmicBestFit extends BestFit
*/ */
public function __construct($yValues, $xValues = [], $const = true) public function __construct($yValues, $xValues = [], $const = true)
{ {
if (parent::__construct($yValues, $xValues) !== false) { parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->logarithmicRegression($yValues, $xValues, $const); $this->logarithmicRegression($yValues, $xValues, $const);
} }
} }

View File

@ -182,7 +182,9 @@ class PolynomialBestFit extends BestFit
*/ */
public function __construct($order, $yValues, $xValues = [], $const = true) public function __construct($order, $yValues, $xValues = [], $const = true)
{ {
if (parent::__construct($yValues, $xValues) !== false) { parent::__construct($yValues, $xValues);
if (!$this->error) {
if ($order < $this->valueCount) { if ($order < $this->valueCount) {
$this->bestFitType .= '_' . $order; $this->bestFitType .= '_' . $order;
$this->order = $order; $this->order = $order;

View File

@ -105,7 +105,9 @@ class PowerBestFit extends BestFit
*/ */
public function __construct($yValues, $xValues = [], $const = true) public function __construct($yValues, $xValues = [], $const = true)
{ {
if (parent::__construct($yValues, $xValues) !== false) { parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->powerRegression($yValues, $xValues, $const); $this->powerRegression($yValues, $xValues, $const);
} }
} }

View File

@ -715,7 +715,7 @@ class Spreadsheet
* *
* @param string $pName Sheet name * @param string $pName Sheet name
* *
* @return Worksheet * @return null|Worksheet
*/ */
public function getSheetByName($pName) public function getSheetByName($pName)
{ {

View File

@ -49,6 +49,8 @@ class NumberFormat extends Supervisor
const FORMAT_CURRENCY_USD = '$#,##0_-'; const FORMAT_CURRENCY_USD = '$#,##0_-';
const FORMAT_CURRENCY_EUR_SIMPLE = '#,##0.00_-"€"'; const FORMAT_CURRENCY_EUR_SIMPLE = '#,##0.00_-"€"';
const FORMAT_CURRENCY_EUR = '#,##0_-"€"'; const FORMAT_CURRENCY_EUR = '#,##0_-"€"';
const FORMAT_ACCOUNTING_USD = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
const FORMAT_ACCOUNTING_EUR = '_("€"* #,##0.00_);_("€"* \(#,##0.00\);_("€"* "-"??_);_(@_)';
/** /**
* Excel built-in number formats. * Excel built-in number formats.
@ -308,6 +310,22 @@ class NumberFormat extends Supervisor
self::$builtInFormats[69] = 't# ?/?'; self::$builtInFormats[69] = 't# ?/?';
self::$builtInFormats[70] = 't# ??/??'; self::$builtInFormats[70] = 't# ??/??';
// JPN
self::$builtInFormats[28] = '[$-411]ggge"年"m"月"d"日"';
self::$builtInFormats[29] = '[$-411]ggge"年"m"月"d"日"';
self::$builtInFormats[31] = 'yyyy"年"m"月"d"日"';
self::$builtInFormats[32] = 'h"時"mm"分"';
self::$builtInFormats[33] = 'h"時"mm"分"ss"秒"';
self::$builtInFormats[34] = 'yyyy"年"m"月"';
self::$builtInFormats[35] = 'm"月"d"日"';
self::$builtInFormats[51] = '[$-411]ggge"年"m"月"d"日"';
self::$builtInFormats[52] = 'yyyy"年"m"月"';
self::$builtInFormats[53] = 'm"月"d"日"';
self::$builtInFormats[54] = '[$-411]ggge"年"m"月"d"日"';
self::$builtInFormats[55] = 'yyyy"年"m"月"';
self::$builtInFormats[56] = 'm"月"d"日"';
self::$builtInFormats[58] = '[$-411]ggge"年"m"月"d"日"';
// Flip array (for faster lookups) // Flip array (for faster lookups)
self::$flippedBuiltInFormats = array_flip(self::$builtInFormats); self::$flippedBuiltInFormats = array_flip(self::$builtInFormats);
} }
@ -596,7 +614,7 @@ class NumberFormat extends Supervisor
} }
// Convert any other escaped characters to quoted strings, e.g. (\T to "T") // Convert any other escaped characters to quoted strings, e.g. (\T to "T")
$format = preg_replace('/(\\\([^ ]))(?=(?:[^"]|"[^"]*")*$)/u', '"${2}"', $format); $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/u', '"${2}"', $format);
// Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal) // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal)
$sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format); $sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format);

View File

@ -64,7 +64,7 @@ abstract class Dimension
*/ */
public function setVisible($pValue) public function setVisible($pValue)
{ {
$this->visible = $pValue; $this->visible = (bool) $pValue;
return $this; return $this;
} }
@ -119,7 +119,7 @@ abstract class Dimension
*/ */
public function setCollapsed($pValue) public function setCollapsed($pValue)
{ {
$this->collapsed = $pValue; $this->collapsed = (bool) $pValue;
return $this; return $this;
} }

View File

@ -170,6 +170,16 @@ class RowCellIterator extends CellIterator
return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex; return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex;
} }
/**
* Return the current iterator position.
*
* @return int
*/
public function getCurrentColumnIndex()
{
return $this->currentColumnIndex;
}
/** /**
* Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
* *

View File

@ -1096,7 +1096,7 @@ class Worksheet implements IComparable
* @param string $column Return the highest data row for the specified column, * @param string $column Return the highest data row for the specified column,
* or the highest data row of any column if no column letter is passed * or the highest data row of any column if no column letter is passed
* *
* @return string Highest row number that contains data * @return int Highest row number that contains data
*/ */
public function getHighestDataRow($column = null) public function getHighestDataRow($column = null)
{ {
@ -2064,7 +2064,7 @@ class Worksheet implements IComparable
/** /**
* Insert a new column, updating all possible related data. * Insert a new column, updating all possible related data.
* *
* @param int $pBefore Insert before this one, eg: 'A' * @param string $pBefore Insert before this one, eg: 'A'
* @param int $pNumCols Number of columns to insert * @param int $pNumCols Number of columns to insert
* *
* @throws Exception * @throws Exception

View File

@ -35,27 +35,11 @@ abstract class BaseWriter implements IWriter
*/ */
private $diskCachingDirectory = './'; private $diskCachingDirectory = './';
/**
* Write charts in workbook?
* If this is true, then the Writer will write definitions for any charts that exist in the PhpSpreadsheet object.
* If false (the default) it will ignore any charts defined in the PhpSpreadsheet object.
*
* @return bool
*/
public function getIncludeCharts() public function getIncludeCharts()
{ {
return $this->includeCharts; return $this->includeCharts;
} }
/**
* Set write charts in workbook
* Set to true, to advise the Writer to include any charts that exist in the PhpSpreadsheet object.
* Set to false (the default) to ignore charts.
*
* @param bool $pValue
*
* @return IWriter
*/
public function setIncludeCharts($pValue) public function setIncludeCharts($pValue)
{ {
$this->includeCharts = (bool) $pValue; $this->includeCharts = (bool) $pValue;
@ -63,30 +47,11 @@ abstract class BaseWriter implements IWriter
return $this; return $this;
} }
/**
* Get Pre-Calculate Formulas flag
* If this is true (the default), then the writer will recalculate all formulae in a workbook when saving,
* so that the pre-calculated values are immediately available to MS Excel or other office spreadsheet
* viewer when opening the file
* If false, then formulae are not calculated on save. This is faster for saving in PhpSpreadsheet, but slower
* when opening the resulting file in MS Excel, because Excel has to recalculate the formulae itself.
*
* @return bool
*/
public function getPreCalculateFormulas() public function getPreCalculateFormulas()
{ {
return $this->preCalculateFormulas; return $this->preCalculateFormulas;
} }
/**
* Set Pre-Calculate Formulas
* Set to true (the default) to advise the Writer to calculate all formulae on save
* Set to false to prevent precalculation of formulae on save.
*
* @param bool $pValue Pre-Calculate Formulas?
*
* @return IWriter
*/
public function setPreCalculateFormulas($pValue) public function setPreCalculateFormulas($pValue)
{ {
$this->preCalculateFormulas = (bool) $pValue; $this->preCalculateFormulas = (bool) $pValue;
@ -94,26 +59,11 @@ abstract class BaseWriter implements IWriter
return $this; return $this;
} }
/**
* Get use disk caching where possible?
*
* @return bool
*/
public function getUseDiskCaching() public function getUseDiskCaching()
{ {
return $this->useDiskCaching; return $this->useDiskCaching;
} }
/**
* Set use disk caching where possible?
*
* @param bool $pValue
* @param string $pDirectory Disk caching directory
*
* @throws Exception when directory does not exist
*
* @return IWriter
*/
public function setUseDiskCaching($pValue, $pDirectory = null) public function setUseDiskCaching($pValue, $pDirectory = null)
{ {
$this->useDiskCaching = $pValue; $this->useDiskCaching = $pValue;
@ -129,11 +79,6 @@ abstract class BaseWriter implements IWriter
return $this; return $this;
} }
/**
* Get disk caching directory.
*
* @return string
*/
public function getDiskCachingDirectory() public function getDiskCachingDirectory()
{ {
return $this->diskCachingDirectory; return $this->diskCachingDirectory;

View File

@ -13,6 +13,49 @@ interface IWriter
*/ */
public function __construct(Spreadsheet $spreadsheet); public function __construct(Spreadsheet $spreadsheet);
/**
* Write charts in workbook?
* If this is true, then the Writer will write definitions for any charts that exist in the PhpSpreadsheet object.
* If false (the default) it will ignore any charts defined in the PhpSpreadsheet object.
*
* @return bool
*/
public function getIncludeCharts();
/**
* Set write charts in workbook
* Set to true, to advise the Writer to include any charts that exist in the PhpSpreadsheet object.
* Set to false (the default) to ignore charts.
*
* @param bool $pValue
*
* @return IWriter
*/
public function setIncludeCharts($pValue);
/**
* Get Pre-Calculate Formulas flag
* If this is true (the default), then the writer will recalculate all formulae in a workbook when saving,
* so that the pre-calculated values are immediately available to MS Excel or other office spreadsheet
* viewer when opening the file
* If false, then formulae are not calculated on save. This is faster for saving in PhpSpreadsheet, but slower
* when opening the resulting file in MS Excel, because Excel has to recalculate the formulae itself.
*
* @return bool
*/
public function getPreCalculateFormulas();
/**
* Set Pre-Calculate Formulas
* Set to true (the default) to advise the Writer to calculate all formulae on save
* Set to false to prevent precalculation of formulae on save.
*
* @param bool $pValue Pre-Calculate Formulas?
*
* @return IWriter
*/
public function setPreCalculateFormulas($pValue);
/** /**
* Save PhpSpreadsheet to file. * Save PhpSpreadsheet to file.
* *
@ -21,4 +64,30 @@ interface IWriter
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/ */
public function save($pFilename); public function save($pFilename);
/**
* Get use disk caching where possible?
*
* @return bool
*/
public function getUseDiskCaching();
/**
* Set use disk caching where possible?
*
* @param bool $pValue
* @param string $pDirectory Disk caching directory
*
* @throws Exception when directory does not exist
*
* @return IWriter
*/
public function setUseDiskCaching($pValue, $pDirectory = null);
/**
* Get disk caching directory.
*
* @return string
*/
public function getDiskCachingDirectory();
} }

View File

@ -1009,7 +1009,7 @@ class Parser
break; break;
case '>': case '>':
if ($this->lookAhead == '=') { // it's a GE token if ($this->lookAhead === '=') { // it's a GE token
break; break;
} }
@ -1018,7 +1018,7 @@ class Parser
break; break;
case '<': case '<':
// it's a LE or a NE token // it's a LE or a NE token
if (($this->lookAhead == '=') or ($this->lookAhead == '>')) { if (($this->lookAhead === '=') or ($this->lookAhead === '>')) {
break; break;
} }
@ -1027,12 +1027,12 @@ class Parser
break; break;
default: default:
// if it's a reference A1 or $A$1 or $A1 or A$1 // if it's a reference A1 or $A$1 or $A1 or A$1
if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $token) and !preg_match('/\d/', $this->lookAhead) and ($this->lookAhead != ':') and ($this->lookAhead != '.') and ($this->lookAhead != '!')) { if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $token) and !preg_match('/\d/', $this->lookAhead) and ($this->lookAhead !== ':') and ($this->lookAhead !== '.') and ($this->lookAhead !== '!')) {
return $token; return $token;
} elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $token) and !preg_match('/\d/', $this->lookAhead) and ($this->lookAhead != ':') and ($this->lookAhead != '.')) { } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $token) and !preg_match('/\d/', $this->lookAhead) and ($this->lookAhead !== ':') and ($this->lookAhead !== '.')) {
// If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1) // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
return $token; return $token;
} elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $token) and !preg_match('/\d/', $this->lookAhead) and ($this->lookAhead != ':') and ($this->lookAhead != '.')) { } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $token) and !preg_match('/\d/', $this->lookAhead) and ($this->lookAhead !== ':') and ($this->lookAhead !== '.')) {
// If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1) // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
return $token; return $token;
} elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead)) { } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead)) {
@ -1044,19 +1044,19 @@ class Parser
} elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $token) and !preg_match('/\d/', $this->lookAhead)) { } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $token) and !preg_match('/\d/', $this->lookAhead)) {
// If it's an external range like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2 // If it's an external range like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
return $token; return $token;
} elseif (is_numeric($token) and (!is_numeric($token . $this->lookAhead) or ($this->lookAhead == '')) and ($this->lookAhead != '!') and ($this->lookAhead != ':')) { } elseif (is_numeric($token) and (!is_numeric($token . $this->lookAhead) or ($this->lookAhead == '')) and ($this->lookAhead !== '!') and ($this->lookAhead !== ':')) {
// If it's a number (check that it's not a sheet name or range) // If it's a number (check that it's not a sheet name or range)
return $token; return $token;
} elseif (preg_match('/"([^"]|""){0,255}"/', $token) and $this->lookAhead != '"' and (substr_count($token, '"') % 2 == 0)) { } elseif (preg_match('/"([^"]|""){0,255}"/', $token) and $this->lookAhead !== '"' and (substr_count($token, '"') % 2 == 0)) {
// If it's a string (of maximum 255 characters) // If it's a string (of maximum 255 characters)
return $token; return $token;
} elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) or $token == '#N/A') { } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) or $token === '#N/A') {
// If it's an error code // If it's an error code
return $token; return $token;
} elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $token) and ($this->lookAhead == '(')) { } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $token) and ($this->lookAhead === '(')) {
// if it's a function call // if it's a function call
return $token; return $token;
} elseif (substr($token, -1) == ')') { } elseif (substr($token, -1) === ')') {
// It's an argument of some description (e.g. a named range), // It's an argument of some description (e.g. a named range),
// precise nature yet to be determined // precise nature yet to be determined
return $token; return $token;
@ -1078,8 +1078,7 @@ class Parser
{ {
$this->currentCharacter = 0; $this->currentCharacter = 0;
$this->formula = $formula; $this->formula = $formula;
$this->lookAhead = isset($formula[1]) ? $formula[1] $this->lookAhead = isset($formula[1]) ? $formula[1] : '';
: '';
$this->advance(); $this->advance();
$this->parseTree = $this->condition(); $this->parseTree = $this->condition();
@ -1248,10 +1247,10 @@ class Parser
*/ */
private function fact() private function fact()
{ {
if ($this->currentToken == '(') { if ($this->currentToken === '(') {
$this->advance(); // eat the "(" $this->advance(); // eat the "("
$result = $this->parenthesizedExpression(); $result = $this->parenthesizedExpression();
if ($this->currentToken != ')') { if ($this->currentToken !== ')') {
throw new WriterException("')' token expected."); throw new WriterException("')' token expected.");
} }
$this->advance(); // eat the ")" $this->advance(); // eat the ")"
@ -1299,7 +1298,7 @@ class Parser
return $result; return $result;
} elseif (is_numeric($this->currentToken)) { } elseif (is_numeric($this->currentToken)) {
// If it's a number or a percent // If it's a number or a percent
if ($this->lookAhead == '%') { if ($this->lookAhead === '%') {
$result = $this->createTree('ptgPercent', $this->currentToken, ''); $result = $this->createTree('ptgPercent', $this->currentToken, '');
$this->advance(); // Skip the percentage operator once we've pre-built that tree $this->advance(); // Skip the percentage operator once we've pre-built that tree
} else { } else {
@ -1331,9 +1330,9 @@ class Parser
$result = ''; // initialize result $result = ''; // initialize result
$this->advance(); $this->advance();
$this->advance(); // eat the "(" $this->advance(); // eat the "("
while ($this->currentToken != ')') { while ($this->currentToken !== ')') {
if ($num_args > 0) { if ($num_args > 0) {
if ($this->currentToken == ',' || $this->currentToken == ';') { if ($this->currentToken === ',' || $this->currentToken === ';') {
$this->advance(); // eat the "," or ";" $this->advance(); // eat the "," or ";"
} else { } else {
throw new WriterException("Syntax error: comma expected in function $function, arg #{$num_args}"); throw new WriterException("Syntax error: comma expected in function $function, arg #{$num_args}");

View File

@ -267,9 +267,7 @@ class Workbook extends BIFFwriter
$this->xfWriters[] = $xfWriter; $this->xfWriters[] = $xfWriter;
$xfIndex = count($this->xfWriters) - 1; return count($this->xfWriters) - 1;
return $xfIndex;
} }
/** /**
@ -319,7 +317,7 @@ class Workbook extends BIFFwriter
if ($colorIndex) { if ($colorIndex) {
$this->colors[$rgb] = $colorIndex; $this->colors[$rgb] = $colorIndex;
} else { } else {
if (count($this->colors) == 0) { if (count($this->colors) === 0) {
$lastColor = 7; $lastColor = 7;
} else { } else {
$lastColor = end($this->colors); $lastColor = end($this->colors);
@ -437,7 +435,7 @@ class Workbook extends BIFFwriter
// Prepare part 3 of the workbook global stream, what goes after the SHEET records // Prepare part 3 of the workbook global stream, what goes after the SHEET records
$part3 = ''; $part3 = '';
if ($this->countryCode != -1) { if ($this->countryCode !== -1) {
$part3 .= $this->writeCountry(); $part3 .= $this->writeCountry();
} }
$part3 .= $this->writeRecalcId(); $part3 .= $this->writeRecalcId();
@ -918,7 +916,7 @@ class Workbook extends BIFFwriter
$record = 0x0022; // Record identifier $record = 0x0022; // Record identifier
$length = 0x0002; // Bytes to follow $length = 0x0002; // Bytes to follow
$f1904 = (Date::getExcelCalendar() == Date::CALENDAR_MAC_1904) $f1904 = (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904)
? 1 ? 1
: 0; // Flag for 1904 date system : 0; // Flag for 1904 date system

View File

@ -328,6 +328,17 @@ class Xlsx extends BaseWriter
$zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); $zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
} }
// Add unparsed drawings
if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) {
foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) {
$drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']);
if ($drawingFile !== false) {
$drawingFile = ltrim($drawingFile, '.');
$zip->addFromString('xl' . $drawingFile, $drawingXml);
}
}
}
// Add comment relationship parts // Add comment relationship parts
if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) {
// VML Comments // VML Comments
@ -338,8 +349,8 @@ class Xlsx extends BaseWriter
} }
// Add unparsed relationship parts // Add unparsed relationship parts
if (isset($unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'])) { if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) {
foreach ($unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'] as $vmlDrawing) { foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) {
$zip->addFromString($vmlDrawing['filePath'], $vmlDrawing['content']); $zip->addFromString($vmlDrawing['filePath'], $vmlDrawing['content']);
} }
} }

View File

@ -93,7 +93,7 @@ class Workbook extends WriterPart
{ {
$objWriter->startElement('workbookPr'); $objWriter->startElement('workbookPr');
if (Date::getExcelCalendar() == Date::CALENDAR_MAC_1904) { if (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) {
$objWriter->writeAttribute('date1904', '1'); $objWriter->writeAttribute('date1904', '1');
} }
@ -225,7 +225,7 @@ class Workbook extends WriterPart
$objWriter->startElement('sheet'); $objWriter->startElement('sheet');
$objWriter->writeAttribute('name', $pSheetname); $objWriter->writeAttribute('name', $pSheetname);
$objWriter->writeAttribute('sheetId', $pSheetId); $objWriter->writeAttribute('sheetId', $pSheetId);
if ($sheetState != 'visible' && $sheetState != '') { if ($sheetState !== 'visible' && $sheetState != '') {
$objWriter->writeAttribute('state', $sheetState); $objWriter->writeAttribute('state', $sheetState);
} }
$objWriter->writeAttribute('r:id', 'rId' . $pRelId); $objWriter->writeAttribute('r:id', 'rId' . $pRelId);

View File

@ -141,7 +141,7 @@ class Worksheet extends WriterPart
$objWriter->startElement('sheetPr'); $objWriter->startElement('sheetPr');
if ($pSheet->getParent()->hasMacros()) { if ($pSheet->getParent()->hasMacros()) {
//if the workbook have macros, we need to have codeName for the sheet //if the workbook have macros, we need to have codeName for the sheet
if ($pSheet->hasCodeName() == false) { if (!$pSheet->hasCodeName()) {
$pSheet->setCodeName($pSheet->getTitle()); $pSheet->setCodeName($pSheet->getTitle());
} }
$objWriter->writeAttribute('codeName', $pSheet->getCodeName()); $objWriter->writeAttribute('codeName', $pSheet->getCodeName());
@ -322,7 +322,7 @@ class Worksheet extends WriterPart
} }
// Set Zero Height row // Set Zero Height row
if ((string) $pSheet->getDefaultRowDimension()->getZeroHeight() == '1' || if ((string) $pSheet->getDefaultRowDimension()->getZeroHeight() === '1' ||
strtolower((string) $pSheet->getDefaultRowDimension()->getZeroHeight()) == 'true') { strtolower((string) $pSheet->getDefaultRowDimension()->getZeroHeight()) == 'true') {
$objWriter->writeAttribute('zeroHeight', '1'); $objWriter->writeAttribute('zeroHeight', '1');
} }
@ -383,7 +383,7 @@ class Worksheet extends WriterPart
} }
// Column visibility // Column visibility
if ($colDimension->getVisible() == false) { if ($colDimension->getVisible() === false) {
$objWriter->writeAttribute('hidden', 'true'); $objWriter->writeAttribute('hidden', 'true');
} }
@ -398,7 +398,7 @@ class Worksheet extends WriterPart
} }
// Collapsed // Collapsed
if ($colDimension->getCollapsed() == true) { if ($colDimension->getCollapsed() === true) {
$objWriter->writeAttribute('collapsed', 'true'); $objWriter->writeAttribute('collapsed', 'true');
} }
@ -428,7 +428,7 @@ class Worksheet extends WriterPart
// sheetProtection // sheetProtection
$objWriter->startElement('sheetProtection'); $objWriter->startElement('sheetProtection');
if ($pSheet->getProtection()->getPassword() != '') { if ($pSheet->getProtection()->getPassword() !== '') {
$objWriter->writeAttribute('password', $pSheet->getProtection()->getPassword()); $objWriter->writeAttribute('password', $pSheet->getProtection()->getPassword());
} }
@ -519,6 +519,9 @@ class Worksheet extends WriterPart
// Formula // Formula
$objWriter->writeElement('formula', $formula); $objWriter->writeElement('formula', $formula);
} }
} elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) {
// formula copied from ms xlsx xml source file
$objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0');
} }
$objWriter->endElement(); $objWriter->endElement();
@ -624,8 +627,9 @@ class Worksheet extends WriterPart
$objWriter->writeAttribute('location', str_replace('sheet://', '', $hyperlink->getUrl())); $objWriter->writeAttribute('location', str_replace('sheet://', '', $hyperlink->getUrl()));
} }
if ($hyperlink->getTooltip() != '') { if ($hyperlink->getTooltip() !== '') {
$objWriter->writeAttribute('tooltip', $hyperlink->getTooltip()); $objWriter->writeAttribute('tooltip', $hyperlink->getTooltip());
$objWriter->writeAttribute('display', $hyperlink->getTooltip());
} }
$objWriter->endElement(); $objWriter->endElement();
@ -991,12 +995,12 @@ class Worksheet extends WriterPart
} }
// Row visibility // Row visibility
if ($rowDimension->getVisible() == false) { if (!$rowDimension->getVisible() === true) {
$objWriter->writeAttribute('hidden', 'true'); $objWriter->writeAttribute('hidden', 'true');
} }
// Collapsed // Collapsed
if ($rowDimension->getCollapsed() == true) { if ($rowDimension->getCollapsed() === true) {
$objWriter->writeAttribute('collapsed', 'true'); $objWriter->writeAttribute('collapsed', 'true');
} }
@ -1101,7 +1105,7 @@ class Worksheet extends WriterPart
break; break;
case 'f': // Formula case 'f': // Formula
$attributes = $pCell->getFormulaAttributes(); $attributes = $pCell->getFormulaAttributes();
if ($attributes['t'] == 'array') { if ($attributes['t'] === 'array') {
$objWriter->startElement('f'); $objWriter->startElement('f');
$objWriter->writeAttribute('t', 'array'); $objWriter->writeAttribute('t', 'array');
$objWriter->writeAttribute('ref', $pCellAddress); $objWriter->writeAttribute('ref', $pCellAddress);
@ -1114,7 +1118,7 @@ class Worksheet extends WriterPart
} }
if ($this->getParentWriter()->getOffice2003Compatibility() === false) { if ($this->getParentWriter()->getOffice2003Compatibility() === false) {
if ($this->getParentWriter()->getPreCalculateFormulas()) { if ($this->getParentWriter()->getPreCalculateFormulas()) {
if (!is_array($calculatedValue) && substr($calculatedValue, 0, 1) != '#') { if (!is_array($calculatedValue) && substr($calculatedValue, 0, 1) !== '#') {
$objWriter->writeElement('v', StringHelper::formatNumber($calculatedValue)); $objWriter->writeElement('v', StringHelper::formatNumber($calculatedValue));
} else { } else {
$objWriter->writeElement('v', '0'); $objWriter->writeElement('v', '0');
@ -1135,7 +1139,7 @@ class Worksheet extends WriterPart
break; break;
case 'e': // Error case 'e': // Error
if (substr($cellValue, 0, 1) == '=') { if (substr($cellValue, 0, 1) === '=') {
$objWriter->writeElement('f', substr($cellValue, 1)); $objWriter->writeElement('f', substr($cellValue, 1));
$objWriter->writeElement('v', substr($cellValue, 1)); $objWriter->writeElement('v', substr($cellValue, 1));
} else { } else {