filePath = $filePath; $this->sheetDataXMLFilePath = $this->normalizeSheetDataXMLFilePath($sheetDataXMLFilePath); $this->xmlReader = new XMLReader(); $this->styleHelper = new StyleHelper($filePath); $this->cellValueFormatter = new CellValueFormatter($sharedStringsHelper, $this->styleHelper); } /** * @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml * @return string Path of the XML file containing the sheet data, * without the leading slash. */ protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath) { return ltrim($sheetDataXMLFilePath, '/'); } /** * Rewind the Iterator to the first element. * Initializes the XMLReader object that reads the associated sheet data. * The XMLReader is configured to be safe from billion laughs attack. * @link http://php.net/manual/en/iterator.rewind.php * * @return void * @throws \Box\Spout\Common\Exception\IOException If the sheet data XML cannot be read */ public function rewind() { $this->xmlReader->close(); $sheetDataFilePath = 'zip://' . $this->filePath . '#' . $this->sheetDataXMLFilePath; if ($this->xmlReader->open($sheetDataFilePath) === false) { throw new IOException("Could not open \"{$this->sheetDataXMLFilePath}\"."); } $this->numReadRows = 0; $this->rowDataBuffer = null; $this->hasReachedEndOfFile = false; $this->numColumns = 0; $this->next(); } /** * Checks if current position is valid * @link http://php.net/manual/en/iterator.valid.php * * @return boolean */ public function valid() { return (!$this->hasReachedEndOfFile); } /** * Move forward to next element. Empty rows will be skipped. * @link http://php.net/manual/en/iterator.next.php * * @return void * @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If a shared string was not found * @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML */ public function next() { $rowData = []; try { while ($this->xmlReader->read()) { if ($this->xmlReader->isPositionedOnStartingNode(self::XML_NODE_DIMENSION)) { // Read dimensions of the sheet $dimensionRef = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_REF); // returns 'A1:M13' for instance (or 'A1' for empty sheet) if (preg_match('/[A-Z\d]+:([A-Z\d]+)/', $dimensionRef, $matches)) { $lastCellIndex = $matches[1]; $this->numColumns = CellHelper::getColumnIndexFromCellIndex($lastCellIndex) + 1; } } else if ($this->xmlReader->isPositionedOnStartingNode(self::XML_NODE_ROW)) { // Start of the row description // Read spans info if present $numberOfColumnsForRow = $this->numColumns; $spans = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance if ($spans) { list(, $numberOfColumnsForRow) = explode(':', $spans); $numberOfColumnsForRow = intval($numberOfColumnsForRow); } $rowData = ($numberOfColumnsForRow !== 0) ? array_fill(0, $numberOfColumnsForRow, '') : []; } else if ($this->xmlReader->isPositionedOnStartingNode(self::XML_NODE_CELL)) { // Start of a cell description $currentCellIndex = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_CELL_INDEX); $currentColumnIndex = CellHelper::getColumnIndexFromCellIndex($currentCellIndex); $node = $this->xmlReader->expand(); $rowData[$currentColumnIndex] = $this->getCellValue($node); } else if ($this->xmlReader->isPositionedOnEndingNode(self::XML_NODE_ROW)) { // End of the row description // If needed, we fill the empty cells $rowData = ($this->numColumns !== 0) ? $rowData : CellHelper::fillMissingArrayIndexes($rowData); $this->numReadRows++; break; } else if ($this->xmlReader->isPositionedOnEndingNode(self::XML_NODE_WORKSHEET)) { // The closing "" marks the end of the file $this->hasReachedEndOfFile = true; break; } } } catch (XMLProcessingException $exception) { throw new IOException("The {$this->sheetDataXMLFilePath} file cannot be read. [{$exception->getMessage()}]"); } $this->rowDataBuffer = $rowData; } /** * Returns the (unescaped) correctly marshalled, cell value associated to the given XML node. * * @param \DOMNode $node * @return string|int|float|bool|\DateTime|null The value associated with the cell (null when the cell has an error) */ protected function getCellValue($node) { return $this->cellValueFormatter->extractAndFormatNodeValue($node); } /** * Return the current element, from the buffer. * @link http://php.net/manual/en/iterator.current.php * * @return array|null */ public function current() { return $this->rowDataBuffer; } /** * Return the key of the current element * @link http://php.net/manual/en/iterator.key.php * * @return int */ public function key() { return $this->numReadRows; } /** * Cleans up what was created to iterate over the object. * * @return void */ public function end() { $this->xmlReader->close(); } }