From 03866a6604949854cab60f3901ff0d3486e44373 Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Sun, 29 May 2016 22:16:59 -0700 Subject: [PATCH 1/4] Support XLSX with prefixed XML files (#237) While the standard is not to have prefixes, some XLSX files have XML files containing a prefix. Microsoft has a tool that generates such files: https://msdn.microsoft.com/en-us/library/office/gg278316.aspx --- src/Spout/Reader/Wrapper/XMLReader.php | 27 +++- src/Spout/Reader/XLSX/Helper/SheetHelper.php | 128 +++++++----------- tests/Spout/Reader/Wrapper/XMLReaderTest.php | 33 +++++ tests/Spout/Reader/XLSX/ReaderTest.php | 17 +++ .../xlsx/sheet_with_prefixed_xml_files.xlsx | Bin 0 -> 7084 bytes 5 files changed, 119 insertions(+), 86 deletions(-) create mode 100644 tests/resources/xlsx/sheet_with_prefixed_xml_files.xlsx diff --git a/src/Spout/Reader/Wrapper/XMLReader.php b/src/Spout/Reader/Wrapper/XMLReader.php index 42bd92c..94b28eb 100644 --- a/src/Spout/Reader/Wrapper/XMLReader.php +++ b/src/Spout/Reader/Wrapper/XMLReader.php @@ -138,9 +138,10 @@ class XMLReader extends \XMLReader */ public function readUntilNodeFound($nodeName) { - while (($wasReadSuccessful = $this->read()) && ($this->nodeType !== \XMLReader::ELEMENT || $this->name !== $nodeName)) { - // do nothing - } + do { + $wasReadSuccessful = $this->read(); + $isNotPositionedOnStartingNode = !$this->isPositionedOnStartingNode($nodeName); + } while ($wasReadSuccessful && $isNotPositionedOnStartingNode); return $wasReadSuccessful; } @@ -170,7 +171,7 @@ class XMLReader extends \XMLReader */ public function isPositionedOnStartingNode($nodeName) { - return ($this->nodeType === XMLReader::ELEMENT && $this->name === $nodeName); + return $this->isPositionedOnNode($nodeName, XMLReader::ELEMENT); } /** @@ -179,6 +180,22 @@ class XMLReader extends \XMLReader */ public function isPositionedOnEndingNode($nodeName) { - return ($this->nodeType === XMLReader::END_ELEMENT && $this->name === $nodeName); + return $this->isPositionedOnNode($nodeName, XMLReader::END_ELEMENT); + } + + /** + * @param string $nodeName + * @param int $nodeType + * @return bool Whether the XML Reader is currently positioned on the node with given name and type + */ + private function isPositionedOnNode($nodeName, $nodeType) + { + // In some cases, the node has a prefix (for instance, "" can also be ""). + // So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName"). + // @see https://github.com/box/spout/issues/233 + $hasPrefix = (strpos($nodeName, ':') !== false); + $currentNodeName = ($hasPrefix) ? $this->name : $this->localName; + + return ($this->nodeType === $nodeType && $currentNodeName === $nodeName); } } diff --git a/src/Spout/Reader/XLSX/Helper/SheetHelper.php b/src/Spout/Reader/XLSX/Helper/SheetHelper.php index 5f74f44..ae7b8e0 100644 --- a/src/Spout/Reader/XLSX/Helper/SheetHelper.php +++ b/src/Spout/Reader/XLSX/Helper/SheetHelper.php @@ -2,7 +2,7 @@ namespace Box\Spout\Reader\XLSX\Helper; -use Box\Spout\Reader\Wrapper\SimpleXMLElement; +use Box\Spout\Reader\Wrapper\XMLReader; use Box\Spout\Reader\XLSX\Sheet; /** @@ -17,10 +17,6 @@ class SheetHelper const WORKBOOK_XML_RELS_FILE_PATH = 'xl/_rels/workbook.xml.rels'; const WORKBOOK_XML_FILE_PATH = 'xl/workbook.xml'; - /** Namespaces for the XML files */ - const MAIN_NAMESPACE_FOR_WORKBOOK_XML_RELS = 'http://schemas.openxmlformats.org/package/2006/relationships'; - const MAIN_NAMESPACE_FOR_WORKBOOK_XML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'; - /** @var string Path of the XLSX file being read */ protected $filePath; @@ -33,12 +29,6 @@ class SheetHelper /** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */ protected $shouldFormatDates; - /** @var \Box\Spout\Reader\Wrapper\SimpleXMLElement XML element representing the workbook.xml.rels file */ - protected $workbookXMLRelsAsXMLElement; - - /** @var \Box\Spout\Reader\Wrapper\SimpleXMLElement XML element representing the workbook.xml file */ - protected $workbookXMLAsXMLElement; - /** * @param string $filePath Path of the XLSX file being read * @param \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings @@ -62,13 +52,21 @@ class SheetHelper public function getSheets() { $sheets = []; + $sheetIndex = 0; - // Starting from "workbook.xml" as this file is the source of truth for the sheets order - $workbookXMLElement = $this->getWorkbookXMLAsXMLElement(); - $sheetNodes = $workbookXMLElement->xpath('//ns:sheet'); + $xmlReader = new XMLReader(); + if ($xmlReader->open('zip://' . $this->filePath . '#' . self::WORKBOOK_XML_FILE_PATH)) { + while ($xmlReader->read()) { + if ($xmlReader->isPositionedOnStartingNode('sheet')) { + $sheets[] = $this->getSheetFromSheetXMLNode($xmlReader, $sheetIndex); + $sheetIndex++; + } else if ($xmlReader->isPositionedOnEndingNode('sheets')) { + // stop reading once all sheets have been read + break; + } + } - foreach ($sheetNodes as $sheetIndex => $sheetNode) { - $sheets[] = $this->getSheetFromSheetXMLNode($sheetNode, $sheetIndex); + $xmlReader->close(); } return $sheets; @@ -79,88 +77,56 @@ class SheetHelper * We can find the XML file path describing the sheet inside "workbook.xml.res", by mapping with the sheet ID * ("r:id" in "workbook.xml", "Id" in "workbook.xml.res"). * - * @param \Box\Spout\Reader\Wrapper\SimpleXMLElement $sheetNode XML Node describing the sheet, as defined in "workbook.xml" + * @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReaderOnSheetNode XML Reader instance, pointing on the node describing the sheet, as defined in "workbook.xml" * @param int $sheetIndexZeroBased Index of the sheet, based on order of appearance in the workbook (zero-based) * @return \Box\Spout\Reader\XLSX\Sheet Sheet instance */ - protected function getSheetFromSheetXMLNode($sheetNode, $sheetIndexZeroBased) + protected function getSheetFromSheetXMLNode($xmlReaderOnSheetNode, $sheetIndexZeroBased) { - // To retrieve namespaced attributes, some versions of LibXML will accept prefixing the attribute - // with the namespace directly (tested on LibXML 2.9.3). For older versions (tested on LibXML 2.7.8), - // attributes need to be retrieved without the namespace hint. - $sheetId = $sheetNode->getAttribute('r:id'); - if ($sheetId === null) { - $sheetId = $sheetNode->getAttribute('id'); - } - - $escapedSheetName = $sheetNode->getAttribute('name'); + $sheetId = $xmlReaderOnSheetNode->getAttribute('r:id'); + $escapedSheetName = $xmlReaderOnSheetNode->getAttribute('name'); /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ $escaper = new \Box\Spout\Common\Escaper\XLSX(); $sheetName = $escaper->unescape($escapedSheetName); - // find the file path of the sheet, by looking at the "workbook.xml.res" file - $workbookXMLResElement = $this->getWorkbookXMLRelsAsXMLElement(); - $relationshipNodes = $workbookXMLResElement->xpath('//ns:Relationship[@Id="' . $sheetId . '"]'); - $relationshipNode = $relationshipNodes[0]; - - // In workbook.xml.rels, it is only "worksheets/sheet1.xml" - // In [Content_Types].xml, the path is "/xl/worksheets/sheet1.xml" - $sheetDataXMLFilePath = '/xl/' . $relationshipNode->getAttribute('Target'); + $sheetDataXMLFilePath = $this->getSheetDataXMLFilePathForSheetId($sheetId); return new Sheet($this->filePath, $sheetDataXMLFilePath, $this->sharedStringsHelper, $this->shouldFormatDates, $sheetIndexZeroBased, $sheetName); } /** - * Returns a representation of the workbook.xml.rels file, ready to be parsed. - * The returned value is cached. - * - * @return \Box\Spout\Reader\Wrapper\SimpleXMLElement XML element representating the workbook.xml.rels file + * @param string $sheetId The sheet ID, as defined in "workbook.xml" + * @return string The XML file path describing the sheet inside "workbook.xml.res", for the given sheet ID */ - protected function getWorkbookXMLRelsAsXMLElement() + protected function getSheetDataXMLFilePathForSheetId($sheetId) { - if (!$this->workbookXMLRelsAsXMLElement) { - $this->workbookXMLRelsAsXMLElement = $this->getFileAsXMLElementWithNamespace( - self::WORKBOOK_XML_RELS_FILE_PATH, - self::MAIN_NAMESPACE_FOR_WORKBOOK_XML_RELS - ); + $sheetDataXMLFilePath = ''; + + // find the file path of the sheet, by looking at the "workbook.xml.res" file + $xmlReader = new XMLReader(); + if ($xmlReader->open('zip://' . $this->filePath . '#' . self::WORKBOOK_XML_RELS_FILE_PATH)) { + while ($xmlReader->read()) { + if ($xmlReader->isPositionedOnStartingNode('Relationship')) { + $relationshipSheetId = $xmlReader->getAttribute('Id'); + + if ($relationshipSheetId === $sheetId) { + // In workbook.xml.rels, it is only "worksheets/sheet1.xml" + // In [Content_Types].xml, the path is "/xl/worksheets/sheet1.xml" + $sheetDataXMLFilePath = $xmlReader->getAttribute('Target'); + + // sometimes, the sheet data file path already contains "/xl/"... + if (strpos($sheetDataXMLFilePath, '/xl/') !== 0) { + $sheetDataXMLFilePath = '/xl/' . $sheetDataXMLFilePath; + break; + } + } + } + } + + $xmlReader->close(); } - return $this->workbookXMLRelsAsXMLElement; - } - - /** - * Returns a representation of the workbook.xml file, ready to be parsed. - * The returned value is cached. - * - * @return \Box\Spout\Reader\Wrapper\SimpleXMLElement XML element representating the workbook.xml.rels file - */ - protected function getWorkbookXMLAsXMLElement() - { - if (!$this->workbookXMLAsXMLElement) { - $this->workbookXMLAsXMLElement = $this->getFileAsXMLElementWithNamespace( - self::WORKBOOK_XML_FILE_PATH, - self::MAIN_NAMESPACE_FOR_WORKBOOK_XML - ); - } - - return $this->workbookXMLAsXMLElement; - } - - /** - * Loads the contents of the given file in an XML parser and register the given XPath namespace. - * - * @param string $xmlFilePath The path of the XML file inside the XLSX file - * @param string $mainNamespace The main XPath namespace to register - * @return \Box\Spout\Reader\Wrapper\SimpleXMLElement The XML element representing the file - */ - protected function getFileAsXMLElementWithNamespace($xmlFilePath, $mainNamespace) - { - $xmlContents = $this->globalFunctionsHelper->file_get_contents('zip://' . $this->filePath . '#' . $xmlFilePath); - - $xmlElement = new SimpleXMLElement($xmlContents); - $xmlElement->registerXPathNamespace('ns', $mainNamespace); - - return $xmlElement; + return $sheetDataXMLFilePath; } } diff --git a/tests/Spout/Reader/Wrapper/XMLReaderTest.php b/tests/Spout/Reader/Wrapper/XMLReaderTest.php index a4deacd..1f7ffc4 100644 --- a/tests/Spout/Reader/Wrapper/XMLReaderTest.php +++ b/tests/Spout/Reader/Wrapper/XMLReaderTest.php @@ -198,4 +198,37 @@ class XMLReaderTest extends \PHPUnit_Framework_TestCase unlink($tempFolder . '/test.xlsx'); } + + /** + * @return array + */ + public function dataProviderForTestIsPositionedOnStartingAndEndingNode() + { + return [ + [''], // not prefixed + [''], // prefixed + ]; + } + + /** + * @dataProvider dataProviderForTestIsPositionedOnStartingAndEndingNode + * + * @param string $testXML + * @return void + */ + public function testIsPositionedOnStartingAndEndingNode($testXML) + { + $xmlReader = new XMLReader(); + $xmlReader->XML($testXML); + + // the first read moves the pointer to "" + $xmlReader->read(); + $this->assertTrue($xmlReader->isPositionedOnStartingNode('test')); + $this->assertFalse($xmlReader->isPositionedOnEndingNode('test')); + + // the seconds read moves the pointer to "" + $xmlReader->read(); + $this->assertFalse($xmlReader->isPositionedOnStartingNode('test')); + $this->assertTrue($xmlReader->isPositionedOnEndingNode('test')); + } } diff --git a/tests/Spout/Reader/XLSX/ReaderTest.php b/tests/Spout/Reader/XLSX/ReaderTest.php index 8620ed5..2703799 100644 --- a/tests/Spout/Reader/XLSX/ReaderTest.php +++ b/tests/Spout/Reader/XLSX/ReaderTest.php @@ -95,6 +95,23 @@ class ReaderTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedRows, $allRows); } + /** + * @return void + */ + public function testReadShouldSupportPrefixedXMLFiles() + { + // The XML files of this spreadsheet are prefixed. + // For instance, they use "" instead of "", etc. + $allRows = $this->getAllRowsForFile('sheet_with_prefixed_xml_files.xlsx'); + + $expectedRows = [ + ['s1 - A1', 's1 - B1', 's1 - C1'], + ['s1 - A2', 's1 - B2', 's1 - C2'], + ['s1 - A3', 's1 - B3', 's1 - C3'], + ]; + $this->assertEquals($expectedRows, $allRows); + } + /** * @return void */ diff --git a/tests/resources/xlsx/sheet_with_prefixed_xml_files.xlsx b/tests/resources/xlsx/sheet_with_prefixed_xml_files.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d8bbc39f5fda8ba0679a47e3df108e0b5f529216 GIT binary patch literal 7084 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 ihV*g|Q9=LU Date: Sun, 29 May 2016 23:22:57 -0700 Subject: [PATCH 2/4] Adding open_file_in_zip() helper function to XMLReader (#238) --- src/Spout/Reader/ODS/SheetIterator.php | 6 +- src/Spout/Reader/Wrapper/XMLReader.php | 56 +++++----------- src/Spout/Reader/XLSX/Helper/SheetHelper.php | 4 +- src/Spout/Reader/XLSX/Helper/StyleHelper.php | 3 +- tests/Spout/Reader/Wrapper/XMLReaderTest.php | 64 +++++-------------- tests/Spout/Writer/ODS/WriterTest.php | 3 +- .../Spout/Writer/ODS/WriterWithStyleTest.php | 21 +++--- .../Spout/Writer/XLSX/WriterWithStyleTest.php | 18 ++++-- 8 files changed, 64 insertions(+), 111 deletions(-) diff --git a/src/Spout/Reader/ODS/SheetIterator.php b/src/Spout/Reader/ODS/SheetIterator.php index d0010bd..4f2ec01 100644 --- a/src/Spout/Reader/ODS/SheetIterator.php +++ b/src/Spout/Reader/ODS/SheetIterator.php @@ -15,6 +15,8 @@ use Box\Spout\Reader\Wrapper\XMLReader; */ class SheetIterator implements IteratorInterface { + const CONTENT_XML_FILE_PATH = 'content.xml'; + /** Definition of XML nodes name and attribute used to parse sheet data */ const XML_NODE_TABLE = 'table:table'; const XML_ATTRIBUTE_TABLE_NAME = 'table:name'; @@ -63,8 +65,8 @@ class SheetIterator implements IteratorInterface { $this->xmlReader->close(); - $contentXmlFilePath = $this->filePath . '#content.xml'; - if ($this->xmlReader->open('zip://' . $contentXmlFilePath) === false) { + if ($this->xmlReader->openFileInZip($this->filePath, self::CONTENT_XML_FILE_PATH) === false) { + $contentXmlFilePath = $this->filePath . '#' . self::CONTENT_XML_FILE_PATH; throw new IOException("Could not open \"{$contentXmlFilePath}\"."); } diff --git a/src/Spout/Reader/Wrapper/XMLReader.php b/src/Spout/Reader/Wrapper/XMLReader.php index 94b28eb..c979819 100644 --- a/src/Spout/Reader/Wrapper/XMLReader.php +++ b/src/Spout/Reader/Wrapper/XMLReader.php @@ -14,66 +14,44 @@ class XMLReader extends \XMLReader { use XMLInternalErrorsHelper; + const ZIP_WRAPPER = 'zip://'; + /** - * Set the URI containing the XML to parse - * @see \XMLReader::open + * Opens the XML Reader to read a file located inside a ZIP file. * - * @param string $URI URI pointing to the document - * @param string|null|void $encoding The document encoding - * @param int $options A bitmask of the LIBXML_* constants + * @param string $zipFilePath Path to the ZIP file + * @param string $fileInsideZipPath Relative or absolute path of the file inside the zip * @return bool TRUE on success or FALSE on failure */ - public function open($URI, $encoding = null, $options = 0) + public function openFileInZip($zipFilePath, $fileInsideZipPath) { $wasOpenSuccessful = false; - $realPathURI = $this->convertURIToUseRealPath($URI); + $realPathURI = $this->getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath); // HHVM does not check if file exists within zip file // @link https://github.com/facebook/hhvm/issues/5779 - if ($this->isRunningHHVM() && $this->isZipStream($realPathURI)) { + if ($this->isRunningHHVM()) { if ($this->fileExistsWithinZip($realPathURI)) { - $wasOpenSuccessful = parent::open($realPathURI, $encoding, $options|LIBXML_NONET); + $wasOpenSuccessful = $this->open($realPathURI, null, LIBXML_NONET); } } else { - $wasOpenSuccessful = parent::open($realPathURI, $encoding, $options|LIBXML_NONET); + $wasOpenSuccessful = $this->open($realPathURI, null, LIBXML_NONET); } return $wasOpenSuccessful; } /** - * Updates the given URI to use a real path. - * This is to avoid issues on some Windows setup. + * Returns the real path for the given path components. + * This is useful to avoid issues on some Windows setup. * - * @param string $URI URI - * @return string The URI using a real path + * @param string $zipFilePath Path to the ZIP file + * @param string $fileInsideZipPath Relative or absolute path of the file inside the zip + * @return string The real path URI */ - protected function convertURIToUseRealPath($URI) + public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath) { - $realPathURI = $URI; - - if ($this->isZipStream($URI)) { - if (preg_match('/zip:\/\/(.*)#(.*)/', $URI, $matches)) { - $documentPath = $matches[1]; - $documentInsideZipPath = $matches[2]; - $realPathURI = 'zip://' . realpath($documentPath) . '#' . $documentInsideZipPath; - } - } else { - $realPathURI = realpath($URI); - } - - return $realPathURI; - } - - /** - * Returns whether the given URI is a zip stream. - * - * @param string $URI URI pointing to a document - * @return bool TRUE if URI is a zip stream, FALSE otherwise - */ - protected function isZipStream($URI) - { - return (strpos($URI, 'zip://') === 0); + return (self::ZIP_WRAPPER . realpath($zipFilePath) . '#' . $fileInsideZipPath); } /** diff --git a/src/Spout/Reader/XLSX/Helper/SheetHelper.php b/src/Spout/Reader/XLSX/Helper/SheetHelper.php index ae7b8e0..175de5b 100644 --- a/src/Spout/Reader/XLSX/Helper/SheetHelper.php +++ b/src/Spout/Reader/XLSX/Helper/SheetHelper.php @@ -55,7 +55,7 @@ class SheetHelper $sheetIndex = 0; $xmlReader = new XMLReader(); - if ($xmlReader->open('zip://' . $this->filePath . '#' . self::WORKBOOK_XML_FILE_PATH)) { + if ($xmlReader->openFileInZip($this->filePath, self::WORKBOOK_XML_FILE_PATH)) { while ($xmlReader->read()) { if ($xmlReader->isPositionedOnStartingNode('sheet')) { $sheets[] = $this->getSheetFromSheetXMLNode($xmlReader, $sheetIndex); @@ -105,7 +105,7 @@ class SheetHelper // find the file path of the sheet, by looking at the "workbook.xml.res" file $xmlReader = new XMLReader(); - if ($xmlReader->open('zip://' . $this->filePath . '#' . self::WORKBOOK_XML_RELS_FILE_PATH)) { + if ($xmlReader->openFileInZip($this->filePath, self::WORKBOOK_XML_RELS_FILE_PATH)) { while ($xmlReader->read()) { if ($xmlReader->isPositionedOnStartingNode('Relationship')) { $relationshipSheetId = $xmlReader->getAttribute('Id'); diff --git a/src/Spout/Reader/XLSX/Helper/StyleHelper.php b/src/Spout/Reader/XLSX/Helper/StyleHelper.php index 462433c..6fdcd80 100644 --- a/src/Spout/Reader/XLSX/Helper/StyleHelper.php +++ b/src/Spout/Reader/XLSX/Helper/StyleHelper.php @@ -76,10 +76,9 @@ class StyleHelper $this->customNumberFormats = []; $this->stylesAttributes = []; - $stylesXmlFilePath = $this->filePath .'#' . self::STYLES_XML_FILE_PATH; $xmlReader = new XMLReader(); - if ($xmlReader->open('zip://' . $stylesXmlFilePath)) { + if ($xmlReader->openFileInZip($this->filePath, self::STYLES_XML_FILE_PATH)) { while ($xmlReader->read()) { if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_NUM_FMTS)) { $numFmtsNode = new SimpleXMLElement($xmlReader->readOuterXml()); diff --git a/tests/Spout/Reader/Wrapper/XMLReaderTest.php b/tests/Spout/Reader/Wrapper/XMLReaderTest.php index 1f7ffc4..b16c648 100644 --- a/tests/Spout/Reader/Wrapper/XMLReaderTest.php +++ b/tests/Spout/Reader/Wrapper/XMLReaderTest.php @@ -20,12 +20,11 @@ class XMLReaderTest extends \PHPUnit_Framework_TestCase public function testOpenShouldFailIfFileInsideZipDoesNotExist() { $resourcePath = $this->getResourcePath('one_sheet_with_inline_strings.xlsx'); - $nonExistingXMLFilePath = 'zip://' . $resourcePath . '#path/to/fake/file.xml'; $xmlReader = new XMLReader(); // using "@" to prevent errors/warning to be displayed - $wasOpenSuccessful = @$xmlReader->open($nonExistingXMLFilePath); + $wasOpenSuccessful = @$xmlReader->openFileInZip($resourcePath, 'path/to/fake/file.xml'); $this->assertTrue($wasOpenSuccessful === false); } @@ -72,10 +71,9 @@ class XMLReaderTest extends \PHPUnit_Framework_TestCase public function testReadShouldThrowExceptionOnError() { $resourcePath = $this->getResourcePath('one_sheet_with_invalid_xml_characters.xlsx'); - $sheetDataXMLFilePath = 'zip://' . $resourcePath . '#xl/worksheets/sheet1.xml'; $xmlReader = new XMLReader(); - if ($xmlReader->open($sheetDataXMLFilePath) === false) { + if ($xmlReader->openFileInZip($resourcePath, 'xl/worksheets/sheet1.xml') === false) { $this->fail(); } @@ -95,43 +93,13 @@ class XMLReaderTest extends \PHPUnit_Framework_TestCase // The sharedStrings.xml file in "attack_billion_laughs.xlsx" contains // a doctype element that causes read errors $resourcePath = $this->getResourcePath('attack_billion_laughs.xlsx'); - $sheetDataXMLFilePath = 'zip://' . $resourcePath . '#xl/sharedStrings.xml'; $xmlReader = new XMLReader(); - if ($xmlReader->open($sheetDataXMLFilePath) !== false) { + if ($xmlReader->openFileInZip($resourcePath, 'xl/sharedStrings.xml') !== false) { @$xmlReader->next('sst'); } } - /** - * @return array - */ - public function dataProviderForTestIsZipStream() - { - return [ - ['/absolute/path/to/file.xlsx', false], - ['relative/path/to/file.xlsx', false], - ['php://temp', false], - ['zip:///absolute/path/to/file.xlsx', true], - ['zip://relative/path/to/file.xlsx', true], - ]; - } - - /** - * @dataProvider dataProviderForTestIsZipStream - * - * @param string $URI - * @param bool $expectedResult - * @return void - */ - public function testIsZipStream($URI, $expectedResult) - { - $xmlReader = new XMLReader(); - $isZipStream = \ReflectionHelper::callMethodOnObject($xmlReader, 'isZipStream', $URI); - - $this->assertEquals($expectedResult, $isZipStream); - } - /** * @return array */ @@ -167,34 +135,34 @@ class XMLReaderTest extends \PHPUnit_Framework_TestCase /** * @return array */ - public function dataProviderForTestConvertURIToUseRealPath() + public function dataProviderForTestGetRealPathURIForFileInZip() { $tempFolder = realpath(sys_get_temp_dir()); + $expectedRealPathURI = 'zip://' . $tempFolder . '/test.xlsx#test.xml'; return [ - ['/../../../' . $tempFolder . '/test.xlsx', $tempFolder . '/test.xlsx'], - [$tempFolder . '/test.xlsx', $tempFolder . '/test.xlsx'], - ['zip://' . $tempFolder . '/test.xlsx#test.xml', 'zip://' . $tempFolder . '/test.xlsx#test.xml'], - ['zip:///../../../' . $tempFolder . '/test.xlsx#test.xml', 'zip://' . $tempFolder . '/test.xlsx#test.xml'], + [$tempFolder, "$tempFolder/test.xlsx", 'test.xml', $expectedRealPathURI], + [$tempFolder, "/../../../$tempFolder/test.xlsx", 'test.xml', $expectedRealPathURI], ]; } /** - * @dataProvider dataProviderForTestConvertURIToUseRealPath + * @dataProvider dataProviderForTestGetRealPathURIForFileInZip * - * @param string $URI - * @param string $expectedConvertedURI + * @param string $tempFolder + * @param string $zipFilePath + * @param string $fileInsideZipPath + * @param string $expectedRealPathURI * @return void */ - public function testConvertURIToUseRealPath($URI, $expectedConvertedURI) + public function testGetRealPathURIForFileInZip($tempFolder, $zipFilePath, $fileInsideZipPath, $expectedRealPathURI) { - $tempFolder = sys_get_temp_dir(); touch($tempFolder . '/test.xlsx'); $xmlReader = new XMLReader(); - $convertedURI = \ReflectionHelper::callMethodOnObject($xmlReader, 'convertURIToUseRealPath', $URI); + $realPathURI = \ReflectionHelper::callMethodOnObject($xmlReader, 'getRealPathURIForFileInZip', $zipFilePath, $fileInsideZipPath); - $this->assertEquals($expectedConvertedURI, $convertedURI); + $this->assertEquals($expectedRealPathURI, $realPathURI); unlink($tempFolder . '/test.xlsx'); } @@ -230,5 +198,7 @@ class XMLReaderTest extends \PHPUnit_Framework_TestCase $xmlReader->read(); $this->assertFalse($xmlReader->isPositionedOnStartingNode('test')); $this->assertTrue($xmlReader->isPositionedOnEndingNode('test')); + + $xmlReader->close(); } } diff --git a/tests/Spout/Writer/ODS/WriterTest.php b/tests/Spout/Writer/ODS/WriterTest.php index 4c67e4e..43383e4 100644 --- a/tests/Spout/Writer/ODS/WriterTest.php +++ b/tests/Spout/Writer/ODS/WriterTest.php @@ -538,10 +538,9 @@ class WriterTest extends \PHPUnit_Framework_TestCase private function moveReaderToCorrectTableNode($fileName, $sheetIndex) { $resourcePath = $this->getGeneratedResourcePath($fileName); - $pathToSheetFile = $resourcePath . '#content.xml'; $xmlReader = new XMLReader(); - $xmlReader->open('zip://' . $pathToSheetFile); + $xmlReader->openFileInZip($resourcePath, 'content.xml'); $xmlReader->readUntilNodeFound('table:table'); for ($i = 1; $i < $sheetIndex; $i++) { diff --git a/tests/Spout/Writer/ODS/WriterWithStyleTest.php b/tests/Spout/Writer/ODS/WriterWithStyleTest.php index cc3fc50..9888bfc 100644 --- a/tests/Spout/Writer/ODS/WriterWithStyleTest.php +++ b/tests/Spout/Writer/ODS/WriterWithStyleTest.php @@ -296,17 +296,18 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase $cellElements = []; $resourcePath = $this->getGeneratedResourcePath($fileName); - $pathToStylesXmlFile = $resourcePath . '#content.xml'; - $xmlReader = new \XMLReader(); - $xmlReader->open('zip://' . $pathToStylesXmlFile); + $xmlReader = new XMLReader(); + $xmlReader->openFileInZip($resourcePath, 'content.xml'); while ($xmlReader->read()) { - if ($xmlReader->nodeType === \XMLReader::ELEMENT && $xmlReader->name === 'table:table-cell' && $xmlReader->getAttribute('office:value-type') !== null) { + if ($xmlReader->isPositionedOnStartingNode('table:table-cell') && $xmlReader->getAttribute('office:value-type') !== null) { $cellElements[] = $xmlReader->expand(); } } + $xmlReader->close(); + return $cellElements; } @@ -319,17 +320,18 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase $cellStyleElements = []; $resourcePath = $this->getGeneratedResourcePath($fileName); - $pathToStylesXmlFile = $resourcePath . '#content.xml'; - $xmlReader = new \XMLReader(); - $xmlReader->open('zip://' . $pathToStylesXmlFile); + $xmlReader = new XMLReader(); + $xmlReader->openFileInZip($resourcePath, 'content.xml'); while ($xmlReader->read()) { - if ($xmlReader->nodeType === \XMLReader::ELEMENT && $xmlReader->name === 'style:style' && $xmlReader->getAttribute('style:family') === 'table-cell') { + if ($xmlReader->isPositionedOnStartingNode('style:style') && $xmlReader->getAttribute('style:family') === 'table-cell') { $cellStyleElements[] = $xmlReader->expand(); } } + $xmlReader->close(); + return $cellStyleElements; } @@ -341,10 +343,9 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase private function getXmlSectionFromStylesXmlFile($fileName, $section) { $resourcePath = $this->getGeneratedResourcePath($fileName); - $pathToStylesXmlFile = $resourcePath . '#styles.xml'; $xmlReader = new XMLReader(); - $xmlReader->open('zip://' . $pathToStylesXmlFile); + $xmlReader->openFileInZip($resourcePath, 'styles.xml'); $xmlReader->readUntilNodeFound($section); return $xmlReader->expand(); diff --git a/tests/Spout/Writer/XLSX/WriterWithStyleTest.php b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php index 9a531b9..d43b566 100644 --- a/tests/Spout/Writer/XLSX/WriterWithStyleTest.php +++ b/tests/Spout/Writer/XLSX/WriterWithStyleTest.php @@ -293,13 +293,16 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase private function getXmlSectionFromStylesXmlFile($fileName, $section) { $resourcePath = $this->getGeneratedResourcePath($fileName); - $pathToStylesXmlFile = $resourcePath . '#xl/styles.xml'; $xmlReader = new XMLReader(); - $xmlReader->open('zip://' . $pathToStylesXmlFile); + $xmlReader->openFileInZip($resourcePath, 'xl/styles.xml'); $xmlReader->readUntilNodeFound($section); - return $xmlReader->expand(); + $xmlSection = $xmlReader->expand(); + + $xmlReader->close(); + + return $xmlSection; } /** @@ -311,17 +314,18 @@ class WriterWithStyleTest extends \PHPUnit_Framework_TestCase $cellElements = []; $resourcePath = $this->getGeneratedResourcePath($fileName); - $pathToStylesXmlFile = $resourcePath . '#xl/worksheets/sheet1.xml'; - $xmlReader = new \XMLReader(); - $xmlReader->open('zip://' . $pathToStylesXmlFile); + $xmlReader = new XMLReader(); + $xmlReader->openFileInZip($resourcePath, 'xl/worksheets/sheet1.xml'); while ($xmlReader->read()) { - if ($xmlReader->nodeType === \XMLReader::ELEMENT && $xmlReader->name === 'c') { + if ($xmlReader->isPositionedOnStartingNode('c')) { $cellElements[] = $xmlReader->expand(); } } + $xmlReader->close(); + return $cellElements; } From efebfb2bc21fee43bd86758a556ae31f78e92aae Mon Sep 17 00:00:00 2001 From: Ingmar Runge Date: Mon, 30 May 2016 19:25:30 +0200 Subject: [PATCH 3/4] CellValueFormatterTest: fix expectations for 32bit PHP (#234) --- tests/Spout/Reader/XLSX/Helper/CellValueFormatterTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/Spout/Reader/XLSX/Helper/CellValueFormatterTest.php b/tests/Spout/Reader/XLSX/Helper/CellValueFormatterTest.php index 92831ab..96c71a9 100644 --- a/tests/Spout/Reader/XLSX/Helper/CellValueFormatterTest.php +++ b/tests/Spout/Reader/XLSX/Helper/CellValueFormatterTest.php @@ -87,6 +87,10 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase */ public function dataProviderForTestFormatNumericCellValueWithNumbers() { + // Some test values exceed PHP_INT_MAX on 32-bit PHP. They are + // therefore converted to as doubles automatically by PHP. + $expectedBigNumberType = (PHP_INT_SIZE < 8 ? 'double' : 'integer'); + return [ [42, 42, 'integer'], [42.5, 42.5, 'double'], @@ -94,8 +98,8 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase [-42.5, -42.5, 'double'], ['42', 42, 'integer'], ['42.5', 42.5, 'double'], - [865640023012945, 865640023012945, 'integer'], - ['865640023012945', 865640023012945, 'integer'], + [865640023012945, 865640023012945, $expectedBigNumberType], + ['865640023012945', 865640023012945, $expectedBigNumberType], [865640023012945.5, 865640023012945.5, 'double'], ['865640023012945.5', 865640023012945.5, 'double'], [PHP_INT_MAX, PHP_INT_MAX, 'integer'], From 1d3a9f939c43bfa6f2827f21ebb1c6e625847dee Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Mon, 30 May 2016 13:55:21 -0700 Subject: [PATCH 4/4] Convert escapers to singletons (#239) --- src/Spout/Common/Escaper/ODS.php | 4 ++ src/Spout/Common/Escaper/XLSX.php | 8 +++- src/Spout/Common/Helper/EncodingHelper.php | 2 +- src/Spout/Common/Singleton.php | 41 +++++++++++++++++++ src/Spout/Reader/CSV/RowIterator.php | 1 + .../Reader/ODS/Helper/CellValueFormatter.php | 2 +- src/Spout/Reader/ODS/SheetIterator.php | 2 +- .../Reader/XLSX/Helper/CellValueFormatter.php | 2 +- .../XLSX/Helper/SharedStringsHelper.php | 2 +- src/Spout/Reader/XLSX/Helper/SheetHelper.php | 2 +- src/Spout/Writer/ODS/Internal/Worksheet.php | 2 +- .../Writer/XLSX/Helper/FileSystemHelper.php | 2 +- .../XLSX/Helper/SharedStringsHelper.php | 2 +- src/Spout/Writer/XLSX/Internal/Worksheet.php | 2 +- tests/Spout/Common/Escaper/XLSXTest.php | 4 +- 15 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 src/Spout/Common/Singleton.php diff --git a/src/Spout/Common/Escaper/ODS.php b/src/Spout/Common/Escaper/ODS.php index 3e252a7..86caeb3 100644 --- a/src/Spout/Common/Escaper/ODS.php +++ b/src/Spout/Common/Escaper/ODS.php @@ -2,6 +2,8 @@ namespace Box\Spout\Common\Escaper; +use Box\Spout\Common\Singleton; + /** * Class ODS * Provides functions to escape and unescape data for ODS files @@ -10,6 +12,8 @@ namespace Box\Spout\Common\Escaper; */ class ODS implements EscaperInterface { + use Singleton; + /** * Escapes the given string to make it compatible with XLSX * diff --git a/src/Spout/Common/Escaper/XLSX.php b/src/Spout/Common/Escaper/XLSX.php index 6f5bd1f..b5719a0 100644 --- a/src/Spout/Common/Escaper/XLSX.php +++ b/src/Spout/Common/Escaper/XLSX.php @@ -2,6 +2,8 @@ namespace Box\Spout\Common\Escaper; +use Box\Spout\Common\Singleton; + /** * Class XLSX * Provides functions to escape and unescape data for XLSX files @@ -10,13 +12,15 @@ namespace Box\Spout\Common\Escaper; */ class XLSX implements EscaperInterface { + use Singleton; + /** @var string[] Control characters to be escaped */ protected $controlCharactersEscapingMap; /** - * + * Initializes the singleton instance */ - public function __construct() + protected function init() { $this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap(); } diff --git a/src/Spout/Common/Helper/EncodingHelper.php b/src/Spout/Common/Helper/EncodingHelper.php index 3edd7a1..3a30aaa 100644 --- a/src/Spout/Common/Helper/EncodingHelper.php +++ b/src/Spout/Common/Helper/EncodingHelper.php @@ -59,7 +59,7 @@ class EncodingHelper { $byteOffsetToSkipBom = 0; - if ($this->hasBom($filePointer, $encoding)) { + if ($this->hasBOM($filePointer, $encoding)) { $bomUsed = $this->supportedEncodingsWithBom[$encoding]; // we skip the N first bytes diff --git a/src/Spout/Common/Singleton.php b/src/Spout/Common/Singleton.php new file mode 100644 index 0000000..015ede8 --- /dev/null +++ b/src/Spout/Common/Singleton.php @@ -0,0 +1,41 @@ +init(); + } + + /** + * Initializes the singleton + * @return void + */ + protected function init() {} + + final private function __wakeup() {} + final private function __clone() {} +} diff --git a/src/Spout/Reader/CSV/RowIterator.php b/src/Spout/Reader/CSV/RowIterator.php index 95b2596..5189dfc 100644 --- a/src/Spout/Reader/CSV/RowIterator.php +++ b/src/Spout/Reader/CSV/RowIterator.php @@ -57,6 +57,7 @@ class RowIterator implements IteratorInterface * @param string $fieldDelimiter Character that delimits fields * @param string $fieldEnclosure Character that enclose fields * @param string $encoding Encoding of the CSV file to be read + * @param string $endOfLineDelimiter End of line delimiter * @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper */ public function __construct($filePointer, $fieldDelimiter, $fieldEnclosure, $encoding, $endOfLineDelimiter, $globalFunctionsHelper) diff --git a/src/Spout/Reader/ODS/Helper/CellValueFormatter.php b/src/Spout/Reader/ODS/Helper/CellValueFormatter.php index b39af21..38c1c85 100644 --- a/src/Spout/Reader/ODS/Helper/CellValueFormatter.php +++ b/src/Spout/Reader/ODS/Helper/CellValueFormatter.php @@ -48,7 +48,7 @@ class CellValueFormatter $this->shouldFormatDates = $shouldFormatDates; /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $this->escaper = new \Box\Spout\Common\Escaper\ODS(); + $this->escaper = \Box\Spout\Common\Escaper\ODS::getInstance(); } /** diff --git a/src/Spout/Reader/ODS/SheetIterator.php b/src/Spout/Reader/ODS/SheetIterator.php index 4f2ec01..f6cfdbe 100644 --- a/src/Spout/Reader/ODS/SheetIterator.php +++ b/src/Spout/Reader/ODS/SheetIterator.php @@ -51,7 +51,7 @@ class SheetIterator implements IteratorInterface $this->xmlReader = new XMLReader(); /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $this->escaper = new \Box\Spout\Common\Escaper\ODS(); + $this->escaper = \Box\Spout\Common\Escaper\ODS::getInstance(); } /** diff --git a/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php b/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php index 286d348..a272a2e 100644 --- a/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php +++ b/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php @@ -62,7 +62,7 @@ class CellValueFormatter $this->shouldFormatDates = $shouldFormatDates; /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $this->escaper = new \Box\Spout\Common\Escaper\XLSX(); + $this->escaper = \Box\Spout\Common\Escaper\XLSX::getInstance(); } /** diff --git a/src/Spout/Reader/XLSX/Helper/SharedStringsHelper.php b/src/Spout/Reader/XLSX/Helper/SharedStringsHelper.php index 1ba4e6a..750c53e 100644 --- a/src/Spout/Reader/XLSX/Helper/SharedStringsHelper.php +++ b/src/Spout/Reader/XLSX/Helper/SharedStringsHelper.php @@ -80,7 +80,7 @@ class SharedStringsHelper $xmlReader = new XMLReader(); $sharedStringIndex = 0; /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $escaper = new \Box\Spout\Common\Escaper\XLSX(); + $escaper = \Box\Spout\Common\Escaper\XLSX::getInstance(); $sharedStringsFilePath = $this->getSharedStringsFilePath(); if ($xmlReader->open($sharedStringsFilePath) === false) { diff --git a/src/Spout/Reader/XLSX/Helper/SheetHelper.php b/src/Spout/Reader/XLSX/Helper/SheetHelper.php index 175de5b..a6ff909 100644 --- a/src/Spout/Reader/XLSX/Helper/SheetHelper.php +++ b/src/Spout/Reader/XLSX/Helper/SheetHelper.php @@ -87,7 +87,7 @@ class SheetHelper $escapedSheetName = $xmlReaderOnSheetNode->getAttribute('name'); /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $escaper = new \Box\Spout\Common\Escaper\XLSX(); + $escaper = \Box\Spout\Common\Escaper\XLSX::getInstance(); $sheetName = $escaper->unescape($escapedSheetName); $sheetDataXMLFilePath = $this->getSheetDataXMLFilePathForSheetId($sheetId); diff --git a/src/Spout/Writer/ODS/Internal/Worksheet.php b/src/Spout/Writer/ODS/Internal/Worksheet.php index 7a0df1a..cec30af 100644 --- a/src/Spout/Writer/ODS/Internal/Worksheet.php +++ b/src/Spout/Writer/ODS/Internal/Worksheet.php @@ -47,7 +47,7 @@ class Worksheet implements WorksheetInterface { $this->externalSheet = $externalSheet; /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $this->stringsEscaper = new \Box\Spout\Common\Escaper\ODS(); + $this->stringsEscaper = \Box\Spout\Common\Escaper\ODS::getInstance(); $this->worksheetFilePath = $worksheetFilesFolder . '/sheet' . $externalSheet->getIndex() . '.xml'; $this->stringHelper = new StringHelper(); diff --git a/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php b/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php index 59df98a..786e62e 100644 --- a/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php +++ b/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php @@ -284,7 +284,7 @@ EOD; EOD; /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $escaper = new \Box\Spout\Common\Escaper\XLSX(); + $escaper = \Box\Spout\Common\Escaper\XLSX::getInstance(); /** @var Worksheet $worksheet */ foreach ($worksheets as $worksheet) { diff --git a/src/Spout/Writer/XLSX/Helper/SharedStringsHelper.php b/src/Spout/Writer/XLSX/Helper/SharedStringsHelper.php index 9d9639d..292b663 100644 --- a/src/Spout/Writer/XLSX/Helper/SharedStringsHelper.php +++ b/src/Spout/Writer/XLSX/Helper/SharedStringsHelper.php @@ -49,7 +49,7 @@ EOD; fwrite($this->sharedStringsFilePointer, $header); /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $this->stringsEscaper = new \Box\Spout\Common\Escaper\XLSX(); + $this->stringsEscaper = \Box\Spout\Common\Escaper\XLSX::getInstance(); } /** diff --git a/src/Spout/Writer/XLSX/Internal/Worksheet.php b/src/Spout/Writer/XLSX/Internal/Worksheet.php index af3fbc4..354554a 100644 --- a/src/Spout/Writer/XLSX/Internal/Worksheet.php +++ b/src/Spout/Writer/XLSX/Internal/Worksheet.php @@ -56,7 +56,7 @@ EOD; $this->shouldUseInlineStrings = $shouldUseInlineStrings; /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $this->stringsEscaper = new \Box\Spout\Common\Escaper\XLSX(); + $this->stringsEscaper = \Box\Spout\Common\Escaper\XLSX::getInstance(); $this->worksheetFilePath = $worksheetFilesFolder . '/' . strtolower($this->externalSheet->getName()) . '.xml'; $this->startSheet(); diff --git a/tests/Spout/Common/Escaper/XLSXTest.php b/tests/Spout/Common/Escaper/XLSXTest.php index 7c0b2db..289e2a2 100644 --- a/tests/Spout/Common/Escaper/XLSXTest.php +++ b/tests/Spout/Common/Escaper/XLSXTest.php @@ -35,7 +35,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase public function testEscape($stringToEscape, $expectedEscapedString) { /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $escaper = new \Box\Spout\Common\Escaper\XLSX(); + $escaper = \Box\Spout\Common\Escaper\XLSX::getInstance(); $escapedString = $escaper->escape($stringToEscape); $this->assertEquals($expectedEscapedString, $escapedString, 'Incorrect escaped string'); @@ -67,7 +67,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase public function testUnescape($stringToUnescape, $expectedUnescapedString) { /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ - $escaper = new \Box\Spout\Common\Escaper\XLSX(); + $escaper = \Box\Spout\Common\Escaper\XLSX::getInstance(); $unescapedString = $escaper->unescape($stringToUnescape); $this->assertEquals($expectedUnescapedString, $unescapedString, 'Incorrect escaped string');