Skip to content

Commit

Permalink
Writer Xls Parser Backport 2 Changes
Browse files Browse the repository at this point in the history
PR #4233 and PR #4244.
  • Loading branch information
oleibman committed Feb 7, 2025
1 parent cf35718 commit 6ef0269
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 10 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ 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.3.8

### Fixed

- Xls writer Parser Mishandling True/False Argument. Backport of [PR #4333](https://github.com/PHPOffice/PhpSpreadsheet/pull/4333)
- Xls writer Parser Parse By Character Not Byte. Backport of [PR #4344](https://github.com/PHPOffice/PhpSpreadsheet/pull/4344)

# 2025-01-26 - 2.3.7

### Fixed
Expand Down
25 changes: 15 additions & 10 deletions src/PhpSpreadsheet/Writer/Xls/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class Parser
. '[$]?[A-Ia-i]?[A-Za-z][$]?(\\d+)'
. '$~u';

private const UTF8 = 'UTF-8';

/**
* The index of the character we are currently looking at.
*/
Expand Down Expand Up @@ -991,37 +993,38 @@ private function advance(): void
{
$token = '';
$i = $this->currentCharacter;
$formula_length = strlen($this->formula);
$formula = mb_str_split($this->formula, 1, self::UTF8);
$formula_length = count($formula);
// eat up white spaces
if ($i < $formula_length) {
while ($this->formula[$i] == ' ') {
while ($formula[$i] === ' ') {
++$i;
}

if ($i < ($formula_length - 1)) {
$this->lookAhead = $this->formula[$i + 1];
$this->lookAhead = $formula[$i + 1];
}
$token = '';
}

while ($i < $formula_length) {
$token .= $this->formula[$i];
$token .= $formula[$i];

if ($i < ($formula_length - 1)) {
$this->lookAhead = $this->formula[$i + 1];
$this->lookAhead = $formula[$i + 1];
} else {
$this->lookAhead = '';
}

if ($this->match($token) != '') {
if ($this->match($token) !== '') {
$this->currentCharacter = $i + 1;
$this->currentToken = $token;

return;
}

if ($i < ($formula_length - 2)) {
$this->lookAhead = $this->formula[$i + 2];
$this->lookAhead = $formula[$i + 2];
} else { // if we run out of characters lookAhead becomes empty
$this->lookAhead = '';
}
Expand Down Expand Up @@ -1198,8 +1201,8 @@ private function match(string $token): string
public function parse(string $formula): bool
{
$this->currentCharacter = 0;
$this->formula = (string) $formula;
$this->lookAhead = $formula[1] ?? '';
$this->formula = $formula;
$this->lookAhead = mb_substr($formula, 1, 1, self::UTF8);
$this->advance();
$this->parseTree = $this->condition();

Expand Down Expand Up @@ -1624,7 +1627,9 @@ public function toReversePolish(array $tree = []): string
}

// add its left subtree and return.
return $left_tree . $this->convertFunction($tree['value'], $tree['right']);
if ($left_tree !== '' || $tree['right'] !== '') {
return $left_tree . $this->convertFunction($tree['value'], $tree['right'] ?: 0);
}
}
$converted_tree = $this->convert($tree['value']);

Expand Down
56 changes: 56 additions & 0 deletions tests/PhpSpreadsheetTests/Writer/Xls/Issue4331Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests\Writer\Xls;

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;

class Issue4331Test extends AbstractFunctional
{
public function testIssue4331(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$c3 = '=VLOOKUP(B3,$B$10:$C$13,2,FALSE)';
$d3 = '=VLOOKUP("intermediate",$B$10:$C$13,2,TRUE)';
$c4 = '=VLOOKUP(B3,$B$10:$C$13,2,FALSE())';
$d4 = '=VLOOKUP("intermediate",$B$10:$C$13,2,TRUE())';
$sheet->fromArray(
[
['level', 'result'],
['medium', $c3, $d3],
[null, $c4, $d4],
],
null,
'B2',
true
);
$sheet->fromArray(
[
['high', 6],
['low', 2],
['medium', 4],
['none', 0],
],
null,
'B10',
true
);

$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls');
$spreadsheet->disconnectWorksheets();

$worksheet = $reloadedSpreadsheet->getActiveSheet();
self::assertSame($c3, $worksheet->getCell('C3')->getValue());
self::assertSame(4, $worksheet->getCell('C3')->getCalculatedValue());
self::assertSame($d3, $worksheet->getCell('D3')->getValue());
self::assertSame(6, $worksheet->getCell('D3')->getCalculatedValue());
self::assertSame($c4, $worksheet->getCell('C4')->getValue());
self::assertSame(4, $worksheet->getCell('C4')->getCalculatedValue());
self::assertSame($d4, $worksheet->getCell('D4')->getValue());
self::assertSame(6, $worksheet->getCell('D4')->getCalculatedValue());
$reloadedSpreadsheet->disconnectWorksheets();
}
}
65 changes: 65 additions & 0 deletions tests/PhpSpreadsheetTests/Writer/Xls/NonLatinFormulasTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace PhpOffice\PhpSpreadsheetTests\Writer\Xls;

use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
use PhpOffice\PhpSpreadsheet\Cell\DataValidator;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;

class NonLatinFormulasTest extends AbstractFunctional
{
public function testNonLatin(): void
{
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->getActiveSheet();

$validation = $worksheet->getCell('B1')->getDataValidation();
$validation->setType(DataValidation::TYPE_LIST);
$validation->setErrorStyle(DataValidation::STYLE_STOP);
$validation->setAllowBlank(false);
$validation->setShowInputMessage(true);
$validation->setShowErrorMessage(true);
$validation->setShowDropDown(true);
$validation->setFormula1('"слово, сло"');

$dataValidator = new DataValidator();
$worksheet->getCell('B1')->setValue('слово');
self::assertTrue(
$dataValidator->isValid($worksheet->getCell('B1'))
);
$worksheet->getCell('B1')->setValue('слов');
self::assertFalse(
$dataValidator->isValid($worksheet->getCell('B1'))
);

$worksheet->setTitle('словслов');
$worksheet->getCell('A1')->setValue('=словслов!B1');
$worksheet->getCell('A2')->setValue("='словслов'!B1");
$spreadsheet->addNamedRange(new NamedRange('слсл', $worksheet, '$B$1'));
$worksheet->getCell('A3')->setValue('=слсл');

$robj = $this->writeAndReload($spreadsheet, 'Xls');
$spreadsheet->disconnectWorksheets();
$sheet0 = $robj->getActiveSheet();
self::assertSame('словслов', $sheet0->getTitle());
self::assertSame('=словслов!B1', $sheet0->getCell('A1')->getValue());
self::assertSame('слов', $sheet0->getCell('A1')->getCalculatedValue());
// Quotes around sheet name are stripped off - harmless
//self::assertSame("='словслов'!B1", $sheet0->getCell('A2')->getValue());
self::assertSame('слов', $sheet0->getCell('A2')->getCalculatedValue());
// Formulas with defined names don't work in Xls Writer
//self::assertSame('=слсл', $sheet0->getCell('A3')->getValue());
// But result should be accurate
self::assertSame('слов', $sheet0->getCell('A3')->getCalculatedValue());
$names = $robj->getDefinedNames();
self::assertCount(1, $names);
// name has been uppercased
$namedRange = $names['СЛСЛ'] ?? null;
self::assertInstanceOf(NamedRange::class, $namedRange);
self::assertSame('$B$1', $namedRange->getRange());

$robj->disconnectWorksheets();
}
}

0 comments on commit 6ef0269

Please sign in to comment.