Skip to content

Commit a07be5f

Browse files
gurrumpaladFrederic Delaunay
authored and
Frederic Delaunay
committed
Sheet title can contain exclamation mark (in formulas)
When extracting sheet title from string reference (like `"Work!sheet1!A1"`), PHP function `explode()` divide this string into three parts: `['Work', 'sheet1', 'A1']`. And then these wrong values are used in formulas, ranges, etc. This change fix that problem by using special function `Worksheet::extractSheetTitle()`. This function also has been changed to make sure that worksheet title can contain "!" character. So, that function search last position of "!" in reference string and divide it to 2 parts correctly: `['Work!sheet1', 'A1']`. Fixes PHPOffice#325 Fixes PHPOffice#662
1 parent a83b948 commit a07be5f

File tree

16 files changed

+78
-73
lines changed

16 files changed

+78
-73
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [Unreleased]
9+
10+
### Fixed
11+
12+
- Sheet title can contain exclamation mark - [#325](https://github.com/PHPOffice/PhpSpreadsheet/issues/325)
13+
814
## [1.4.1] - 2018-09-30
915

1016
### Fixed

src/PhpSpreadsheet/Calculation/Calculation.php

+7-12
Original file line numberDiff line numberDiff line change
@@ -3488,19 +3488,15 @@ private function _parseFormula($formula, Cell $pCell = null)
34883488
$testPrevOp = $stack->last(1);
34893489
if ($testPrevOp['value'] == ':') {
34903490
$startRowColRef = $output[count($output) - 1]['value'];
3491-
$rangeWS1 = '';
3492-
if (strpos('!', $startRowColRef) !== false) {
3493-
list($rangeWS1, $startRowColRef) = explode('!', $startRowColRef);
3494-
}
3491+
list($rangeWS1, $startRowColRef) = Worksheet::extractSheetTitle($startRowColRef, true);
34953492
if ($rangeWS1 != '') {
34963493
$rangeWS1 .= '!';
34973494
}
3498-
$rangeWS2 = $rangeWS1;
3499-
if (strpos('!', $val) !== false) {
3500-
list($rangeWS2, $val) = explode('!', $val);
3501-
}
3495+
list($rangeWS2, $val) = Worksheet::extractSheetTitle($val, true);
35023496
if ($rangeWS2 != '') {
35033497
$rangeWS2 .= '!';
3498+
} else {
3499+
$rangeWS2 = $rangeWS1;
35043500
}
35053501
if ((is_int($startRowColRef)) && (ctype_digit($val)) &&
35063502
($startRowColRef <= 1048576) && ($val <= 1048576)) {
@@ -3679,13 +3675,12 @@ private function processTokenStack($tokens, $cellID = null, Cell $pCell = null)
36793675
case ':': // Range
36803676
$sheet1 = $sheet2 = '';
36813677
if (strpos($operand1Data['reference'], '!') !== false) {
3682-
list($sheet1, $operand1Data['reference']) = explode('!', $operand1Data['reference']);
3678+
list($sheet1, $operand1Data['reference']) = Worksheet::extractSheetTitle($operand1Data['reference'], true);
36833679
} else {
36843680
$sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
36853681
}
3686-
if (strpos($operand2Data['reference'], '!') !== false) {
3687-
list($sheet2, $operand2Data['reference']) = explode('!', $operand2Data['reference']);
3688-
} else {
3682+
list($sheet2, $operand2Data['reference']) = Worksheet::extractSheetTitle($operand2Data['reference'], true);
3683+
if (empty($sheet2)) {
36893684
$sheet2 = $sheet1;
36903685
}
36913686
if ($sheet1 == $sheet2) {

src/PhpSpreadsheet/Calculation/LookupRef.php

+6-9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpOffice\PhpSpreadsheet\Cell\Cell;
66
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
7+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
78

89
class LookupRef
910
{
@@ -96,9 +97,7 @@ public static function COLUMN($cellAddress = null)
9697
return (int) Coordinate::columnIndexFromString($columnKey);
9798
}
9899
} else {
99-
if (strpos($cellAddress, '!') !== false) {
100-
list($sheet, $cellAddress) = explode('!', $cellAddress);
101-
}
100+
list($sheet, $cellAddress) = Worksheet::extractSheetTitle($cellAddress, true);
102101
if (strpos($cellAddress, ':') !== false) {
103102
list($startAddress, $endAddress) = explode(':', $cellAddress);
104103
$startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
@@ -175,9 +174,7 @@ public static function ROW($cellAddress = null)
175174
}
176175
}
177176
} else {
178-
if (strpos($cellAddress, '!') !== false) {
179-
list($sheet, $cellAddress) = explode('!', $cellAddress);
180-
}
177+
list($sheet, $cellAddress) = Worksheet::extractSheetTitle($cellAddress, true);
181178
if (strpos($cellAddress, ':') !== false) {
182179
list($startAddress, $endAddress) = explode(':', $cellAddress);
183180
$startAddress = preg_replace('/\D/', '', $startAddress);
@@ -297,7 +294,7 @@ public static function INDIRECT($cellAddress = null, Cell $pCell = null)
297294
}
298295

299296
if (strpos($cellAddress, '!') !== false) {
300-
list($sheetName, $cellAddress) = explode('!', $cellAddress);
297+
list($sheetName, $cellAddress) = Worksheet::extractSheetTitle($cellAddress, true);
301298
$sheetName = trim($sheetName, "'");
302299
$pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
303300
} else {
@@ -308,7 +305,7 @@ public static function INDIRECT($cellAddress = null, Cell $pCell = null)
308305
}
309306

310307
if (strpos($cellAddress, '!') !== false) {
311-
list($sheetName, $cellAddress) = explode('!', $cellAddress);
308+
list($sheetName, $cellAddress) = Worksheet::extractSheetTitle($cellAddress, true);
312309
$sheetName = trim($sheetName, "'");
313310
$pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
314311
} else {
@@ -361,7 +358,7 @@ public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $hei
361358

362359
$sheetName = null;
363360
if (strpos($cellAddress, '!')) {
364-
list($sheetName, $cellAddress) = explode('!', $cellAddress);
361+
list($sheetName, $cellAddress) = Worksheet::extractSheetTitle($cellAddress, true);
365362
$sheetName = trim($sheetName, "'");
366363
}
367364
if (strpos($cellAddress, ':')) {

src/PhpSpreadsheet/Cell/Coordinate.php

+3-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheet\Cell;
44

55
use PhpOffice\PhpSpreadsheet\Exception;
6+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
67

78
/**
89
* Helper class to manipulate cell coordinates.
@@ -70,11 +71,7 @@ public static function absoluteReference($pCoordinateString)
7071
}
7172

7273
// Split out any worksheet name from the reference
73-
$worksheet = '';
74-
$cellAddress = explode('!', $pCoordinateString);
75-
if (count($cellAddress) > 1) {
76-
list($worksheet, $pCoordinateString) = $cellAddress;
77-
}
74+
list($worksheet, $pCoordinateString) = Worksheet::extractSheetTitle($pCoordinateString, true);
7875
if ($worksheet > '') {
7976
$worksheet .= '!';
8077
}
@@ -105,11 +102,7 @@ public static function absoluteCoordinate($pCoordinateString)
105102
}
106103

107104
// Split out any worksheet name from the coordinate
108-
$worksheet = '';
109-
$cellAddress = explode('!', $pCoordinateString);
110-
if (count($cellAddress) > 1) {
111-
list($worksheet, $pCoordinateString) = $cellAddress;
112-
}
105+
list($worksheet, $pCoordinateString) = Worksheet::extractSheetTitle($pCoordinateString, true);
113106
if ($worksheet > '') {
114107
$worksheet .= '!';
115108
}

src/PhpSpreadsheet/Chart/DataSeriesValues.php

+1-5
Original file line numberDiff line numberDiff line change
@@ -354,11 +354,7 @@ public function refresh(Worksheet $worksheet, $flatten = true)
354354
}
355355
unset($dataValue);
356356
} else {
357-
$cellRange = explode('!', $this->dataSource);
358-
if (count($cellRange) > 1) {
359-
list(, $cellRange) = $cellRange;
360-
}
361-
357+
list($worksheet, $cellRange) = Worksheet::extractSheetTitle($this->dataSource, true);
362358
$dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange));
363359
if (($dimensions[0] == 1) || ($dimensions[1] == 1)) {
364360
$this->dataValues = Functions::flattenArray($newDataValues);

src/PhpSpreadsheet/Reader/Gnumeric.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PhpOffice\PhpSpreadsheet\Style\Borders;
1717
use PhpOffice\PhpSpreadsheet\Style\Fill;
1818
use PhpOffice\PhpSpreadsheet\Style\Font;
19+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
1920
use XMLReader;
2021

2122
class Gnumeric extends BaseReader
@@ -784,7 +785,7 @@ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet)
784785
continue;
785786
}
786787

787-
$range = explode('!', $range);
788+
$range = Worksheet::extractSheetTitle($range, true);
788789
$range[0] = trim($range[0], "'");
789790
if ($worksheet = $spreadsheet->getSheetByName($range[0])) {
790791
$extractedRange = str_replace('$', '', $range[1]);

src/PhpSpreadsheet/Reader/Xls.php

+5-6
Original file line numberDiff line numberDiff line change
@@ -1213,7 +1213,7 @@ public function load($pFilename)
12131213
// $range should look like one of these
12141214
// Foo!$C$7:$J$66
12151215
// Bar!$A$1:$IV$2
1216-
$explodes = explode('!', $range); // FIXME: what if sheetname contains exclamation mark?
1216+
$explodes = Worksheet::extractSheetTitle($range, true);
12171217
$sheetName = trim($explodes[0], "'");
12181218
if (count($explodes) == 2) {
12191219
if (strpos($explodes[1], ':') === false) {
@@ -1243,8 +1243,8 @@ public function load($pFilename)
12431243
// $range should look like this one of these
12441244
// Sheet!$A$1:$B$65536
12451245
// Sheet!$A$1:$IV$2
1246-
$explodes = explode('!', $range);
1247-
if (count($explodes) == 2) {
1246+
if (strpos($range, '!') !== false) {
1247+
$explodes = Worksheet::extractSheetTitle($range, true);
12481248
if ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) {
12491249
$extractedRange = $explodes[1];
12501250
$extractedRange = str_replace('$', '', $extractedRange);
@@ -1270,9 +1270,8 @@ public function load($pFilename)
12701270
}
12711271
} else {
12721272
// Extract range
1273-
$explodes = explode('!', $definedName['formula']);
1274-
1275-
if (count($explodes) == 2) {
1273+
if (strpos($definedName['formula'], '!') !== false) {
1274+
$explodes = Worksheet::extractSheetTitle($definedName['formula'], true);
12761275
if (($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) ||
12771276
($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'")))) {
12781277
$extractedRange = $explodes[1];

src/PhpSpreadsheet/Reader/Xlsx.php

+4-6
Original file line numberDiff line numberDiff line change
@@ -1848,8 +1848,7 @@ public function load($pFilename)
18481848
$rangeSets = preg_split("/'(.*?)'(?:![A-Z0-9]+:[A-Z0-9]+,?)/", $extractedRange, PREG_SPLIT_NO_EMPTY);
18491849
$newRangeSets = [];
18501850
foreach ($rangeSets as $rangeSet) {
1851-
$range = explode('!', $rangeSet); // FIXME: what if sheetname contains exclamation mark?
1852-
$rangeSet = isset($range[1]) ? $range[1] : $range[0];
1851+
list($sheetName, $rangeSet) = Worksheet::extractSheetTitle($rangeSet, true);
18531852
if (strpos($rangeSet, ':') === false) {
18541853
$rangeSet = $rangeSet . ':' . $rangeSet;
18551854
}
@@ -1896,8 +1895,8 @@ public function load($pFilename)
18961895
break;
18971896
default:
18981897
if ($mapSheetId[(int) $definedName['localSheetId']] !== null) {
1899-
$range = explode('!', (string) $definedName);
1900-
if (count($range) == 2) {
1898+
if (strpos((string) $definedName, '!') !== false) {
1899+
$range = Worksheet::extractSheetTitle((string) $definedName, true);
19011900
$range[0] = str_replace("''", "'", $range[0]);
19021901
$range[0] = str_replace("'", '', $range[0]);
19031902
if ($worksheet = $docSheet->getParent()->getSheetByName($range[0])) {
@@ -1923,8 +1922,7 @@ public function load($pFilename)
19231922
$locatedSheet = $excel->getSheetByName($extractedSheetName);
19241923

19251924
// Modify range
1926-
$range = explode('!', $extractedRange);
1927-
$extractedRange = isset($range[1]) ? $range[1] : $range[0];
1925+
list($worksheetName, $extractedRange) = Worksheet::extractSheetTitle($extractedRange, true);
19281926
}
19291927

19301928
if ($locatedSheet !== null) {

src/PhpSpreadsheet/Worksheet/AutoFilter.php

+2-5
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,8 @@ public function getRange()
8989
*/
9090
public function setRange($pRange)
9191
{
92-
// Uppercase coordinate
93-
$cellAddress = explode('!', strtoupper($pRange));
94-
if (count($cellAddress) > 1) {
95-
list($worksheet, $pRange) = $cellAddress;
96-
}
92+
// extract coordinate
93+
list($worksheet, $pRange) = Worksheet::extractSheetTitle($pRange, true);
9794

9895
if (strpos($pRange, ':') !== false) {
9996
$this->range = $pRange;

src/PhpSpreadsheet/Worksheet/Worksheet.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -2751,12 +2751,12 @@ public function getHashCode()
27512751
public static function extractSheetTitle($pRange, $returnRange = false)
27522752
{
27532753
// Sheet title included?
2754-
if (($sep = strpos($pRange, '!')) === false) {
2755-
return '';
2754+
if (($sep = strrpos($pRange, '!')) === false) {
2755+
return $returnRange ? ['', $pRange] : '';
27562756
}
27572757

27582758
if ($returnRange) {
2759-
return [trim(substr($pRange, 0, $sep), "'"), substr($pRange, $sep + 1)];
2759+
return [substr($pRange, 0, $sep), substr($pRange, $sep + 1)];
27602760
}
27612761

27622762
return substr($pRange, $sep + 1);

src/PhpSpreadsheet/Writer/Xls/Parser.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
44

55
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
6+
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as PhpspreadsheetWorksheet;
67
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
78

89
// Original file header of PEAR::Spreadsheet_Excel_Writer_Parser (used as the base for this class):
@@ -643,7 +644,7 @@ private function convertRange2d($range, $class = 0)
643644
private function convertRange3d($token)
644645
{
645646
// Split the ref at the ! symbol
646-
list($ext_ref, $range) = explode('!', $token);
647+
list($ext_ref, $range) = PhpspreadsheetWorksheet::extractSheetTitle($token, true);
647648

648649
// Convert the external reference part (different for BIFF8)
649650
$ext_ref = $this->getRefIndex($ext_ref);
@@ -695,7 +696,7 @@ private function convertRef2d($cell)
695696
private function convertRef3d($cell)
696697
{
697698
// Split the ref at the ! symbol
698-
list($ext_ref, $cell) = explode('!', $cell);
699+
list($ext_ref, $cell) = PhpspreadsheetWorksheet::extractSheetTitle($cell, true);
699700

700701
// Convert the external reference part (different for BIFF8)
701702
$ext_ref = $this->getRefIndex($ext_ref);

src/PhpSpreadsheet/Writer/Xlsx/Workbook.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,7 @@ private function writeDefinedNameForAutofilter(XMLWriter $objWriter, Worksheet $
339339
$range = Coordinate::splitRange($autoFilterRange);
340340
$range = $range[0];
341341
// Strip any worksheet ref so we can make the cell ref absolute
342-
if (strpos($range[0], '!') !== false) {
343-
list($ws, $range[0]) = explode('!', $range[0]);
344-
}
342+
list($ws, $range[0]) = Worksheet::extractSheetTitle($range[0], true);
345343

346344
$range[0] = Coordinate::absoluteCoordinate($range[0]);
347345
$range[1] = Coordinate::absoluteCoordinate($range[1]);

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -748,9 +748,7 @@ private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $
748748
$range = Coordinate::splitRange($autoFilterRange);
749749
$range = $range[0];
750750
// Strip any worksheet ref
751-
if (strpos($range[0], '!') !== false) {
752-
list($ws, $range[0]) = explode('!', $range[0]);
753-
}
751+
list($ws, $range[0]) = PhpspreadsheetWorksheet::extractSheetTitle($range[0], true);
754752
$range = implode(':', $range);
755753

756754
$objWriter->writeAttribute('ref', str_replace('$', '', $range));

tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php

+26
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,30 @@ public function testFreezePaneSelectedCell()
130130
$worksheet->freezePane('B2');
131131
self::assertSame('B2', $worksheet->getTopLeftCell());
132132
}
133+
134+
public function extractSheetTitleProvider()
135+
{
136+
return [
137+
['B2', '', '', 'B2'],
138+
['testTitle!B2', 'testTitle', 'B2', 'B2'],
139+
['test!Title!B2', 'test!Title', 'B2', 'B2'],
140+
];
141+
}
142+
143+
/**
144+
* @param string $range
145+
* @param string $expectTitle
146+
* @param string $expectCell
147+
* @param string $expectCell2
148+
* @dataProvider extractSheetTitleProvider
149+
*/
150+
public function testExtractSheetTitle($range, $expectTitle, $expectCell, $expectCell2)
151+
{
152+
// only cell reference
153+
self::assertSame($expectCell, Worksheet::extractSheetTitle($range));
154+
// with title in array
155+
$arRange = Worksheet::extractSheetTitle($range, true);
156+
self::assertSame($expectTitle, $arRange[0]);
157+
self::assertSame($expectCell2, $arRange[1]);
158+
}
133159
}

tests/data/CellAbsoluteCoordinate.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@
4242
'\'Worksheet1\'!$AI256',
4343
],
4444
[
45-
'\'Worksheet1\'!$AI$256',
46-
'\'Worksheet1\'!AI$256',
45+
'\'Work!sheet1!\'!$AI$256',
46+
'\'Work!sheet1!\'!AI$256',
4747
],
4848
[
49-
'\'Worksheet1\'!$AI$256',
50-
'\'Worksheet1\'!$AI$256',
49+
'\'Work!sheet1!\'!$AI$256',
50+
'\'Work!sheet1!\'!$AI$256',
5151
],
5252
];

tests/data/CellAbsoluteReference.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626
'AI2012',
2727
],
2828
[
29-
'\'Worksheet1\'!$AI$256',
30-
'\'Worksheet1\'!AI256',
29+
'\'Work!sheet1\'!$AI$256',
30+
'\'Work!sheet1\'!AI256',
3131
],
3232
[
33-
'Worksheet1!$AI$256',
34-
'Worksheet1!AI256',
33+
'Work!sheet1!$AI$256',
34+
'Work!sheet1!AI256',
3535
],
3636
[
3737
'\'Data Worksheet\'!$AI$256',

0 commit comments

Comments
 (0)