Add option to suppress validation of sheet titles (#186)
Add option to suppress validation of sheet titles Based on a "lowest common denominator" approach to compatibility, we will continue to enforce a 31-character limit for sheet titles. However, this limit should not be enforced when loading an existing file. Added a new optional parameter to Worksheet::setTitle() and Worksheet::setCodeName() to suppress validation and massaging, based on the premise that existing files should be given a best-effort approach to loading and parsing. Unfortunately, it's not possible with the current architecture to prevent users from making use of this functionality, aside from with a strongly-worded warning. Added test coverage. I didn't see any existing unit tests of the Worksheet class, so I created a new test to cover these methods. Fixes #176
This commit is contained in:
parent
fb12e82d62
commit
1853aaac79
|
@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
- Fix to getCell() method when cell reference includes a worksheet reference - @MarkBaker
|
- Fix to getCell() method when cell reference includes a worksheet reference - @MarkBaker
|
||||||
- Ignore inlineStr type if formula element exists - @ncrypthic [#570](https://github.com/PHPOffice/PHPExcel/issues/570)
|
- Ignore inlineStr type if formula element exists - @ncrypthic [#570](https://github.com/PHPOffice/PHPExcel/issues/570)
|
||||||
- Excel 2007 Reader freezes because of conditional formatting - @rentalhost [#575](https://github.com/PHPOffice/PHPExcel/issues/575)
|
- Excel 2007 Reader freezes because of conditional formatting - @rentalhost [#575](https://github.com/PHPOffice/PHPExcel/issues/575)
|
||||||
|
- Readers will now parse files containing worksheet titles over 31 characters [#176](https://github.com/PHPOffice/PhpSpreadsheet/pull/176)
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
|
||||||
|
|
|
@ -351,7 +351,7 @@ class Gnumeric extends BaseReader implements IReader
|
||||||
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
|
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
|
||||||
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
|
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
|
||||||
// name in line with the formula, not the reverse
|
// name in line with the formula, not the reverse
|
||||||
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false);
|
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
|
||||||
|
|
||||||
if ((!$this->readDataOnly) && (isset($sheet->PrintInformation))) {
|
if ((!$this->readDataOnly) && (isset($sheet->PrintInformation))) {
|
||||||
if (isset($sheet->PrintInformation->Margins)) {
|
if (isset($sheet->PrintInformation->Margins)) {
|
||||||
|
|
|
@ -317,7 +317,7 @@ class Html extends BaseReader implements IReader
|
||||||
break;
|
break;
|
||||||
case 'title':
|
case 'title':
|
||||||
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
|
||||||
$sheet->setTitle($cellContent);
|
$sheet->setTitle($cellContent, true, false);
|
||||||
$cellContent = '';
|
$cellContent = '';
|
||||||
break;
|
break;
|
||||||
case 'span':
|
case 'span':
|
||||||
|
|
|
@ -440,7 +440,7 @@ class Ods extends BaseReader implements IReader
|
||||||
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
|
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
|
||||||
// formula cells... during the load, all formulae should be correct, and we're simply
|
// formula cells... during the load, all formulae should be correct, and we're simply
|
||||||
// bringing the worksheet name in line with the formula, not the reverse
|
// bringing the worksheet name in line with the formula, not the reverse
|
||||||
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false);
|
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go through every child of table element
|
// Go through every child of table element
|
||||||
|
|
|
@ -808,7 +808,7 @@ class Xls extends BaseReader implements IReader
|
||||||
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
|
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
|
||||||
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
|
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
|
||||||
// name in line with the formula, not the reverse
|
// name in line with the formula, not the reverse
|
||||||
$this->phpSheet->setTitle($sheet['name'], false);
|
$this->phpSheet->setTitle($sheet['name'], false, false);
|
||||||
$this->phpSheet->setSheetState($sheet['sheetState']);
|
$this->phpSheet->setSheetState($sheet['sheetState']);
|
||||||
|
|
||||||
$this->pos = $sheet['offset'];
|
$this->pos = $sheet['offset'];
|
||||||
|
|
|
@ -697,7 +697,7 @@ class Xlsx extends BaseReader implements IReader
|
||||||
// references in formula cells... during the load, all formulae should be correct,
|
// references in formula cells... during the load, all formulae should be correct,
|
||||||
// and we're simply bringing the worksheet name in line with the formula, not the
|
// and we're simply bringing the worksheet name in line with the formula, not the
|
||||||
// reverse
|
// reverse
|
||||||
$docSheet->setTitle((string) $eleSheet['name'], false);
|
$docSheet->setTitle((string) $eleSheet['name'], false, false);
|
||||||
$fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
|
$fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')];
|
||||||
$xmlSheet = simplexml_load_string(
|
$xmlSheet = simplexml_load_string(
|
||||||
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
//~ http://schemas.openxmlformats.org/spreadsheetml/2006/main"
|
||||||
|
@ -766,7 +766,7 @@ class Xlsx extends BaseReader implements IReader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isset($xmlSheet->sheetPr) && isset($xmlSheet->sheetPr['codeName'])) {
|
if (isset($xmlSheet->sheetPr) && isset($xmlSheet->sheetPr['codeName'])) {
|
||||||
$docSheet->setCodeName((string) $xmlSheet->sheetPr['codeName']);
|
$docSheet->setCodeName((string) $xmlSheet->sheetPr['codeName'], false);
|
||||||
}
|
}
|
||||||
if (isset($xmlSheet->sheetPr) && isset($xmlSheet->sheetPr->outlinePr)) {
|
if (isset($xmlSheet->sheetPr) && isset($xmlSheet->sheetPr->outlinePr)) {
|
||||||
if (isset($xmlSheet->sheetPr->outlinePr['summaryRight']) &&
|
if (isset($xmlSheet->sheetPr->outlinePr['summaryRight']) &&
|
||||||
|
|
|
@ -552,7 +552,7 @@ class Xml extends BaseReader implements IReader
|
||||||
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
|
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
|
||||||
// formula cells... during the load, all formulae should be correct, and we're simply bringing
|
// formula cells... during the load, all formulae should be correct, and we're simply bringing
|
||||||
// the worksheet name in line with the formula, not the reverse
|
// the worksheet name in line with the formula, not the reverse
|
||||||
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false);
|
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$columnID = 'A';
|
$columnID = 'A';
|
||||||
|
|
|
@ -825,52 +825,54 @@ class Worksheet implements IComparable
|
||||||
* Set title.
|
* Set title.
|
||||||
*
|
*
|
||||||
* @param string $pValue String containing the dimension of this worksheet
|
* @param string $pValue String containing the dimension of this worksheet
|
||||||
* @param string $updateFormulaCellReferences boolean Flag indicating whether cell references in formulae should
|
* @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should
|
||||||
* be updated to reflect the new sheet name.
|
* be updated to reflect the new sheet name.
|
||||||
* This should be left as the default true, unless you are
|
* This should be left as the default true, unless you are
|
||||||
* certain that no formula cells on any worksheet contain
|
* certain that no formula cells on any worksheet contain
|
||||||
* references to this worksheet
|
* references to this worksheet
|
||||||
|
* @param bool $validate False to skip validation of new title. WARNING: This should only be set
|
||||||
|
* at parse time (by Readers), where titles can be assumed to be valid.
|
||||||
*
|
*
|
||||||
* @return Worksheet
|
* @return Worksheet
|
||||||
*/
|
*/
|
||||||
public function setTitle($pValue, $updateFormulaCellReferences = true)
|
public function setTitle($pValue, $updateFormulaCellReferences = true, $validate = true)
|
||||||
{
|
{
|
||||||
// Is this a 'rename' or not?
|
// Is this a 'rename' or not?
|
||||||
if ($this->getTitle() == $pValue) {
|
if ($this->getTitle() == $pValue) {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Syntax check
|
|
||||||
self::checkSheetTitle($pValue);
|
|
||||||
|
|
||||||
// Old title
|
// Old title
|
||||||
$oldTitle = $this->getTitle();
|
$oldTitle = $this->getTitle();
|
||||||
|
|
||||||
if ($this->parent) {
|
if ($validate) {
|
||||||
// Is there already such sheet name?
|
// Syntax check
|
||||||
if ($this->parent->sheetNameExists($pValue)) {
|
self::checkSheetTitle($pValue);
|
||||||
// Use name, but append with lowest possible integer
|
|
||||||
|
|
||||||
if (Shared\StringHelper::countCharacters($pValue) > 29) {
|
if ($this->parent) {
|
||||||
$pValue = Shared\StringHelper::substring($pValue, 0, 29);
|
// Is there already such sheet name?
|
||||||
}
|
if ($this->parent->sheetNameExists($pValue)) {
|
||||||
$i = 1;
|
// Use name, but append with lowest possible integer
|
||||||
while ($this->parent->sheetNameExists($pValue . ' ' . $i)) {
|
|
||||||
++$i;
|
if (Shared\StringHelper::countCharacters($pValue) > 29) {
|
||||||
if ($i == 10) {
|
$pValue = Shared\StringHelper::substring($pValue, 0, 29);
|
||||||
if (Shared\StringHelper::countCharacters($pValue) > 28) {
|
}
|
||||||
$pValue = Shared\StringHelper::substring($pValue, 0, 28);
|
$i = 1;
|
||||||
}
|
while ($this->parent->sheetNameExists($pValue . ' ' . $i)) {
|
||||||
} elseif ($i == 100) {
|
++$i;
|
||||||
if (Shared\StringHelper::countCharacters($pValue) > 27) {
|
if ($i == 10) {
|
||||||
$pValue = Shared\StringHelper::substring($pValue, 0, 27);
|
if (Shared\StringHelper::countCharacters($pValue) > 28) {
|
||||||
|
$pValue = Shared\StringHelper::substring($pValue, 0, 28);
|
||||||
|
}
|
||||||
|
} elseif ($i == 100) {
|
||||||
|
if (Shared\StringHelper::countCharacters($pValue) > 27) {
|
||||||
|
$pValue = Shared\StringHelper::substring($pValue, 0, 27);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$pValue .= " $i";
|
||||||
}
|
}
|
||||||
|
|
||||||
$altTitle = $pValue . ' ' . $i;
|
|
||||||
|
|
||||||
return $this->setTitle($altTitle, $updateFormulaCellReferences);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2976,51 +2978,55 @@ class Worksheet implements IComparable
|
||||||
/**
|
/**
|
||||||
* Define the code name of the sheet.
|
* Define the code name of the sheet.
|
||||||
*
|
*
|
||||||
* @param null|string Same rule as Title minus space not allowed (but, like Excel, change silently space to underscore)
|
* @param string $pValue Same rule as Title minus space not allowed (but, like Excel, change
|
||||||
* @param null|mixed $pValue
|
* silently space to underscore)
|
||||||
|
* @param bool $validate False to skip validation of new title. WARNING: This should only be set
|
||||||
|
* at parse time (by Readers), where titles can be assumed to be valid.
|
||||||
*
|
*
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*
|
*
|
||||||
* @return objWorksheet
|
* @return objWorksheet
|
||||||
*/
|
*/
|
||||||
public function setCodeName($pValue)
|
public function setCodeName($pValue, $validate = true)
|
||||||
{
|
{
|
||||||
// Is this a 'rename' or not?
|
// Is this a 'rename' or not?
|
||||||
if ($this->getCodeName() == $pValue) {
|
if ($this->getCodeName() == $pValue) {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
$pValue = str_replace(' ', '_', $pValue); //Excel does this automatically without flinching, we are doing the same
|
|
||||||
// Syntax check
|
|
||||||
// throw an exception if not valid
|
|
||||||
self::checkSheetCodeName($pValue);
|
|
||||||
|
|
||||||
// We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
|
if ($validate) {
|
||||||
|
$pValue = str_replace(' ', '_', $pValue); //Excel does this automatically without flinching, we are doing the same
|
||||||
|
|
||||||
if ($this->getParent()) {
|
// Syntax check
|
||||||
// Is there already such sheet name?
|
// throw an exception if not valid
|
||||||
if ($this->getParent()->sheetCodeNameExists($pValue)) {
|
self::checkSheetCodeName($pValue);
|
||||||
// Use name, but append with lowest possible integer
|
|
||||||
|
|
||||||
if (Shared\StringHelper::countCharacters($pValue) > 29) {
|
// We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
|
||||||
$pValue = Shared\StringHelper::substring($pValue, 0, 29);
|
|
||||||
}
|
if ($this->getParent()) {
|
||||||
$i = 1;
|
// Is there already such sheet name?
|
||||||
while ($this->getParent()->sheetCodeNameExists($pValue . '_' . $i)) {
|
if ($this->getParent()->sheetCodeNameExists($pValue)) {
|
||||||
++$i;
|
// Use name, but append with lowest possible integer
|
||||||
if ($i == 10) {
|
|
||||||
if (Shared\StringHelper::countCharacters($pValue) > 28) {
|
if (Shared\StringHelper::countCharacters($pValue) > 29) {
|
||||||
$pValue = Shared\StringHelper::substring($pValue, 0, 28);
|
$pValue = Shared\StringHelper::substring($pValue, 0, 29);
|
||||||
}
|
}
|
||||||
} elseif ($i == 100) {
|
$i = 1;
|
||||||
if (Shared\StringHelper::countCharacters($pValue) > 27) {
|
while ($this->getParent()->sheetCodeNameExists($pValue . '_' . $i)) {
|
||||||
$pValue = Shared\StringHelper::substring($pValue, 0, 27);
|
++$i;
|
||||||
|
if ($i == 10) {
|
||||||
|
if (Shared\StringHelper::countCharacters($pValue) > 28) {
|
||||||
|
$pValue = Shared\StringHelper::substring($pValue, 0, 28);
|
||||||
|
}
|
||||||
|
} elseif ($i == 100) {
|
||||||
|
if (Shared\StringHelper::countCharacters($pValue) > 27) {
|
||||||
|
$pValue = Shared\StringHelper::substring($pValue, 0, 27);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$pValue = $pValue . '_' . $i; // ok, we have a valid name
|
$pValue = $pValue . '_' . $i; // ok, we have a valid name
|
||||||
//codeName is'nt used in formula : no need to call for an update
|
}
|
||||||
//return $this->setTitle($altTitle, $updateFormulaCellReferences);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheetTests;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet;
|
||||||
|
use PHPUnit_Framework_TestCase;
|
||||||
|
|
||||||
|
class WorksheetTest extends PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testSetTitle()
|
||||||
|
{
|
||||||
|
$testTitle = str_repeat('a', 31);
|
||||||
|
|
||||||
|
$worksheet = new Worksheet();
|
||||||
|
$worksheet->setTitle($testTitle);
|
||||||
|
$this->assertSame($testTitle, $worksheet->getTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTitleInvalidProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[str_repeat('a', 32), 'Maximum 31 characters allowed in sheet title.'],
|
||||||
|
['invalid*title', 'Invalid character found in sheet title'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $title
|
||||||
|
* @param string $expectMessage
|
||||||
|
* @dataProvider setTitleInvalidProvider
|
||||||
|
*/
|
||||||
|
public function testSetTitleInvalid($title, $expectMessage)
|
||||||
|
{
|
||||||
|
// First, test setting title with validation disabled -- should be successful
|
||||||
|
$worksheet = new Worksheet();
|
||||||
|
$worksheet->setTitle($title, true, false);
|
||||||
|
|
||||||
|
// Next, test again with validation enabled -- this time we should fail
|
||||||
|
$worksheet = new Worksheet();
|
||||||
|
$this->expectException(\Exception::class);
|
||||||
|
$this->expectExceptionMessage($expectMessage);
|
||||||
|
$worksheet->setTitle($title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetTitleDuplicate()
|
||||||
|
{
|
||||||
|
// Create a Spreadsheet with three Worksheets (the first is created automatically)
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
$spreadsheet->createSheet();
|
||||||
|
$spreadsheet->createSheet();
|
||||||
|
|
||||||
|
// Set unique title -- should be unchanged
|
||||||
|
$sheet = $spreadsheet->getSheet(0);
|
||||||
|
$sheet->setTitle('Test Title');
|
||||||
|
$this->assertSame('Test Title', $sheet->getTitle());
|
||||||
|
|
||||||
|
// Set duplicate title -- should have numeric suffix appended
|
||||||
|
$sheet = $spreadsheet->getSheet(1);
|
||||||
|
$sheet->setTitle('Test Title');
|
||||||
|
$this->assertSame('Test Title 1', $sheet->getTitle());
|
||||||
|
|
||||||
|
// Set duplicate title with validation disabled -- should be unchanged
|
||||||
|
$sheet = $spreadsheet->getSheet(2);
|
||||||
|
$sheet->setTitle('Test Title', true, false);
|
||||||
|
$this->assertSame('Test Title', $sheet->getTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetCodeName()
|
||||||
|
{
|
||||||
|
$testCodeName = str_repeat('a', 31);
|
||||||
|
|
||||||
|
$worksheet = new Worksheet();
|
||||||
|
$worksheet->setCodeName($testCodeName);
|
||||||
|
$this->assertSame($testCodeName, $worksheet->getCodeName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCodeNameInvalidProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[str_repeat('a', 32), 'Maximum 31 characters allowed in sheet code name.'],
|
||||||
|
['invalid*code*name', 'Invalid character found in sheet code name'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $codeName
|
||||||
|
* @param string $expectMessage
|
||||||
|
* @dataProvider setCodeNameInvalidProvider
|
||||||
|
*/
|
||||||
|
public function testSetCodeNameInvalid($codeName, $expectMessage)
|
||||||
|
{
|
||||||
|
// First, test setting code name with validation disabled -- should be successful
|
||||||
|
$worksheet = new Worksheet();
|
||||||
|
$worksheet->setCodeName($codeName, false);
|
||||||
|
|
||||||
|
// Next, test again with validation enabled -- this time we should fail
|
||||||
|
$worksheet = new Worksheet();
|
||||||
|
$this->expectException(\Exception::class);
|
||||||
|
$this->expectExceptionMessage($expectMessage);
|
||||||
|
$worksheet->setCodeName($codeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetCodeNameDuplicate()
|
||||||
|
{
|
||||||
|
// Create a Spreadsheet with three Worksheets (the first is created automatically)
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
$spreadsheet->createSheet();
|
||||||
|
$spreadsheet->createSheet();
|
||||||
|
|
||||||
|
// Set unique code name -- should be massaged to Snake_Case
|
||||||
|
$sheet = $spreadsheet->getSheet(0);
|
||||||
|
$sheet->setCodeName('Test Code Name');
|
||||||
|
$this->assertSame('Test_Code_Name', $sheet->getCodeName());
|
||||||
|
|
||||||
|
// Set duplicate code name -- should be massaged and have numeric suffix appended
|
||||||
|
$sheet = $spreadsheet->getSheet(1);
|
||||||
|
$sheet->setCodeName('Test Code Name');
|
||||||
|
$this->assertSame('Test_Code_Name_1', $sheet->getCodeName());
|
||||||
|
|
||||||
|
// Set duplicate code name with validation disabled -- should be unchanged, and unmassaged
|
||||||
|
$sheet = $spreadsheet->getSheet(2);
|
||||||
|
$sheet->setCodeName('Test Code Name', false);
|
||||||
|
$this->assertSame('Test Code Name', $sheet->getCodeName());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue