Completed
Push — master ( e25589...984c9c )
by Adrien
02:42
created

Sheet::throwIfNameIsInvalid()   C

Complexity

Conditions 8
Paths 21

Size

Total Lines 37
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 8

Importance

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