diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12b595933..6044ff12f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,11 +5,34 @@ 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).
-## TBD - 2.2.0
+## TBD - 3.0.0
+
+### Added
+
+- Nothing
+
+### Changed
+
+- Nothing
+
+### Deprecated
+
+- Nothing
+
+### Moved
+
+- Nothing
+
+### Fixed
+
+- Add Sheet may leave Active Sheet uninitialized. [Issue #4112](https://github.com/PHPOffice/PhpSpreadsheet/issues/4112) [PR #4114](https://github.com/PHPOffice/PhpSpreadsheet/pull/4114)
+
+## 2024-07-24 - 2.2.0
### Added
- Xlsx Reader Optionally Ignore Rows With No Cells. [Issue #3982](https://github.com/PHPOffice/PhpSpreadsheet/issues/3982) [PR #4035](https://github.com/PHPOffice/PhpSpreadsheet/pull/4035)
+- Means to change style without affecting current cell/sheet. [PR #4073](https://github.com/PHPOffice/PhpSpreadsheet/pull/4073)
- Option for CSV output file to have varying numbers of columns for each row. [Issue #1415](https://github.com/PHPOffice/PhpSpreadsheet/issues/1415) [PR #4076](https://github.com/PHPOffice/PhpSpreadsheet/pull/4076)
### Changed
@@ -34,10 +57,19 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Mpdf and Tcpdf Borders on Merged Cells. [Issue #3557](https://github.com/PHPOffice/PhpSpreadsheet/issues/3557) [PR #4047](https://github.com/PHPOffice/PhpSpreadsheet/pull/4047)
- Xls Conditional Format Improvements. [PR #4030](https://github.com/PHPOffice/PhpSpreadsheet/pull/4030) [PR #4033](https://github.com/PHPOffice/PhpSpreadsheet/pull/4033)
- Conditional Range Unions and Intersections [Issue #4039](https://github.com/PHPOffice/PhpSpreadsheet/issues/4039) [PR #4042](https://github.com/PHPOffice/PhpSpreadsheet/pull/4042)
+- Ods comments with newlines. [Issue #4081](https://github.com/PHPOffice/PhpSpreadsheet/issues/4081) [PR #4086](https://github.com/PHPOffice/PhpSpreadsheet/pull/4086)
+- Propagate errors in Text functions. [Issue #2581](https://github.com/PHPOffice/PhpSpreadsheet/issues/2581) [PR #4080](https://github.com/PHPOffice/PhpSpreadsheet/pull/4080)
- Csv Reader allow use of html mimetype. [Issue #4036](https://github.com/PHPOffice/PhpSpreadsheet/issues/4036) [PR #4040](https://github.com/PHPOffice/PhpSpreadsheet/pull/4040)
- Problem rendering line chart with missing plot label. [PR #4074](https://github.com/PHPOffice/PhpSpreadsheet/pull/4074)
- More RTL in Xlsx/Html Comments [Issue #4004](https://github.com/PHPOffice/PhpSpreadsheet/issues/4004) [PR #4065](https://github.com/PHPOffice/PhpSpreadsheet/pull/4065)
- Empty String in sharedStrings. [Issue #4063](https://github.com/PHPOffice/PhpSpreadsheet/issues/4063) [PR #4064](https://github.com/PHPOffice/PhpSpreadsheet/pull/4064)
+- Xlsx Writer RichText and TYPE_STRING. [Issue #476](https://github.com/PHPOffice/PhpSpreadsheet/issues/476) [PR #4094](https://github.com/PHPOffice/PhpSpreadsheet/pull/4094)
+- Ods boolean data. [Issue #460](https://github.com/PHPOffice/PhpSpreadsheet/issues/460) [PR #4093](https://github.com/PHPOffice/PhpSpreadsheet/pull/4093)
+- Html Writer Minor Fixes. [PR #4089](https://github.com/PHPOffice/PhpSpreadsheet/pull/4089)
+- Changes to INDEX function. [Issue #64](https://github.com/PHPOffice/PhpSpreadsheet/issues/64) [PR #4088](https://github.com/PHPOffice/PhpSpreadsheet/pull/4088)
+- Ods Reader and Whitespace Text Nodes. [Issue #804](https://github.com/PHPOffice/PhpSpreadsheet/issues/804) [PR #4087](https://github.com/PHPOffice/PhpSpreadsheet/pull/4087)
+- Ods Xml Reader and Whitespace Text Nodes. [Issue #804](https://github.com/PHPOffice/PhpSpreadsheet/issues/804) [PR #4087](https://github.com/PHPOffice/PhpSpreadsheet/pull/4087)
+- Treat invalid formulas as strings. [Issue #1310](https://github.com/PHPOffice/PhpSpreadsheet/issues/1310) [PR #4073](https://github.com/PHPOffice/PhpSpreadsheet/pull/4073)
## 2024-05-11 - 2.1.0
diff --git a/docs/index.md b/docs/index.md
index 9505181b7..d577d8982 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -20,6 +20,9 @@ allow you to read and write various spreadsheet file formats such as Excel and L
|CSV | ✓ | ✓ |
|PDF (using either the TCPDF, Dompdf or mPDF libraries, which need to be installed separately)| | ✓ |
+Note - reading or writing certain aspects of a spreadsheet may not be supported in all formats. For more details, please consult
+[Features Cross-reference](./references/features-cross-reference.md).
+
# Getting started
## Software requirements
diff --git a/docs/references/features-cross-reference.md b/docs/references/features-cross-reference.md
index 37668ba82..746357894 100644
--- a/docs/references/features-cross-reference.md
+++ b/docs/references/features-cross-reference.md
@@ -28,7 +28,7 @@
✔ |
✔ |
● |
- ● |
+ ● 6 |
✔ |
N/A |
N/A |
@@ -389,7 +389,7 @@
| Conditional Formatting |
- ● |
+ ✔ |
✔ |
✖ |
✖ |
@@ -414,7 +414,7 @@
✔ |
✔ |
✔ |
- ✖ |
+ ✔ |
✔ |
N/A |
N/A |
@@ -495,14 +495,14 @@
✔ |
N/A |
● |
- ● |
+ ● 7 |
| Number Format Mask |
✔ |
✔ |
✔ |
- ✔ |
+ ● |
✔ |
N/A |
✔ |
@@ -517,7 +517,7 @@
✔ |
N/A |
✖ |
- ● |
+ ● 7 |
| Horizontal |
@@ -583,7 +583,7 @@
✔ |
N/A |
✔ |
- ✔ |
+ ● 7 |
| Patterned |
@@ -605,7 +605,7 @@
✔ |
N/A |
✔ |
- ✔ |
+ ● 7 |
| Font Face |
@@ -704,7 +704,7 @@
✔ |
N/A |
● |
- ✔ |
+ ● 7 |
| Line Style |
@@ -797,13 +797,13 @@
| Alignment |
✖ 3 |
- ✖ |
+ ✔ |
✖ |
✖ |
✖ |
N/A |
✖ |
- ✖ |
+ ✔ |
| Background Image |
@@ -929,7 +929,7 @@
| Macros |
✖ |
- ✔ |
+ ● 5
| ✖ |
✖ |
✖ |
@@ -940,7 +940,7 @@
| Form Controls |
✖ |
- ✖ |
+ ● 4
| ✖ |
✖ |
✖ |
@@ -1001,6 +1001,10 @@
1.
2.
3.
+4.
+5.
+6.
+7.
## Writers
@@ -1184,7 +1188,7 @@
Row Height/Column Width |
✔ |
✔ |
- ✖ |
+ ✔ |
N/A |
✔ |
✔ |
@@ -1256,7 +1260,7 @@
Number Format Mask |
✔ |
✔ |
- ✔ |
+ ● |
N/A |
✔ |
✔ |
@@ -1472,10 +1476,10 @@
Merged Cells |
✔ |
✔ |
- ✖ |
+ ✔ |
N/A |
✔ |
- ✖ |
+ ✔ |
| Cell Comments |
@@ -1498,10 +1502,10 @@
| Alignment |
✖ |
- ✖ |
+ ✔ |
✖ |
N/A |
- ✖ |
+ ✔ |
N/A |
@@ -1606,7 +1610,7 @@
| Macros |
✖ |
- ✔ |
+ ● 5
| ✖ |
N/A |
✖ |
@@ -1615,7 +1619,7 @@
| Form Controls |
✖ |
- ✖ |
+ ● 4
| ✖ |
N/A |
✖ |
@@ -1803,8 +1807,8 @@
| Macros |
- $spreadsheet->getMacrosCode(); |
- $spreadsheet->setMacrosCode(); |
+ $spreadsheet->getMacrosCode();5 |
+ $spreadsheet->setMacrosCode();5 |
| Security |
diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php
index 2f13a1634..d372005a2 100644
--- a/src/PhpSpreadsheet/Calculation/Calculation.php
+++ b/src/PhpSpreadsheet/Calculation/Calculation.php
@@ -4065,7 +4065,7 @@ class Calculation
$opCharacter = $formula[$index]; // Get the first character of the value at the current index position
// Check for two-character operators (e.g. >=, <=, <>)
- if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) {
+ if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && isset($formula[$index + 1], self::$comparisonOperators[$formula[$index + 1]])) {
$opCharacter .= $formula[++$index];
}
// Find out if we're currently at the beginning of a number, variable, cell/row/column reference,
@@ -4792,13 +4792,20 @@ class Calculation
for ($row = 0; $row < $rows; ++$row) {
for ($column = 0; $column < $columns; ++$column) {
- $operand1[$row][$column]
- = Shared\StringHelper::substring(
- self::boolToString($operand1[$row][$column])
- . self::boolToString($operand2[$row][$column]),
- 0,
- DataType::MAX_STRING_LENGTH
- );
+ $op1x = self::boolToString($operand1[$row][$column]);
+ $op2x = self::boolToString($operand2[$row][$column]);
+ if (Information\ErrorValue::isError($op1x)) {
+ // no need to do anything
+ } elseif (Information\ErrorValue::isError($op2x)) {
+ $operand1[$row][$column] = $op2x;
+ } else {
+ $operand1[$row][$column]
+ = Shared\StringHelper::substring(
+ $op1x . $op2x,
+ 0,
+ DataType::MAX_STRING_LENGTH
+ );
+ }
}
}
$result = $operand1;
@@ -4808,7 +4815,13 @@ class Calculation
// using the concatenation operator
// with literals that fits in 32K,
// so I don't think we can overflow here.
- $result = self::FORMULA_STRING_QUOTE . str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE;
+ if (Information\ErrorValue::isError($operand1)) {
+ $result = $operand1;
+ } elseif (Information\ErrorValue::isError($operand2)) {
+ $result = $operand2;
+ } else {
+ $result = self::FORMULA_STRING_QUOTE . str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE;
+ }
}
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result));
$stack->push('Value', $result);
@@ -5074,9 +5087,10 @@ class Calculation
if ($cell === null || $pCellWorksheet === null) {
return $this->raiseFormulaError("undefined name '$token'");
}
+ $specifiedWorksheet = trim($matches[2], "'");
$this->debugLog->writeDebugLog('Evaluating Defined Name %s', $definedName);
- $namedRange = DefinedName::resolveName($definedName, $pCellWorksheet);
+ $namedRange = DefinedName::resolveName($definedName, $pCellWorksheet, $specifiedWorksheet);
// If not Defined Name, try as Table.
if ($namedRange === null && $this->spreadsheet !== null) {
$table = $this->spreadsheet->getTableByName($definedName);
@@ -5101,7 +5115,7 @@ class Calculation
return $this->raiseFormulaError("undefined name '$definedName'");
}
- $result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack);
+ $result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack, $specifiedWorksheet !== '');
if (isset($storeKey)) {
$branchStore[$storeKey] = $result;
}
@@ -5580,10 +5594,10 @@ class Calculation
return $args;
}
- private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksheet $cellWorksheet, Stack $stack): mixed
+ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksheet $cellWorksheet, Stack $stack, bool $ignoreScope = false): mixed
{
$definedNameScope = $namedRange->getScope();
- if ($definedNameScope !== null && $definedNameScope !== $cellWorksheet) {
+ if ($definedNameScope !== null && $definedNameScope !== $cellWorksheet && !$ignoreScope) {
// The defined name isn't in our current scope, so #REF
$result = ExcelError::REF();
$stack->push('Error', $result, $namedRange->getName());
diff --git a/src/PhpSpreadsheet/Calculation/Functions.php b/src/PhpSpreadsheet/Calculation/Functions.php
index 6b7450069..77f8317ae 100644
--- a/src/PhpSpreadsheet/Calculation/Functions.php
+++ b/src/PhpSpreadsheet/Calculation/Functions.php
@@ -26,6 +26,8 @@ class Functions
const RETURNDATE_PHP_DATETIME_OBJECT = 'O';
const RETURNDATE_EXCEL = 'E';
+ public const NOT_YET_IMPLEMENTED = '#Not Yet Implemented';
+
/**
* Compatibility mode to use for error checking and responses.
*/
@@ -123,7 +125,7 @@ class Functions
*/
public static function DUMMY(): string
{
- return '#Not Yet Implemented';
+ return self::NOT_YET_IMPLEMENTED;
}
public static function isMatrixValue(mixed $idx): bool
diff --git a/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php b/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php
index dcef43990..f3a746273 100644
--- a/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php
+++ b/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php
@@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Information;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class ErrorValue
{
@@ -35,7 +36,7 @@ class ErrorValue
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
- public static function isError(mixed $value = ''): array|bool
+ public static function isError(mixed $value = '', bool $tryNotImplemented = false): array|bool
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
@@ -44,6 +45,9 @@ class ErrorValue
if (!is_string($value)) {
return false;
}
+ if ($tryNotImplemented && $value === Functions::NOT_YET_IMPLEMENTED) {
+ return true;
+ }
return in_array($value, ExcelError::ERROR_CODES, true);
}
diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php b/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php
index d578854de..228b46448 100644
--- a/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php
+++ b/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php
@@ -81,7 +81,6 @@ class Matrix
}
$rowNum = $rowNum ?? 0;
- $originalColumnNum = $columnNum;
$columnNum = $columnNum ?? 0;
try {
@@ -91,6 +90,17 @@ class Matrix
return $e->getMessage();
}
+ if (is_array($matrix) && count($matrix) === 1 && $rowNum > 1) {
+ $matrixKey = array_keys($matrix)[0];
+ if (is_array($matrix[$matrixKey])) {
+ $tempMatrix = [];
+ foreach ($matrix[$matrixKey] as $key => $value) {
+ $tempMatrix[$key] = [$value];
+ }
+ $matrix = $tempMatrix;
+ }
+ }
+
if (!is_array($matrix) || ($rowNum > count($matrix))) {
return ExcelError::REF();
}
@@ -101,9 +111,6 @@ class Matrix
if ($columnNum > count($columnKeys)) {
return ExcelError::REF();
}
- if ($originalColumnNum === null && 1 < count($columnKeys)) {
- return ExcelError::REF();
- }
if ($columnNum === 0) {
return self::extractRowValue($matrix, $rowKeys, $rowNum);
diff --git a/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php b/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php
index 83cc4ee1f..6667bac54 100644
--- a/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php
+++ b/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php
@@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class CaseConvert
@@ -26,7 +27,11 @@ class CaseConvert
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
}
- $mixedCaseValue = Helpers::extractString($mixedCaseValue);
+ try {
+ $mixedCaseValue = Helpers::extractString($mixedCaseValue, true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
return StringHelper::strToLower($mixedCaseValue);
}
@@ -48,7 +53,11 @@ class CaseConvert
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
}
- $mixedCaseValue = Helpers::extractString($mixedCaseValue);
+ try {
+ $mixedCaseValue = Helpers::extractString($mixedCaseValue, true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
return StringHelper::strToUpper($mixedCaseValue);
}
@@ -70,7 +79,11 @@ class CaseConvert
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $mixedCaseValue);
}
- $mixedCaseValue = Helpers::extractString($mixedCaseValue);
+ try {
+ $mixedCaseValue = Helpers::extractString($mixedCaseValue, true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
return StringHelper::strToTitle($mixedCaseValue);
}
diff --git a/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php b/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
index 8d90a17ba..06d0f9009 100644
--- a/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
+++ b/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php
@@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
@@ -26,7 +27,12 @@ class CharacterConvert
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $character);
}
- $character = Helpers::validateInt($character);
+ try {
+ $character = Helpers::validateInt($character, true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
+
$min = Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE ? 0 : 1;
if ($character < $min || $character > 255) {
return ExcelError::VALUE();
@@ -52,7 +58,12 @@ class CharacterConvert
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $characters);
}
- $characters = Helpers::extractString($characters);
+ try {
+ $characters = Helpers::extractString($characters, true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
+
if ($characters === '') {
return ExcelError::VALUE();
}
diff --git a/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php b/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php
index c2281d436..78940ed16 100644
--- a/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php
+++ b/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php
@@ -27,7 +27,7 @@ class Concatenate
foreach ($aArgs as $arg) {
$value = Helpers::extractString($arg);
- if (ErrorValue::isError($value)) {
+ if (ErrorValue::isError($value, true)) {
$returnValue = $value;
break;
@@ -85,7 +85,7 @@ class Concatenate
{
foreach ($aArgs as $key => &$arg) {
$value = Helpers::extractString($arg);
- if (ErrorValue::isError($value)) {
+ if (ErrorValue::isError($value, true)) {
return $value;
}
@@ -123,7 +123,7 @@ class Concatenate
if (!is_numeric($repeatCount) || $repeatCount < 0) {
$returnValue = ExcelError::VALUE();
- } elseif (ErrorValue::isError($stringValue)) {
+ } elseif (ErrorValue::isError($stringValue, true)) {
$returnValue = $stringValue;
} else {
$returnValue = str_repeat($stringValue, (int) $repeatCount);
diff --git a/src/PhpSpreadsheet/Calculation/TextData/Extract.php b/src/PhpSpreadsheet/Calculation/TextData/Extract.php
index 32e5b967a..1dfb724cd 100644
--- a/src/PhpSpreadsheet/Calculation/TextData/Extract.php
+++ b/src/PhpSpreadsheet/Calculation/TextData/Extract.php
@@ -31,7 +31,7 @@ class Extract
}
try {
- $value = Helpers::extractString($value);
+ $value = Helpers::extractString($value, true);
$chars = Helpers::extractInt($chars, 0, 1);
} catch (CalcExp $e) {
return $e->getMessage();
@@ -61,7 +61,7 @@ class Extract
}
try {
- $value = Helpers::extractString($value);
+ $value = Helpers::extractString($value, true);
$start = Helpers::extractInt($start, 1);
$chars = Helpers::extractInt($chars, 0);
} catch (CalcExp $e) {
@@ -90,7 +90,7 @@ class Extract
}
try {
- $value = Helpers::extractString($value);
+ $value = Helpers::extractString($value, true);
$chars = Helpers::extractInt($chars, 0, 1);
} catch (CalcExp $e) {
return $e->getMessage();
@@ -132,7 +132,13 @@ class Extract
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
}
- $text = Helpers::extractString($text ?? '');
+ try {
+ $text = Helpers::extractString($text ?? '', true);
+ Helpers::extractString(Functions::flattenSingleValue($delimiter ?? ''), true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
+
$instance = (int) $instance;
$matchMode = (int) $matchMode;
$matchEnd = (int) $matchEnd;
@@ -190,7 +196,13 @@ class Extract
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound);
}
- $text = Helpers::extractString($text ?? '');
+ try {
+ $text = Helpers::extractString($text ?? '', true);
+ Helpers::extractString(Functions::flattenSingleValue($delimiter ?? ''), true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
+
$instance = (int) $instance;
$matchMode = (int) $matchMode;
$matchEnd = (int) $matchEnd;
diff --git a/src/PhpSpreadsheet/Calculation/TextData/Format.php b/src/PhpSpreadsheet/Calculation/TextData/Format.php
index 40335ced7..0560b376b 100644
--- a/src/PhpSpreadsheet/Calculation/TextData/Format.php
+++ b/src/PhpSpreadsheet/Calculation/TextData/Format.php
@@ -8,6 +8,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
@@ -123,8 +124,13 @@ class Format
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format);
}
- $value = Helpers::extractString($value);
- $format = Helpers::extractString($format);
+ try {
+ $value = Helpers::extractString($value, true);
+ $format = Helpers::extractString($format, true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
+
$format = (string) NumberFormat::convertSystemFormats($format);
if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) {
@@ -152,6 +158,9 @@ class Format
}
if (is_string($value)) {
$value = trim($value);
+ if (ErrorValue::isError($value, true)) {
+ throw new CalcExp($value);
+ }
if ($spacesMeanZero && $value === '') {
$value = 0;
}
@@ -220,7 +229,7 @@ class Format
}
/**
- * TEXT.
+ * VALUETOTEXT.
*
* @param mixed $value The value to format
* Or can be an array of values
diff --git a/src/PhpSpreadsheet/Calculation/TextData/Helpers.php b/src/PhpSpreadsheet/Calculation/TextData/Helpers.php
index 15b046704..719de04a8 100644
--- a/src/PhpSpreadsheet/Calculation/TextData/Helpers.php
+++ b/src/PhpSpreadsheet/Calculation/TextData/Helpers.php
@@ -27,7 +27,7 @@ class Helpers
if (is_bool($value)) {
return self::convertBooleanValue($value);
}
- if ($throwIfError && is_string($value) && ErrorValue::isError($value)) {
+ if ($throwIfError && is_string($value) && ErrorValue::isError($value, true)) {
throw new CalcExp($value);
}
@@ -63,18 +63,28 @@ class Helpers
$value = (float) $value;
}
if (!is_numeric($value)) {
+ if (is_string($value) && ErrorValue::isError($value, true)) {
+ throw new CalcExp($value);
+ }
+
throw new CalcExp(ExcelError::VALUE());
}
return (float) $value;
}
- public static function validateInt(mixed $value): int
+ public static function validateInt(mixed $value, bool $throwIfError = false): int
{
if ($value === null) {
$value = 0;
} elseif (is_bool($value)) {
$value = (int) $value;
+ } elseif ($throwIfError && is_string($value) && !is_numeric($value)) {
+ if (!ErrorValue::isError($value, true)) {
+ $value = ExcelError::VALUE();
+ }
+
+ throw new CalcExp($value);
}
return (int) $value;
diff --git a/src/PhpSpreadsheet/Calculation/TextData/Search.php b/src/PhpSpreadsheet/Calculation/TextData/Search.php
index ad83f1a37..663d49fc2 100644
--- a/src/PhpSpreadsheet/Calculation/TextData/Search.php
+++ b/src/PhpSpreadsheet/Calculation/TextData/Search.php
@@ -32,8 +32,8 @@ class Search
}
try {
- $needle = Helpers::extractString($needle);
- $haystack = Helpers::extractString($haystack);
+ $needle = Helpers::extractString($needle, true);
+ $haystack = Helpers::extractString($haystack, true);
$offset = Helpers::extractInt($offset, 1, 0, true);
} catch (CalcExp $e) {
return $e->getMessage();
@@ -74,8 +74,8 @@ class Search
}
try {
- $needle = Helpers::extractString($needle);
- $haystack = Helpers::extractString($haystack);
+ $needle = Helpers::extractString($needle, true);
+ $haystack = Helpers::extractString($haystack, true);
$offset = Helpers::extractInt($offset, 1, 0, true);
} catch (CalcExp $e) {
return $e->getMessage();
diff --git a/src/PhpSpreadsheet/Calculation/TextData/Text.php b/src/PhpSpreadsheet/Calculation/TextData/Text.php
index 44e0cd402..f988a6c19 100644
--- a/src/PhpSpreadsheet/Calculation/TextData/Text.php
+++ b/src/PhpSpreadsheet/Calculation/TextData/Text.php
@@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\TextData;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
@@ -17,16 +18,20 @@ class Text
* @param mixed $value String Value
* Or can be an array of values
*
- * @return array|int If an array of values is passed for the argument, then the returned result
+ * @return array|int|string If an array of values is passed for the argument, then the returned result
* will also be an array with matching dimensions
*/
- public static function length(mixed $value = ''): array|int
+ public static function length(mixed $value = ''): array|int|string
{
if (is_array($value)) {
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
}
- $value = Helpers::extractString($value);
+ try {
+ $value = Helpers::extractString($value, true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
return mb_strlen($value, 'UTF-8');
}
@@ -41,17 +46,21 @@ class Text
* @param mixed $value2 String Value
* Or can be an array of values
*
- * @return array|bool If an array of values is passed for either of the arguments, then the returned result
+ * @return array|bool|string If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
- public static function exact(mixed $value1, mixed $value2): array|bool
+ public static function exact(mixed $value1, mixed $value2): array|bool|string
{
if (is_array($value1) || is_array($value2)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $value1, $value2);
}
- $value1 = Helpers::extractString($value1);
- $value2 = Helpers::extractString($value2);
+ try {
+ $value1 = Helpers::extractString($value1, true);
+ $value2 = Helpers::extractString($value2, true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
return $value2 === $value1;
}
@@ -97,11 +106,14 @@ class Text
* @param mixed $padding The value with which to pad the result.
* The default is #N/A.
*
- * @return array the array built from the text, split by the row and column delimiters
+ * @return array|string the array built from the text, split by the row and column delimiters, or an error string
*/
- public static function split(mixed $text, $columnDelimiter = null, $rowDelimiter = null, bool $ignoreEmpty = false, bool $matchMode = true, mixed $padding = '#N/A'): array
+ public static function split(mixed $text, $columnDelimiter = null, $rowDelimiter = null, bool $ignoreEmpty = false, bool $matchMode = true, mixed $padding = '#N/A'): array|string
{
$text = Functions::flattenSingleValue($text);
+ if (ErrorValue::isError($text, true)) {
+ return $text;
+ }
$flags = self::matchFlags($matchMode);
diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php
index 0f0075bca..6aed63f20 100644
--- a/src/PhpSpreadsheet/Cell/Cell.php
+++ b/src/PhpSpreadsheet/Cell/Cell.php
@@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Collection\Cells;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
@@ -255,6 +256,7 @@ class Cell implements Stringable
public function setValueExplicit(mixed $value, string $dataType = DataType::TYPE_STRING): self
{
$oldValue = $this->value;
+ $quotePrefix = false;
// set the value according to data type
switch ($dataType) {
@@ -267,6 +269,10 @@ class Cell implements Stringable
// no break
case DataType::TYPE_STRING:
// Synonym for string
+ if (is_string($value) && strlen($value) > 1 && $value[0] === '=') {
+ $quotePrefix = true;
+ }
+ // no break
case DataType::TYPE_INLINE:
// Rich text
if ($value !== null && !is_scalar($value) && !($value instanceof Stringable)) {
@@ -312,6 +318,7 @@ class Cell implements Stringable
$this->updateInCollection();
$cellCoordinate = $this->getCoordinate();
self::updateIfCellIsTableHeader($this->getParent()?->getParent(), $this, $oldValue, $value);
+ $this->getWorksheet()->applyStylesFromArray($cellCoordinate, ['quotePrefix' => $quotePrefix]);
return $this->getParent()?->get($cellCoordinate) ?? $this;
}
@@ -409,7 +416,7 @@ class Cell implements Stringable
}
SharedDate::setExcelCalendar($currentCalendar);
- if ($result === '#Not Yet Implemented') {
+ if ($result === Functions::NOT_YET_IMPLEMENTED) {
return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
}
diff --git a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php
index 6f75a90a8..f36934ed3 100644
--- a/src/PhpSpreadsheet/Cell/DefaultValueBinder.php
+++ b/src/PhpSpreadsheet/Cell/DefaultValueBinder.php
@@ -3,6 +3,8 @@
namespace PhpOffice\PhpSpreadsheet\Cell;
use DateTimeInterface;
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
@@ -68,6 +70,23 @@ class DefaultValueBinder implements IValueBinder
throw new SpreadsheetException("unusable type $gettype");
}
if (strlen($value) > 1 && $value[0] === '=') {
+ $calculation = new Calculation();
+ $calculation->disableBranchPruning();
+
+ try {
+ if (empty($calculation->parseFormula($value))) {
+ return DataType::TYPE_STRING;
+ }
+ } catch (CalculationException $e) {
+ $message = $e->getMessage();
+ if (
+ $message === 'Formula Error: An unexpected error occurred'
+ || str_contains($message, 'has no operands')
+ ) {
+ return DataType::TYPE_STRING;
+ }
+ }
+
return DataType::TYPE_FORMULA;
}
if (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) {
diff --git a/src/PhpSpreadsheet/Cell/StringValueBinder.php b/src/PhpSpreadsheet/Cell/StringValueBinder.php
index 6ff258d93..d86cdabd3 100644
--- a/src/PhpSpreadsheet/Cell/StringValueBinder.php
+++ b/src/PhpSpreadsheet/Cell/StringValueBinder.php
@@ -8,7 +8,7 @@ use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use Stringable;
-class StringValueBinder implements IValueBinder
+class StringValueBinder extends DefaultValueBinder implements IValueBinder
{
protected bool $convertNull = true;
@@ -87,12 +87,9 @@ class StringValueBinder implements IValueBinder
$cell->setValueExplicit($value, DataType::TYPE_BOOL);
} elseif ((is_int($value) || is_float($value)) && $this->convertNumeric === false) {
$cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
- } elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=' && $this->convertFormula === false) {
+ } elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=' && $this->convertFormula === false && parent::dataTypeForValue($value) === DataType::TYPE_FORMULA) {
$cell->setValueExplicit($value, DataType::TYPE_FORMULA);
} else {
- if (is_string($value) && strlen($value) > 1 && $value[0] === '=') {
- $cell->getStyle()->setQuotePrefix(true);
- }
$cell->setValueExplicit((string) $value, DataType::TYPE_STRING);
}
diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php
index ceb345dc3..6f130271f 100644
--- a/src/PhpSpreadsheet/Reader/Ods.php
+++ b/src/PhpSpreadsheet/Reader/Ods.php
@@ -6,6 +6,7 @@ use DOMAttr;
use DOMDocument;
use DOMElement;
use DOMNode;
+use DOMText;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Helper\Dimension as HelperDimension;
@@ -403,8 +404,11 @@ class Ods extends BaseReader
}
$columnID = 'A';
- /** @var DOMElement $cellData */
+ /** @var DOMElement|DOMText $cellData */
foreach ($childNode->childNodes as $cellData) {
+ if ($cellData instanceof DOMText) {
+ continue; // should just be whitespace
+ }
if ($this->getReadFilter() !== null) {
if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
@@ -436,14 +440,25 @@ class Ods extends BaseReader
if ($annotation->length > 0 && $annotation->item(0) !== null) {
$textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p');
+ $textNodeLength = $textNode->length;
+ $newLineOwed = false;
+ for ($textNodeIndex = 0; $textNodeIndex < $textNodeLength; ++$textNodeIndex) {
+ $textNodeItem = $textNode->item($textNodeIndex);
+ if ($textNodeItem !== null) {
+ $text = $this->scanElementForText($textNodeItem);
+ if ($newLineOwed) {
+ $spreadsheet->getActiveSheet()
+ ->getComment($columnID . $rowID)
+ ->getText()
+ ->createText("\n");
+ }
+ $newLineOwed = true;
- if ($textNode->length > 0 && $textNode->item(0) !== null) {
- $text = $this->scanElementForText($textNode->item(0));
-
- $spreadsheet->getActiveSheet()
- ->getComment($columnID . $rowID)
- ->setText($this->parseRichText($text));
-// ->setAuthor( $author )
+ $spreadsheet->getActiveSheet()
+ ->getComment($columnID . $rowID)
+ ->getText()
+ ->createText($this->parseRichText($text));
+ }
}
}
@@ -492,7 +507,7 @@ class Ods extends BaseReader
break;
case 'boolean':
$type = DataType::TYPE_BOOL;
- $dataValue = ($allCellDataText == 'TRUE') ? true : false;
+ $dataValue = ($cellData->getAttributeNS($officeNs, 'boolean-value') === 'true') ? true : false;
break;
case 'percentage':
@@ -731,6 +746,8 @@ class Ods extends BaseReader
/** @var DOMNode $child */
if ($child->nodeType == XML_TEXT_NODE) {
$str .= $child->nodeValue;
+ } elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:line-break') {
+ $str .= "\n";
} elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') {
// It's a space
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php b/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php
index 136b92fed..9d71443f4 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php
@@ -122,11 +122,12 @@ class SheetViewOptions extends BaseParserClass
private function printOptions(SimpleXMLElement $printOptionsx): void
{
$printOptions = $printOptionsx->attributes() ?? [];
- if (isset($printOptions['gridLinesSet']) && self::boolean((string) $printOptions['gridLinesSet'])) {
- $this->worksheet->setShowGridlines(true);
- }
+ // Spec is weird. gridLines (default false)
+ // and gridLinesSet (default true) must both be true.
if (isset($printOptions['gridLines']) && self::boolean((string) $printOptions['gridLines'])) {
- $this->worksheet->setPrintGridlines(true);
+ if (!isset($printOptions['gridLinesSet']) || self::boolean((string) $printOptions['gridLinesSet'])) {
+ $this->worksheet->setPrintGridlines(true);
+ }
}
if (isset($printOptions['horizontalCentered']) && self::boolean((string) $printOptions['horizontalCentered'])) {
$this->worksheet->getPageSetup()->setHorizontalCentered(true);
diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php
index e571cc4f6..bcea8e6a7 100644
--- a/src/PhpSpreadsheet/Spreadsheet.php
+++ b/src/PhpSpreadsheet/Spreadsheet.php
@@ -558,6 +558,9 @@ class Spreadsheet implements JsonSerializable
if ($this->activeSheetIndex >= $sheetIndex) {
++$this->activeSheetIndex;
}
+ if ($this->activeSheetIndex < 0) {
+ $this->activeSheetIndex = 0;
+ }
}
if ($worksheet->getParent() === null) {
diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php
index 8adada4c1..7afa82b5d 100644
--- a/src/PhpSpreadsheet/Worksheet/Worksheet.php
+++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php
@@ -3673,4 +3673,21 @@ class Worksheet implements IComparable
}
}
}
+
+ public function applyStylesFromArray(string $coordinate, array $styleArray): bool
+ {
+ $spreadsheet = $this->parent;
+ if ($spreadsheet === null) {
+ return false;
+ }
+ $activeSheetIndex = $spreadsheet->getActiveSheetIndex();
+ $originalSelected = $this->selectedCells;
+ $this->getStyle($coordinate)->applyFromArray($styleArray);
+ $this->selectedCells = $originalSelected;
+ if ($activeSheetIndex >= 0) {
+ $spreadsheet->setActiveSheetIndex($activeSheetIndex);
+ }
+
+ return true;
+ }
}
diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php
index 457af6612..37a9c8994 100644
--- a/src/PhpSpreadsheet/Writer/Html.php
+++ b/src/PhpSpreadsheet/Writer/Html.php
@@ -355,7 +355,11 @@ class Html extends BaseWriter
$html .= ' ' . PHP_EOL;
$html .= ' ' . PHP_EOL;
$html .= ' ' . PHP_EOL;
- $html .= ' ' . htmlspecialchars($properties->getTitle(), Settings::htmlEntityFlags()) . '' . PHP_EOL;
+ $title = $properties->getTitle();
+ if ($title === '') {
+ $title = $this->spreadsheet->getActiveSheet()->getTitle();
+ }
+ $html .= ' ' . htmlspecialchars($title, Settings::htmlEntityFlags()) . '' . PHP_EOL;
$html .= self::generateMeta($properties->getCreator(), 'author');
$html .= self::generateMeta($properties->getTitle(), 'title');
$html .= self::generateMeta($properties->getDescription(), 'description');
@@ -1462,11 +1466,21 @@ class Html extends BaseWriter
$xcssClass['height'] = $height;
}
//** end of redundant code **
+ if ($this->useInlineCss) {
+ foreach (['border-top', 'border-bottom', 'border-right', 'border-left'] as $borderType) {
+ if (($xcssClass[$borderType] ?? '') === 'none #000000') {
+ unset($xcssClass[$borderType]);
+ }
+ }
+ }
if ($htmlx) {
$xcssClass['position'] = 'relative';
}
$html .= ' style="' . $this->assembleCSS($xcssClass) . '"';
+ if ($this->useInlineCss) {
+ $html .= ' class="gridlines gridlinesp"';
+ }
}
$html = $this->generateRowSpans($html, $rowSpan, $colSpan);
diff --git a/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php b/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php
index b0829bf1d..f0b7d5704 100644
--- a/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php
+++ b/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php
@@ -24,7 +24,22 @@ class Comment
$objWriter->writeAttribute('svg:x', $comment->getMarginLeft());
$objWriter->writeAttribute('svg:y', $comment->getMarginTop());
$objWriter->writeElement('dc:creator', $comment->getAuthor());
- $objWriter->writeElement('text:p', $comment->getText()->getPlainText());
+
+ $objWriter->startElement('text:p');
+ $text = $comment->getText()->getPlainText();
+ $textElements = explode("\n", $text);
+ $newLineOwed = false;
+ foreach ($textElements as $textSegment) {
+ if ($newLineOwed) {
+ $objWriter->writeElement('text:line-break');
+ }
+ $newLineOwed = true;
+ if ($textSegment !== '') {
+ $objWriter->writeElement('text:span', $textSegment);
+ }
+ }
+ $objWriter->endElement(); // text:p
+
$objWriter->endElement();
}
}
diff --git a/src/PhpSpreadsheet/Writer/Ods/Content.php b/src/PhpSpreadsheet/Writer/Ods/Content.php
index 7b052bbcc..7ffcd46b6 100644
--- a/src/PhpSpreadsheet/Writer/Ods/Content.php
+++ b/src/PhpSpreadsheet/Writer/Ods/Content.php
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
@@ -209,8 +210,8 @@ class Content extends WriterPart
switch ($cell->getDataType()) {
case DataType::TYPE_BOOL:
$objWriter->writeAttribute('office:value-type', 'boolean');
- $objWriter->writeAttribute('office:value', $cell->getValueString());
- $objWriter->writeElement('text:p', $cell->getValueString());
+ $objWriter->writeAttribute('office:boolean-value', $cell->getValue() ? 'true' : 'false');
+ $objWriter->writeElement('text:p', Calculation::getInstance()->getLocaleBoolean($cell->getValue() ? 'TRUE' : 'FALSE'));
break;
case DataType::TYPE_ERROR:
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
index a1940f329..bd6eec367 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
@@ -1528,7 +1528,7 @@ class Worksheet extends WriterPart
break;
case 's': // String
- $this->writeCellString($objWriter, $mappedType, $cellValueString, $flippedStringTable);
+ $this->writeCellString($objWriter, $mappedType, ($cellValue instanceof RichText) ? $cellValue : $cellValueString, $flippedStringTable);
break;
case 'f': // Formula
diff --git a/tests/PhpSpreadsheetTests/Calculation/CalculationFunctionListTest.php b/tests/PhpSpreadsheetTests/Calculation/CalculationFunctionListTest.php
index f961c7ac7..978162667 100644
--- a/tests/PhpSpreadsheetTests/Calculation/CalculationFunctionListTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/CalculationFunctionListTest.php
@@ -13,21 +13,15 @@ class CalculationFunctionListTest extends TestCase
{
private string $compatibilityMode;
- private string $locale;
-
protected function setUp(): void
{
$this->compatibilityMode = Functions::getCompatibilityMode();
- $calculation = Calculation::getInstance();
- $this->locale = $calculation->getLocale();
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
}
protected function tearDown(): void
{
Functions::setCompatibilityMode($this->compatibilityMode);
- $calculation = Calculation::getInstance();
- $calculation->setLocale($this->locale);
}
/**
diff --git a/tests/PhpSpreadsheetTests/Calculation/CalculationTest.php b/tests/PhpSpreadsheetTests/Calculation/CalculationTest.php
index 79685d267..797b0bfd7 100644
--- a/tests/PhpSpreadsheetTests/Calculation/CalculationTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/CalculationTest.php
@@ -15,21 +15,15 @@ class CalculationTest extends TestCase
{
private string $compatibilityMode;
- private string $locale;
-
protected function setUp(): void
{
$this->compatibilityMode = Functions::getCompatibilityMode();
- $calculation = Calculation::getInstance();
- $this->locale = $calculation->getLocale();
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
}
protected function tearDown(): void
{
Functions::setCompatibilityMode($this->compatibilityMode);
- $calculation = Calculation::getInstance();
- $calculation->setLocale($this->locale);
}
/**
diff --git a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
index fbac0ff91..aa7bc529d 100644
--- a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Engine;
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
@@ -13,13 +14,23 @@ class RangeTest extends TestCase
{
private string $incompleteMessage = 'Must be revisited';
- private Spreadsheet $spreadSheet;
+ private ?Spreadsheet $spreadSheet = null;
- protected function setUp(): void
+ protected function getSpreadsheet(): Spreadsheet
{
- $this->spreadSheet = new Spreadsheet();
- $this->spreadSheet->getActiveSheet()
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->getActiveSheet()
->fromArray(array_chunk(range(1, 240), 6), null, 'A1', true);
+
+ return $spreadsheet;
+ }
+
+ protected function tearDown(): void
+ {
+ if ($this->spreadSheet !== null) {
+ $this->spreadSheet->disconnectWorksheets();
+ $this->spreadSheet = null;
+ }
}
/**
@@ -27,6 +38,7 @@ class RangeTest extends TestCase
*/
public function testRangeEvaluation(string $formula, int|string $expectedResult): void
{
+ $this->spreadSheet = $this->getSpreadsheet();
$workSheet = $this->spreadSheet->getActiveSheet();
$workSheet->setCellValue('H1', $formula);
@@ -64,8 +76,20 @@ class RangeTest extends TestCase
];
}
+ public function test3dRangeParsing(): void
+ {
+ // This test shows that parsing throws exception.
+ // Next test shows that formula is still treated as a formula
+ // despite the parse failure.
+ $this->expectExceptionMessage('3D Range references are not yet supported');
+ $calculation = new Calculation();
+ $calculation->disableBranchPruning();
+ $calculation->parseFormula('=SUM(Worksheet!A1:Worksheet2!B3');
+ }
+
public function test3dRangeEvaluation(): void
{
+ $this->spreadSheet = $this->getSpreadsheet();
$workSheet = $this->spreadSheet->getActiveSheet();
$workSheet->setCellValue('E1', '=SUM(Worksheet!A1:Worksheet2!B3)');
@@ -78,6 +102,7 @@ class RangeTest extends TestCase
*/
public function testNamedRangeEvaluation(array $ranges, string $formula, int $expectedResult): void
{
+ $this->spreadSheet = $this->getSpreadsheet();
$workSheet = $this->spreadSheet->getActiveSheet();
foreach ($ranges as $id => $range) {
$this->spreadSheet->addNamedRange(new NamedRange('GROUP' . ++$id, $workSheet, $range));
@@ -116,6 +141,7 @@ class RangeTest extends TestCase
*/
public function testUTF8NamedRangeEvaluation(array $names, array $ranges, string $formula, int $expectedResult): void
{
+ $this->spreadSheet = $this->getSpreadsheet();
$workSheet = $this->spreadSheet->getActiveSheet();
foreach ($names as $index => $name) {
$range = $ranges[$index];
@@ -144,6 +170,7 @@ class RangeTest extends TestCase
if ($this->incompleteMessage !== '') {
self::markTestIncomplete($this->incompleteMessage);
}
+ $this->spreadSheet = $this->getSpreadsheet();
$workSheet = $this->spreadSheet->getActiveSheet();
$this->spreadSheet->addNamedRange(new NamedRange('COMPOSITE', $workSheet, $composite));
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexOnSpreadsheetTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexOnSpreadsheetTest.php
index e893fc7f3..3f508405a 100644
--- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexOnSpreadsheetTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndexOnSpreadsheetTest.php
@@ -34,4 +34,37 @@ class IndexOnSpreadsheetTest extends AllSetupTeardown
{
return require 'tests/data/Calculation/LookupRef/INDEXonSpreadsheet.php';
}
+
+ /**
+ * @dataProvider providerIndexLiteralArrays
+ */
+ public function testLiteralArrays(mixed $expectedResult, string $indexArgs): void
+ {
+ $sheet = $this->getSheet();
+ $sheet->getCell('A10')->setValue(10);
+ $sheet->getCell('B10')->setValue(11);
+ $sheet->getCell('C10')->setValue(12);
+ $sheet->getCell('D10')->setValue(13);
+ $sheet->getCell('X10')->setValue(10);
+ $sheet->getCell('X11')->setValue(11);
+ $sheet->getCell('X12')->setValue(12);
+ $sheet->getCell('X13')->setValue(13);
+ $sheet->getCell('A1')->setValue("=INDEX($indexArgs)");
+ $result = $sheet->getCell('A1')->getCalculatedValue();
+ self::assertEquals($expectedResult, $result);
+ }
+
+ public static function providerIndexLiteralArrays(): array
+ {
+ return [
+ 'issue 64' => ['Fourth', '{"First","Second","Third","Fourth","Fifth","Sixth","Seventh"}, 4'],
+ 'issue 64 selecting first "row"' => ['First', '{"First","Second","Third","Fourth","Fifth","Sixth","Seventh"}, 1'],
+ 'array result condensed to single value' => [40, '{10,11;20,21;30,31;40,41;50,51;60,61},4'],
+ 'both row and column' => [41, '{10,11;20,21;30,31;40,41;50,51;60,61},4,2'],
+ '1*1 array' => ['first', '{"first"},1'],
+ 'array expressed in rows' => [20, '{10;20;30;40},2'],
+ 'spreadsheet single row' => [11, 'A10:D10,2'],
+ 'spreadsheet single column' => [13, 'X10:X13,4'],
+ ];
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ErrorPropagationTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ErrorPropagationTest.php
new file mode 100644
index 000000000..9ab19d94c
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Calculation/Functions/TextData/ErrorPropagationTest.php
@@ -0,0 +1,96 @@
+getSheet();
+ $sheet->getCell('A1')->setValue('=ABS("X")');
+ self::assertSame('#VALUE!', $sheet->getCell('A1')->getCalculatedValue());
+ $sheet->getCell('A2')->setValue('=SQRT(-1)');
+ self::assertSame('#NUM!', $sheet->getCell('A2')->getCalculatedValue());
+ $sheet->getCell('A3')->setValue('=3/0');
+ self::assertSame('#DIV/0!', $sheet->getCell('A3')->getCalculatedValue());
+ $sheet->getCell('A4')->setValue('=XXXX()');
+ self::assertSame('#NAME?', $sheet->getCell('A4')->getCalculatedValue());
+ $sheet->getCell('A5')->setValue('=ABS("X")');
+ self::assertSame('#VALUE!', $sheet->getCell('A5')->getCalculatedValue());
+
+ $sheet->getCell('B1')->setValue('=UPPER(A1)');
+ self::assertSame('#VALUE!', $sheet->getCell('A1')->getCalculatedValue());
+ $sheet->getCell('B2')->setValue('=LOWER(A2)');
+ self::assertSame('#NUM!', $sheet->getCell('A2')->getCalculatedValue());
+ $sheet->getCell('B3')->setValue('=PROPER(A3)');
+ self::assertSame('#DIV/0!', $sheet->getCell('A3')->getCalculatedValue());
+
+ $sheet->getCell('C2')->setValue('=CHAR(A2)');
+ self::assertSame('#NUM!', $sheet->getCell('C2')->getCalculatedValue());
+ $sheet->getCell('C3')->setValue('=CODE(A3)');
+ self::assertSame('#DIV/0!', $sheet->getCell('C3')->getCalculatedValue());
+
+ $sheet->getCell('D1')->setValue('=CONCATENATE(A1,A1)');
+ self::assertSame('#VALUE!', $sheet->getCell('D1')->getCalculatedValue());
+ $sheet->getCell('D2')->setValue('=TEXTJOIN(",",TRUE,A2,A3)');
+ self::assertSame('#NUM!', $sheet->getCell('D2')->getCalculatedValue());
+ $sheet->getCell('D3')->setValue('=REPT(A3,3)');
+ self::assertSame('#DIV/0!', $sheet->getCell('D3')->getCalculatedValue());
+ $sheet->getCell('D4')->setValue('=CONCAT(A4,A4)');
+ self::assertSame('#NAME?', $sheet->getCell('D4')->getCalculatedValue());
+ $sheet->getCell('D5')->setValue('="X"&A4');
+ self::assertSame('#NAME?', $sheet->getCell('D5')->getCalculatedValue());
+ $sheet->getCell('D6')->setValue('=A2&"X"');
+ self::assertSame('#NUM!', $sheet->getCell('D6')->getCalculatedValue());
+
+ $sheet->getCell('E1')->setValue('=LEFT(A1)');
+ self::assertSame('#VALUE!', $sheet->getCell('E1')->getCalculatedValue());
+ $sheet->getCell('E2')->setValue('=RIGHT(A2)');
+ self::assertSame('#NUM!', $sheet->getCell('E2')->getCalculatedValue());
+ $sheet->getCell('E3')->setValue('=MID(A3,2,2)');
+ self::assertSame('#DIV/0!', $sheet->getCell('E3')->getCalculatedValue());
+ $sheet->getCell('E4')->setValue('=TEXTBEFORE(A4,"M")');
+ self::assertSame('#NAME?', $sheet->getCell('E4')->getCalculatedValue());
+ $sheet->getCell('E5')->setValue('=TEXTAFTER(A5,"U")');
+ self::assertSame('#VALUE!', $sheet->getCell('E5')->getCalculatedValue());
+
+ $sheet->getCell('F1')->setValue('=VALUETOTEXT(A1)');
+ self::assertSame('#VALUE!', $sheet->getCell('F1')->getCalculatedValue());
+ $sheet->getCell('F2')->setValue('=DOLLAR(A2)');
+ self::assertSame('#NUM!', $sheet->getCell('F2')->getCalculatedValue());
+ $sheet->getCell('F3')->setValue('=FIXED(A3)');
+ self::assertSame('#DIV/0!', $sheet->getCell('E3')->getCalculatedValue());
+ $sheet->getCell('F4')->setValue('=TEXT(A4,"M")');
+ self::assertSame('#NAME?', $sheet->getCell('F4')->getCalculatedValue());
+ $sheet->getCell('F5')->setValue('=VALUE(A2)');
+ self::assertSame('#NUM!', $sheet->getCell('F5')->getCalculatedValue());
+ $sheet->getCell('F6')->setValue('=NUMBERVALUE(A3)');
+ self::assertSame('#DIV/0!', $sheet->getCell('F6')->getCalculatedValue());
+
+ $sheet->getCell('G1')->setValue('=REPLACE("oldtext",2,2,A1)');
+ self::assertSame('#VALUE!', $sheet->getCell('G1')->getCalculatedValue());
+ $sheet->getCell('G2')->setValue('=SUBSTITUTE(A2,"U","V")');
+ self::assertSame('#NUM!', $sheet->getCell('G2')->getCalculatedValue());
+
+ $sheet->getCell('H1')->setValue('=FIND(A1, "U")');
+ self::assertSame('#VALUE!', $sheet->getCell('H1')->getCalculatedValue());
+ $sheet->getCell('H2')->setValue('=SEARCH(A2,"U")');
+ self::assertSame('#NUM!', $sheet->getCell('H2')->getCalculatedValue());
+
+ $sheet->getCell('I1')->setValue('=LEN(A1)');
+ self::assertSame('#VALUE!', $sheet->getCell('I1')->getCalculatedValue());
+ $sheet->getCell('I2')->setValue('=EXACT(A2,A2)');
+ self::assertSame('#NUM!', $sheet->getCell('I2')->getCalculatedValue());
+ $sheet->getCell('I3')->setValue('=T(A3)');
+ self::assertSame('#DIV/0!', $sheet->getCell('I3')->getCalculatedValue());
+ $sheet->getCell('I4')->setValue('=TEXTSPLIT(A4,"M")');
+ self::assertSame('#NAME?', $sheet->getCell('I4')->getCalculatedValue());
+
+ $sheet->getCell('J1')->setValue('=TRIM(A1)');
+ self::assertSame('#VALUE!', $sheet->getCell('J1')->getCalculatedValue());
+ $sheet->getCell('J2')->setValue('=CLEAN(A2)');
+ self::assertSame('#NUM!', $sheet->getCell('J2')->getCalculatedValue());
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php b/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php
index d3f0ad664..d8de97a33 100644
--- a/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php
+++ b/tests/PhpSpreadsheetTests/Cell/AdvancedValueBinderTest.php
@@ -6,6 +6,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Cell;
use PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\IValueBinder;
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
@@ -232,4 +233,31 @@ class AdvancedValueBinderTest extends TestCase
["Hello\nWorld", true],
];
}
+
+ /**
+ * @dataProvider formulaProvider
+ */
+ public function testFormula(string $value, string $dataType): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+
+ $sheet->getCell('A1')->setValue($value);
+ self::assertSame($dataType, $sheet->getCell('A1')->getDataType());
+ if ($dataType === DataType::TYPE_FORMULA) {
+ self::assertFalse($sheet->getStyle('A1')->getQuotePrefix());
+ } else {
+ self::assertTrue($sheet->getStyle('A1')->getQuotePrefix());
+ }
+
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public static function formulaProvider(): array
+ {
+ return [
+ 'normal formula' => ['=SUM(A1:C3)', DataType::TYPE_FORMULA],
+ 'issue 1310' => ['======', DataType::TYPE_STRING],
+ ];
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Cell/StringValueBinderTest.php b/tests/PhpSpreadsheetTests/Cell/StringValueBinderTest.php
index 43ac2fbd1..71006fff8 100644
--- a/tests/PhpSpreadsheetTests/Cell/StringValueBinderTest.php
+++ b/tests/PhpSpreadsheetTests/Cell/StringValueBinderTest.php
@@ -211,13 +211,19 @@ class StringValueBinderTest extends TestCase
$cell->setValue($value);
self::assertSame($expectedValue, $cell->getValue());
self::assertSame($expectedDataType, $cell->getDataType());
+ if ($expectedDataType === DataType::TYPE_FORMULA) {
+ self::assertFalse($sheet->getStyle('A1')->getQuotePrefix());
+ } else {
+ self::assertTrue($sheet->getStyle('A1')->getQuotePrefix());
+ }
$spreadsheet->disconnectWorksheets();
}
public static function providerDataValuesSuppressFormulaConversion(): array
{
return [
- ['=SUM(A1:C3)', '=SUM(A1:C3)', DataType::TYPE_FORMULA, false],
+ 'normal formula' => ['=SUM(A1:C3)', '=SUM(A1:C3)', DataType::TYPE_FORMULA],
+ 'issue 1310' => ['======', '======', DataType::TYPE_STRING],
];
}
diff --git a/tests/PhpSpreadsheetTests/NamedRange3Test.php b/tests/PhpSpreadsheetTests/NamedRange3Test.php
new file mode 100644
index 000000000..c25dec561
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/NamedRange3Test.php
@@ -0,0 +1,52 @@
+getActiveSheet();
+ $sheet1->setTitle('sheet1');
+ $sheet1->setCellValue('B1', 100);
+ $sheet1->setCellValue('B2', 200);
+ $sheet1->setCellValue('B3', 300);
+ $sheet1->setCellValue('B4', 400);
+ $sheet1->setCellValue('B5', 500);
+
+ $sheet2 = $spreadsheet->createsheet();
+ $sheet2->setTitle('sheet2');
+ $sheet2->setCellValue('A1', 10);
+ $sheet2->setCellValue('A2', 20);
+ $sheet2->setCellValue('A3', 30);
+ $sheet2->setCellValue('A4', 40);
+ $sheet2->setCellValue('A5', 50);
+
+ $spreadsheet->addNamedRange(
+ new NamedRange('somecells', $sheet2, '$A$1:$A$5', true)
+ );
+ $spreadsheet->addNamedRange(
+ new NamedRange('cellsonsheet1', $sheet1, '$B$1:$B$5')
+ );
+
+ $sheet1->getCell('G1')->setValue('=SUM(cellsonsheet1)');
+ self::assertSame(1500, $sheet1->getCell('G1')->getCalculatedValue());
+ $sheet1->getCell('G2')->setValue('=SUM(sheet2!somecells)');
+ self::assertSame(150, $sheet1->getCell('G2')->getCalculatedValue());
+ $sheet1->getCell('G3')->setValue('=SUM(somecells)');
+ self::assertSame('#NAME?', $sheet1->getCell('G3')->getCalculatedValue());
+ $sheet1->getCell('G4')->setValue('=SUM(sheet2!cellsonsheet1)');
+ self::assertSame(1500, $sheet1->getCell('G4')->getCalculatedValue());
+ $sheet1->getCell('G5')->setValue('=SUM(sheet2xxx!cellsonsheet1)');
+ self::assertSame('#NAME?', $sheet1->getCell('G5')->getCalculatedValue());
+
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/BooleanDataTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/BooleanDataTest.php
new file mode 100644
index 000000000..28efec197
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Ods/BooleanDataTest.php
@@ -0,0 +1,83 @@
+locale = $calculation->getLocale();
+ }
+
+ protected function tearDown(): void
+ {
+ $calculation = Calculation::getInstance();
+ $calculation->setLocale($this->locale);
+ if ($this->tempfile !== '') {
+ unlink($this->tempfile);
+ $this->tempfile = '';
+ }
+ }
+
+ public function testBooleanData(): void
+ {
+ $spreadsheetOld = new Spreadsheet();
+ $sheetOld = $spreadsheetOld->getActiveSheet();
+ $sheetOld->getCell('A1')->setValue(true);
+ $sheetOld->getCell('A2')->setValue(false);
+ $writer = new OdsWriter($spreadsheetOld);
+ $this->tempfile = File::temporaryFileName();
+ $writer->save($this->tempfile);
+ $spreadsheetOld->disconnectWorksheets();
+ $reader = new OdsReader();
+ $spreadsheet = $reader->load($this->tempfile);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertTrue($sheet->getCell('A1')->getValue());
+ self::assertFalse($sheet->getCell('A2')->getValue());
+ $spreadsheet->disconnectWorksheets();
+ $zipFile = 'zip://' . $this->tempfile . '#content.xml';
+ $contents = (string) file_get_contents($zipFile);
+ self::assertStringContainsString('TRUE', $contents);
+ self::assertStringContainsString('FALSE', $contents);
+ }
+
+ public function testBooleanDataGerman(): void
+ {
+ $calculation = Calculation::getInstance();
+ $calculation->setLocale('de');
+ $spreadsheetOld = new Spreadsheet();
+ $sheetOld = $spreadsheetOld->getActiveSheet();
+ $sheetOld->getCell('A1')->setValue(true);
+ $sheetOld->getCell('A2')->setValue(false);
+ $writer = new OdsWriter($spreadsheetOld);
+ $this->tempfile = File::temporaryFileName();
+ $writer->save($this->tempfile);
+ $spreadsheetOld->disconnectWorksheets();
+ $reader = new OdsReader();
+ $spreadsheet = $reader->load($this->tempfile);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertTrue($sheet->getCell('A1')->getValue());
+ self::assertFalse($sheet->getCell('A2')->getValue());
+ $spreadsheet->disconnectWorksheets();
+ $zipFile = 'zip://' . $this->tempfile . '#content.xml';
+ $contents = (string) file_get_contents($zipFile);
+ self::assertStringContainsString('WAHR', $contents);
+ self::assertStringContainsString('FALSCH', $contents);
+ self::assertStringNotContainsString('TRUE', $contents);
+ self::assertStringNotContainsString('FALSE', $contents);
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/Issue804Test.php b/tests/PhpSpreadsheetTests/Reader/Ods/Issue804Test.php
new file mode 100644
index 000000000..afb33dabe
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Ods/Issue804Test.php
@@ -0,0 +1,38 @@
+
+
+ Name', $data);
+ }
+ }
+
+ public function testIssue2810(): void
+ {
+ // Whitespace between Xml nodes
+ $filename = 'tests/data/Reader/Ods/issue.804.ods';
+ $reader = new Ods();
+ $spreadsheet = $reader->load($filename);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame('Straße', $sheet->getCell('G1')->getValue());
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/MultiLineCommentTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/MultiLineCommentTest.php
new file mode 100644
index 000000000..f6fd5657a
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Ods/MultiLineCommentTest.php
@@ -0,0 +1,41 @@
+load($filename);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame("First line.\n\nSecond line.", $sheet->getComment('A1')->getText()->getPlainText());
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testOneParagraphMultipleSpans(): void
+ {
+ $spreadsheetOld = new Spreadsheet();
+ $sheetOld = $spreadsheetOld->getActiveSheet();
+ $sheetOld->getCell('A1')->setValue('Hello');
+ $text = $sheetOld->getComment('A1')->getText();
+ $text->createText('First');
+ $text->createText(' line.');
+ $text->createText("\n");
+ $text->createText("\n");
+ $text->createText("Second line.\nThird line.");
+ $spreadsheet = $this->writeAndReload($spreadsheetOld, 'Ods');
+ $spreadsheetOld->disconnectWorksheets();
+
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame("First line.\n\nSecond line.\nThird line.", $sheet->getComment('A1')->getText()->getPlainText());
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php
new file mode 100644
index 000000000..5387ea738
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php
@@ -0,0 +1,50 @@
+getActiveSheet();
+ $sheet2 = $spreadsheet->createSheet();
+ $sheet2->setTitle('deliberatelyblank');
+ $sheet1->setShowGridlines($display);
+ $sheet1->setPrintGridlines($print);
+ $sheet1->fromArray(
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9],
+ ]
+ );
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
+ $spreadsheet->disconnectWorksheets();
+ $rsheet1 = $reloadedSpreadsheet->getSheet(0);
+ $rsheet2 = $reloadedSpreadsheet->getSheet(1);
+ self::assertSame($display, $rsheet1->getShowGridlines());
+ self::assertSame($print, $rsheet1->getPrintGridlines());
+ self::assertTrue($rsheet2->getShowGridlines());
+ self::assertFalse($rsheet2->getPrintGridlines());
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
+
+ public static function loadDataProvider(): array
+ {
+ return [
+ [true, true],
+ [true, false],
+ [false, true],
+ [false, false],
+ ];
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2581Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2581Test.php
new file mode 100644
index 000000000..6ba87bd53
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2581Test.php
@@ -0,0 +1,25 @@
+load($filename);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame('=CONCATENATE("Prefix ",MID(CELL("filename"),FIND("]",CELL("filename"))+1,255), " Suffix")', $sheet->getCell('B1')->getValue());
+ self::assertSame('Prefix SomeName Suffix', $sheet->getCell('B1')->getCalculatedValue());
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php b/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php
new file mode 100644
index 000000000..9b230a9c9
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php
@@ -0,0 +1,43 @@
+removeSheetByIndex(0);
+ $worksheet = new Worksheet($mySpreadsheet, 'addedsheet');
+ self::assertSame(-1, $mySpreadsheet->getActiveSheetIndex());
+ $mySpreadsheet->addSheet($worksheet, $sheetNumber);
+ self::assertSame('addedsheet', $mySpreadsheet->getActiveSheet()->getTitle());
+ $row = 1;
+ $col = 1;
+ $worksheet->getCell([$col, $row])->setValue('id_uti');
+ self::assertSame('id_uti', $worksheet->getCell([$col, $row])->getValue());
+ $mySpreadsheet->disconnectWorksheets();
+ }
+
+ public static function providerSheetNumber(): array
+ {
+ return [
+ 'problem case' => [0],
+ 'normal case' => [null],
+ 'negative 1 (as if there were no sheets)' => [-1],
+ 'diffeent negative number' => [-4],
+ 'positive number' => [4],
+ ];
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Html/Issue3678Test.php b/tests/PhpSpreadsheetTests/Writer/Html/Issue3678Test.php
index cabcaca8f..e4f292c2d 100644
--- a/tests/PhpSpreadsheetTests/Writer/Html/Issue3678Test.php
+++ b/tests/PhpSpreadsheetTests/Writer/Html/Issue3678Test.php
@@ -24,7 +24,8 @@ class Issue3678Test extends TestCase
];
$sheet->getStyle('A1')->applyFromArray($styleArray);
$style1 = "vertical-align:bottom; border-bottom:none #000000; border-top:none #000000; border-left:none #000000; border-right:none #000000; color:#000000; font-family:'Calibri'; font-size:11pt; background-color:#FFFF00";
- $style2 = $style1 . '; text-align:right; width:42pt';
+ $style2 = "vertical-align:bottom; color:#000000; font-family:'Calibri'; font-size:11pt; background-color:#FFFF00";
+ $style2 .= '; text-align:right; width:42pt';
$writer = new Html($spreadsheet);
$html = $writer->generateHtmlAll();
self::assertStringContainsString('td.style1, th.style1 { ' . $style1 . ' }', $html);
@@ -33,7 +34,7 @@ class Issue3678Test extends TestCase
self::assertStringContainsString('.n { text-align:right }', $html);
$writer->setUseInlineCss(true);
$html = $writer->generateHtmlAll();
- self::assertStringContainsString('1 | ', $html);
+ self::assertStringContainsString('1 | ', $html);
$spreadsheet->disconnectWorksheets();
}
}
diff --git a/tests/PhpSpreadsheetTests/Writer/Html/NoTitleTest.php b/tests/PhpSpreadsheetTests/Writer/Html/NoTitleTest.php
new file mode 100644
index 000000000..7faf8a6ad
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Html/NoTitleTest.php
@@ -0,0 +1,62 @@
+load($file);
+ self::assertSame('', $spreadsheet->getProperties()->getTitle());
+
+ $writer = new Html($spreadsheet);
+ $writer->setUseInlineCss(true);
+ $html = $writer->generateHTMLAll();
+ self::assertStringContainsString('Sheet1', $html);
+ self::assertStringContainsString('C1 | ', $html);
+ $writer->setUseInlineCss(false);
+ $html = $writer->generateHTMLAll();
+ self::assertStringContainsString('C1 | ', $html);
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testHideSomeGridlines(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $sheet = $spreadsheet->getActiveSheet();
+ $sheet->fromArray(
+ [
+ [1, 2, 3, 4, 5, 6],
+ [7, 8, 9, 10, 11, 12],
+ [17, 18, 19, 20, 21, 22],
+ [27, 28, 29, 30, 31, 32],
+ [37, 38, 39, 40, 41, 42],
+ ]
+ );
+ $sheet->getStyle('B2:D4')->getBorders()->applyFromArray(
+ [
+ 'allBorders' => [
+ 'borderStyle' => Border::BORDER_NONE,
+ 'color' => ['rgb' => '808080'],
+ ],
+ ],
+ );
+
+ $writer = new Html($spreadsheet);
+ $writer->setUseInlineCss(true);
+ $html = $writer->generateHTMLAll();
+ self::assertStringContainsString('7 | ', $html);
+ self::assertStringContainsString('19 | ', $html);
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php
index 74901dd40..21e64f2a9 100644
--- a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php
+++ b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php
@@ -9,6 +9,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
class XlsGifBmpTest extends AbstractFunctional
@@ -83,6 +84,34 @@ class XlsGifBmpTest extends AbstractFunctional
$reloadedSpreadsheet->disconnectWorksheets();
}
+ public function testGifIssue4112(): void
+ {
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->removeSheetByIndex(0);
+ $sheet = new Worksheet($spreadsheet, 'Insured List');
+ $spreadsheet->addSheet($sheet, 0);
+
+ // Add a drawing to the worksheet
+ $drawing = new Drawing();
+ $drawing->setName('Letters G, I, and G');
+ $drawing->setDescription('Handwritten G, I, and F');
+ $drawing->setPath(__DIR__ . '/../../../../samples/images/gif.gif');
+ $drawing->setHeight(36);
+ $drawing->setWorksheet($sheet);
+ $drawing->setCoordinates('A1');
+
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls');
+ $spreadsheet->disconnectWorksheets();
+ $worksheet = $reloadedSpreadsheet->getActiveSheet();
+ $drawings = $worksheet->getDrawingCollection();
+ self::assertCount(1, $drawings);
+ foreach ($worksheet->getDrawingCollection() as $drawing) {
+ $mimeType = ($drawing instanceof MemoryDrawing) ? $drawing->getMimeType() : 'notmemorydrawing';
+ self::assertEquals('image/png', $mimeType);
+ }
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
+
public function testInvalidTimestamp(): void
{
$this->expectException(ReaderException::class);
diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue476Test.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue476Test.php
new file mode 100644
index 000000000..513fe7940
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue476Test.php
@@ -0,0 +1,30 @@
+load('tests/data/Writer/XLSX/issue.476.xlsx');
+
+ $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
+ $spreadsheet->disconnectWorksheets();
+
+ $sheet = $reloadedSpreadsheet->getActiveSheet();
+ $richText = $sheet->getCell('A1')->getValue();
+ self::assertInstanceOf(RichText::class, $richText);
+ $plainText = $richText->getPlainText();
+ self::assertSame("Art. 1A of the Geneva Refugee Convention and Protocol or other international or national instruments.\n", $plainText);
+
+ $reloadedSpreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/data/Calculation/LookupRef/INDEXonSpreadsheet.php b/tests/data/Calculation/LookupRef/INDEXonSpreadsheet.php
index 76f6ddd46..b2202704d 100644
--- a/tests/data/Calculation/LookupRef/INDEXonSpreadsheet.php
+++ b/tests/data/Calculation/LookupRef/INDEXonSpreadsheet.php
@@ -82,7 +82,7 @@ return [
2,
],
'Column number omitted from 2-column matrix' => [
- '#REF!', // Expected
+ 'abc', // Expected
[
['abc', 'def'],
['xyz', 'tuv'],
diff --git a/tests/data/Cell/DefaultValueBinder.php b/tests/data/Cell/DefaultValueBinder.php
index b9f3d51e1..bd4295c0b 100644
--- a/tests/data/Cell/DefaultValueBinder.php
+++ b/tests/data/Cell/DefaultValueBinder.php
@@ -83,4 +83,7 @@ return [
's',
'1234567890123459012345689012345690',
],
+ 'Issue 1310 Multiple = at start' => ['s', '======'],
+ 'Issue 1310 Variant 1' => ['s', '= ====='],
+ 'Issue 1310 Variant 2' => ['s', '=2*3='],
];
diff --git a/tests/data/Reader/Ods/issue.4081.ods b/tests/data/Reader/Ods/issue.4081.ods
new file mode 100644
index 000000000..6d690bd4e
Binary files /dev/null and b/tests/data/Reader/Ods/issue.4081.ods differ
diff --git a/tests/data/Reader/Ods/issue.804.ods b/tests/data/Reader/Ods/issue.804.ods
new file mode 100644
index 000000000..b42de4cf6
Binary files /dev/null and b/tests/data/Reader/Ods/issue.804.ods differ
diff --git a/tests/data/Reader/XLSX/issue.2581.xlsx b/tests/data/Reader/XLSX/issue.2581.xlsx
new file mode 100644
index 000000000..82af1c627
Binary files /dev/null and b/tests/data/Reader/XLSX/issue.2581.xlsx differ
diff --git a/tests/data/Writer/Ods/content-with-data.xml b/tests/data/Writer/Ods/content-with-data.xml
index 12140fa92..bb115583c 100644
--- a/tests/data/Writer/Ods/content-with-data.xml
+++ b/tests/data/Writer/Ods/content-with-data.xml
@@ -95,11 +95,11 @@
-
- 1
+
+ TRUE
-
-
+
+ FALSE
1 1
diff --git a/tests/data/Writer/XLSX/issue.476.xlsx b/tests/data/Writer/XLSX/issue.476.xlsx
new file mode 100644
index 000000000..f76f2d227
Binary files /dev/null and b/tests/data/Writer/XLSX/issue.476.xlsx differ