Feature: Labelprint für Kistenetiketten hinzugefügt
This commit is contained in:
35
vendor/phpoffice/phpspreadsheet/CHANGELOG.md
vendored
35
vendor/phpoffice/phpspreadsheet/CHANGELOG.md
vendored
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org). Thia is always true of the master branch. Some earlier branches remain supported and security fixes are applied to them; if the security fix represents a breaking change, it may have to be applied as a minor or patch version.
|
||||
|
||||
## TBD - 5.2.0
|
||||
## TBD - 5.3.0
|
||||
|
||||
### Added
|
||||
|
||||
@@ -31,6 +31,39 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a
|
||||
|
||||
- Nothing yet.
|
||||
|
||||
## 2025-10-25 - 5.2.0
|
||||
|
||||
### Added
|
||||
|
||||
- This release should be usable with Php8.5 Release Candidates without deprecation messages.
|
||||
- Option to display numbers with less precision. [Issue #4626](https://github.com/PHPOffice/PhpSpreadsheet/issues/4626) [PR #4640](https://github.com/PHPOffice/PhpSpreadsheet/pull/4640)
|
||||
- Offer Tcpdf Interface which throws exception rather than die. [PR #4666](https://github.com/PHPOffice/PhpSpreadsheet/pull/4666)
|
||||
- Xls Reader ListWorksheetDimensions method. [PR #4689](https://github.com/PHPOffice/PhpSpreadsheet/pull/4689)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Worksheet::getHashInt serves no useful purpose. No replacement.
|
||||
- Spreadsheet::getId serves no useful purpose. No replacement.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Performance improvement when working with large amounts of cells. [Issue #4607](https://github.com/PHPOffice/PhpSpreadsheet/issues/4607) [PR #4609](https://github.com/PHPOffice/PhpSpreadsheet/pull/4609)
|
||||
- Minor improvements to Calculation coverage. [PR #4624](https://github.com/PHPOffice/PhpSpreadsheet/pull/4624)
|
||||
- Conditional formatting in extLst. [Issue #4629](https://github.com/PHPOffice/PhpSpreadsheet/issues/4629) [PR #4633](https://github.com/PHPOffice/PhpSpreadsheet/pull/4633)
|
||||
- Php8.5 deprecates use of null as array index. [PR #4634](https://github.com/PHPOffice/PhpSpreadsheet/pull/4634)
|
||||
- Wrapped cells and default row height. [Issue #4584](https://github.com/PHPOffice/PhpSpreadsheet/issues/4584) [PR #4645](https://github.com/PHPOffice/PhpSpreadsheet/pull/4645)
|
||||
- For Php8.5, replace one of our two uses of `__wakeup` with `__unserialize`, and eliminate the other. [PR #4639](https://github.com/PHPOffice/PhpSpreadsheet/pull/4639)
|
||||
- Use prefix _xlfn for BASE function. [Issue #4638](https://github.com/PHPOffice/PhpSpreadsheet/issues/4638) [PR #4641](https://github.com/PHPOffice/PhpSpreadsheet/pull/4641)
|
||||
- Warning messages with corrupt Xls file. [Issue #4647](https://github.com/PHPOffice/PhpSpreadsheet/issues/4647) [PR #4648](https://github.com/PHPOffice/PhpSpreadsheet/pull/4648)
|
||||
- Additional support for union and intersection. [PR #4596](https://github.com/PHPOffice/PhpSpreadsheet/pull/4596)
|
||||
- Missing array keys x,o,v for Xml Reader. [Issue #4668](https://github.com/PHPOffice/PhpSpreadsheet/issues/4668) [PR #4669](https://github.com/PHPOffice/PhpSpreadsheet/pull/4669)
|
||||
- Missing array key x for Xlsx Reader VML. [Issue #4505](https://github.com/PHPOffice/PhpSpreadsheet/issues/4505) [PR #4676](https://github.com/PHPOffice/PhpSpreadsheet/pull/4676)
|
||||
- Better support for Style Alignment Read Order. [Issue #850](https://github.com/PHPOffice/PhpSpreadsheet/issues/850) [PR #4655](https://github.com/PHPOffice/PhpSpreadsheet/pull/4655)
|
||||
- More sophisticated workbook password algorithms (Xlsx only). [Issue #4673](https://github.com/PHPOffice/PhpSpreadsheet/issues/4673) [PR #4675](https://github.com/PHPOffice/PhpSpreadsheet/pull/4675)
|
||||
- Xls Writer fix DIMENSIONS record. [Issue #4682](https://github.com/PHPOffice/PhpSpreadsheet/issues/4682) [PR #4687](https://github.com/PHPOffice/PhpSpreadsheet/pull/4687)
|
||||
- Xls Reader listWorksheetInfo process MULRK records. [PR #4689](https://github.com/PHPOffice/PhpSpreadsheet/pull/4689)
|
||||
- Make Reader Xls Escher generic. [PR #4690](https://github.com/PHPOffice/PhpSpreadsheet/pull/4690)
|
||||
|
||||
## 2025-09-03 - 5.1.0
|
||||
|
||||
### Added
|
||||
|
||||
@@ -92,18 +92,18 @@
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||
"dompdf/dompdf": "^2.0 || ^3.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"mitoteam/jpgraph": "^10.3",
|
||||
"mitoteam/jpgraph": "^10.5",
|
||||
"mpdf/mpdf": "^8.1.1",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpstan/phpstan": "^1.1 || ^2.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0 || ^2.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0 || ^2.0",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"tecnickcom/tcpdf": "^6.5"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "PHP Internationalization Functions",
|
||||
"ext-intl": "PHP Internationalization Functions, regquired for NumberFormat Wizard",
|
||||
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
|
||||
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
|
||||
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer",
|
||||
|
||||
@@ -888,7 +888,7 @@ class Calculation extends CalculationLocale
|
||||
}
|
||||
}
|
||||
if ($matrix1Rows < $matrix2Rows) {
|
||||
$x = ($matrix1Rows === 1) ? $matrix1[0] : array_fill(0, $matrix1Columns, null);
|
||||
$x = ($matrix1Rows === 1) ? $matrix1[0] : array_fill(0, $matrix2Columns, null);
|
||||
for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
|
||||
$matrix1[$i] = $x;
|
||||
}
|
||||
@@ -1095,6 +1095,10 @@ class Calculation extends CalculationLocale
|
||||
$this->branchPruner->initialiseForLoop();
|
||||
|
||||
$opCharacter = $formula[$index]; // Get the first character of the value at the current index position
|
||||
if ($opCharacter === "\xe2") { // intersection or union
|
||||
$opCharacter .= $formula[++$index];
|
||||
$opCharacter .= $formula[++$index];
|
||||
}
|
||||
|
||||
// Check for two-character operators (e.g. >=, <=, <>)
|
||||
if ((isset(self::COMPARISON_OPERATORS[$opCharacter])) && (strlen($formula) > $index) && isset($formula[$index + 1], self::COMPARISON_OPERATORS[$formula[$index + 1]])) {
|
||||
@@ -1115,7 +1119,7 @@ class Calculation extends CalculationLocale
|
||||
++$index;
|
||||
} elseif ($opCharacter === '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded?
|
||||
++$index; // Drop the redundant plus symbol
|
||||
} elseif ((($opCharacter === '~') || ($opCharacter === '∩') || ($opCharacter === '∪')) && (!$isOperandOrFunction)) {
|
||||
} elseif ((($opCharacter === '~') /*|| ($opCharacter === '∩') || ($opCharacter === '∪')*/) && (!$isOperandOrFunction)) {
|
||||
// We have to explicitly deny a tilde, union or intersect because they are legal
|
||||
return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
|
||||
} elseif ((isset(self::CALCULATION_OPERATORS[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
|
||||
@@ -1233,6 +1237,15 @@ class Calculation extends CalculationLocale
|
||||
// MS Excel allows this if the content is cell references; but doesn't allow actual values,
|
||||
// but at this point, we can't differentiate (so allow both)
|
||||
return $this->raiseFormulaError('Formula Error: Unexpected ,');
|
||||
/* The following code may be a better choice, but, with
|
||||
the other changes for this PR, I can no longer come up
|
||||
with a test case that gets here
|
||||
$stack->push('Binary Operator', '∪');
|
||||
|
||||
++$index;
|
||||
$expectingOperator = false;
|
||||
|
||||
continue;*/
|
||||
}
|
||||
|
||||
/** @var array<string, int> $d */
|
||||
@@ -1699,7 +1712,7 @@ class Calculation extends CalculationLocale
|
||||
return $this->raiseFormulaError($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
} elseif (!is_numeric($token) && !is_object($token) && isset(self::BINARY_OPERATORS[$token])) {
|
||||
} elseif (!is_numeric($token) && !is_object($token) && isset($token, self::BINARY_OPERATORS[$token])) { //* @phpstan-ignore-line
|
||||
// if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
|
||||
// We must have two operands, error if we don't
|
||||
$operand2Data = $stack->pop();
|
||||
@@ -1927,6 +1940,14 @@ class Calculation extends CalculationLocale
|
||||
$stack->push('Value', $cellIntersect, $cellRef);
|
||||
}
|
||||
|
||||
break;
|
||||
case '∪': // union
|
||||
/** @var mixed[][] $operand1 */
|
||||
/** @var mixed[][] $operand2 */
|
||||
$cellUnion = array_merge($operand1, $operand2);
|
||||
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellUnion));
|
||||
$stack->push('Value', $cellUnion, 'A1');
|
||||
|
||||
break;
|
||||
}
|
||||
} elseif (($token === '~') || ($token === '%')) {
|
||||
@@ -2792,6 +2813,14 @@ class Calculation extends CalculationLocale
|
||||
|
||||
$definedNameValue = $namedRange->getValue();
|
||||
$definedNameType = $namedRange->isFormula() ? 'Formula' : 'Range';
|
||||
if ($definedNameType === 'Range') {
|
||||
if (preg_match('/^(.*!)?(.*)$/', $definedNameValue, $matches) === 1) {
|
||||
$matches2 = trim($matches[2]);
|
||||
$matches2 = preg_replace('/ +/', ' ∩ ', $matches2) ?? $matches2;
|
||||
$matches2 = preg_replace('/,/', ' ∪ ', $matches2) ?? $matches2;
|
||||
$definedNameValue = $matches[1] . $matches2;
|
||||
}
|
||||
}
|
||||
$definedNameWorksheet = $namedRange->getWorksheet();
|
||||
|
||||
if ($definedNameValue[0] !== '=') {
|
||||
@@ -2879,6 +2908,7 @@ class Calculation extends CalculationLocale
|
||||
if ($stack->count() > 0) {
|
||||
$o2 = $stack->last();
|
||||
if ($o2) {
|
||||
/** @var array{value: string} $o2 */
|
||||
if (isset(self::CALCULATION_OPERATORS[$o2['value']])) {
|
||||
$retVal = (self::OPERATOR_PRECEDENCE[$opCharacter] ?? 0) <= self::OPERATOR_PRECEDENCE[$o2['value']];
|
||||
}
|
||||
|
||||
@@ -63,6 +63,11 @@ class CalculationLocale extends CalculationBase
|
||||
$filename = substr($filename, strlen($localeFileDirectory));
|
||||
if ($filename != 'en') {
|
||||
self::$validLocaleLanguages[] = $filename;
|
||||
$subdirs = glob("$localeFileDirectory$filename/*", GLOB_ONLYDIR) ?: [];
|
||||
foreach ($subdirs as $subdir) {
|
||||
$subdirx = basename($subdir);
|
||||
self::$validLocaleLanguages[] = "{$filename}_{$subdirx}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,9 +117,9 @@ abstract class DatabaseAbstract
|
||||
/** @var mixed[] $row */
|
||||
foreach ($database as $rowKey => $row) {
|
||||
$keys = array_keys($row);
|
||||
$key = $keys[$field] ?? null;
|
||||
$key = ($field === null) ? null : ($keys[$field] ?? null);
|
||||
$columnKey = $key ?? 'A';
|
||||
$columnData[$rowKey][$columnKey] = $row[$key] ?? $defaultReturnColumnValue;
|
||||
$columnData[$rowKey][$columnKey] = ($key === null) ? $defaultReturnColumnValue : ($row[$key] ?? $defaultReturnColumnValue);
|
||||
}
|
||||
|
||||
return $columnData;
|
||||
|
||||
@@ -102,7 +102,7 @@ class ArrayArgumentProcessor
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $matrixIndexes
|
||||
* @param array<int|string> $matrixIndexes
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
|
||||
@@ -78,7 +78,7 @@ class BranchPruner
|
||||
|
||||
private function initialiseCondition(): void
|
||||
{
|
||||
if (isset($this->conditionMap[$this->pendingStoreKey]) && $this->conditionMap[$this->pendingStoreKey]) {
|
||||
if (isset($this->pendingStoreKey, $this->conditionMap[$this->pendingStoreKey]) && $this->conditionMap[$this->pendingStoreKey]) {
|
||||
$this->currentCondition = $this->pendingStoreKey;
|
||||
$stackDepth = count($this->storeKeysStack);
|
||||
if ($stackDepth > 1) {
|
||||
@@ -90,7 +90,7 @@ class BranchPruner
|
||||
|
||||
private function initialiseThen(): void
|
||||
{
|
||||
if (isset($this->thenMap[$this->pendingStoreKey]) && $this->thenMap[$this->pendingStoreKey]) {
|
||||
if (isset($this->pendingStoreKey, $this->thenMap[$this->pendingStoreKey]) && $this->thenMap[$this->pendingStoreKey]) {
|
||||
$this->currentOnlyIf = $this->pendingStoreKey;
|
||||
} elseif (
|
||||
isset($this->previousStoreKey, $this->thenMap[$this->previousStoreKey])
|
||||
@@ -102,7 +102,7 @@ class BranchPruner
|
||||
|
||||
private function initialiseElse(): void
|
||||
{
|
||||
if (isset($this->elseMap[$this->pendingStoreKey]) && $this->elseMap[$this->pendingStoreKey]) {
|
||||
if (isset($this->pendingStoreKey, $this->elseMap[$this->pendingStoreKey]) && $this->elseMap[$this->pendingStoreKey]) {
|
||||
$this->currentOnlyIfNot = $this->pendingStoreKey;
|
||||
} elseif (
|
||||
isset($this->previousStoreKey, $this->elseMap[$this->previousStoreKey])
|
||||
|
||||
@@ -21,8 +21,10 @@ class CyclicReferenceStack
|
||||
|
||||
/**
|
||||
* Push a new entry onto the stack.
|
||||
*
|
||||
* @param int|string $value The value to test
|
||||
*/
|
||||
public function push(mixed $value): void
|
||||
public function push($value): void
|
||||
{
|
||||
$this->stack[$value] = $value;
|
||||
}
|
||||
@@ -38,9 +40,9 @@ class CyclicReferenceStack
|
||||
/**
|
||||
* Test to see if a specified entry exists on the stack.
|
||||
*
|
||||
* @param mixed $value The value to test
|
||||
* @param int|string $value The value to test
|
||||
*/
|
||||
public function onStack(mixed $value): bool
|
||||
public function onStack($value): bool
|
||||
{
|
||||
return isset($this->stack[$value]);
|
||||
}
|
||||
|
||||
@@ -135,14 +135,14 @@ class Matrix
|
||||
return self::extractRowValue($matrix, $rowKeys, $rowNum);
|
||||
}
|
||||
|
||||
$columnNum = $columnKeys[--$columnNum];
|
||||
$columnNum = $columnKeys[--$columnNum]; //* @phpstan-ignore-line
|
||||
if ($rowNum === 0) {
|
||||
return array_map(
|
||||
fn ($value): array => [$value],
|
||||
array_column($matrix, $columnNum)
|
||||
);
|
||||
}
|
||||
$rowNum = $rowKeys[--$rowNum];
|
||||
$rowNum = $rowKeys[--$rowNum]; //* @phpstan-ignore-line
|
||||
/** @var mixed[][] $matrix */
|
||||
|
||||
return $matrix[$rowNum][$columnNum];
|
||||
@@ -159,7 +159,7 @@ class Matrix
|
||||
}
|
||||
|
||||
$rowNum = $rowKeys[--$rowNum];
|
||||
$row = $matrix[$rowNum];
|
||||
$row = $matrix[$rowNum]; //* @phpstan-ignore-line
|
||||
if (is_array($row)) {
|
||||
return [$rowNum => $row];
|
||||
}
|
||||
|
||||
@@ -335,6 +335,7 @@ class Sort extends LookupRefValidations
|
||||
// Building a new array in the correct (sorted) order works; but may be memory heavy for larger arrays
|
||||
$sortedArray = [];
|
||||
foreach ($sortVector as $index) {
|
||||
/** @var int|string $index */
|
||||
$sortedArray[] = $sortArray[$index];
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class Arabic
|
||||
/**
|
||||
* Recursively calculate the arabic value of a roman numeral.
|
||||
*
|
||||
* @param mixed[] $roman
|
||||
* @param string[] $roman
|
||||
*/
|
||||
private static function calculateArabic(array $roman, int &$sum = 0, int $subtract = 0): int
|
||||
{
|
||||
|
||||
@@ -77,11 +77,13 @@ class Round
|
||||
);
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($number < 0.0) {
|
||||
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
|
||||
}
|
||||
|
||||
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,11 +123,13 @@ class Round
|
||||
);
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($number < 0.0) {
|
||||
return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
|
||||
}
|
||||
|
||||
return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -163,7 +163,7 @@ class Averages extends AggregateBase
|
||||
sort($aArgs, SORT_NUMERIC);
|
||||
$valueCount = $valueCount / 2;
|
||||
if ($valueCount == floor($valueCount)) {
|
||||
$returnValue = ($aArgs[$valueCount--] + $aArgs[$valueCount]) / 2;
|
||||
$returnValue = ($aArgs[$valueCount--] + $aArgs[$valueCount]) / 2; //* @phpstan-ignore-line
|
||||
} else {
|
||||
$valueCount = (int) floor($valueCount);
|
||||
$returnValue = $aArgs[$valueCount];
|
||||
@@ -243,7 +243,7 @@ class Averages extends AggregateBase
|
||||
$maxfreqkey = $key;
|
||||
$maxfreqdatum = $datum;
|
||||
} elseif ($freq == $maxfreq) {
|
||||
if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) {
|
||||
if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) { //* @phpstan-ignore-line
|
||||
$maxfreqkey = $key;
|
||||
$maxfreqdatum = $datum;
|
||||
}
|
||||
|
||||
@@ -453,6 +453,7 @@ class Cell implements Stringable
|
||||
}
|
||||
$newColumn = $this->getColumn();
|
||||
if (is_array($result)) {
|
||||
$result = self::convertSpecialArray($result);
|
||||
$this->formulaAttributes['t'] = 'array';
|
||||
$this->formulaAttributes['ref'] = $maxCoordinate = $coordinate;
|
||||
$newRow = $row = $this->getRow();
|
||||
@@ -581,6 +582,36 @@ class Cell implements Stringable
|
||||
return $this->convertDateTimeInt($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert array like the following (preserve values, lose indexes):
|
||||
* [
|
||||
* rowNumber1 => [colLetter1 => value, colLetter2 => value ...],
|
||||
* rowNumber2 => [colLetter1 => value, colLetter2 => value ...],
|
||||
* ...
|
||||
* ].
|
||||
*
|
||||
* @param mixed[] $array
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
private static function convertSpecialArray(array $array): array
|
||||
{
|
||||
$newArray = [];
|
||||
foreach ($array as $rowIndex => $row) {
|
||||
if (!is_int($rowIndex) || $rowIndex <= 0 || !is_array($row)) {
|
||||
return $array;
|
||||
}
|
||||
$keys = array_keys($row);
|
||||
$key0 = $keys[0] ?? '';
|
||||
if (!is_string($key0)) {
|
||||
return $array;
|
||||
}
|
||||
$newArray[] = array_values($row);
|
||||
}
|
||||
|
||||
return $newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set old calculated value (cached).
|
||||
*
|
||||
|
||||
@@ -333,16 +333,12 @@ class DataSeriesValues extends Properties
|
||||
* Method for validating hex color.
|
||||
*
|
||||
* @param string $color value for color
|
||||
*
|
||||
* @return bool true if validation was successful
|
||||
*/
|
||||
private function validateColor(string $color): bool
|
||||
private function validateColor(string $color): void
|
||||
{
|
||||
if (!preg_match('/^[a-f0-9]{6}$/i', $color)) {
|
||||
throw new Exception(sprintf('Invalid hex color for chart series (color: "%s")', $color));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,6 +43,25 @@ class Cells
|
||||
*/
|
||||
private array $index = [];
|
||||
|
||||
/**
|
||||
* Flag to avoid sorting the index every time.
|
||||
*/
|
||||
private bool $indexSorted = false;
|
||||
|
||||
/**
|
||||
* Index keys cache to avoid recalculating on large arrays.
|
||||
*
|
||||
* @var null|string[]
|
||||
*/
|
||||
private ?array $indexKeysCache = null;
|
||||
|
||||
/**
|
||||
* Index values cache to avoid recalculating on large arrays.
|
||||
*
|
||||
* @var null|int[]
|
||||
*/
|
||||
private ?array $indexValuesCache = null;
|
||||
|
||||
/**
|
||||
* Prefix used to uniquely identify cache data for this worksheet.
|
||||
*/
|
||||
@@ -112,6 +131,10 @@ class Cells
|
||||
|
||||
unset($this->index[$cellCoordinate]);
|
||||
|
||||
// Clear index caches
|
||||
$this->indexKeysCache = null;
|
||||
$this->indexValuesCache = null;
|
||||
|
||||
// Delete the entry from cache
|
||||
$this->cache->delete($this->cachePrefix . $cellCoordinate);
|
||||
}
|
||||
@@ -123,7 +146,12 @@ class Cells
|
||||
*/
|
||||
public function getCoordinates(): array
|
||||
{
|
||||
return array_keys($this->index);
|
||||
// Build or rebuild index keys cache
|
||||
if ($this->indexKeysCache === null) {
|
||||
$this->indexKeysCache = array_keys($this->index);
|
||||
}
|
||||
|
||||
return $this->indexKeysCache;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,9 +161,21 @@ class Cells
|
||||
*/
|
||||
public function getSortedCoordinates(): array
|
||||
{
|
||||
asort($this->index);
|
||||
// Sort only when required
|
||||
if (!$this->indexSorted) {
|
||||
asort($this->index);
|
||||
$this->indexSorted = true;
|
||||
// Clear unsorted cache
|
||||
$this->indexKeysCache = null;
|
||||
$this->indexValuesCache = null;
|
||||
}
|
||||
|
||||
return array_keys($this->index);
|
||||
// Build or rebuild index keys cache
|
||||
if ($this->indexKeysCache === null) {
|
||||
$this->indexKeysCache = array_keys($this->index);
|
||||
}
|
||||
|
||||
return $this->indexKeysCache;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,9 +185,19 @@ class Cells
|
||||
*/
|
||||
public function getSortedCoordinatesInt(): array
|
||||
{
|
||||
asort($this->index);
|
||||
if (!$this->indexSorted) {
|
||||
asort($this->index);
|
||||
$this->indexSorted = true;
|
||||
// Clear unsorted cache
|
||||
$this->indexKeysCache = null;
|
||||
$this->indexValuesCache = null;
|
||||
}
|
||||
|
||||
return array_values($this->index);
|
||||
if ($this->indexValuesCache === null) {
|
||||
$this->indexValuesCache = array_values($this->index);
|
||||
}
|
||||
|
||||
return $this->indexValuesCache;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,6 +350,11 @@ class Cells
|
||||
}
|
||||
}
|
||||
|
||||
// Clear index sorted flag and index caches
|
||||
$newCollection->indexSorted = false;
|
||||
$newCollection->indexKeysCache = null;
|
||||
$newCollection->indexValuesCache = null;
|
||||
|
||||
return $newCollection;
|
||||
}
|
||||
|
||||
@@ -390,6 +445,11 @@ class Cells
|
||||
/** @var int $row */
|
||||
$this->index[$cellCoordinate] = (--$row * self::MAX_COLUMN_ID) + Coordinate::columnIndexFromString((string) $column);
|
||||
|
||||
// Clear index sorted flag and index caches
|
||||
$this->indexSorted = false;
|
||||
$this->indexKeysCache = null;
|
||||
$this->indexValuesCache = null;
|
||||
|
||||
$this->currentCoordinate = $cellCoordinate;
|
||||
$this->currentCell = $cell;
|
||||
$this->currentCellIsDirty = true;
|
||||
@@ -444,6 +504,11 @@ class Cells
|
||||
|
||||
$this->index = [];
|
||||
|
||||
// Clear index sorted flag and index caches
|
||||
$this->indexSorted = false;
|
||||
$this->indexKeysCache = null;
|
||||
$this->indexValuesCache = null;
|
||||
|
||||
// detach ourself from the worksheet, so that it can then delete this object successfully
|
||||
$this->parent = null;
|
||||
}
|
||||
|
||||
@@ -31,12 +31,21 @@ class Security
|
||||
*/
|
||||
private string $workbookPassword = '';
|
||||
|
||||
/**
|
||||
* Create a new Document Security instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
private string $workbookAlgorithmName = '';
|
||||
|
||||
private string $workbookHashValue = '';
|
||||
|
||||
private string $workbookSaltValue = '';
|
||||
|
||||
private int $workbookSpinCount = 0;
|
||||
|
||||
private string $revisionsAlgorithmName = '';
|
||||
|
||||
private string $revisionsHashValue = '';
|
||||
|
||||
private string $revisionsSaltValue = '';
|
||||
|
||||
private int $revisionsSpinCount = 0;
|
||||
|
||||
/**
|
||||
* Is some sort of document security enabled?
|
||||
@@ -105,10 +114,18 @@ class Security
|
||||
public function setRevisionsPassword(?string $password, bool $alreadyHashed = false): static
|
||||
{
|
||||
if ($password !== null) {
|
||||
if (!$alreadyHashed) {
|
||||
$password = PasswordHasher::hashPassword($password);
|
||||
if ($this->advancedRevisionsPassword()) {
|
||||
if (!$alreadyHashed) {
|
||||
$password = PasswordHasher::hashPassword($password, $this->revisionsAlgorithmName, $this->revisionsSaltValue, $this->revisionsSpinCount);
|
||||
}
|
||||
$this->revisionsHashValue = $password;
|
||||
$this->revisionsPassword = '';
|
||||
} else {
|
||||
if (!$alreadyHashed) {
|
||||
$password = PasswordHasher::hashPassword($password);
|
||||
}
|
||||
$this->revisionsPassword = $password;
|
||||
}
|
||||
$this->revisionsPassword = $password;
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -129,12 +146,112 @@ class Security
|
||||
public function setWorkbookPassword(?string $password, bool $alreadyHashed = false): static
|
||||
{
|
||||
if ($password !== null) {
|
||||
if (!$alreadyHashed) {
|
||||
$password = PasswordHasher::hashPassword($password);
|
||||
if ($this->advancedPassword()) {
|
||||
if (!$alreadyHashed) {
|
||||
$password = PasswordHasher::hashPassword($password, $this->workbookAlgorithmName, $this->workbookSaltValue, $this->workbookSpinCount);
|
||||
}
|
||||
$this->workbookHashValue = $password;
|
||||
$this->workbookPassword = '';
|
||||
} else {
|
||||
if (!$alreadyHashed) {
|
||||
$password = PasswordHasher::hashPassword($password);
|
||||
}
|
||||
$this->workbookPassword = $password;
|
||||
}
|
||||
$this->workbookPassword = $password;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWorkbookHashValue(): string
|
||||
{
|
||||
return $this->advancedPassword() ? $this->workbookHashValue : '';
|
||||
}
|
||||
|
||||
public function advancedPassword(): bool
|
||||
{
|
||||
return $this->workbookAlgorithmName !== '' && $this->workbookSaltValue !== '' && $this->workbookSpinCount > 0;
|
||||
}
|
||||
|
||||
public function getWorkbookAlgorithmName(): string
|
||||
{
|
||||
return $this->workbookAlgorithmName;
|
||||
}
|
||||
|
||||
public function setWorkbookAlgorithmName(string $workbookAlgorithmName): static
|
||||
{
|
||||
$this->workbookAlgorithmName = $workbookAlgorithmName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWorkbookSpinCount(): int
|
||||
{
|
||||
return $this->workbookSpinCount;
|
||||
}
|
||||
|
||||
public function setWorkbookSpinCount(int $workbookSpinCount): static
|
||||
{
|
||||
$this->workbookSpinCount = $workbookSpinCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWorkbookSaltValue(): string
|
||||
{
|
||||
return $this->workbookSaltValue;
|
||||
}
|
||||
|
||||
public function setWorkbookSaltValue(string $workbookSaltValue, bool $base64Required): static
|
||||
{
|
||||
$this->workbookSaltValue = $base64Required ? base64_encode($workbookSaltValue) : $workbookSaltValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRevisionsHashValue(): string
|
||||
{
|
||||
return $this->advancedRevisionsPassword() ? $this->revisionsHashValue : '';
|
||||
}
|
||||
|
||||
public function advancedRevisionsPassword(): bool
|
||||
{
|
||||
return $this->revisionsAlgorithmName !== '' && $this->revisionsSaltValue !== '' && $this->revisionsSpinCount > 0;
|
||||
}
|
||||
|
||||
public function getRevisionsAlgorithmName(): string
|
||||
{
|
||||
return $this->revisionsAlgorithmName;
|
||||
}
|
||||
|
||||
public function setRevisionsAlgorithmName(string $revisionsAlgorithmName): static
|
||||
{
|
||||
$this->revisionsAlgorithmName = $revisionsAlgorithmName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRevisionsSpinCount(): int
|
||||
{
|
||||
return $this->revisionsSpinCount;
|
||||
}
|
||||
|
||||
public function setRevisionsSpinCount(int $revisionsSpinCount): static
|
||||
{
|
||||
$this->revisionsSpinCount = $revisionsSpinCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRevisionsSaltValue(): string
|
||||
{
|
||||
return $this->revisionsSaltValue;
|
||||
}
|
||||
|
||||
public function setRevisionsSaltValue(string $revisionsSaltValue, bool $base64Required): static
|
||||
{
|
||||
$this->revisionsSaltValue = $base64Required ? base64_encode($revisionsSaltValue) : $revisionsSaltValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use PhpOffice\PhpSpreadsheet\Helper\Html as HelperHtml;
|
||||
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Color;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
@@ -727,13 +728,13 @@ class Html extends BaseReader
|
||||
// Reload the HTML file into the DOM object
|
||||
try {
|
||||
$convert = $this->getSecurityScannerOrThrow()->scanFile($filename);
|
||||
$convert = self::replaceNonAsciiIfNeeded($convert);
|
||||
$convert = static::replaceNonAsciiIfNeeded($convert);
|
||||
$loaded = ($convert === null) ? false : $dom->loadHTML($convert);
|
||||
} catch (Throwable $e) {
|
||||
$loaded = false;
|
||||
}
|
||||
if ($loaded === false) {
|
||||
throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null);
|
||||
throw new Exception('Failed to load file ' . $filename . ' as a DOM Document', 0, $e ?? null);
|
||||
}
|
||||
self::loadProperties($dom, $spreadsheet);
|
||||
|
||||
@@ -821,7 +822,7 @@ class Html extends BaseReader
|
||||
return '&#' . mb_ord($matches[0], 'UTF-8') . ';';
|
||||
}
|
||||
|
||||
private static function replaceNonAsciiIfNeeded(string $convert): ?string
|
||||
protected static function replaceNonAsciiIfNeeded(string $convert): ?string
|
||||
{
|
||||
if (preg_match(self::STARTS_WITH_BOM, $convert) !== 1 && preg_match(self::DECLARES_CHARSET, $convert) !== 1) {
|
||||
$lowend = "\u{80}";
|
||||
@@ -846,7 +847,7 @@ class Html extends BaseReader
|
||||
// Reload the HTML file into the DOM object
|
||||
try {
|
||||
$convert = $this->getSecurityScannerOrThrow()->scan($content);
|
||||
$convert = self::replaceNonAsciiIfNeeded($convert);
|
||||
$convert = static::replaceNonAsciiIfNeeded($convert);
|
||||
$loaded = ($convert === null) ? false : $dom->loadHTML($convert);
|
||||
} catch (Throwable $e) {
|
||||
$loaded = false;
|
||||
@@ -1014,6 +1015,17 @@ class Html extends BaseReader
|
||||
|
||||
break;
|
||||
|
||||
case 'direction':
|
||||
if ($styleValue === 'rtl') {
|
||||
$cellStyle->getAlignment()
|
||||
->setReadOrder(Alignment::READORDER_RTL);
|
||||
} elseif ($styleValue === 'ltr') {
|
||||
$cellStyle->getAlignment()
|
||||
->setReadOrder(Alignment::READORDER_LTR);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'font-weight':
|
||||
if ($styleValue === 'bold' || $styleValue >= 500) {
|
||||
$cellStyle->getFont()->setBold(true);
|
||||
@@ -1083,8 +1095,11 @@ class Html extends BaseReader
|
||||
break;
|
||||
|
||||
case 'text-indent':
|
||||
$indentDimension = new CssDimension($styleValueString);
|
||||
$indent = $indentDimension
|
||||
->toUnit(CssDimension::UOM_PIXELS);
|
||||
$cellStyle->getAlignment()->setIndent(
|
||||
(int) str_replace(['px'], '', $styleValueString)
|
||||
(int) ($indent / Alignment::INDENT_UNITS_TO_PIXELS)
|
||||
);
|
||||
|
||||
break;
|
||||
@@ -1183,13 +1198,13 @@ class Html extends BaseReader
|
||||
if (substr($arrayValue, -2) === 'px') {
|
||||
$arrayValue = (string) (((float) substr($arrayValue, 0, -2)));
|
||||
} else {
|
||||
$arrayValue = (new CssDimension($arrayValue))->width();
|
||||
$arrayValue = (new CssDimension($arrayValue))->toUnit(CssDimension::UOM_PIXELS);
|
||||
}
|
||||
} elseif ($arrayKey === 'height') {
|
||||
if (substr($arrayValue, -2) === 'px') {
|
||||
$arrayValue = substr($arrayValue, 0, -2);
|
||||
} else {
|
||||
$arrayValue = (new CssDimension($arrayValue))->height();
|
||||
$arrayValue = (new CssDimension($arrayValue))->toUnit(CssDimension::UOM_PIXELS);
|
||||
}
|
||||
}
|
||||
$styleArray[$arrayKey] = $arrayValue;
|
||||
|
||||
@@ -173,6 +173,9 @@ class Slk extends BaseReader
|
||||
'U' => 'underline',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param-out true $hasCalculatedValue
|
||||
*/
|
||||
private function processFormula(string $rowDatum, bool &$hasCalculatedValue, string &$cellDataFormula, string $row, string $column): void
|
||||
{
|
||||
$cellDataFormula = '=' . substr($rowDatum, 1);
|
||||
|
||||
@@ -140,7 +140,7 @@ class Xls extends XlsBase
|
||||
/**
|
||||
* REF structures. Only applies to BIFF8.
|
||||
*
|
||||
* @var mixed[][]
|
||||
* @var array<int, array{'externalBookIndex': int, 'firstSheetIndex': int, 'lastSheetIndex': int}>
|
||||
*/
|
||||
protected array $ref;
|
||||
|
||||
@@ -294,6 +294,16 @@ class Xls extends XlsBase
|
||||
return (new Xls\ListFunctions())->listWorksheetInfo2($filename, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
|
||||
*
|
||||
* @return array<int, array{worksheetName: string, dimensionsMinR: int, dimensionsMinC: int, dimensionsMaxR: int, dimensionsMaxC: int, lastColumnLetter: string}>
|
||||
*/
|
||||
public function listWorksheetDimensions(string $filename): array
|
||||
{
|
||||
return (new Xls\ListFunctions())->listWorksheetDimensions2($filename, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads PhpSpreadsheet from file.
|
||||
*/
|
||||
@@ -1254,6 +1264,8 @@ class Xls extends XlsBase
|
||||
|
||||
break;
|
||||
}
|
||||
$readOrder = (0xC0 & ord($recordData[8])) >> 6;
|
||||
$objStyle->getAlignment()->setReadOrder($readOrder);
|
||||
|
||||
// offset: 9; size: 1; Flags used for attribute groups
|
||||
|
||||
@@ -2022,8 +2034,8 @@ class Xls extends XlsBase
|
||||
|
||||
// repeated option flags
|
||||
// OpenOffice.org documentation 5.21
|
||||
$option = ord($recordData[$pos]);
|
||||
/** @var int $pos */
|
||||
$option = ord($recordData[$pos]);
|
||||
++$pos;
|
||||
|
||||
/** @var int $limitpos */
|
||||
@@ -2647,7 +2659,14 @@ class Xls extends XlsBase
|
||||
$useDefaultHeight = (0x8000 & self::getUInt2d($recordData, 6)) >> 15;
|
||||
|
||||
if (!$useDefaultHeight) {
|
||||
$this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
|
||||
if (
|
||||
$this->phpSheet->getDefaultRowDimension()->getRowHeight() > 0
|
||||
) {
|
||||
$this->phpSheet->getRowDimension($r + 1)
|
||||
->setCustomFormat(true, ($height === 255) ? -1 : ($height / 20));
|
||||
} else {
|
||||
$this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
|
||||
}
|
||||
}
|
||||
|
||||
// offset: 8; size: 2; not used
|
||||
|
||||
@@ -12,6 +12,9 @@ use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
|
||||
|
||||
/**
|
||||
* @template T of BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer
|
||||
*/
|
||||
class Escher
|
||||
{
|
||||
const DGGCONTAINER = 0xF000;
|
||||
@@ -50,11 +53,15 @@ class Escher
|
||||
|
||||
/**
|
||||
* The object to be returned by the reader. Modified during load.
|
||||
*
|
||||
* @var T
|
||||
*/
|
||||
private BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer $object;
|
||||
|
||||
/**
|
||||
* Create a new Escher instance.
|
||||
*
|
||||
* @param T $object
|
||||
*/
|
||||
public function __construct(BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer $object)
|
||||
{
|
||||
@@ -84,6 +91,8 @@ class Escher
|
||||
|
||||
/**
|
||||
* Load Escher stream data. May be a partial Escher stream.
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
public function load(string $data): BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer
|
||||
{
|
||||
|
||||
@@ -124,6 +124,7 @@ class ListFunctions extends Xls
|
||||
case self::XLS_TYPE_FORMULA:
|
||||
case self::XLS_TYPE_BOOLERR:
|
||||
case self::XLS_TYPE_LABEL:
|
||||
case self::XLS_TYPE_MULRK:
|
||||
$length = self::getUInt2d($xls->data, $xls->pos + 2);
|
||||
$recordData = $xls->readRecordData($xls->data, $xls->pos + 4, $length);
|
||||
|
||||
@@ -131,7 +132,11 @@ class ListFunctions extends Xls
|
||||
$xls->pos += 4 + $length;
|
||||
|
||||
$rowIndex = self::getUInt2d($recordData, 0) + 1;
|
||||
$columnIndex = self::getUInt2d($recordData, 2);
|
||||
if ($code === self::XLS_TYPE_MULRK) {
|
||||
$columnIndex = self::getUInt2d($recordData, $length - 2);
|
||||
} else {
|
||||
$columnIndex = self::getUInt2d($recordData, 2);
|
||||
}
|
||||
|
||||
$tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
|
||||
$tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
|
||||
@@ -160,4 +165,103 @@ class ListFunctions extends Xls
|
||||
|
||||
return $worksheetInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
|
||||
*
|
||||
* @return array<int, array{worksheetName: string, dimensionsMinR: int, dimensionsMinC: int, dimensionsMaxR: int, dimensionsMaxC: int, lastColumnLetter: string}>
|
||||
*/
|
||||
protected function listWorksheetDimensions2(string $filename, Xls $xls): array
|
||||
{
|
||||
File::assertFile($filename);
|
||||
|
||||
$worksheetInfo = [];
|
||||
|
||||
// Read the OLE file
|
||||
$xls->loadOLE($filename);
|
||||
|
||||
// total byte size of Excel data (workbook global substream + sheet substreams)
|
||||
$xls->dataSize = strlen($xls->data);
|
||||
|
||||
// initialize
|
||||
$xls->pos = 0;
|
||||
$xls->sheets = [];
|
||||
|
||||
// Parse Workbook Global Substream
|
||||
while ($xls->pos < $xls->dataSize) {
|
||||
$code = self::getUInt2d($xls->data, $xls->pos);
|
||||
|
||||
match ($code) {
|
||||
self::XLS_TYPE_BOF => $xls->readBof(),
|
||||
self::XLS_TYPE_SHEET => $xls->readSheet(),
|
||||
self::XLS_TYPE_EOF => $xls->readDefault(),
|
||||
self::XLS_TYPE_CODEPAGE => $xls->readCodepage(),
|
||||
default => $xls->readDefault(),
|
||||
};
|
||||
|
||||
if ($code === self::XLS_TYPE_EOF) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the individual sheets
|
||||
foreach ($xls->sheets as $sheet) {
|
||||
if ($sheet['sheetType'] !== 0x00) {
|
||||
// 0x00: Worksheet
|
||||
// 0x02: Chart
|
||||
// 0x06: Visual Basic module
|
||||
continue;
|
||||
}
|
||||
|
||||
$tmpInfo = [];
|
||||
$tmpInfo['worksheetName'] = StringHelper::convertToString($sheet['name']);
|
||||
$tmpInfo['dimensionsMinR'] = -1;
|
||||
$tmpInfo['dimensionsMaxR'] = -1;
|
||||
$tmpInfo['dimensionsMinC'] = -1;
|
||||
$tmpInfo['dimensionsMaxC'] = -1;
|
||||
$tmpInfo['lastColumnLetter'] = '';
|
||||
|
||||
$xls->pos = $sheet['offset'];
|
||||
|
||||
while ($xls->pos <= $xls->dataSize - 4) {
|
||||
$code = self::getUInt2d($xls->data, $xls->pos);
|
||||
|
||||
switch ($code) {
|
||||
case self::XLS_TYPE_BOF:
|
||||
$xls->readBof();
|
||||
|
||||
break;
|
||||
case self::XLS_TYPE_EOF:
|
||||
$xls->readDefault();
|
||||
|
||||
break 2;
|
||||
case self::XLS_TYPE_DIMENSION:
|
||||
$length = self::getUInt2d($xls->data, $xls->pos + 2);
|
||||
if ($length === 14) {
|
||||
$dimensionsData = substr($xls->data, $xls->pos + 4, $length);
|
||||
$data = unpack('VrwMic/VrwMac/vcolMic/vcolMac/vreserved', $dimensionsData);
|
||||
if (is_array($data)) {
|
||||
/** @var int[] $data */
|
||||
$tmpInfo['dimensionsMinR'] = $data['rwMic'];
|
||||
$tmpInfo['dimensionsMaxR'] = $data['rwMac'];
|
||||
$tmpInfo['dimensionsMinC'] = $data['colMic'];
|
||||
$tmpInfo['dimensionsMaxC'] = $data['colMac'];
|
||||
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['dimensionsMaxC']);
|
||||
}
|
||||
}
|
||||
$xls->readDefault();
|
||||
|
||||
break;
|
||||
default:
|
||||
$xls->readDefault();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$worksheetInfo[] = $tmpInfo;
|
||||
}
|
||||
|
||||
return $worksheetInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +435,7 @@ class LoadSpreadsheet extends Xls
|
||||
|
||||
// get all spContainers in one long array, so they can be mapped to OBJ records
|
||||
/** @var SpContainer[] $allSpContainers */
|
||||
$allSpContainers = method_exists($escherWorksheet, 'getDgContainer') ? $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers() : [];
|
||||
$allSpContainers = $escherWorksheet->getDgContainerOrThrow()->getSpgrContainerOrThrow()->getAllSpContainers();
|
||||
}
|
||||
|
||||
// treat OBJ records
|
||||
@@ -497,7 +497,7 @@ class LoadSpreadsheet extends Xls
|
||||
|
||||
if ($escherWorkbook) {
|
||||
/** @var BSE[] */
|
||||
$BSECollection = method_exists($escherWorkbook, 'getDggContainer') ? $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection() : [];
|
||||
$BSECollection = $escherWorkbook->getDggContainerOrThrow()->getBstoreContainerOrThrow()->getBSECollection();
|
||||
$BSE = $BSECollection[$BSEindex - 1];
|
||||
$blipType = $BSE->getBlipType();
|
||||
|
||||
@@ -549,6 +549,7 @@ class LoadSpreadsheet extends Xls
|
||||
foreach ($xls->sharedFormulaParts as $cell => $baseCell) {
|
||||
/** @var int $row */
|
||||
[$column, $row] = Coordinate::coordinateFromString($cell);
|
||||
/** @var string $baseCell */
|
||||
if ($xls->getReadFilter()->readCell($column, $row, $xls->phpSheet->getTitle())) {
|
||||
/** @var string */
|
||||
$temp = $xls->sharedFormulas[$baseCell];
|
||||
|
||||
@@ -25,8 +25,6 @@ class XlsBase extends BaseReader
|
||||
final const XLS_TYPE_FORMULA = 0x0006;
|
||||
final const XLS_TYPE_EOF = 0x000A;
|
||||
final const XLS_TYPE_PROTECT = 0x0012;
|
||||
final const XLS_TYPE_OBJECTPROTECT = 0x0063;
|
||||
final const XLS_TYPE_SCENPROTECT = 0x00DD;
|
||||
final const XLS_TYPE_PASSWORD = 0x0013;
|
||||
final const XLS_TYPE_HEADER = 0x0014;
|
||||
final const XLS_TYPE_FOOTER = 0x0015;
|
||||
@@ -50,6 +48,7 @@ class XlsBase extends BaseReader
|
||||
final const XLS_TYPE_CODEPAGE = 0x0042;
|
||||
final const XLS_TYPE_DEFCOLWIDTH = 0x0055;
|
||||
final const XLS_TYPE_OBJ = 0x005D;
|
||||
final const XLS_TYPE_OBJECTPROTECT = 0x0063;
|
||||
final const XLS_TYPE_COLINFO = 0x007D;
|
||||
final const XLS_TYPE_IMDATA = 0x007F;
|
||||
final const XLS_TYPE_SHEETPR = 0x0081;
|
||||
@@ -62,6 +61,7 @@ class XlsBase extends BaseReader
|
||||
final const XLS_TYPE_MULRK = 0x00BD;
|
||||
final const XLS_TYPE_MULBLANK = 0x00BE;
|
||||
final const XLS_TYPE_DBCELL = 0x00D7;
|
||||
final const XLS_TYPE_SCENPROTECT = 0x00DD;
|
||||
final const XLS_TYPE_XF = 0x00E0;
|
||||
final const XLS_TYPE_MERGEDCELLS = 0x00E5;
|
||||
final const XLS_TYPE_MSODRAWINGGROUP = 0x00EB;
|
||||
@@ -70,6 +70,8 @@ class XlsBase extends BaseReader
|
||||
final const XLS_TYPE_LABELSST = 0x00FD;
|
||||
final const XLS_TYPE_EXTSST = 0x00FF;
|
||||
final const XLS_TYPE_EXTERNALBOOK = 0x01AE;
|
||||
final const XLS_TYPE_CFHEADER = 0x01B0;
|
||||
final const XLS_TYPE_CFRULE = 0x01B1;
|
||||
final const XLS_TYPE_DATAVALIDATIONS = 0x01B2;
|
||||
final const XLS_TYPE_TXO = 0x01B6;
|
||||
final const XLS_TYPE_HYPERLINK = 0x01B8;
|
||||
@@ -90,13 +92,11 @@ class XlsBase extends BaseReader
|
||||
final const XLS_TYPE_FORMAT = 0x041E;
|
||||
final const XLS_TYPE_SHAREDFMLA = 0x04BC;
|
||||
final const XLS_TYPE_BOF = 0x0809;
|
||||
final const XLS_TYPE_SHEETLAYOUT = 0x0862;
|
||||
final const XLS_TYPE_SHEETPROTECTION = 0x0867;
|
||||
final const XLS_TYPE_RANGEPROTECTION = 0x0868;
|
||||
final const XLS_TYPE_SHEETLAYOUT = 0x0862;
|
||||
final const XLS_TYPE_XFEXT = 0x087D;
|
||||
final const XLS_TYPE_PAGELAYOUTVIEW = 0x088B;
|
||||
final const XLS_TYPE_CFHEADER = 0x01B0;
|
||||
final const XLS_TYPE_CFRULE = 0x01B1;
|
||||
final const XLS_TYPE_UNKNOWN = 0xFFFF;
|
||||
|
||||
// Encryption type
|
||||
@@ -368,11 +368,20 @@ class XlsBase extends BaseReader
|
||||
return StringHelper::convertEncoding($string, 'UTF-8', $this->codepage);
|
||||
}
|
||||
|
||||
protected static function confirmPos(string $data, int $pos): void
|
||||
{
|
||||
if ($pos >= strlen($data)) {
|
||||
throw new PhpSpreadsheetException('File appears to be corrupt'); // @codeCoverageIgnore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read 16-bit unsigned integer.
|
||||
*/
|
||||
public static function getUInt2d(string $data, int $pos): int
|
||||
{
|
||||
self::confirmPos($data, $pos + 1);
|
||||
|
||||
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
|
||||
}
|
||||
|
||||
@@ -381,6 +390,8 @@ class XlsBase extends BaseReader
|
||||
*/
|
||||
public static function getInt2d(string $data, int $pos): int
|
||||
{
|
||||
self::confirmPos($data, $pos + 1);
|
||||
|
||||
return unpack('s', $data[$pos] . $data[$pos + 1])[1]; // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
@@ -389,6 +400,8 @@ class XlsBase extends BaseReader
|
||||
*/
|
||||
public static function getInt4d(string $data, int $pos): int
|
||||
{
|
||||
self::confirmPos($data, $pos + 3);
|
||||
|
||||
// FIX: represent numbers correctly on 64-bit system
|
||||
// http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
|
||||
// Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
|
||||
|
||||
@@ -1079,7 +1079,8 @@ class Xlsx extends BaseReader
|
||||
$childNode = $node->addChild('formula1');
|
||||
if ($childNode !== null) { // null should never happen
|
||||
// see https://github.com/phpstan/phpstan/issues/8236
|
||||
$childNode[0] = (string) $item->formula1->children(Namespaces::DATA_VALIDATIONS2)->f; // @phpstan-ignore-line
|
||||
// resolved with Phpstan 2.1.23
|
||||
$childNode[0] = (string) $item->formula1->children(Namespaces::DATA_VALIDATIONS2)->f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1204,7 +1205,10 @@ class Xlsx extends BaseReader
|
||||
$shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape');
|
||||
foreach ($shapes as $shape) {
|
||||
/** @var SimpleXMLElement $shape */
|
||||
$shape->registerXPathNamespace('v', Namespaces::URN_VML);
|
||||
$vmlNamespaces = $shape->getNamespaces();
|
||||
$shape->registerXPathNamespace('v', $vmlNamespaces['v'] ?? Namespaces::URN_VML);
|
||||
$shape->registerXPathNamespace('x', $vmlNamespaces['x'] ?? Namespaces::URN_EXCEL);
|
||||
$shape->registerXPathNamespace('o', $vmlNamespaces['o'] ?? Namespaces::URN_MSOFFICE);
|
||||
|
||||
if (isset($shape['style'])) {
|
||||
$style = (string) $shape['style'];
|
||||
@@ -1229,6 +1233,7 @@ class Xlsx extends BaseReader
|
||||
$clientData = $clientData[0];
|
||||
|
||||
if (isset($clientData['ObjectType']) && (string) $clientData['ObjectType'] == 'Note') {
|
||||
$clientData->registerXPathNamespace('x', $vmlNamespaces['x'] ?? Namespaces::URN_EXCEL);
|
||||
$temp = $clientData->xpath('.//x:Row');
|
||||
if (is_array($temp)) {
|
||||
$row = $temp[0];
|
||||
@@ -1277,7 +1282,7 @@ class Xlsx extends BaseReader
|
||||
// Set comment properties
|
||||
$comment = $docSheet->getComment([(int) $column + 1, (int) $row + 1]);
|
||||
$comment->getFillColor()->setRGB($fillColor);
|
||||
if (isset($drowingImages[$fillImageRelId])) {
|
||||
if (isset($fillImageRelId, $drowingImages[$fillImageRelId])) {
|
||||
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
|
||||
$objDrawing->setName($fillImageTitle);
|
||||
$imagePath = str_replace(['../', '/xl/'], 'xl/', $drowingImages[$fillImageRelId]);
|
||||
@@ -2200,23 +2205,79 @@ class Xlsx extends BaseReader
|
||||
return;
|
||||
}
|
||||
|
||||
$excel->getSecurity()->setLockRevision(self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision'));
|
||||
$excel->getSecurity()->setLockStructure(self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure'));
|
||||
$excel->getSecurity()->setLockWindows(self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows'));
|
||||
$security = $excel->getSecurity();
|
||||
$security->setLockRevision(
|
||||
self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision')
|
||||
);
|
||||
$security->setLockStructure(
|
||||
self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure')
|
||||
);
|
||||
$security->setLockWindows(
|
||||
self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows')
|
||||
);
|
||||
|
||||
if ($xmlWorkbook->workbookProtection['revisionsPassword']) {
|
||||
$excel->getSecurity()->setRevisionsPassword(
|
||||
$security->setRevisionsPassword(
|
||||
(string) $xmlWorkbook->workbookProtection['revisionsPassword'],
|
||||
true
|
||||
);
|
||||
}
|
||||
if ($xmlWorkbook->workbookProtection['revisionsAlgorithmName']) {
|
||||
$security->setRevisionsAlgorithmName(
|
||||
(string) $xmlWorkbook->workbookProtection['revisionsAlgorithmName']
|
||||
);
|
||||
}
|
||||
if ($xmlWorkbook->workbookProtection['revisionsSaltValue']) {
|
||||
$security->setRevisionsSaltValue(
|
||||
(string) $xmlWorkbook->workbookProtection['revisionsSaltValue'],
|
||||
false
|
||||
);
|
||||
}
|
||||
if ($xmlWorkbook->workbookProtection['revisionsSpinCount']) {
|
||||
$security->setRevisionsSpinCount(
|
||||
(int) $xmlWorkbook->workbookProtection['revisionsSpinCount']
|
||||
);
|
||||
}
|
||||
if ($xmlWorkbook->workbookProtection['revisionsHashValue']) {
|
||||
if ($security->advancedRevisionsPassword()) {
|
||||
$security->setRevisionsPassword(
|
||||
(string) $xmlWorkbook->workbookProtection['revisionsHashValue'],
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($xmlWorkbook->workbookProtection['workbookPassword']) {
|
||||
$excel->getSecurity()->setWorkbookPassword(
|
||||
$security->setWorkbookPassword(
|
||||
(string) $xmlWorkbook->workbookProtection['workbookPassword'],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if ($xmlWorkbook->workbookProtection['workbookAlgorithmName']) {
|
||||
$security->setWorkbookAlgorithmName(
|
||||
(string) $xmlWorkbook->workbookProtection['workbookAlgorithmName']
|
||||
);
|
||||
}
|
||||
if ($xmlWorkbook->workbookProtection['workbookSaltValue']) {
|
||||
$security->setWorkbookSaltValue(
|
||||
(string) $xmlWorkbook->workbookProtection['workbookSaltValue'],
|
||||
false
|
||||
);
|
||||
}
|
||||
if ($xmlWorkbook->workbookProtection['workbookSpinCount']) {
|
||||
$security->setWorkbookSpinCount(
|
||||
(int) $xmlWorkbook->workbookProtection['workbookSpinCount']
|
||||
);
|
||||
}
|
||||
if ($xmlWorkbook->workbookProtection['workbookHashValue']) {
|
||||
if ($security->advancedPassword()) {
|
||||
$security->setWorkbookPassword(
|
||||
(string) $xmlWorkbook->workbookProtection['workbookHashValue'],
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function getLockValue(SimpleXMLElement $protection, string $key): ?bool
|
||||
|
||||
@@ -50,32 +50,40 @@ class ColumnAndRowAttributes extends BaseParserClass
|
||||
* Set Worksheet row attributes by attributes array passed.
|
||||
*
|
||||
* @param int $rowNumber 1, 2, 3, ... 99, ...
|
||||
* @param array{xfIndex?: int, visible?: bool, collapsed?: bool, collapsed?: bool, outlineLevel?: int, rowHeight?: float} $rowAttributes array of attributes (indexes are attribute name, values are value)
|
||||
* @param array{xfIndex?: int, visible?: bool, collapsed?: bool, collapsed?: bool, outlineLevel?: int, rowHeight?: float, customFormat?: bool, ht?: float} $rowAttributes array of attributes (indexes are attribute name, values are value)
|
||||
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ?
|
||||
*/
|
||||
private function setRowAttributes(int $rowNumber, array $rowAttributes): void
|
||||
{
|
||||
if (isset($rowAttributes['xfIndex'])) {
|
||||
$this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']);
|
||||
$this->worksheet->getRowDimension($rowNumber)
|
||||
->setXfIndex($rowAttributes['xfIndex']);
|
||||
}
|
||||
if (isset($rowAttributes['visible'])) {
|
||||
$this->worksheet->getRowDimension($rowNumber)->setVisible($rowAttributes['visible']);
|
||||
$this->worksheet->getRowDimension($rowNumber)
|
||||
->setVisible($rowAttributes['visible']);
|
||||
}
|
||||
if (isset($rowAttributes['collapsed'])) {
|
||||
$this->worksheet->getRowDimension($rowNumber)->setCollapsed($rowAttributes['collapsed']);
|
||||
$this->worksheet->getRowDimension($rowNumber)
|
||||
->setCollapsed($rowAttributes['collapsed']);
|
||||
}
|
||||
if (isset($rowAttributes['outlineLevel'])) {
|
||||
$this->worksheet->getRowDimension($rowNumber)->setOutlineLevel($rowAttributes['outlineLevel']);
|
||||
$this->worksheet->getRowDimension($rowNumber)
|
||||
->setOutlineLevel($rowAttributes['outlineLevel']);
|
||||
}
|
||||
if (isset($rowAttributes['rowHeight'])) {
|
||||
$this->worksheet->getRowDimension($rowNumber)->setRowHeight($rowAttributes['rowHeight']);
|
||||
if (isset($rowAttributes['customFormat'], $rowAttributes['rowHeight'])) {
|
||||
$this->worksheet->getRowDimension($rowNumber)
|
||||
->setCustomFormat($rowAttributes['customFormat'], $rowAttributes['rowHeight']);
|
||||
} elseif (isset($rowAttributes['rowHeight'])) {
|
||||
$this->worksheet->getRowDimension($rowNumber)
|
||||
->setRowHeight($rowAttributes['rowHeight']);
|
||||
}
|
||||
}
|
||||
|
||||
public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false, bool $ignoreRowsWithNoCells = false): void
|
||||
public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false, bool $ignoreRowsWithNoCells = false): bool
|
||||
{
|
||||
if ($this->worksheetXml === null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if ($readFilter !== null && $readFilter::class === DefaultReadFilter::class) {
|
||||
$readFilter = null;
|
||||
@@ -119,6 +127,8 @@ class ColumnAndRowAttributes extends BaseParserClass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @param mixed[] $rowsAttributes */
|
||||
@@ -203,20 +213,25 @@ class ColumnAndRowAttributes extends BaseParserClass
|
||||
$row = $rowx->attributes();
|
||||
if ($row !== null && (!$ignoreRowsWithNoCells || isset($rowx->c))) {
|
||||
$rowIndex = (int) $row['r'];
|
||||
if (isset($row['ht']) && !$readDataOnly) {
|
||||
$rowAttributes[$rowIndex]['rowHeight'] = (float) $row['ht'];
|
||||
}
|
||||
if (isset($row['hidden']) && self::boolean($row['hidden'])) {
|
||||
$rowAttributes[$rowIndex]['visible'] = false;
|
||||
}
|
||||
if (isset($row['collapsed']) && self::boolean($row['collapsed'])) {
|
||||
$rowAttributes[$rowIndex]['collapsed'] = true;
|
||||
}
|
||||
if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) {
|
||||
$rowAttributes[$rowIndex]['outlineLevel'] = (int) $row['outlineLevel'];
|
||||
}
|
||||
if (isset($row['s']) && !$readDataOnly) {
|
||||
$rowAttributes[$rowIndex]['xfIndex'] = (int) $row['s'];
|
||||
if (!$readDataOnly) {
|
||||
if (isset($row['ht'])) {
|
||||
$rowAttributes[$rowIndex]['rowHeight'] = (float) $row['ht'];
|
||||
}
|
||||
if (isset($row['customFormat']) && self::boolean($row['customFormat'])) {
|
||||
$rowAttributes[$rowIndex]['customFormat'] = true;
|
||||
}
|
||||
if (isset($row['hidden']) && self::boolean($row['hidden'])) {
|
||||
$rowAttributes[$rowIndex]['visible'] = false;
|
||||
}
|
||||
if (isset($row['collapsed']) && self::boolean($row['collapsed'])) {
|
||||
$rowAttributes[$rowIndex]['collapsed'] = true;
|
||||
}
|
||||
if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) {
|
||||
$rowAttributes[$rowIndex]['outlineLevel'] = (int) $row['outlineLevel'];
|
||||
}
|
||||
if (isset($row['s'])) {
|
||||
$rowAttributes[$rowIndex]['xfIndex'] = (int) $row['s'];
|
||||
}
|
||||
}
|
||||
if ($readFilterIsNotNull && empty($rowAttributes[$rowIndex])) {
|
||||
$rowAttributes[$rowIndex]['exists'] = true;
|
||||
|
||||
@@ -71,6 +71,11 @@ class ConditionalStyles
|
||||
ksort($cfRules);
|
||||
// Priority is used as the key for sorting; but may not start at 0,
|
||||
// so we use array_values to reset the index after sorting.
|
||||
$existing = $this->worksheet->getConditionalStylesCollection();
|
||||
if (array_key_exists($conditionalRange, $existing)) {
|
||||
$conditionalStyle = $existing[$conditionalRange];
|
||||
$cfRules = array_merge($conditionalStyle, $cfRules);
|
||||
}
|
||||
$this->worksheet->getStyle($conditionalRange)
|
||||
->setConditionalStyles(array_values($cfRules));
|
||||
}
|
||||
@@ -133,6 +138,7 @@ class ConditionalStyles
|
||||
$conditionType = (string) $attributes->type;
|
||||
$operatorType = (string) $attributes->operator;
|
||||
$priority = (int) (string) $attributes->priority;
|
||||
$stopIfTrue = (int) (string) $attributes->stopIfTrue;
|
||||
|
||||
$operands = [];
|
||||
foreach ($cfRuleXml->children($this->ns['xm']) as $cfRuleOperandsXml) {
|
||||
@@ -143,6 +149,7 @@ class ConditionalStyles
|
||||
$conditional->setConditionType($conditionType);
|
||||
$conditional->setOperatorType($operatorType);
|
||||
$conditional->setPriority($priority);
|
||||
$conditional->setStopIfTrue($stopIfTrue === 1);
|
||||
if (
|
||||
$conditionType === Conditional::CONDITION_CONTAINSTEXT
|
||||
|| $conditionType === Conditional::CONDITION_NOTCONTAINSTEXT
|
||||
@@ -169,6 +176,9 @@ class ConditionalStyles
|
||||
if ($styleXML->fill) {
|
||||
$this->styleReader->readFillStyle($cfStyle->getFill(), $styleXML->fill);
|
||||
}
|
||||
if ($styleXML->font) {
|
||||
$this->styleReader->readFontStyle($cfStyle->getFont(), $styleXML->font);
|
||||
}
|
||||
}
|
||||
|
||||
return $cfStyle;
|
||||
|
||||
@@ -76,7 +76,7 @@ class Style
|
||||
break;
|
||||
case 'Protection':
|
||||
$locked = $hidden = null;
|
||||
$styleAttributesP = $styleData->attributes($namespaces['x']);
|
||||
$styleAttributesP = array_key_exists('x', $namespaces) ? $styleData->attributes($namespaces['x']) : [];
|
||||
if (isset($styleAttributes['Protected'])) {
|
||||
$locked = ((bool) (string) $styleAttributes['Protected']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,14 @@ class Alignment extends StyleBase
|
||||
case 'Indent':
|
||||
$style['alignment']['indent'] = $styleAttributeValue;
|
||||
|
||||
break;
|
||||
case 'ReadingOrder':
|
||||
if ($styleAttributeValue === 'RightToLeft') {
|
||||
$style['alignment']['readOrder'] = AlignmentStyles::READORDER_RTL;
|
||||
} elseif ($styleAttributeValue === 'LeftToRight') {
|
||||
$style['alignment']['readOrder'] = AlignmentStyles::READORDER_LTR;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ class Border extends StyleBase
|
||||
}
|
||||
}
|
||||
|
||||
/** @var int|string $borderPosition */
|
||||
if ($borderPosition) {
|
||||
$style['borders'][$borderPosition] = $thisBorder;
|
||||
} elseif ($diagonalDirection) {
|
||||
@@ -99,6 +100,7 @@ class Border extends StyleBase
|
||||
/** @return mixed[] */
|
||||
protected function parsePosition(string $borderStyleValue, string $diagonalDirection): array
|
||||
{
|
||||
// TODO diagonalDirection seems to return int not string
|
||||
$borderStyleValue = strtolower($borderStyleValue);
|
||||
|
||||
if (in_array($borderStyleValue, self::BORDER_POSITIONS)) {
|
||||
|
||||
@@ -15,7 +15,7 @@ class NumberFormat extends StyleBase
|
||||
$toFormats = ['-', ' '];
|
||||
|
||||
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
|
||||
$styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
|
||||
$styleAttributeValue = str_replace($fromFormats, $toFormats, (string) $styleAttributeValue);
|
||||
|
||||
switch ($styleAttributeValue) {
|
||||
case 'Short Date':
|
||||
|
||||
@@ -1045,7 +1045,7 @@ class ReferenceHelper
|
||||
{
|
||||
$cellAddress = $definedName->getValue();
|
||||
$asFormula = ($cellAddress[0] === '=');
|
||||
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashInt() === $worksheet->getHashInt()) {
|
||||
if ($definedName->getWorksheet() === $worksheet) {
|
||||
/**
|
||||
* If we delete the entire range that is referenced by a Named Range, MS Excel sets the value to #REF!
|
||||
* PhpSpreadsheet still only does a basic adjustment, so the Named Range will still reference Cells.
|
||||
@@ -1064,7 +1064,7 @@ class ReferenceHelper
|
||||
|
||||
private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
|
||||
{
|
||||
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashInt() === $worksheet->getHashInt()) {
|
||||
if ($definedName->getWorksheet() === $worksheet) {
|
||||
/**
|
||||
* If we delete the entire range that is referenced by a Named Formula, MS Excel sets the value to #REF!
|
||||
* PhpSpreadsheet still only does a basic adjustment, so the Named Formula will still reference Cells.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Shared;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
|
||||
|
||||
class Escher
|
||||
{
|
||||
/**
|
||||
@@ -22,6 +24,14 @@ class Escher
|
||||
return $this->dggContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Drawing Group Container.
|
||||
*/
|
||||
public function getDggContainerOrThrow(): Escher\DggContainer
|
||||
{
|
||||
return $this->dggContainer ?? throw new SpreadsheetException('dggContainer is unexpectedly null');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Drawing Group Container.
|
||||
*/
|
||||
@@ -38,6 +48,14 @@ class Escher
|
||||
return $this->dgContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Drawing Container.
|
||||
*/
|
||||
public function getDgContainerOrThrow(): Escher\DgContainer
|
||||
{
|
||||
return $this->dgContainer ?? throw new SpreadsheetException('dgContainer is unexpectedly null');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Drawing Container.
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Shared\Escher;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
|
||||
|
||||
class DggContainer
|
||||
{
|
||||
/**
|
||||
@@ -94,6 +96,14 @@ class DggContainer
|
||||
return $this->bstoreContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get BLIP Store Container.
|
||||
*/
|
||||
public function getBstoreContainerOrThrow(): DggContainer\BstoreContainer
|
||||
{
|
||||
return $this->bstoreContainer ?? throw new SpreadsheetException('bstoreContainer is unexpectedly null');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set BLIP Store Container.
|
||||
*/
|
||||
|
||||
@@ -78,7 +78,7 @@ class PasswordHasher
|
||||
*
|
||||
* @param string $password Password to hash
|
||||
* @param string $algorithm Hash algorithm used to compute the password hash value
|
||||
* @param string $salt Pseudorandom string
|
||||
* @param string $salt Pseudorandom base64-encoded string
|
||||
* @param int $spinCount Number of times to iterate on a hash of a password
|
||||
*
|
||||
* @return string Hashed password
|
||||
|
||||
@@ -420,7 +420,7 @@ class StringHelper
|
||||
*/
|
||||
public static function convertEncoding(string $textValue, string $to, string $from): string
|
||||
{
|
||||
if (self::getIsIconvEnabled()) {
|
||||
if (static::getIsIconvEnabled()) {
|
||||
$result = iconv($from, $to . self::$iconvOptions, $textValue);
|
||||
if (false !== $result) {
|
||||
return $result;
|
||||
@@ -669,13 +669,16 @@ class StringHelper
|
||||
return strlen("$string");
|
||||
}
|
||||
|
||||
/** @param bool $convertBool If true, convert bool to locale-aware TRUE/FALSE rather than 1/null-string */
|
||||
public static function convertToString(mixed $value, bool $throw = true, string $default = '', bool $convertBool = false): string
|
||||
/**
|
||||
* @param bool $convertBool If true, convert bool to locale-aware TRUE/FALSE rather than 1/null-string
|
||||
* @param bool $lessFloatPrecision If true, floats will be converted to a more human-friendly but less computationally accurate value
|
||||
*/
|
||||
public static function convertToString(mixed $value, bool $throw = true, string $default = '', bool $convertBool = false, bool $lessFloatPrecision = false): string
|
||||
{
|
||||
if ($convertBool && is_bool($value)) {
|
||||
return $value ? Calculation::getTRUE() : Calculation::getFALSE();
|
||||
}
|
||||
if (is_float($value)) {
|
||||
if (is_float($value) && !$lessFloatPrecision) {
|
||||
$string = (string) $value;
|
||||
// look out for scientific notation
|
||||
if (!Preg::isMatch('/[^-+0-9.]/', $string)) {
|
||||
|
||||
@@ -39,6 +39,10 @@ class XMLWriter extends \XMLWriter
|
||||
if (empty($this->tempFileName) || $this->openUri($this->tempFileName) === false) {
|
||||
// Fallback to memory...
|
||||
$this->openMemory();
|
||||
if ($this->tempFileName != '') {
|
||||
@unlink($this->tempFileName);
|
||||
}
|
||||
$this->tempFileName = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +64,8 @@ class XMLWriter extends \XMLWriter
|
||||
}
|
||||
}
|
||||
|
||||
public function __wakeup(): void
|
||||
/** @param mixed[] $data */
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
$this->tempFileName = '';
|
||||
|
||||
|
||||
@@ -735,9 +735,8 @@ class Spreadsheet implements JsonSerializable
|
||||
*/
|
||||
public function getIndex(Worksheet $worksheet, bool $noThrow = false): int
|
||||
{
|
||||
$wsHash = $worksheet->getHashInt();
|
||||
foreach ($this->workSheetCollection as $key => $value) {
|
||||
if ($value->getHashInt() === $wsHash) {
|
||||
if ($value === $worksheet) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
@@ -1468,6 +1467,10 @@ class Spreadsheet implements JsonSerializable
|
||||
|
||||
/**
|
||||
* Return the unique ID value assigned to this spreadsheet workbook.
|
||||
*
|
||||
* @deprecated 5.2.0 Serves no useful purpose. No replacement.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getID(): string
|
||||
{
|
||||
|
||||
@@ -92,6 +92,8 @@ class Alignment extends Supervisor
|
||||
const TEXTROTATION_STACK_EXCEL = 255;
|
||||
const TEXTROTATION_STACK_PHPSPREADSHEET = -165; // 90 - 255
|
||||
|
||||
public const INDENT_UNITS_TO_PIXELS = 9;
|
||||
|
||||
/**
|
||||
* Horizontal alignment.
|
||||
*/
|
||||
|
||||
@@ -467,12 +467,13 @@ class NumberFormat extends Supervisor
|
||||
* @param string $format Format code: see = self::FORMAT_* for predefined values;
|
||||
* or can be any valid MS Excel custom format string
|
||||
* @param ?mixed[] $callBack Callback function for additional formatting of string
|
||||
* @param bool $lessFloatPrecision If true, unstyled floats will be converted to a more human-friendly but less computationally accurate value
|
||||
*
|
||||
* @return string Formatted string
|
||||
*/
|
||||
public static function toFormattedString(mixed $value, string $format, ?array $callBack = null): string
|
||||
public static function toFormattedString(mixed $value, string $format, ?array $callBack = null, bool $lessFloatPrecision = false): string
|
||||
{
|
||||
return NumberFormat\Formatter::toFormattedString($value, $format, $callBack);
|
||||
return NumberFormat\Formatter::toFormattedString($value, $format, $callBack, $lessFloatPrecision);
|
||||
}
|
||||
|
||||
/** @return mixed[] */
|
||||
|
||||
@@ -119,10 +119,11 @@ class Formatter extends BaseFormatter
|
||||
* @param string $format Format code: see = self::FORMAT_* for predefined values;
|
||||
* or can be any valid MS Excel custom format string
|
||||
* @param null|array<mixed>|callable $callBack Callback function for additional formatting of string
|
||||
* @param bool $lessFloatPrecision If true, unstyled floats will be converted to a more human-friendly but less computationally accurate value
|
||||
*
|
||||
* @return string Formatted string
|
||||
*/
|
||||
public static function toFormattedString($value, string $format, null|array|callable $callBack = null): string
|
||||
public static function toFormattedString($value, string $format, null|array|callable $callBack = null, bool $lessFloatPrecision = false): string
|
||||
{
|
||||
while (is_array($value)) {
|
||||
$value = array_shift($value);
|
||||
@@ -135,13 +136,13 @@ class Formatter extends BaseFormatter
|
||||
$formatx = str_replace('\"', self::QUOTE_REPLACEMENT, $format);
|
||||
if (preg_match(self::SECTION_SPLIT, $format) === 0 && preg_match(self::SYMBOL_AT, $formatx) === 1) {
|
||||
if (!str_contains($format, '"')) {
|
||||
return str_replace('@', StringHelper::convertToString($value), $format);
|
||||
return str_replace('@', StringHelper::convertToString($value, lessFloatPrecision: $lessFloatPrecision), $format);
|
||||
}
|
||||
//escape any dollar signs on the string, so they are not replaced with an empty value
|
||||
$value = str_replace(
|
||||
['$', '"'],
|
||||
['\$', self::QUOTE_REPLACEMENT],
|
||||
StringHelper::convertToString($value)
|
||||
StringHelper::convertToString($value, lessFloatPrecision: $lessFloatPrecision)
|
||||
);
|
||||
|
||||
return str_replace(
|
||||
@@ -153,14 +154,18 @@ class Formatter extends BaseFormatter
|
||||
|
||||
// If we have a text value, return it "as is"
|
||||
if (!is_numeric($value)) {
|
||||
return StringHelper::convertToString($value);
|
||||
return StringHelper::convertToString($value, lessFloatPrecision: $lessFloatPrecision);
|
||||
}
|
||||
|
||||
// For 'General' format code, we just pass the value although this is not entirely the way Excel does it,
|
||||
// it seems to round numbers to a total of 10 digits.
|
||||
if (($format === NumberFormat::FORMAT_GENERAL) || ($format === NumberFormat::FORMAT_TEXT)) {
|
||||
if (is_float($value) && $lessFloatPrecision) {
|
||||
return self::adjustSeparators((string) $value);
|
||||
}
|
||||
|
||||
return self::adjustSeparators(
|
||||
StringHelper::convertToString($value)
|
||||
StringHelper::convertToString($value, lessFloatPrecision: $lessFloatPrecision)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,6 @@ final class Locale
|
||||
|
||||
public function __construct(?string $locale, int $style)
|
||||
{
|
||||
if (class_exists(NumberFormatter::class) === false) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$formatterLocale = str_replace('-', '_', $locale ?? '');
|
||||
$this->formatter = new NumberFormatter($formatterLocale, $style);
|
||||
if ($this->formatter->getLocale() !== $formatterLocale) {
|
||||
|
||||
@@ -400,6 +400,7 @@ class Style extends Supervisor
|
||||
}
|
||||
|
||||
// Find existing style by hash.
|
||||
/** @var string $styleHash */
|
||||
$existingStyle = self::$cachedStyles['styleByHash'][$styleHash] ?? null;
|
||||
|
||||
if (!$existingStyle) {
|
||||
|
||||
@@ -274,7 +274,8 @@ class Rule
|
||||
throw new PhpSpreadsheetException('Invalid rule value for column AutoFilter Rule.');
|
||||
}
|
||||
// Set the dateTime grouping that we've anticipated
|
||||
$this->setGrouping(self::DATE_TIME_GROUPS[$grouping]);
|
||||
// I have no idea what Phpstan is complaining about below
|
||||
$this->setGrouping(self::DATE_TIME_GROUPS[$grouping]); // @phpstan-ignore-line
|
||||
}
|
||||
$this->value = $value;
|
||||
|
||||
|
||||
@@ -422,7 +422,7 @@ class BaseDrawing implements IComparable
|
||||
return md5(
|
||||
$this->name
|
||||
. $this->description
|
||||
. (($this->worksheet === null) ? '' : (string) $this->worksheet->getHashInt())
|
||||
. (($this->worksheet === null) ? '' : (string) spl_object_id($this->worksheet))
|
||||
. $this->coordinates
|
||||
. $this->offsetX
|
||||
. $this->offsetY
|
||||
|
||||
@@ -6,9 +6,6 @@ use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension;
|
||||
|
||||
class RowDimension extends Dimension
|
||||
{
|
||||
/**
|
||||
* Row index.
|
||||
*/
|
||||
private ?int $rowIndex;
|
||||
|
||||
/**
|
||||
@@ -23,9 +20,9 @@ class RowDimension extends Dimension
|
||||
*/
|
||||
private bool $zeroHeight = false;
|
||||
|
||||
private bool $customFormat = false;
|
||||
|
||||
/**
|
||||
* Create a new RowDimension.
|
||||
*
|
||||
* @param ?int $index Numeric row index
|
||||
*/
|
||||
public function __construct(?int $index = 0)
|
||||
@@ -37,19 +34,11 @@ class RowDimension extends Dimension
|
||||
parent::__construct(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Row Index.
|
||||
*/
|
||||
public function getRowIndex(): ?int
|
||||
{
|
||||
return $this->rowIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Row Index.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setRowIndex(int $index): static
|
||||
{
|
||||
$this->rowIndex = $index;
|
||||
@@ -76,35 +65,41 @@ class RowDimension extends Dimension
|
||||
* @param float $height in points. A value of -1 tells Excel to display this column in its default height.
|
||||
* By default, this will be the passed argument value; but this method also accepts an optional unit of measure
|
||||
* argument, and will convert the passed argument value to points from the specified UoM
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setRowHeight(float $height, ?string $unitOfMeasure = null): static
|
||||
{
|
||||
$this->height = ($unitOfMeasure === null || $height < 0)
|
||||
? $height
|
||||
: (new CssDimension("{$height}{$unitOfMeasure}"))->height();
|
||||
$this->customFormat = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ZeroHeight.
|
||||
*/
|
||||
public function getZeroHeight(): bool
|
||||
{
|
||||
return $this->zeroHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ZeroHeight.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setZeroHeight(bool $zeroHeight): static
|
||||
{
|
||||
$this->zeroHeight = $zeroHeight;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCustomFormat(): bool
|
||||
{
|
||||
return $this->customFormat;
|
||||
}
|
||||
|
||||
public function setCustomFormat(bool $customFormat, ?float $height = -1): self
|
||||
{
|
||||
$this->customFormat = $customFormat;
|
||||
if ($height !== null) {
|
||||
$this->height = $height;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,11 +306,6 @@ class Worksheet
|
||||
*/
|
||||
private ?Color $tabColor = null;
|
||||
|
||||
/**
|
||||
* Hash.
|
||||
*/
|
||||
private int $hash;
|
||||
|
||||
/**
|
||||
* CodeName.
|
||||
*/
|
||||
@@ -323,7 +318,6 @@ class Worksheet
|
||||
{
|
||||
// Set parent and title
|
||||
$this->parent = $parent;
|
||||
$this->hash = spl_object_id($this);
|
||||
$this->setTitle($title, false);
|
||||
// setTitle can change $pTitle
|
||||
$this->setCodeName($this->getTitle());
|
||||
@@ -380,11 +374,6 @@ class Worksheet
|
||||
unset($this->rowDimensions, $this->columnDimensions, $this->tableCollection, $this->drawingCollection, $this->chartCollection, $this->autoFilter);
|
||||
}
|
||||
|
||||
public function __wakeup(): void
|
||||
{
|
||||
$this->hash = spl_object_id($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cell collection.
|
||||
*/
|
||||
@@ -2949,12 +2938,15 @@ class Worksheet
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $calculateFormulas Whether to calculate cell's value if it is a formula.
|
||||
* @param null|bool|float|int|RichText|string $nullValue value to use when null
|
||||
* @param bool $formatData Whether to format data according to cell's style.
|
||||
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Calculation\Exception
|
||||
*/
|
||||
protected function cellToArray(Cell $cell, bool $calculateFormulas, bool $formatData, mixed $nullValue): mixed
|
||||
protected function cellToArray(Cell $cell, bool $calculateFormulas, bool $formatData, mixed $nullValue, bool $lessFloatPrecision = false): mixed
|
||||
{
|
||||
$returnValue = $nullValue;
|
||||
|
||||
@@ -2971,7 +2963,8 @@ class Worksheet
|
||||
$returnValuex = $returnValue;
|
||||
$returnValue = NumberFormat::toFormattedString(
|
||||
$returnValuex,
|
||||
$style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL
|
||||
$style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL,
|
||||
lessFloatPrecision: $lessFloatPrecision
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2989,6 +2982,8 @@ class Worksheet
|
||||
* True - Return rows and columns indexed by their actual row and column IDs
|
||||
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
|
||||
* True - Don't return values for rows/columns that are defined as hidden.
|
||||
* @param bool $reduceArrays If true and result is a formula which evaluates to an array, reduce it to the top leftmost value.
|
||||
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
|
||||
*
|
||||
* @return mixed[][]
|
||||
*/
|
||||
@@ -2999,12 +2994,14 @@ class Worksheet
|
||||
bool $formatData = true,
|
||||
bool $returnCellRef = false,
|
||||
bool $ignoreHidden = false,
|
||||
bool $reduceArrays = false
|
||||
bool $reduceArrays = false,
|
||||
bool $lessFloatPrecision = false
|
||||
): array {
|
||||
$returnValue = [];
|
||||
|
||||
// Loop through rows
|
||||
foreach ($this->rangeToArrayYieldRows($range, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays) as $rowRef => $rowArray) {
|
||||
foreach ($this->rangeToArrayYieldRows($range, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays, $lessFloatPrecision) as $rowRef => $rowArray) {
|
||||
/** @var int $rowRef */
|
||||
$returnValue[$rowRef] = $rowArray;
|
||||
}
|
||||
|
||||
@@ -3022,6 +3019,8 @@ class Worksheet
|
||||
* True - Return rows and columns indexed by their actual row and column IDs
|
||||
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
|
||||
* True - Don't return values for rows/columns that are defined as hidden.
|
||||
* @param bool $reduceArrays If true and result is a formula which evaluates to an array, reduce it to the top leftmost value.
|
||||
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
|
||||
*
|
||||
* @return mixed[][]
|
||||
*/
|
||||
@@ -3032,14 +3031,16 @@ class Worksheet
|
||||
bool $formatData = true,
|
||||
bool $returnCellRef = false,
|
||||
bool $ignoreHidden = false,
|
||||
bool $reduceArrays = false
|
||||
bool $reduceArrays = false,
|
||||
bool $lessFloatPrecision = false,
|
||||
): array {
|
||||
$returnValue = [];
|
||||
|
||||
$parts = explode(',', $ranges);
|
||||
foreach ($parts as $part) {
|
||||
// Loop through rows
|
||||
foreach ($this->rangeToArrayYieldRows($part, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays) as $rowRef => $rowArray) {
|
||||
foreach ($this->rangeToArrayYieldRows($part, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays, $lessFloatPrecision) as $rowRef => $rowArray) {
|
||||
/** @var int $rowRef */
|
||||
$returnValue[$rowRef] = $rowArray;
|
||||
}
|
||||
}
|
||||
@@ -3058,6 +3059,8 @@ class Worksheet
|
||||
* True - Return rows and columns indexed by their actual row and column IDs
|
||||
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
|
||||
* True - Don't return values for rows/columns that are defined as hidden.
|
||||
* @param bool $reduceArrays If true and result is a formula which evaluates to an array, reduce it to the top leftmost value.
|
||||
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
|
||||
*
|
||||
* @return Generator<array<mixed>>
|
||||
*/
|
||||
@@ -3068,7 +3071,8 @@ class Worksheet
|
||||
bool $formatData = true,
|
||||
bool $returnCellRef = false,
|
||||
bool $ignoreHidden = false,
|
||||
bool $reduceArrays = false
|
||||
bool $reduceArrays = false,
|
||||
bool $lessFloatPrecision = false
|
||||
) {
|
||||
$range = Validations::validateCellOrCellRange($range);
|
||||
|
||||
@@ -3100,9 +3104,30 @@ class Worksheet
|
||||
|
||||
$index = ($row - 1) * AddressRange::MAX_COLUMN_INT + 1;
|
||||
$indexPlus = $index + AddressRange::MAX_COLUMN_INT - 1;
|
||||
|
||||
// Binary search to quickly approach the correct index
|
||||
$keyIndex = intdiv($keysCount, 2);
|
||||
$boundLow = 0;
|
||||
$boundHigh = $keysCount - 1;
|
||||
while ($boundLow <= $boundHigh) {
|
||||
$keyIndex = intdiv($boundLow + $boundHigh, 2);
|
||||
if ($keys[$keyIndex] < $index) {
|
||||
$boundLow = $keyIndex + 1;
|
||||
} elseif ($keys[$keyIndex] > $index) {
|
||||
$boundHigh = $keyIndex - 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Realign to the proper index value
|
||||
while ($keyIndex > 0 && $keys[$keyIndex] > $index) {
|
||||
--$keyIndex;
|
||||
}
|
||||
while ($keyIndex < $keysCount && $keys[$keyIndex] < $index) {
|
||||
++$keyIndex;
|
||||
}
|
||||
|
||||
while ($keyIndex < $keysCount && $keys[$keyIndex] <= $indexPlus) {
|
||||
$key = $keys[$keyIndex];
|
||||
$thisRow = intdiv($key - 1, AddressRange::MAX_COLUMN_INT) + 1;
|
||||
@@ -3113,7 +3138,7 @@ class Worksheet
|
||||
$columnRef = $returnCellRef ? $col : ($thisCol - $minColInt);
|
||||
$cell = $this->cellCollection->get("{$col}{$thisRow}");
|
||||
if ($cell !== null) {
|
||||
$value = $this->cellToArray($cell, $calculateFormulas, $formatData, $nullValue);
|
||||
$value = $this->cellToArray($cell, $calculateFormulas, $formatData, $nullValue, lessFloatPrecision: $lessFloatPrecision);
|
||||
if ($reduceArrays) {
|
||||
while (is_array($value)) {
|
||||
$value = array_shift($value);
|
||||
@@ -3189,7 +3214,7 @@ class Worksheet
|
||||
|
||||
if ($namedRange->getLocalOnly()) {
|
||||
$worksheet = $namedRange->getWorksheet();
|
||||
if ($worksheet === null || $this->hash !== $worksheet->getHashInt()) {
|
||||
if ($worksheet === null || $this !== $worksheet) {
|
||||
if ($returnNullIfInvalid) {
|
||||
return null;
|
||||
}
|
||||
@@ -3214,6 +3239,8 @@ class Worksheet
|
||||
* True - Return rows and columns indexed by their actual row and column IDs
|
||||
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
|
||||
* True - Don't return values for rows/columns that are defined as hidden.
|
||||
* @param bool $reduceArrays If true and result is a formula which evaluates to an array, reduce it to the top leftmost value.
|
||||
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
|
||||
*
|
||||
* @return mixed[][]
|
||||
*/
|
||||
@@ -3224,7 +3251,8 @@ class Worksheet
|
||||
bool $formatData = true,
|
||||
bool $returnCellRef = false,
|
||||
bool $ignoreHidden = false,
|
||||
bool $reduceArrays = false
|
||||
bool $reduceArrays = false,
|
||||
bool $lessFloatPrecision = false
|
||||
): array {
|
||||
$retVal = [];
|
||||
$namedRange = $this->validateNamedRange($definedName);
|
||||
@@ -3233,7 +3261,7 @@ class Worksheet
|
||||
$cellRange = str_replace('$', '', $cellRange);
|
||||
$workSheet = $namedRange->getWorksheet();
|
||||
if ($workSheet !== null) {
|
||||
$retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays);
|
||||
$retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays, $lessFloatPrecision);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3250,6 +3278,8 @@ class Worksheet
|
||||
* True - Return rows and columns indexed by their actual row and column IDs
|
||||
* @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
|
||||
* True - Don't return values for rows/columns that are defined as hidden.
|
||||
* @param bool $reduceArrays If true and result is a formula which evaluates to an array, reduce it to the top leftmost value.
|
||||
* @param bool $lessFloatPrecision If true, formatting unstyled floats will convert them to a more human-friendly but less computationally accurate value
|
||||
*
|
||||
* @return mixed[][]
|
||||
*/
|
||||
@@ -3259,7 +3289,8 @@ class Worksheet
|
||||
bool $formatData = true,
|
||||
bool $returnCellRef = false,
|
||||
bool $ignoreHidden = false,
|
||||
bool $reduceArrays = false
|
||||
bool $reduceArrays = false,
|
||||
bool $lessFloatPrecision = false
|
||||
): array {
|
||||
// Garbage collect...
|
||||
$this->garbageCollect();
|
||||
@@ -3270,7 +3301,7 @@ class Worksheet
|
||||
$maxRow = $this->getHighestRow();
|
||||
|
||||
// Return
|
||||
return $this->rangeToArray("A1:{$maxCol}{$maxRow}", $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays);
|
||||
return $this->rangeToArray("A1:{$maxCol}{$maxRow}", $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden, $reduceArrays, $lessFloatPrecision);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3321,11 +3352,7 @@ class Worksheet
|
||||
}
|
||||
|
||||
// Cache values
|
||||
if ($highestColumn < 1) {
|
||||
$this->cachedHighestColumn = 1;
|
||||
} else {
|
||||
$this->cachedHighestColumn = $highestColumn;
|
||||
}
|
||||
$this->cachedHighestColumn = max(1, $highestColumn);
|
||||
/** @var int $highestRow */
|
||||
$this->cachedHighestRow = $highestRow;
|
||||
|
||||
@@ -3333,9 +3360,14 @@ class Worksheet
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 5.2.0 Serves no useful purpose. No replacement.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getHashInt(): int
|
||||
{
|
||||
return $this->hash;
|
||||
return spl_object_id($this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3722,7 +3754,6 @@ class Worksheet
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->hash = spl_object_id($this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -123,13 +123,18 @@ abstract class BaseWriter implements IWriter
|
||||
$this->shouldCloseFile = true;
|
||||
}
|
||||
|
||||
protected function tryClose(): bool
|
||||
{
|
||||
return fclose($this->fileHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close file handle only if we opened it ourselves.
|
||||
*/
|
||||
protected function maybeCloseFileHandle(): void
|
||||
{
|
||||
if ($this->shouldCloseFile) {
|
||||
if (!fclose($this->fileHandle)) {
|
||||
if (!$this->tryClose()) {
|
||||
throw new Exception('Could not close file after writing.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1115,7 +1115,12 @@ class Html extends BaseWriter
|
||||
if ($textAlign) {
|
||||
$css['text-align'] = $textAlign;
|
||||
if (in_array($textAlign, ['left', 'right'])) {
|
||||
$css['padding-' . $textAlign] = (string) ((int) $alignment->getIndent() * 9) . 'px';
|
||||
$css['padding-' . $textAlign] = (string) ($alignment->getIndent() * Alignment::INDENT_UNITS_TO_PIXELS) . 'px';
|
||||
}
|
||||
} else {
|
||||
$indent = $alignment->getIndent();
|
||||
if ($indent !== 0) {
|
||||
$css['text-indent'] = (string) ($alignment->getIndent() * Alignment::INDENT_UNITS_TO_PIXELS) . 'px';
|
||||
}
|
||||
}
|
||||
$rotation = $alignment->getTextRotation();
|
||||
@@ -1126,6 +1131,12 @@ class Html extends BaseWriter
|
||||
$css['transform'] = "rotate({$rotation}deg)";
|
||||
}
|
||||
}
|
||||
$direction = $alignment->getReadOrder();
|
||||
if ($direction === Alignment::READORDER_LTR) {
|
||||
$css['direction'] = 'ltr';
|
||||
} elseif ($direction === Alignment::READORDER_RTL) {
|
||||
$css['direction'] = 'rtl';
|
||||
}
|
||||
|
||||
return $css;
|
||||
}
|
||||
@@ -1516,7 +1527,6 @@ class Html extends BaseWriter
|
||||
/** @param string|string[] $cssClass */
|
||||
private function generateRowCellData(Worksheet $worksheet, null|Cell|string $cell, array|string &$cssClass): string
|
||||
{
|
||||
$cellData = ' ';
|
||||
if ($cell instanceof Cell) {
|
||||
$cellData = '';
|
||||
// Don't know what this does, and no test cases.
|
||||
@@ -1565,13 +1575,21 @@ class Html extends BaseWriter
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$cellData = "$cell";
|
||||
// Use default borders for empty cell
|
||||
if (is_string($cssClass)) {
|
||||
$cssClass .= ' style0';
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Browsers may remove an entirely empty row.
|
||||
* An interesting option is to leave an empty cell empty using css.
|
||||
* td:empty::after{content: "\00a0";}
|
||||
* This works well in modern browsers.
|
||||
* Alas, none of our Pdf writers can handle it.
|
||||
*/
|
||||
|
||||
return $cellData;
|
||||
return (trim($cellData) === '') ? ' ' : $cellData;
|
||||
}
|
||||
|
||||
private function generateRowIncludeCharts(Worksheet $worksheet, string $coordinate): string
|
||||
@@ -2093,6 +2111,7 @@ class Html extends BaseWriter
|
||||
// For each of the omitted rows we found above, the affected rowspans should be subtracted by 1
|
||||
if (isset($this->isSpannedRow[$sheetIndex])) {
|
||||
foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) {
|
||||
/** @var int $rowIndex */
|
||||
$adjustedBaseCells = [];
|
||||
$c = -1;
|
||||
$e = $countColumns - 1;
|
||||
|
||||
@@ -151,6 +151,7 @@ class Style
|
||||
$vAlign = $style->getAlignment()->getVertical();
|
||||
$wrap = $style->getAlignment()->getWrapText();
|
||||
$indent = $style->getAlignment()->getIndent();
|
||||
$readOrder = $style->getAlignment()->getReadOrder();
|
||||
|
||||
$this->writer->startElement('style:table-cell-properties');
|
||||
if (!empty($vAlign) || $wrap) {
|
||||
@@ -172,7 +173,7 @@ class Style
|
||||
|
||||
$this->writer->endElement();
|
||||
|
||||
if ($hAlign !== '' || !empty($indent)) {
|
||||
if ($hAlign !== '' || !empty($indent) || $readOrder === Alignment::READORDER_RTL || $readOrder === Alignment::READORDER_LTR) {
|
||||
$this->writer
|
||||
->startElement('style:paragraph-properties');
|
||||
if ($hAlign !== '') {
|
||||
@@ -182,6 +183,11 @@ class Style
|
||||
$indentString = sprintf('%.4f', $indent * self::INDENT_TO_INCHES) . 'in';
|
||||
$this->writer->writeAttribute('fo:margin-left', $indentString);
|
||||
}
|
||||
if ($readOrder === Alignment::READORDER_RTL) {
|
||||
$this->writer->writeAttribute('style:writing-mode', 'rl-tb');
|
||||
} elseif ($readOrder === Alignment::READORDER_LTR) {
|
||||
$this->writer->writeAttribute('style:writing-mode', 'lr-tb');
|
||||
}
|
||||
$this->writer->endElement();
|
||||
}
|
||||
}
|
||||
@@ -302,6 +308,21 @@ class Style
|
||||
$this->writer->endElement(); // Close style:style
|
||||
}
|
||||
|
||||
public function writeDefaultRowStyle(RowDimension $rowDimension, int $sheetId): void
|
||||
{
|
||||
$this->writer->startElement('style:style');
|
||||
$this->writer->writeAttribute('style:family', 'table-row');
|
||||
$this->writer->writeAttribute(
|
||||
'style:name',
|
||||
sprintf('%s%d', self::ROW_STYLE_PREFIX, $sheetId)
|
||||
);
|
||||
|
||||
$this->writeRowProperties($rowDimension);
|
||||
|
||||
// End
|
||||
$this->writer->endElement(); // Close style:style
|
||||
}
|
||||
|
||||
public function writeTableStyle(Worksheet $worksheet, int $sheetId): void
|
||||
{
|
||||
$this->writer->startElement('style:style');
|
||||
|
||||
@@ -174,6 +174,11 @@ class Content extends WriterPart
|
||||
'table:style-name',
|
||||
sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex())
|
||||
);
|
||||
} elseif ($sheet->getDefaultRowDimension()->getRowHeight() > 0.0 && !$sheet->getRowDimension($row->getRowIndex())->getCustomFormat()) {
|
||||
$objWriter->writeAttribute(
|
||||
'table:style-name',
|
||||
sprintf('%s%d', Style::ROW_STYLE_PREFIX, $sheetIndex)
|
||||
);
|
||||
}
|
||||
$this->writeCells($objWriter, $cellIterator);
|
||||
$objWriter->endElement();
|
||||
@@ -323,6 +328,10 @@ class Content extends WriterPart
|
||||
}
|
||||
for ($i = 0; $i < $sheetCount; ++$i) {
|
||||
$worksheet = $spreadsheet->getSheet($i);
|
||||
$default = $worksheet->getDefaultRowDimension();
|
||||
if ($default->getRowHeight() > 0.0) {
|
||||
$styleWriter->writeDefaultRowStyle($default, $i);
|
||||
}
|
||||
foreach ($worksheet->getRowDimensions() as $rowDimension) {
|
||||
if ($rowDimension->getRowHeight() > 0.0) {
|
||||
$styleWriter->writeRowStyles($rowDimension, $i);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
|
||||
use Composer\Pcre\Preg;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
@@ -88,9 +89,7 @@ class Settings extends WriterPart
|
||||
$objWriter->writeAttribute('config:name', $worksheet->getTitle());
|
||||
|
||||
$this->writeSelectedCells($objWriter, $worksheet);
|
||||
if ($worksheet->getFreezePane() !== null) {
|
||||
$this->writeFreezePane($objWriter, $worksheet);
|
||||
}
|
||||
$this->writeFreezePane($objWriter, $worksheet);
|
||||
|
||||
$objWriter->endElement(); // config:config-item-map-entry Worksheet
|
||||
}
|
||||
@@ -125,7 +124,7 @@ class Settings extends WriterPart
|
||||
|
||||
private function writeFreezePane(XMLWriter $objWriter, Worksheet $worksheet): void
|
||||
{
|
||||
$freezePane = CellAddress::fromCellAddress($worksheet->getFreezePane() ?? '');
|
||||
$freezePane = CellAddress::fromCellAddress($worksheet->getFreezePane() ?? 'A1');
|
||||
if ($freezePane->cellAddress() === 'A1') {
|
||||
return;
|
||||
}
|
||||
@@ -139,7 +138,7 @@ class Settings extends WriterPart
|
||||
$this->writeSplitValue($objWriter, 'PositionLeft', 'short', '0');
|
||||
$this->writeSplitValue($objWriter, 'PositionRight', 'short', (string) ($columnId - 1));
|
||||
|
||||
for ($column = 'A'; $column !== $columnName; ++$column) {
|
||||
for ($column = 'A'; $column !== $columnName; StringHelper::stringIncrement($column)) {
|
||||
$worksheet->getColumnDimension($column)->setAutoSize(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,13 +44,6 @@ class Dompdf extends Pdf
|
||||
$orientation = ($orientation == 'L') ? 'landscape' : 'portrait';
|
||||
|
||||
// Create PDF
|
||||
$restoreHandler = false;
|
||||
if (PHP_VERSION_ID >= self::$temporaryVersionCheck) {
|
||||
// @codeCoverageIgnoreStart
|
||||
set_error_handler(self::specialErrorHandler(...));
|
||||
$restoreHandler = true;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
$pdf = $this->createExternalWriterInstance();
|
||||
$pdf->setPaper($paperSize, $orientation);
|
||||
|
||||
@@ -58,29 +51,8 @@ class Dompdf extends Pdf
|
||||
$pdf->render();
|
||||
|
||||
// Write to file
|
||||
fwrite($fileHandle, $pdf->output() ?? '');
|
||||
fwrite($fileHandle, $pdf->output());
|
||||
|
||||
if ($restoreHandler) {
|
||||
restore_error_handler(); // @codeCoverageIgnore
|
||||
}
|
||||
parent::restoreStateAfterSave();
|
||||
}
|
||||
|
||||
protected static int $temporaryVersionCheck = 80500;
|
||||
|
||||
/**
|
||||
* Temporary handler for Php8.5 waiting for Dompdf release.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function specialErrorHandler(int $errno, string $errstr, string $filename, int $lineno): bool
|
||||
{
|
||||
if ($errno === E_DEPRECATED) {
|
||||
if (preg_match('/canonical|imagedestroy|http_get_last_response_headers/', $errstr) === 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // continue error handling
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,13 @@ class Mpdf extends Pdf
|
||||
|
||||
// Create PDF
|
||||
$config = ['tempDir' => $this->tempDir . '/mpdf'];
|
||||
$restoreHandler = false;
|
||||
if (PHP_VERSION_ID >= self::$temporaryVersionCheck) {
|
||||
// @codeCoverageIgnoreStart
|
||||
set_error_handler(self::specialErrorHandler(...));
|
||||
$restoreHandler = true;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
$pdf = $this->createExternalWriterInstance($config);
|
||||
$ortmp = $orientation;
|
||||
$pdf->_setPageSize($paperSize, $ortmp);
|
||||
@@ -83,9 +90,30 @@ class Mpdf extends Pdf
|
||||
$str = $pdf->Output('', 'S');
|
||||
fwrite($fileHandle, $str);
|
||||
|
||||
if ($restoreHandler) {
|
||||
restore_error_handler(); // @codeCoverageIgnore
|
||||
}
|
||||
parent::restoreStateAfterSave();
|
||||
}
|
||||
|
||||
protected static int $temporaryVersionCheck = 80500;
|
||||
|
||||
/**
|
||||
* Temporary handler for Php8.5 waiting for Dompdf release.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function specialErrorHandler(int $errno, string $errstr, string $filename, int $lineno): bool
|
||||
{
|
||||
if ($errno === E_DEPRECATED) {
|
||||
if (preg_match('/Providing an empty string is deprecated/', $errstr) === 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // continue error handling
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert inches to mm.
|
||||
*/
|
||||
|
||||
@@ -30,9 +30,15 @@ class Tcpdf extends Pdf
|
||||
*/
|
||||
protected function createExternalWriterInstance(string $orientation, string $unit, $paperSize): \TCPDF
|
||||
{
|
||||
$this->defines();
|
||||
|
||||
return new \TCPDF($orientation, $unit, $paperSize);
|
||||
}
|
||||
|
||||
protected function defines(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Spreadsheet to file.
|
||||
*
|
||||
|
||||
27
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/TcpdfNoDie.php
vendored
Normal file
27
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/TcpdfNoDie.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Writer\Pdf;
|
||||
|
||||
class TcpdfNoDie extends Tcpdf
|
||||
{
|
||||
/**
|
||||
* By default, Tcpdf will die sometimes rather than throwing exception.
|
||||
* And this is controlled by a defined constant in the global namespace,
|
||||
* not by an instance property. Ugh!
|
||||
* Using this class instead of the class which it extends will probably
|
||||
* be suitable for most users. But not for those who have customized
|
||||
* their config file. Which is why this isn't the default, so that
|
||||
* there is no breaking change for those users.
|
||||
* Note that if both Tcpdf and TcpdfNoDie are used in the same process,
|
||||
* the first one used "wins" the battle of the defines.
|
||||
*/
|
||||
protected function defines(): void
|
||||
{
|
||||
if (!defined('K_TCPDF_EXTERNAL_CONFIG')) {
|
||||
define('K_TCPDF_EXTERNAL_CONFIG', true);
|
||||
}
|
||||
if (!defined('K_TCPDF_THROW_EXCEPTION_ERROR')) {
|
||||
define('K_TCPDF_THROW_EXCEPTION_ERROR', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,32 +47,20 @@ class CellDataValidation
|
||||
{
|
||||
$validationType = $dataValidation->getType();
|
||||
|
||||
if (array_key_exists($validationType, self::$validationTypeMap)) {
|
||||
return self::$validationTypeMap[$validationType];
|
||||
}
|
||||
|
||||
return self::$validationTypeMap[DataValidation::TYPE_NONE];
|
||||
return self::$validationTypeMap[$validationType] ?? self::$validationTypeMap[DataValidation::TYPE_NONE];
|
||||
}
|
||||
|
||||
public static function errorStyle(DataValidation $dataValidation): int
|
||||
{
|
||||
$errorStyle = $dataValidation->getErrorStyle();
|
||||
|
||||
if (array_key_exists($errorStyle, self::$errorStyleMap)) {
|
||||
return self::$errorStyleMap[$errorStyle];
|
||||
}
|
||||
|
||||
return self::$errorStyleMap[DataValidation::STYLE_STOP];
|
||||
return self::$errorStyleMap[$errorStyle] ?? self::$errorStyleMap[DataValidation::STYLE_STOP];
|
||||
}
|
||||
|
||||
public static function operator(DataValidation $dataValidation): int
|
||||
{
|
||||
$operator = $dataValidation->getOperator();
|
||||
|
||||
if (array_key_exists($operator, self::$operatorMap)) {
|
||||
return self::$operatorMap[$operator];
|
||||
}
|
||||
|
||||
return self::$operatorMap[DataValidation::OPERATOR_BETWEEN];
|
||||
return self::$operatorMap[$operator] ?? self::$operatorMap[DataValidation::OPERATOR_BETWEEN];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,6 @@ class ErrorCode
|
||||
|
||||
public static function error(string $errorCode): int
|
||||
{
|
||||
if (array_key_exists($errorCode, self::$errorCodeMap)) {
|
||||
return self::$errorCodeMap[$errorCode];
|
||||
}
|
||||
|
||||
return 0;
|
||||
return self::$errorCodeMap[$errorCode] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,15 +211,14 @@ class Worksheet extends BIFFwriter
|
||||
$maxC = $this->phpSheet->getHighestColumn();
|
||||
|
||||
// Determine lowest and highest column and row
|
||||
$this->firstRowIndex = $minR;
|
||||
$this->lastRowIndex = ($maxR > 65535) ? 65535 : $maxR;
|
||||
// BIFF8 DIMENSIONS record requires 0-based indices for both rows and columns
|
||||
// Row methods return 1-based values (Excel UI), so subtract 1 to convert to 0-based
|
||||
$this->firstRowIndex = $minR - 1;
|
||||
$this->lastRowIndex = ($maxR > 65536) ? 65535 : ($maxR - 1);
|
||||
|
||||
$this->firstColumnIndex = Coordinate::columnIndexFromString($minC);
|
||||
$this->lastColumnIndex = Coordinate::columnIndexFromString($maxC);
|
||||
|
||||
if ($this->lastColumnIndex > 255) {
|
||||
$this->lastColumnIndex = 255;
|
||||
}
|
||||
// Column methods return 1-based values (columnIndexFromString('A') = 1), so subtract 1
|
||||
$this->firstColumnIndex = Coordinate::columnIndexFromString($minC) - 1;
|
||||
$this->lastColumnIndex = min(255, Coordinate::columnIndexFromString($maxC) - 1);
|
||||
$this->writerWorkbook = $writerWorkbook;
|
||||
}
|
||||
|
||||
@@ -258,7 +257,8 @@ class Worksheet extends BIFFwriter
|
||||
}
|
||||
|
||||
$columnDimensions = $phpSheet->getColumnDimensions();
|
||||
$maxCol = $this->lastColumnIndex - 1;
|
||||
// lastColumnIndex is now 0-based, so no need to subtract 1
|
||||
$maxCol = $this->lastColumnIndex;
|
||||
for ($i = 0; $i <= $maxCol; ++$i) {
|
||||
$hidden = 0;
|
||||
$level = 0;
|
||||
|
||||
@@ -220,8 +220,9 @@ class Xf
|
||||
$header = pack('vv', $record, $length);
|
||||
|
||||
//BIFF8 options: identation, shrinkToFit and text direction
|
||||
$biff8_options = $this->style->getAlignment()->getIndent();
|
||||
$biff8_options = $this->style->getAlignment()->getIndent() & 15;
|
||||
$biff8_options |= (int) $this->style->getAlignment()->getShrinkToFit() << 4;
|
||||
$biff8_options |= $this->style->getAlignment()->getReadOrder() << 6;
|
||||
|
||||
$data = pack('vvvC', $ifnt, $ifmt, $style, $align);
|
||||
$data .= pack('CCC', self::mapTextRotation((int) $this->style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib);
|
||||
|
||||
@@ -66,6 +66,8 @@ class FunctionPrefix
|
||||
. '|var[.]s'
|
||||
. '|weibull[.]dist'
|
||||
. '|z[.]test'
|
||||
// probably added with Excel 2010 but not properly documented
|
||||
. '|base'
|
||||
// functions added with Excel 2013
|
||||
. '|acot'
|
||||
. '|acoth'
|
||||
|
||||
@@ -219,7 +219,7 @@ class Rels extends WriterPart
|
||||
// (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::writeDrawings)
|
||||
reset($drawingOriginalIds);
|
||||
$relPath = key($drawingOriginalIds);
|
||||
if (isset($drawingOriginalIds[$relPath])) {
|
||||
if (isset($relPath, $drawingOriginalIds[$relPath])) {
|
||||
$rId = (int) (substr($drawingOriginalIds[$relPath], 3));
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ class StringTable extends WriterPart
|
||||
foreach ($worksheet->getCellCollection()->getCoordinates() as $coordinate) {
|
||||
/** @var Cell $cell */
|
||||
$cell = $worksheet->getCellCollection()->get($coordinate);
|
||||
/** @var null|int|RichText|string */
|
||||
$cellValue = $cell->getValue();
|
||||
if (
|
||||
!is_object($cellValue)
|
||||
@@ -305,7 +306,7 @@ class StringTable extends WriterPart
|
||||
$objWriter->writeAttribute('val', $value);
|
||||
$alpha = $underlineColor->getAlpha();
|
||||
if (is_numeric($alpha)) {
|
||||
$objWriter->startElement('a:alpha');
|
||||
$objWriter->startElement($prefix . 'alpha');
|
||||
$objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha));
|
||||
$objWriter->endElement();
|
||||
}
|
||||
|
||||
@@ -272,6 +272,9 @@ class Style extends WriterPart
|
||||
$objWriter->endElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param-out true $fontStarted
|
||||
*/
|
||||
private function startFont(XMLWriter $objWriter, bool &$fontStarted): void
|
||||
{
|
||||
if (!$fontStarted) {
|
||||
|
||||
@@ -125,18 +125,35 @@ class Workbook extends WriterPart
|
||||
*/
|
||||
private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
|
||||
{
|
||||
if ($spreadsheet->getSecurity()->isSecurityEnabled()) {
|
||||
$security = $spreadsheet->getSecurity();
|
||||
if ($security->isSecurityEnabled()) {
|
||||
$objWriter->startElement('workbookProtection');
|
||||
$objWriter->writeAttribute('lockRevision', ($spreadsheet->getSecurity()->getLockRevision() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('lockStructure', ($spreadsheet->getSecurity()->getLockStructure() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('lockWindows', ($spreadsheet->getSecurity()->getLockWindows() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('lockRevision', ($security->getLockRevision() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('lockStructure', ($security->getLockStructure() ? 'true' : 'false'));
|
||||
$objWriter->writeAttribute('lockWindows', ($security->getLockWindows() ? 'true' : 'false'));
|
||||
|
||||
if ($spreadsheet->getSecurity()->getRevisionsPassword() != '') {
|
||||
$objWriter->writeAttribute('revisionsPassword', $spreadsheet->getSecurity()->getRevisionsPassword());
|
||||
if ($security->getRevisionsPassword() !== '') {
|
||||
$objWriter->writeAttribute('revisionsPassword', $security->getRevisionsPassword());
|
||||
} else {
|
||||
$hashValue = $security->getRevisionsHashValue();
|
||||
if ($hashValue !== '') {
|
||||
$objWriter->writeAttribute('revisionsAlgorithmName', $security->getRevisionsAlgorithmName());
|
||||
$objWriter->writeAttribute('revisionsHashValue', $hashValue);
|
||||
$objWriter->writeAttribute('revisionsSaltValue', $security->getRevisionsSaltValue());
|
||||
$objWriter->writeAttribute('revisionsSpinCount', (string) $security->getRevisionsSpinCount());
|
||||
}
|
||||
}
|
||||
|
||||
if ($spreadsheet->getSecurity()->getWorkbookPassword() != '') {
|
||||
$objWriter->writeAttribute('workbookPassword', $spreadsheet->getSecurity()->getWorkbookPassword());
|
||||
if ($security->getWorkbookPassword() !== '') {
|
||||
$objWriter->writeAttribute('workbookPassword', $security->getWorkbookPassword());
|
||||
} else {
|
||||
$hashValue = $security->getWorkbookHashValue();
|
||||
if ($hashValue !== '') {
|
||||
$objWriter->writeAttribute('workbookAlgorithmName', $security->getWorkbookAlgorithmName());
|
||||
$objWriter->writeAttribute('workbookHashValue', $hashValue);
|
||||
$objWriter->writeAttribute('workbookSaltValue', $security->getWorkbookSaltValue());
|
||||
$objWriter->writeAttribute('workbookSpinCount', (string) $security->getWorkbookSpinCount());
|
||||
}
|
||||
}
|
||||
|
||||
$objWriter->endElement();
|
||||
|
||||
@@ -1398,6 +1398,16 @@ class Worksheet extends WriterPart
|
||||
}
|
||||
}
|
||||
|
||||
$customHeightNeeded = false;
|
||||
if ($worksheet->getDefaultRowDimension()->getRowHeight() >= 0) {
|
||||
foreach ($worksheet->getRowDimensions() as $rowDimension) {
|
||||
if ($rowDimension->getCustomFormat()) {
|
||||
$customHeightNeeded = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$currentRow = 0;
|
||||
$emptyDimension = new RowDimension();
|
||||
while ($currentRow++ < $highestRow) {
|
||||
@@ -1411,6 +1421,7 @@ class Worksheet extends WriterPart
|
||||
|
||||
if ($writeCurrentRow) {
|
||||
// Start a new row
|
||||
$customFormatWritten = false;
|
||||
$objWriter->startElement('row');
|
||||
$objWriter->writeAttribute('r', "$currentRow");
|
||||
$objWriter->writeAttribute('spans', '1:' . $colCount);
|
||||
@@ -1419,6 +1430,12 @@ class Worksheet extends WriterPart
|
||||
if ($rowDimension->getRowHeight() >= 0) {
|
||||
$objWriter->writeAttribute('customHeight', '1');
|
||||
$objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight()));
|
||||
} elseif ($rowDimension->getCustomFormat()) {
|
||||
$objWriter->writeAttribute('customFormat', '1');
|
||||
$customFormatWritten = true;
|
||||
$objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight()));
|
||||
} elseif ($customHeightNeeded) {
|
||||
$objWriter->writeAttribute('customHeight', '1');
|
||||
}
|
||||
|
||||
// Row visibility
|
||||
@@ -1439,7 +1456,9 @@ class Worksheet extends WriterPart
|
||||
// Style
|
||||
if ($rowDimension->getXfIndex() !== null) {
|
||||
$objWriter->writeAttribute('s', (string) $rowDimension->getXfIndex());
|
||||
$objWriter->writeAttribute('customFormat', '1');
|
||||
if (!$customFormatWritten) {
|
||||
$objWriter->writeAttribute('customFormat', '1');
|
||||
}
|
||||
}
|
||||
|
||||
// Write cells
|
||||
|
||||
Reference in New Issue
Block a user