Merge pull request #2627 from PHPOffice/Improve-Translation-Separator-Conflict
Resolve `translateSeparator()` method to handle separators cleanly Some refactoring of the Ods Reader classes to eliminate code duplication
This commit is contained in:
commit
fe969f59cc
|
|
@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
- Gnumeric Reader now loads number formatting for cells.
|
- Gnumeric Reader now loads number formatting for cells.
|
||||||
- Gnumeric Reader now correctly identifies selected worksheet.
|
- Gnumeric Reader now correctly identifies selected worksheet.
|
||||||
|
- Some Refactoring of the Ods Reader, moving all formula and address translation from Ods to Excel into a separate class to eliminate code duplication and ensure consistency.
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
|
|
@ -27,6 +28,12 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed behaviour of XLSX font style vertical align settings
|
- Fixed behaviour of XLSX font style vertical align settings
|
||||||
|
- Resolved formula translations to handle separators (row and column) for array functions as well as for function argument separators; and cleanly handle nesting levels.
|
||||||
|
|
||||||
|
Note that this method is used when translating Excel functions between en and other locale languages, as well as when converting formulae between different spreadsheet formats (e.g. Ods to Excel).
|
||||||
|
|
||||||
|
Nor is this a perfect solution, as there may still be issues when function calls have array arguments that themselves contain function calls; but it's still better than the current logic.
|
||||||
|
|
||||||
|
|
||||||
## 1.22.0 - 2022-02-18
|
## 1.22.0 - 2022-02-18
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ parameters:
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#3 \\$formula of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:translateSeparator\\(\\) expects string, string\\|null given\\.$#"
|
message: "#^Parameter \\#3 \\$formula of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:translateSeparator\\(\\) expects string, string\\|null given\\.$#"
|
||||||
count: 2
|
count: 1
|
||||||
path: src/PhpSpreadsheet/Calculation/Calculation.php
|
path: src/PhpSpreadsheet/Calculation/Calculation.php
|
||||||
|
|
||||||
-
|
-
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,10 @@ class Calculation
|
||||||
const RETURN_ARRAY_AS_VALUE = 'value';
|
const RETURN_ARRAY_AS_VALUE = 'value';
|
||||||
const RETURN_ARRAY_AS_ARRAY = 'array';
|
const RETURN_ARRAY_AS_ARRAY = 'array';
|
||||||
|
|
||||||
const FORMULA_OPEN_FUNCTION_BRACE = '{';
|
const FORMULA_OPEN_FUNCTION_BRACE = '(';
|
||||||
const FORMULA_CLOSE_FUNCTION_BRACE = '}';
|
const FORMULA_CLOSE_FUNCTION_BRACE = ')';
|
||||||
|
const FORMULA_OPEN_MATRIX_BRACE = '{';
|
||||||
|
const FORMULA_CLOSE_MATRIX_BRACE = '}';
|
||||||
const FORMULA_STRING_QUOTE = '"';
|
const FORMULA_STRING_QUOTE = '"';
|
||||||
|
|
||||||
private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
|
private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
|
||||||
|
|
@ -3084,30 +3086,28 @@ class Calculation
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static function translateSeparator(
|
||||||
* @param string $fromSeparator
|
string $fromSeparator,
|
||||||
* @param string $toSeparator
|
string $toSeparator,
|
||||||
* @param string $formula
|
string $formula,
|
||||||
* @param bool $inBraces
|
int &$inBracesLevel,
|
||||||
*
|
string $openBrace = self::FORMULA_OPEN_FUNCTION_BRACE,
|
||||||
* @return string
|
string $closeBrace = self::FORMULA_CLOSE_FUNCTION_BRACE
|
||||||
*/
|
): string {
|
||||||
public static function translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces)
|
|
||||||
{
|
|
||||||
$strlen = mb_strlen($formula);
|
$strlen = mb_strlen($formula);
|
||||||
for ($i = 0; $i < $strlen; ++$i) {
|
for ($i = 0; $i < $strlen; ++$i) {
|
||||||
$chr = mb_substr($formula, $i, 1);
|
$chr = mb_substr($formula, $i, 1);
|
||||||
switch ($chr) {
|
switch ($chr) {
|
||||||
case self::FORMULA_OPEN_FUNCTION_BRACE:
|
case $openBrace:
|
||||||
$inBraces = true;
|
++$inBracesLevel;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case self::FORMULA_CLOSE_FUNCTION_BRACE:
|
case $closeBrace:
|
||||||
$inBraces = false;
|
--$inBracesLevel;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case $fromSeparator:
|
case $fromSeparator:
|
||||||
if (!$inBraces) {
|
if ($inBracesLevel > 0) {
|
||||||
$formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
|
$formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3116,31 +3116,47 @@ class Calculation
|
||||||
return $formula;
|
return $formula;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static function translateFormulaBlock(
|
||||||
* @param string[] $from
|
array $from,
|
||||||
* @param string[] $to
|
array $to,
|
||||||
* @param string $formula
|
string $formula,
|
||||||
* @param string $fromSeparator
|
int &$inFunctionBracesLevel,
|
||||||
* @param string $toSeparator
|
int &$inMatrixBracesLevel,
|
||||||
*
|
string $fromSeparator,
|
||||||
* @return string
|
string $toSeparator
|
||||||
*/
|
): string {
|
||||||
private static function translateFormula(array $from, array $to, $formula, $fromSeparator, $toSeparator)
|
// Function Names
|
||||||
|
$formula = preg_replace($from, $to, $formula);
|
||||||
|
|
||||||
|
// Temporarily adjust matrix separators so that they won't be confused with function arguments
|
||||||
|
$formula = self::translateSeparator(';', '|', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
|
$formula = self::translateSeparator(',', '!', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
|
// Function Argument Separators
|
||||||
|
$formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inFunctionBracesLevel);
|
||||||
|
// Restore matrix separators
|
||||||
|
$formula = self::translateSeparator('|', ';', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
|
$formula = self::translateSeparator('!', ',', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
|
|
||||||
|
return $formula;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function translateFormula(array $from, array $to, string $formula, string $fromSeparator, string $toSeparator): string
|
||||||
{
|
{
|
||||||
// Convert any Excel function names to the required language
|
// Convert any Excel function names and constant names to the required language;
|
||||||
|
// and adjust function argument separators
|
||||||
if (self::$localeLanguage !== 'en_us') {
|
if (self::$localeLanguage !== 'en_us') {
|
||||||
$inBraces = false;
|
$inFunctionBracesLevel = 0;
|
||||||
// If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
|
$inMatrixBracesLevel = 0;
|
||||||
|
// If there is the possibility of separators within a quoted string, then we treat them as literals
|
||||||
if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
|
if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
|
||||||
// So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
|
// So instead we skip replacing in any quoted strings by only replacing in every other array element
|
||||||
// the formula
|
// after we've exploded the formula
|
||||||
$temp = explode(self::FORMULA_STRING_QUOTE, $formula);
|
$temp = explode(self::FORMULA_STRING_QUOTE, $formula);
|
||||||
$i = false;
|
$i = false;
|
||||||
foreach ($temp as &$value) {
|
foreach ($temp as &$value) {
|
||||||
// Only count/replace in alternating array entries
|
// Only adjust in alternating array entries
|
||||||
if ($i = !$i) {
|
if ($i = !$i) {
|
||||||
$value = preg_replace($from, $to, $value);
|
$value = self::translateFormulaBlock($from, $to, $value, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator);
|
||||||
$value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unset($value);
|
unset($value);
|
||||||
|
|
@ -3148,8 +3164,7 @@ class Calculation
|
||||||
$formula = implode(self::FORMULA_STRING_QUOTE, $temp);
|
$formula = implode(self::FORMULA_STRING_QUOTE, $temp);
|
||||||
} else {
|
} else {
|
||||||
// If there's no quoted strings, then we do a simple count/replace
|
// If there's no quoted strings, then we do a simple count/replace
|
||||||
$formula = preg_replace($from, $to, $formula);
|
$formula = self::translateFormulaBlock($from, $to, $formula, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator);
|
||||||
$formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3162,6 +3177,7 @@ class Calculation
|
||||||
|
|
||||||
public function _translateFormulaToLocale($formula)
|
public function _translateFormulaToLocale($formula)
|
||||||
{
|
{
|
||||||
|
// Build list of function names and constants for translation
|
||||||
if (self::$functionReplaceFromExcel === null) {
|
if (self::$functionReplaceFromExcel === null) {
|
||||||
self::$functionReplaceFromExcel = [];
|
self::$functionReplaceFromExcel = [];
|
||||||
foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
|
foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
|
||||||
|
|
@ -3798,11 +3814,11 @@ class Calculation
|
||||||
*/
|
*/
|
||||||
private function convertMatrixReferences($formula)
|
private function convertMatrixReferences($formula)
|
||||||
{
|
{
|
||||||
static $matrixReplaceFrom = [self::FORMULA_OPEN_FUNCTION_BRACE, ';', self::FORMULA_CLOSE_FUNCTION_BRACE];
|
static $matrixReplaceFrom = [self::FORMULA_OPEN_MATRIX_BRACE, ';', self::FORMULA_CLOSE_MATRIX_BRACE];
|
||||||
static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
|
static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
|
||||||
|
|
||||||
// Convert any Excel matrix references to the MKMATRIX() function
|
// Convert any Excel matrix references to the MKMATRIX() function
|
||||||
if (strpos($formula, self::FORMULA_OPEN_FUNCTION_BRACE) !== false) {
|
if (strpos($formula, self::FORMULA_OPEN_MATRIX_BRACE) !== false) {
|
||||||
// If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
|
// If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
|
||||||
if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
|
if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
|
||||||
// So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
|
// So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
|
||||||
|
|
@ -3814,8 +3830,8 @@ class Calculation
|
||||||
foreach ($temp as &$value) {
|
foreach ($temp as &$value) {
|
||||||
// Only count/replace in alternating array entries
|
// Only count/replace in alternating array entries
|
||||||
if ($i = !$i) {
|
if ($i = !$i) {
|
||||||
$openCount += substr_count($value, self::FORMULA_OPEN_FUNCTION_BRACE);
|
$openCount += substr_count($value, self::FORMULA_OPEN_MATRIX_BRACE);
|
||||||
$closeCount += substr_count($value, self::FORMULA_CLOSE_FUNCTION_BRACE);
|
$closeCount += substr_count($value, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
$value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
|
$value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3824,8 +3840,8 @@ class Calculation
|
||||||
$formula = implode(self::FORMULA_STRING_QUOTE, $temp);
|
$formula = implode(self::FORMULA_STRING_QUOTE, $temp);
|
||||||
} else {
|
} else {
|
||||||
// If there's no quoted strings, then we do a simple count/replace
|
// If there's no quoted strings, then we do a simple count/replace
|
||||||
$openCount = substr_count($formula, self::FORMULA_OPEN_FUNCTION_BRACE);
|
$openCount = substr_count($formula, self::FORMULA_OPEN_MATRIX_BRACE);
|
||||||
$closeCount = substr_count($formula, self::FORMULA_CLOSE_FUNCTION_BRACE);
|
$closeCount = substr_count($formula, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
$formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
|
$formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
|
||||||
}
|
}
|
||||||
// Trap for mismatched braces and trigger an appropriate error
|
// Trap for mismatched braces and trigger an appropriate error
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ use DOMAttr;
|
||||||
use DOMDocument;
|
use DOMDocument;
|
||||||
use DOMElement;
|
use DOMElement;
|
||||||
use DOMNode;
|
use DOMNode;
|
||||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\AutoFilter;
|
use PhpOffice\PhpSpreadsheet\Reader\Ods\AutoFilter;
|
||||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\DefinedNames;
|
use PhpOffice\PhpSpreadsheet\Reader\Ods\DefinedNames;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Reader\Ods\FormulaTranslator;
|
||||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\PageSettings;
|
use PhpOffice\PhpSpreadsheet\Reader\Ods\PageSettings;
|
||||||
use PhpOffice\PhpSpreadsheet\Reader\Ods\Properties as DocumentProperties;
|
use PhpOffice\PhpSpreadsheet\Reader\Ods\Properties as DocumentProperties;
|
||||||
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
|
||||||
|
|
@ -512,7 +512,7 @@ class Ods extends BaseReader
|
||||||
if ($hasCalculatedValue) {
|
if ($hasCalculatedValue) {
|
||||||
$type = DataType::TYPE_FORMULA;
|
$type = DataType::TYPE_FORMULA;
|
||||||
$cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1);
|
$cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1);
|
||||||
$cellDataFormula = $this->convertToExcelFormulaValue($cellDataFormula);
|
$cellDataFormula = FormulaTranslator::convertToExcelFormulaValue($cellDataFormula);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
|
if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
|
||||||
|
|
@ -568,31 +568,7 @@ class Ods extends BaseReader
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merged cells
|
// Merged cells
|
||||||
if (
|
$this->processMergedCells($cellData, $tableNs, $type, $columnID, $rowID, $spreadsheet);
|
||||||
$cellData->hasAttributeNS($tableNs, 'number-columns-spanned')
|
|
||||||
|| $cellData->hasAttributeNS($tableNs, 'number-rows-spanned')
|
|
||||||
) {
|
|
||||||
if (($type !== DataType::TYPE_NULL) || (!$this->readDataOnly)) {
|
|
||||||
$columnTo = $columnID;
|
|
||||||
|
|
||||||
if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) {
|
|
||||||
$columnIndex = Coordinate::columnIndexFromString($columnID);
|
|
||||||
$columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned');
|
|
||||||
$columnIndex -= 2;
|
|
||||||
|
|
||||||
$columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$rowTo = $rowID;
|
|
||||||
|
|
||||||
if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) {
|
|
||||||
$rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo;
|
|
||||||
$spreadsheet->getActiveSheet()->mergeCells($cellRange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
++$columnID;
|
++$columnID;
|
||||||
}
|
}
|
||||||
|
|
@ -735,31 +711,38 @@ class Ods extends BaseReader
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function convertToExcelFormulaValue(string $openOfficeFormula): string
|
private function processMergedCells(
|
||||||
{
|
DOMElement $cellData,
|
||||||
$temp = explode('"', $openOfficeFormula);
|
string $tableNs,
|
||||||
$tKey = false;
|
string $type,
|
||||||
foreach ($temp as &$value) {
|
string $columnID,
|
||||||
// Only replace in alternate array entries (i.e. non-quoted blocks)
|
int $rowID,
|
||||||
if ($tKey = !$tKey) {
|
Spreadsheet $spreadsheet
|
||||||
// Cell range reference in another sheet
|
): void {
|
||||||
$value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value);
|
if (
|
||||||
// Cell reference in another sheet
|
$cellData->hasAttributeNS($tableNs, 'number-columns-spanned')
|
||||||
$value = preg_replace('/\[\$?([^\.]+)\.([^\.]+)\]/miu', '$1!$2', $value ?? '');
|
|| $cellData->hasAttributeNS($tableNs, 'number-rows-spanned')
|
||||||
// Cell range reference
|
) {
|
||||||
$value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/miu', '$1:$2', $value ?? '');
|
if (($type !== DataType::TYPE_NULL) || ($this->readDataOnly === false)) {
|
||||||
// Simple cell reference
|
$columnTo = $columnID;
|
||||||
$value = preg_replace('/\[\.([^\.]+)\]/miu', '$1', $value ?? '');
|
|
||||||
// Convert references to defined names/formulae
|
|
||||||
$value = str_replace('$$', '', $value ?? '');
|
|
||||||
|
|
||||||
$value = Calculation::translateSeparator(';', ',', $value, $inBraces);
|
if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) {
|
||||||
|
$columnIndex = Coordinate::columnIndexFromString($columnID);
|
||||||
|
$columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned');
|
||||||
|
$columnIndex -= 2;
|
||||||
|
|
||||||
|
$columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rowTo = $rowID;
|
||||||
|
|
||||||
|
if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) {
|
||||||
|
$rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo;
|
||||||
|
$spreadsheet->getActiveSheet()->mergeCells($cellRange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then rebuild the formula string
|
|
||||||
$excelFormula = implode('"', $temp);
|
|
||||||
|
|
||||||
return $excelFormula;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
|
||||||
use DOMElement;
|
use DOMElement;
|
||||||
use DOMNode;
|
use DOMNode;
|
||||||
|
|
||||||
class AutoFilter extends BaseReader
|
class AutoFilter extends BaseLoader
|
||||||
{
|
{
|
||||||
public function read(DOMElement $workbookData): void
|
public function read(DOMElement $workbookData): void
|
||||||
{
|
{
|
||||||
|
|
@ -20,7 +20,7 @@ class AutoFilter extends BaseReader
|
||||||
foreach ($autofilters->childNodes as $autofilter) {
|
foreach ($autofilters->childNodes as $autofilter) {
|
||||||
$autofilterRange = $this->getAttributeValue($autofilter, 'target-range-address');
|
$autofilterRange = $this->getAttributeValue($autofilter, 'target-range-address');
|
||||||
if ($autofilterRange !== null) {
|
if ($autofilterRange !== null) {
|
||||||
$baseAddress = $this->convertToExcelAddressValue($autofilterRange);
|
$baseAddress = FormulaTranslator::convertToExcelAddressValue($autofilterRange);
|
||||||
$this->spreadsheet->getActiveSheet()->setAutoFilter($baseAddress);
|
$this->spreadsheet->getActiveSheet()->setAutoFilter($baseAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
|
||||||
|
|
||||||
|
use DOMElement;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
|
||||||
|
abstract class BaseLoader
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Spreadsheet
|
||||||
|
*/
|
||||||
|
protected $spreadsheet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $tableNs;
|
||||||
|
|
||||||
|
public function __construct(Spreadsheet $spreadsheet, string $tableNs)
|
||||||
|
{
|
||||||
|
$this->spreadsheet = $spreadsheet;
|
||||||
|
$this->tableNs = $tableNs;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function read(DOMElement $workbookData): void;
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ use DOMElement;
|
||||||
use PhpOffice\PhpSpreadsheet\DefinedName;
|
use PhpOffice\PhpSpreadsheet\DefinedName;
|
||||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
class DefinedNames extends BaseReader
|
class DefinedNames extends BaseLoader
|
||||||
{
|
{
|
||||||
public function read(DOMElement $workbookData): void
|
public function read(DOMElement $workbookData): void
|
||||||
{
|
{
|
||||||
|
|
@ -25,8 +25,8 @@ class DefinedNames extends BaseReader
|
||||||
$baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address');
|
$baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address');
|
||||||
$range = $definedNameElement->getAttributeNS($this->tableNs, 'cell-range-address');
|
$range = $definedNameElement->getAttributeNS($this->tableNs, 'cell-range-address');
|
||||||
|
|
||||||
$baseAddress = $this->convertToExcelAddressValue($baseAddress);
|
$baseAddress = FormulaTranslator::convertToExcelAddressValue($baseAddress);
|
||||||
$range = $this->convertToExcelAddressValue($range);
|
$range = FormulaTranslator::convertToExcelAddressValue($range);
|
||||||
|
|
||||||
$this->addDefinedName($baseAddress, $definedName, $range);
|
$this->addDefinedName($baseAddress, $definedName, $range);
|
||||||
}
|
}
|
||||||
|
|
@ -43,9 +43,9 @@ class DefinedNames extends BaseReader
|
||||||
$baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address');
|
$baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address');
|
||||||
$expression = $definedNameElement->getAttributeNS($this->tableNs, 'expression');
|
$expression = $definedNameElement->getAttributeNS($this->tableNs, 'expression');
|
||||||
|
|
||||||
$baseAddress = $this->convertToExcelAddressValue($baseAddress);
|
$baseAddress = FormulaTranslator::convertToExcelAddressValue($baseAddress);
|
||||||
$expression = substr($expression, strpos($expression, ':=') + 1);
|
$expression = substr($expression, strpos($expression, ':=') + 1);
|
||||||
$expression = $this->convertToExcelFormulaValue($expression);
|
$expression = FormulaTranslator::convertToExcelFormulaValue($expression);
|
||||||
|
|
||||||
$this->addDefinedName($baseAddress, $definedName, $expression);
|
$this->addDefinedName($baseAddress, $definedName, $expression);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,11 @@
|
||||||
|
|
||||||
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
|
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
|
||||||
|
|
||||||
use DOMElement;
|
|
||||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
||||||
|
|
||||||
abstract class BaseReader
|
class FormulaTranslator
|
||||||
{
|
{
|
||||||
/**
|
public static function convertToExcelAddressValue(string $openOfficeAddress): string
|
||||||
* @var Spreadsheet
|
|
||||||
*/
|
|
||||||
protected $spreadsheet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $tableNs;
|
|
||||||
|
|
||||||
public function __construct(Spreadsheet $spreadsheet, string $tableNs)
|
|
||||||
{
|
|
||||||
$this->spreadsheet = $spreadsheet;
|
|
||||||
$this->tableNs = $tableNs;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract public function read(DOMElement $workbookData): void;
|
|
||||||
|
|
||||||
protected function convertToExcelAddressValue(string $openOfficeAddress): string
|
|
||||||
{
|
{
|
||||||
$excelAddress = $openOfficeAddress;
|
$excelAddress = $openOfficeAddress;
|
||||||
|
|
||||||
|
|
@ -46,13 +26,16 @@ abstract class BaseReader
|
||||||
return $excelAddress ?? '';
|
return $excelAddress ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function convertToExcelFormulaValue(string $openOfficeFormula): string
|
public static function convertToExcelFormulaValue(string $openOfficeFormula): string
|
||||||
{
|
{
|
||||||
$temp = explode('"', $openOfficeFormula);
|
$temp = explode(Calculation::FORMULA_STRING_QUOTE, $openOfficeFormula);
|
||||||
$tKey = false;
|
$tKey = false;
|
||||||
|
$inMatrixBracesLevel = 0;
|
||||||
|
$inFunctionBracesLevel = 0;
|
||||||
foreach ($temp as &$value) {
|
foreach ($temp as &$value) {
|
||||||
// @var string $value
|
// @var string $value
|
||||||
// Only replace in alternate array entries (i.e. non-quoted blocks)
|
// Only replace in alternate array entries (i.e. non-quoted blocks)
|
||||||
|
// so that conversion isn't done in string values
|
||||||
if ($tKey = !$tKey) {
|
if ($tKey = !$tKey) {
|
||||||
// Cell range reference in another sheet
|
// Cell range reference in another sheet
|
||||||
$value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value);
|
$value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value);
|
||||||
|
|
@ -65,7 +48,28 @@ abstract class BaseReader
|
||||||
// Convert references to defined names/formulae
|
// Convert references to defined names/formulae
|
||||||
$value = str_replace('$$', '', $value ?? '');
|
$value = str_replace('$$', '', $value ?? '');
|
||||||
|
|
||||||
$value = Calculation::translateSeparator(';', ',', $value, $inBraces);
|
// Convert ODS function argument separators to Excel function argument separators
|
||||||
|
$value = Calculation::translateSeparator(';', ',', $value, $inFunctionBracesLevel);
|
||||||
|
|
||||||
|
// Convert ODS matrix separators to Excel matrix separators
|
||||||
|
$value = Calculation::translateSeparator(
|
||||||
|
';',
|
||||||
|
',',
|
||||||
|
$value,
|
||||||
|
$inMatrixBracesLevel,
|
||||||
|
Calculation::FORMULA_OPEN_MATRIX_BRACE,
|
||||||
|
Calculation::FORMULA_CLOSE_MATRIX_BRACE
|
||||||
|
);
|
||||||
|
$value = Calculation::translateSeparator(
|
||||||
|
'|',
|
||||||
|
';',
|
||||||
|
$value,
|
||||||
|
$inMatrixBracesLevel,
|
||||||
|
Calculation::FORMULA_OPEN_MATRIX_BRACE,
|
||||||
|
Calculation::FORMULA_CLOSE_MATRIX_BRACE
|
||||||
|
);
|
||||||
|
|
||||||
|
$value = preg_replace('/COM\.MICROSOFT\./ui', '', $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,4 +75,9 @@ return [
|
||||||
'tr',
|
'tr',
|
||||||
'=WORKDAY.INTL(B1)',
|
'=WORKDAY.INTL(B1)',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'=STØRST(ABS({2,-3;-4,5}); ABS{-2,3;4,-5})',
|
||||||
|
'nb',
|
||||||
|
'=MAX(ABS({2,-3;-4,5}), ABS{-2,3;4,-5})',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue