| 1 |
<?php |
| 2 |
|
| 3 |
namespace OpenSpout\Writer\Common\Manager; |
| 4 |
|
| 5 |
use OpenSpout\Common\Helper\StringHelper; |
| 6 |
use OpenSpout\Writer\Common\Entity\Sheet; |
| 7 |
use OpenSpout\Writer\Exception\InvalidSheetNameException; |
| 8 |
|
| 9 |
/** |
| 10 |
* Sheet manager. |
| 11 |
*/ |
| 12 |
class SheetManager |
| 13 |
{ |
| 14 |
/** Sheet name should not exceed 31 characters */ |
| 15 |
public const MAX_LENGTH_SHEET_NAME = 31; |
| 16 |
|
| 17 |
/** @var array Invalid characters that cannot be contained in the sheet name */ |
| 18 |
private static $INVALID_CHARACTERS_IN_SHEET_NAME = ['\\', '/', '?', '*', ':', '[', ']']; |
| 19 |
|
| 20 |
/** @var array Associative array [WORKBOOK_ID] => [[SHEET_INDEX] => [SHEET_NAME]] keeping track of sheets' name to enforce uniqueness per workbook */ |
| 21 |
private static $SHEETS_NAME_USED = []; |
| 22 |
|
| 23 |
/** @var StringHelper */ |
| 24 |
private $stringHelper; |
| 25 |
|
| 26 |
/** |
| 27 |
* SheetManager constructor. |
| 28 |
*/ |
| 29 |
public function __construct(StringHelper $stringHelper) |
| 30 |
{ |
| 31 |
$this->stringHelper = $stringHelper; |
| 32 |
} |
| 33 |
|
| 34 |
/** |
| 35 |
* Throws an exception if the given sheet's name is not valid. |
| 36 |
* |
| 37 |
* @see Sheet::setName for validity rules. |
| 38 |
* |
| 39 |
* @param string $name |
| 40 |
* @param Sheet $sheet The sheet whose future name is checked |
| 41 |
* |
| 42 |
* @throws \OpenSpout\Writer\Exception\InvalidSheetNameException if the sheet's name is invalid |
| 43 |
*/ |
| 44 |
public function throwIfNameIsInvalid($name, Sheet $sheet) |
| 45 |
{ |
| 46 |
if (!\is_string($name)) { |
| 47 |
$actualType = \gettype($name); |
| 48 |
$errorMessage = "The sheet's name is invalid. It must be a string ({$actualType} given)."; |
| 49 |
|
| 50 |
throw new InvalidSheetNameException($errorMessage); |
| 51 |
} |
| 52 |
|
| 53 |
$failedRequirements = []; |
| 54 |
$nameLength = $this->stringHelper->getStringLength($name); |
| 55 |
|
| 56 |
if (!$this->isNameUnique($name, $sheet)) { |
| 57 |
$failedRequirements[] = 'It should be unique'; |
| 58 |
} else { |
| 59 |
if (0 === $nameLength) { |
| 60 |
$failedRequirements[] = 'It should not be blank'; |
| 61 |
} else { |
| 62 |
if ($nameLength > self::MAX_LENGTH_SHEET_NAME) { |
| 63 |
$failedRequirements[] = 'It should not exceed 31 characters'; |
| 64 |
} |
| 65 |
|
| 66 |
if ($this->doesContainInvalidCharacters($name)) { |
| 67 |
$failedRequirements[] = 'It should not contain these characters: \\ / ? * : [ or ]'; |
| 68 |
} |
| 69 |
|
| 70 |
if ($this->doesStartOrEndWithSingleQuote($name)) { |
| 71 |
$failedRequirements[] = 'It should not start or end with a single quote'; |
| 72 |
} |
| 73 |
} |
| 74 |
} |
| 75 |
|
| 76 |
if (0 !== \count($failedRequirements)) { |
| 77 |
$errorMessage = "The sheet's name (\"{$name}\") is invalid. It did not respect these rules:\n - "; |
| 78 |
$errorMessage .= implode("\n - ", $failedRequirements); |
| 79 |
|
| 80 |
throw new InvalidSheetNameException($errorMessage); |
| 81 |
} |
| 82 |
} |
| 83 |
|
| 84 |
/** |
| 85 |
* @param string $workbookId Workbook ID associated to a Sheet |
| 86 |
*/ |
| 87 |
public function markWorkbookIdAsUsed($workbookId) |
| 88 |
{ |
| 89 |
if (!isset(self::$SHEETS_NAME_USED[$workbookId])) { |
| 90 |
self::$SHEETS_NAME_USED[$workbookId] = []; |
| 91 |
} |
| 92 |
} |
| 93 |
|
| 94 |
public function markSheetNameAsUsed(Sheet $sheet) |
| 95 |
{ |
| 96 |
self::$SHEETS_NAME_USED[$sheet->getAssociatedWorkbookId()][$sheet->getIndex()] = $sheet->getName(); |
| 97 |
} |
| 98 |
|
| 99 |
/** |
| 100 |
* Returns whether the given name contains at least one invalid character. |
| 101 |
* |
| 102 |
* @see Sheet::$INVALID_CHARACTERS_IN_SHEET_NAME for the full list. |
| 103 |
* |
| 104 |
* @param string $name |
| 105 |
* |
| 106 |
* @return bool TRUE if the name contains invalid characters, FALSE otherwise |
| 107 |
*/ |
| 108 |
private function doesContainInvalidCharacters($name) |
| 109 |
{ |
| 110 |
return str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name; |
| 111 |
} |
| 112 |
|
| 113 |
/** |
| 114 |
* Returns whether the given name starts or ends with a single quote. |
| 115 |
* |
| 116 |
* @param string $name |
| 117 |
* |
| 118 |
* @return bool TRUE if the name starts or ends with a single quote, FALSE otherwise |
| 119 |
*/ |
| 120 |
private function doesStartOrEndWithSingleQuote($name) |
| 121 |
{ |
| 122 |
$startsWithSingleQuote = (0 === $this->stringHelper->getCharFirstOccurrencePosition('\'', $name)); |
| 123 |
$endsWithSingleQuote = ($this->stringHelper->getCharLastOccurrencePosition('\'', $name) === ($this->stringHelper->getStringLength($name) - 1)); |
| 124 |
|
| 125 |
return $startsWithSingleQuote || $endsWithSingleQuote; |
| 126 |
} |
| 127 |
|
| 128 |
/** |
| 129 |
* Returns whether the given name is unique. |
| 130 |
* |
| 131 |
* @param string $name |
| 132 |
* @param Sheet $sheet The sheet whose future name is checked |
| 133 |
* |
| 134 |
* @return bool TRUE if the name is unique, FALSE otherwise |
| 135 |
*/ |
| 136 |
private function isNameUnique($name, Sheet $sheet) |
| 137 |
{ |
| 138 |
foreach (self::$SHEETS_NAME_USED[$sheet->getAssociatedWorkbookId()] as $sheetIndex => $sheetName) { |
| 139 |
if ($sheetIndex !== $sheet->getIndex() && $sheetName === $name) { |
| 140 |
return false; |
| 141 |
} |
| 142 |
} |
| 143 |
|
| 144 |
return true; |
| 145 |
} |
| 146 |
} |
| 147 |
|