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