Completed
Pull Request — master (#398)
by Adrien
05:26 queued 02:58
created

Sheet::setName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 0
cp 0
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 1
crap 2
1
<?php
2
3
namespace Box\Spout\Writer\Common;
4
5
use Box\Spout\Common\Helper\StringHelper;
6
use Box\Spout\Writer\Exception\InvalidSheetNameException;
7
8
/**
9
 * Class Sheet
10
 * External representation of a worksheet
11
 *
12
 * @package Box\Spout\Writer\Common
13
 */
14
class Sheet
15
{
16
    const DEFAULT_SHEET_NAME_PREFIX = 'Sheet';
17
18
    /** Sheet name should not exceed 31 characters */
19
    const MAX_LENGTH_SHEET_NAME = 31;
20
21
    /** @var array Invalid characters that cannot be contained in the sheet name */
22
    private static $INVALID_CHARACTERS_IN_SHEET_NAME = ['\\', '/', '?', '*', ':', '[', ']'];
23
24
    /** @var array Associative array [WORKBOOK_ID] => [[SHEET_INDEX] => [SHEET_NAME]] keeping track of sheets' name to enforce uniqueness per workbook */
25
    protected static $SHEETS_NAME_USED = [];
26
27
    /** @var int Index of the sheet, based on order in the workbook (zero-based) */
28
    protected $index;
29
30
    /** @var string ID of the sheet's associated workbook. Used to restrict sheet name uniqueness enforcement to a single workbook */
31
    protected $associatedWorkbookId;
32
33
    /** @var string Name of the sheet */
34
    protected $name;
35
36
    /** @var \Box\Spout\Common\Helper\StringHelper */
37
    protected $stringHelper;
38
39 297
    /**
40
     * @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
41 297
     * @param string $associatedWorkbookId ID of the sheet's associated workbook
42 297
     */
43 297
    public function __construct($sheetIndex, $associatedWorkbookId)
44 297
    {
45
        $this->index = $sheetIndex;
46
        $this->associatedWorkbookId = $associatedWorkbookId;
47
        if (!isset(self::$SHEETS_NAME_USED[$associatedWorkbookId])) {
48
            self::$SHEETS_NAME_USED[$associatedWorkbookId] = [];
49
        }
50 216
51
        $this->stringHelper = new StringHelper();
52 216
        $this->setName(self::DEFAULT_SHEET_NAME_PREFIX . ($sheetIndex + 1));
53
    }
54
55
    /**
56
     * @api
57
     * @return int Index of the sheet, based on order in the workbook (zero-based)
58
     */
59 225
    public function getIndex()
60
    {
61 225
        return $this->index;
62
    }
63
64
    /**
65
     * @api
66
     * @return string Name of the sheet
67
     */
68
    public function getName()
69
    {
70
        return $this->name;
71
    }
72
73
    /**
74
     * Sets the name of the sheet. Note that Excel has some restrictions on the name:
75
     *  - it should not be blank
76 297
     *  - it should not exceed 31 characters
77
     *  - it should not contain these characters: \ / ? * : [ or ]
78 297
     *  - it should be unique
79
     *
80 297
     * @api
81 297
     * @param string $name Name of the sheet
82
     * @return Sheet
83 297
     * @throws \Box\Spout\Writer\Exception\InvalidSheetNameException If the sheet's name is invalid.
84
     */
85
    public function setName($name)
86
    {
87
        $this->throwIfNameIsInvalid($name);
88
89
        $this->name = $name;
90
        self::$SHEETS_NAME_USED[$this->associatedWorkbookId][$this->index] = $name;
91
92
        return $this;
93
    }
94 297
95
    /**
96 297
     * Throws an exception if the given sheet's name is not valid.
97 6
     * @see Sheet::setName for validity rules.
98 6
     *
99 6
     * @param string $name
100
     * @return void
101
     * @throws \Box\Spout\Writer\Exception\InvalidSheetNameException If the sheet's name is invalid.
102 297
     */
103 297
    protected function throwIfNameIsInvalid($name)
104
    {
105 297
        if (!is_string($name)) {
106 9
            $actualType = gettype($name);
107 9
            $errorMessage = "The sheet's name is invalid. It must be a string ($actualType given).";
108 297
            throw new InvalidSheetNameException($errorMessage);
109 3
        }
110 3
111 297
        $failedRequirements = [];
112 3
        $nameLength = $this->stringHelper->getStringLength($name);
113 3
114
        if (!$this->isNameUnique($name)) {
115 297
            $failedRequirements[] = 'It should be unique';
116 21
        } else {
117 21
            if ($nameLength === 0) {
118
                $failedRequirements[] = 'It should not be blank';
119 297
            } else {
120 6
                if ($nameLength > self::MAX_LENGTH_SHEET_NAME) {
121 6
                    $failedRequirements[] = 'It should not exceed 31 characters';
122
                }
123
124
                if ($this->doesContainInvalidCharacters($name)) {
125 297
                    $failedRequirements[] = 'It should not contain these characters: \\ / ? * : [ or ]';
126 42
                }
127 42
128 42
                if ($this->doesStartOrEndWithSingleQuote($name)) {
129
                    $failedRequirements[] = 'It should not start or end with a single quote';
130 297
                }
131
            }
132
        }
133
134
        if (count($failedRequirements) !== 0) {
135
            $errorMessage = "The sheet's name (\"$name\") is invalid. It did not respect these rules:\n - ";
136
            $errorMessage .= implode("\n - ", $failedRequirements);
137
            throw new InvalidSheetNameException($errorMessage);
138
        }
139 297
    }
140
141 297
    /**
142
     * Returns whether the given name contains at least one invalid character.
143
     * @see Sheet::$INVALID_CHARACTERS_IN_SHEET_NAME for the full list.
144
     *
145
     * @param string $name
146
     * @return bool TRUE if the name contains invalid characters, FALSE otherwise.
147
     */
148
    protected function doesContainInvalidCharacters($name)
149
    {
150 297
        return (str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name);
151
    }
152 297
153 297
    /**
154
     * Returns whether the given name starts or ends with a single quote
155 297
     *
156
     * @param string $name
157
     * @return bool TRUE if the name starts or ends with a single quote, FALSE otherwise.
158
     */
159
    protected function doesStartOrEndWithSingleQuote($name)
160
    {
161
        $startsWithSingleQuote = ($this->stringHelper->getCharFirstOccurrencePosition('\'', $name) === 0);
162
        $endsWithSingleQuote = ($this->stringHelper->getCharLastOccurrencePosition('\'', $name) === ($this->stringHelper->getStringLength($name) - 1));
163
164 297
        return ($startsWithSingleQuote || $endsWithSingleQuote);
165
    }
166 297
167 297
    /**
168 9
     * Returns whether the given name is unique.
169
     *
170 297
     * @param string $name
171
     * @return bool TRUE if the name is unique, FALSE otherwise.
172 297
     */
173
    protected function isNameUnique($name)
174
    {
175
        foreach (self::$SHEETS_NAME_USED[$this->associatedWorkbookId] as $sheetIndex => $sheetName) {
176
            if ($sheetIndex !== $this->index && $sheetName === $name) {
177
                return false;
178
            }
179
        }
180
181
        return true;
182
    }
183
}
184