Completed
Pull Request — master (#235)
by
unknown
02:55
created

RowIterator::getCellValue()   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 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 4
rs 10
ccs 2
cts 2
cp 1
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Box\Spout\Reader\XLSX;
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\Wrapper\XMLReader;
9
use Box\Spout\Reader\XLSX\Helper\CellHelper;
10
use Box\Spout\Reader\XLSX\Helper\CellValueFormatter;
11
use Box\Spout\Reader\XLSX\Helper\StyleHelper;
12
use Box\Spout\Reader\ReaderOptions;
13
14
/**
15
 * Class RowIterator
16
 *
17
 * @package Box\Spout\Reader\XLSX
18
 */
19
class RowIterator implements IteratorInterface
20
{
21
    /** Definition of XML nodes names used to parse data */
22
    const XML_NODE_DIMENSION = 'dimension';
23
    const XML_NODE_WORKSHEET = 'worksheet';
24
    const XML_NODE_ROW = 'row';
25
    const XML_NODE_CELL = 'c';
26
27
    /** Definition of XML attributes used to parse data */
28
    const XML_ATTRIBUTE_REF = 'ref';
29
    const XML_ATTRIBUTE_SPANS = 'spans';
30
    const XML_ATTRIBUTE_CELL_INDEX = 'r';
31
32
    /** @var string Path of the XLSX file being read */
33
    protected $filePath;
34
35
    /** @var string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml */
36
    protected $sheetDataXMLFilePath;
37
38
    /** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
39
    protected $xmlReader;
40
41
    /** @var Helper\CellValueFormatter Helper to format cell values */
42
    protected $cellValueFormatter;
43
44
    /** @var Helper\StyleHelper $styleHelper Helper to work with styles */
45
    protected $styleHelper;
46
47
    /** @var int Key for iterator */
48
    protected $rowIndex = 0;
49
50
    /** @var array|null Buffer used to store the row data, while checking if there are more rows to read */
51
    protected $rowDataBuffer = null;
52
53
    /** @var bool Indicates whether all rows have been read */
54
    protected $hasReachedEndOfFile = false;
55
56
    /** @var int The number of columns the sheet has (0 meaning undefined) */
57
    protected $numColumns = 0;
58
59
    /** @var \Box\Spout\Reader\ReaderOptions */
60
    protected $readerOptions;
61
62
    /**
63
     * @param string $filePath Path of the XLSX file being read
64
     * @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
65
     * @param Helper\SharedStringsHelper $sharedStringsHelper Helper to work with shared strings
66
     * @param \Box\Spout\Reader\ReaderOptions $readerOptions
67
     */
68 81
    public function __construct($filePath, $sheetDataXMLFilePath, $sharedStringsHelper, ReaderOptions $readerOptions)
69
    {
70 81
        $this->filePath = $filePath;
71 81
        $this->sheetDataXMLFilePath = $this->normalizeSheetDataXMLFilePath($sheetDataXMLFilePath);
72
73 81
        $this->xmlReader = new XMLReader();
74
75 81
        $this->styleHelper = new StyleHelper($filePath);
76 81
        $this->readerOptions = $readerOptions;
77 81
        $this->cellValueFormatter = new CellValueFormatter($sharedStringsHelper, $this->styleHelper, $readerOptions->shouldFormatDates());
78 81
    }
79
80
    /**
81
     * @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
82
     * @return string Path of the XML file containing the sheet data,
83
     *                without the leading slash.
84
     */
85 81
    protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath)
86
    {
87 81
        return ltrim($sheetDataXMLFilePath, '/');
88
    }
89
90
    /**
91
     * Rewind the Iterator to the first element.
92
     * Initializes the XMLReader object that reads the associated sheet data.
93
     * The XMLReader is configured to be safe from billion laughs attack.
94
     * @link http://php.net/manual/en/iterator.rewind.php
95
     *
96
     * @return void
97
     * @throws \Box\Spout\Common\Exception\IOException If the sheet data XML cannot be read
98
     */
99 78
    public function rewind()
100
    {
101 78
        $this->xmlReader->close();
102
103 78
        $sheetDataFilePath = 'zip://' . $this->filePath . '#' . $this->sheetDataXMLFilePath;
104 78
        if ($this->xmlReader->open($sheetDataFilePath) === false) {
105 3
            throw new IOException("Could not open \"{$this->sheetDataXMLFilePath}\".");
106
        }
107
108 75
        $this->numReadRows = 0;
0 ignored issues
show
Bug introduced by
The property numReadRows does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
109 75
        $this->rowDataBuffer = null;
110 75
        $this->hasReachedEndOfFile = false;
111 75
        $this->numColumns = 0;
112
113 75
        $this->next();
114 75
    }
115
116
    /**
117
     * Checks if current position is valid
118
     * @link http://php.net/manual/en/iterator.valid.php
119
     *
120
     * @return boolean
121
     */
122 75
    public function valid()
123
    {
124 75
        return (!$this->hasReachedEndOfFile);
125
    }
126
127
    /**
128
     * Move forward to next element. Empty rows will be skipped.
129
     * @link http://php.net/manual/en/iterator.next.php
130
     *
131
     * @return void
132
     * @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
133
     * @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML
134
     */
135 75
    public function next()
136
    {
137 75
        $rowData = [];
138
139
        try {
140 75
            while ($this->xmlReader->read()) {
141 75
                if ($this->xmlReader->isPositionedOnStartingNode(self::XML_NODE_DIMENSION)) {
142
                    // Read dimensions of the sheet
143 36
                    $dimensionRef = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_REF); // returns 'A1:M13' for instance (or 'A1' for empty sheet)
144 36
                    if (preg_match('/[A-Z\d]+:([A-Z\d]+)/', $dimensionRef, $matches)) {
145 33
                        $lastCellIndex = $matches[1];
146 33
                        $this->numColumns = CellHelper::getColumnIndexFromCellIndex($lastCellIndex) + 1;
147 33
                    }
148
149 75
                } else if ($this->xmlReader->isPositionedOnStartingNode(self::XML_NODE_ROW)) {
150
                    // Start of the row description
151
152
                    // Read spans info if present
153 72
                    $numberOfColumnsForRow = $this->numColumns;
154 72
                    $spans = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance
155 72
                    if ($spans) {
156 27
                        list(, $numberOfColumnsForRow) = explode(':', $spans);
157 27
                        $numberOfColumnsForRow = intval($numberOfColumnsForRow);
158 27
                    }
159 72
                    $rowData = ($numberOfColumnsForRow !== 0) ? array_fill(0, $numberOfColumnsForRow, '') : [];
160
161 72
                    if ($this->readerOptions->shouldPreserveRowIndices()) {
162 3
                        $this->rowIndex = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_CELL_INDEX);
0 ignored issues
show
Documentation Bug introduced by
The property $rowIndex was declared of type integer, but $this->xmlReader->getAtt...L_ATTRIBUTE_CELL_INDEX) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
163 3
                    }
164
165 75
                } else if ($this->xmlReader->isPositionedOnStartingNode(self::XML_NODE_CELL)) {
166
                    // Start of a cell description
167 72
                    $currentCellIndex = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_CELL_INDEX);
168 72
                    $currentColumnIndex = CellHelper::getColumnIndexFromCellIndex($currentCellIndex);
169
170 72
                    $node = $this->xmlReader->expand();
171 72
                    $rowData[$currentColumnIndex] = $this->getCellValue($node);
172
173 75
                } else if ($this->xmlReader->isPositionedOnEndingNode(self::XML_NODE_ROW)) {
174
                    // End of the row description
175
                    // If needed, we fill the empty cells
176 72
                    $rowData = ($this->numColumns !== 0) ? $rowData : CellHelper::fillMissingArrayIndexes($rowData);
177
178 72
                    if (!$this->readerOptions->shouldPreserveRowIndices()) {
179 69
                        $this->rowIndex++;
180 69
                    }
181 72
                    break;
182
183 75
                } else if ($this->xmlReader->isPositionedOnEndingNode(self::XML_NODE_WORKSHEET)) {
184
                    // The closing "</worksheet>" marks the end of the file
185 72
                    $this->hasReachedEndOfFile = true;
186 72
                    break;
187
                }
188 75
            }
189
190 75
        } catch (XMLProcessingException $exception) {
191
            throw new IOException("The {$this->sheetDataXMLFilePath} file cannot be read. [{$exception->getMessage()}]");
192
        }
193
194 75
        $this->rowDataBuffer = $rowData;
195 75
    }
196
197
    /**
198
     * Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
199
     *
200
     * @param \DOMNode $node
201
     * @return string|int|float|bool|\DateTime|null The value associated with the cell (null when the cell has an error)
202
     */
203 72
    protected function getCellValue($node)
204
    {
205 72
        return $this->cellValueFormatter->extractAndFormatNodeValue($node);
206
    }
207
208
    /**
209
     * Return the current element, from the buffer.
210
     * @link http://php.net/manual/en/iterator.current.php
211
     *
212
     * @return array|null
213
     */
214 72
    public function current()
215
    {
216 72
        return $this->rowDataBuffer;
217
    }
218
219
    /**
220
     * Return the key of the current element
221
     * @link http://php.net/manual/en/iterator.key.php
222
     *
223
     * @return int
224
     */
225 6
    public function key()
226
    {
227 6
        return $this->rowIndex;
228
    }
229
230
231
    /**
232
     * Cleans up what was created to iterate over the object.
233
     *
234
     * @return void
235
     */
236 78
    public function end()
237
    {
238 78
        $this->xmlReader->close();
239 78
    }
240
}
241