Passed
Pull Request — develop_3.0 (#496)
by Adrien
05:43 queued 03:06
created

SheetIterator::rewind()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 11
cts 11
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 11
nc 4
nop 0
crap 3
1
<?php
2
3
namespace Box\Spout\Reader\ODS;
4
5
use Box\Spout\Common\Exception\IOException;
6
use Box\Spout\Reader\Exception\XMLProcessingException;
7
use Box\Spout\Reader\IteratorInterface;
8
use Box\Spout\Reader\ODS\Creator\EntityFactory;
9
use Box\Spout\Reader\ODS\Helper\SettingsHelper;
10
use Box\Spout\Reader\Wrapper\XMLReader;
11
12
/**
13
 * Class SheetIterator
14
 * Iterate over ODS sheet.
15
 */
16
class SheetIterator implements IteratorInterface
17
{
18
    const CONTENT_XML_FILE_PATH = 'content.xml';
19
20
    const XML_STYLE_NAMESPACE = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0';
21
22
    /** Definition of XML nodes name and attribute used to parse sheet data */
23
    const XML_NODE_AUTOMATIC_STYLES = 'office:automatic-styles';
24
    const XML_NODE_STYLE = 'style';
25
    const XML_NODE_STYLE_TABLE_PROPERTIES = 'table-properties';
26
    const XML_NODE_TABLE = 'table:table';
27
    const XML_ATTRIBUTE_STYLE_NAME = 'style:name';
28
    const XML_ATTRIBUTE_TABLE_NAME = 'table:name';
29
    const XML_ATTRIBUTE_TABLE_STYLE_NAME = 'table:style-name';
30
    const XML_ATTRIBUTE_TABLE_DISPLAY = 'table:display';
31
32
    /** @var string $filePath Path of the file to be read */
33
    protected $filePath;
34
35
    /** @var \Box\Spout\Common\Manager\OptionsManagerInterface Reader's options manager */
36
    protected $optionsManager;
37
38
    /** @var EntityFactory $entityFactory Factory to create entities */
39
    protected $entityFactory;
40
41
    /** @var XMLReader The XMLReader object that will help read sheet's XML data */
42
    protected $xmlReader;
43
44
    /** @var \Box\Spout\Common\Helper\Escaper\ODS Used to unescape XML data */
45
    protected $escaper;
46
47
    /** @var bool Whether there are still at least a sheet to be read */
48
    protected $hasFoundSheet;
49
50
    /** @var int The index of the sheet being read (zero-based) */
51
    protected $currentSheetIndex;
52
53
    /** @var string The name of the sheet that was defined as active */
54
    protected $activeSheetName;
55
56
    /** @var array Associative array [STYLE_NAME] => [IS_SHEET_VISIBLE] */
57
    protected $sheetsVisibility;
58
59
    /**
60
     * @param string $filePath Path of the file to be read
61
     * @param \Box\Spout\Common\Manager\OptionsManagerInterface $optionsManager
62
     * @param \Box\Spout\Common\Helper\Escaper\ODS $escaper Used to unescape XML data
63
     * @param SettingsHelper $settingsHelper Helper to get data from "settings.xml"
64
     * @param EntityFactory $entityFactory Factory to create entities
65
     */
66 31
    public function __construct($filePath, $optionsManager, $escaper, $settingsHelper, $entityFactory)
67
    {
68 31
        $this->filePath = $filePath;
69 31
        $this->optionsManager = $optionsManager;
70 31
        $this->entityFactory = $entityFactory;
71 31
        $this->xmlReader = $entityFactory->createXMLReader();
72 31
        $this->escaper = $escaper;
73 31
        $this->activeSheetName = $settingsHelper->getActiveSheetName($filePath);
74 31
    }
75
76
    /**
77
     * Rewind the Iterator to the first element
78
     * @see http://php.net/manual/en/iterator.rewind.php
79
     *
80
     * @throws \Box\Spout\Common\Exception\IOException If unable to open the XML file containing sheets' data
81
     * @return void
82
     */
83 31
    public function rewind()
84
    {
85 31
        $this->xmlReader->close();
86
87 31
        if ($this->xmlReader->openFileInZip($this->filePath, self::CONTENT_XML_FILE_PATH) === false) {
88 1
            $contentXmlFilePath = $this->filePath . '#' . self::CONTENT_XML_FILE_PATH;
89 1
            throw new IOException("Could not open \"{$contentXmlFilePath}\".");
90
        }
91
92
        try {
93 30
            $this->sheetsVisibility = $this->readSheetsVisibility();
94 30
            $this->hasFoundSheet = $this->xmlReader->readUntilNodeFound(self::XML_NODE_TABLE);
95 1
        } catch (XMLProcessingException $exception) {
96 1
            throw new IOException("The content.xml file is invalid and cannot be read. [{$exception->getMessage()}]");
97
        }
98
99 29
        $this->currentSheetIndex = 0;
100 29
    }
101
102
    /**
103
     * Extracts the visibility of the sheets
104
     *
105
     * @return array Associative array [STYLE_NAME] => [IS_SHEET_VISIBLE]
106
     */
107 30
    private function readSheetsVisibility()
108
    {
109 30
        $sheetsVisibility = [];
110
111 30
        $this->xmlReader->readUntilNodeFound(self::XML_NODE_AUTOMATIC_STYLES);
112 30
        $automaticStylesNode = $this->xmlReader->expand();
113
114 30
        $styleNodes = $automaticStylesNode->getElementsByTagNameNS(self::XML_STYLE_NAMESPACE, self::XML_NODE_STYLE);
115
116
        /** @var \DOMElement $styleNode */
117 30
        foreach ($styleNodes as $styleNode) {
118 30
            $styleName = $styleNode->getAttribute(self::XML_ATTRIBUTE_STYLE_NAME);
119
120 30
            $tableStyleNodes = $styleNode->getElementsByTagNameNS(self::XML_STYLE_NAMESPACE, self::XML_NODE_STYLE_TABLE_PROPERTIES);
121 30
            foreach ($tableStyleNodes as $tableStyleNode) {
122 30
                $isSheetVisible = ($tableStyleNode->getAttribute(self::XML_ATTRIBUTE_TABLE_DISPLAY) !== 'false');
123 30
                $sheetsVisibility[$styleName] = $isSheetVisible;
124
            }
125
        }
126
127 30
        return $sheetsVisibility;
128
    }
129
130
    /**
131
     * Checks if current position is valid
132
     * @see http://php.net/manual/en/iterator.valid.php
133
     *
134
     * @return bool
135
     */
136 29
    public function valid()
137
    {
138 29
        return $this->hasFoundSheet;
139
    }
140
141
    /**
142
     * Move forward to next element
143
     * @see http://php.net/manual/en/iterator.next.php
144
     *
145
     * @return void
146
     */
147 28
    public function next()
148
    {
149 28
        $this->hasFoundSheet = $this->xmlReader->readUntilNodeFound(self::XML_NODE_TABLE);
150
151 28
        if ($this->hasFoundSheet) {
152 6
            $this->currentSheetIndex++;
153
        }
154 28
    }
155
156
    /**
157
     * Return the current element
158
     * @see http://php.net/manual/en/iterator.current.php
159
     *
160
     * @return \Box\Spout\Reader\ODS\Sheet
161
     */
162 29
    public function current()
163
    {
164 29
        $escapedSheetName = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_TABLE_NAME);
165 29
        $sheetName = $this->escaper->unescape($escapedSheetName);
166
167 29
        $isSheetActive = $this->isSheetActive($sheetName, $this->currentSheetIndex, $this->activeSheetName);
168
169 29
        $sheetStyleName = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_TABLE_STYLE_NAME);
170 29
        $isSheetVisible = $this->isSheetVisible($sheetStyleName);
171
172 29
        return $this->entityFactory->createSheet(
173 29
            $this->xmlReader,
174 29
            $this->currentSheetIndex,
175 29
            $sheetName,
176 29
            $isSheetActive,
177 29
            $isSheetVisible,
178 29
            $this->optionsManager
179
        );
180
    }
181
182
    /**
183
     * Returns whether the current sheet was defined as the active one
184
     *
185
     * @param string $sheetName Name of the current sheet
186
     * @param int $sheetIndex Index of the current sheet
187
     * @param string|null $activeSheetName Name of the sheet that was defined as active or NULL if none defined
188
     * @return bool Whether the current sheet was defined as the active one
189
     */
190 29
    private function isSheetActive($sheetName, $sheetIndex, $activeSheetName)
191
    {
192
        // The given sheet is active if its name matches the defined active sheet's name
193
        // or if no information about the active sheet was found, it defaults to the first sheet.
194
        return (
195 29
            ($activeSheetName === null && $sheetIndex === 0) ||
196 29
            ($activeSheetName === $sheetName)
197
        );
198
    }
199
200
    /**
201
     * Returns whether the current sheet is visible
202
     *
203
     * @param string $sheetStyleName Name of the sheet style
204
     * @return bool Whether the current sheet is visible
205
     */
206 29
    private function isSheetVisible($sheetStyleName)
207
    {
208 29
        return isset($this->sheetsVisibility[$sheetStyleName]) ?
209 29
            $this->sheetsVisibility[$sheetStyleName] :
210 29
            true;
211
    }
212
213
    /**
214
     * Return the key of the current element
215
     * @see http://php.net/manual/en/iterator.key.php
216
     *
217
     * @return int
218
     */
219 24
    public function key()
220
    {
221 24
        return $this->currentSheetIndex + 1;
222
    }
223
224
    /**
225
     * Cleans up what was created to iterate over the object.
226
     *
227
     * @return void
228
     */
229 28
    public function end()
230
    {
231 28
        $this->xmlReader->close();
232 28
    }
233
}
234