Passed
Pull Request — develop_3.0 (#496)
by Adrien
02:41
created

SheetIterator::key()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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