Skip to content

Commit 50a9bc8

Browse files
gurrumpaladPowerKiKi
authored andcommitted
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 #325 Fixes #662
1 parent 57404f4 commit 50a9bc8

File tree

16 files changed

+78
-73
lines changed

16 files changed

+78
-73
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
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

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3478,19 +3478,15 @@ private function _parseFormula($formula, Cell $pCell = null)
34783478
$testPrevOp = $stack->last(1);
34793479
if ($testPrevOp['value'] == ':') {
34803480
$startRowColRef = $output[count($output) - 1]['value'];
3481-
$rangeWS1 = '';
3482-
if (strpos('!', $startRowColRef) !== false) {
3483-
list($rangeWS1, $startRowColRef) = explode('!', $startRowColRef);
3484-
}
3481+
list($rangeWS1, $startRowColRef) = Worksheet::extractSheetTitle($startRowColRef, true);
34853482
if ($rangeWS1 != '') {
34863483
$rangeWS1 .= '!';
34873484
}
3488-
$rangeWS2 = $rangeWS1;
3489-
if (strpos('!', $val) !== false) {
3490-
list($rangeWS2, $val) = explode('!', $val);
3491-
}
3485+
list($rangeWS2, $val) = Worksheet::extractSheetTitle($val, true);
34923486
if ($rangeWS2 != '') {
34933487
$rangeWS2 .= '!';
3488+
} else {
3489+
$rangeWS2 = $rangeWS1;
34943490
}
34953491
if ((is_int($startRowColRef)) && (ctype_digit($val)) &&
34963492
($startRowColRef <= 1048576) && ($val <= 1048576)) {
@@ -3665,13 +3661,12 @@ private function processTokenStack($tokens, $cellID = null, Cell $pCell = null)
36653661
case ':': // Range
36663662
$sheet1 = $sheet2 = '';
36673663
if (strpos($operand1Data['reference'], '!') !== false) {
3668-
list($sheet1, $operand1Data['reference']) = explode('!', $operand1Data['reference']);
3664+
list($sheet1, $operand1Data['reference']) = Worksheet::extractSheetTitle($operand1Data['reference'], true);
36693665
} else {
36703666
$sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
36713667
}
3672-
if (strpos($operand2Data['reference'], '!') !== false) {
3673-
list($sheet2, $operand2Data['reference']) = explode('!', $operand2Data['reference']);
3674-
} else {
3668+
list($sheet2, $operand2Data['reference']) = Worksheet::extractSheetTitle($operand2Data['reference'], true);
3669+
if (empty($sheet2)) {
36753670
$sheet2 = $sheet1;
36763671
}
36773672
if ($sheet1 == $sheet2) {

src/PhpSpreadsheet/Calculation/LookupRef.php

Lines changed: 6 additions & 9 deletions
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

Lines changed: 3 additions & 10 deletions
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

Lines changed: 1 addition & 5 deletions
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 5 additions & 6 deletions
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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,8 +1833,7 @@ public function load($pFilename)
18331833
$rangeSets = preg_split("/'(.*?)'(?:![A-Z0-9]+:[A-Z0-9]+,?)/", $extractedRange, PREG_SPLIT_NO_EMPTY);
18341834
$newRangeSets = [];
18351835
foreach ($rangeSets as $rangeSet) {
1836-
$range = explode('!', $rangeSet); // FIXME: what if sheetname contains exclamation mark?
1837-
$rangeSet = isset($range[1]) ? $range[1] : $range[0];
1836+
list($sheetName, $rangeSet) = Worksheet::extractSheetTitle($rangeSet, true);
18381837
if (strpos($rangeSet, ':') === false) {
18391838
$rangeSet = $rangeSet . ':' . $rangeSet;
18401839
}
@@ -1881,8 +1880,8 @@ public function load($pFilename)
18811880
break;
18821881
default:
18831882
if ($mapSheetId[(int) $definedName['localSheetId']] !== null) {
1884-
$range = explode('!', (string) $definedName);
1885-
if (count($range) == 2) {
1883+
if (strpos((string) $definedName, '!') !== false) {
1884+
$range = Worksheet::extractSheetTitle((string) $definedName, true);
18861885
$range[0] = str_replace("''", "'", $range[0]);
18871886
$range[0] = str_replace("'", '', $range[0]);
18881887
if ($worksheet = $docSheet->getParent()->getSheetByName($range[0])) {
@@ -1908,8 +1907,7 @@ public function load($pFilename)
19081907
$locatedSheet = $excel->getSheetByName($extractedSheetName);
19091908

19101909
// Modify range
1911-
$range = explode('!', $extractedRange);
1912-
$extractedRange = isset($range[1]) ? $range[1] : $range[0];
1910+
list($worksheetName, $extractedRange) = Worksheet::extractSheetTitle($extractedRange, true);
19131911
}
19141912

19151913
if ($locatedSheet !== null) {

src/PhpSpreadsheet/Worksheet/AutoFilter.php

Lines changed: 2 additions & 5 deletions
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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2729,12 +2729,12 @@ public function getHashCode()
27292729
public static function extractSheetTitle($pRange, $returnRange = false)
27302730
{
27312731
// Sheet title included?
2732-
if (($sep = strpos($pRange, '!')) === false) {
2733-
return '';
2732+
if (($sep = strrpos($pRange, '!')) === false) {
2733+
return $returnRange ? ['', $pRange] : '';
27342734
}
27352735

27362736
if ($returnRange) {
2737-
return [trim(substr($pRange, 0, $sep), "'"), substr($pRange, $sep + 1)];
2737+
return [substr($pRange, 0, $sep), substr($pRange, $sep + 1)];
27382738
}
27392739

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

src/PhpSpreadsheet/Writer/Xls/Parser.php

Lines changed: 3 additions & 2 deletions
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

Lines changed: 1 addition & 3 deletions
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

Lines changed: 1 addition & 3 deletions
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

Lines changed: 26 additions & 0 deletions
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

Lines changed: 4 additions & 4 deletions
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

Lines changed: 4 additions & 4 deletions
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)