Merge branch 'master' into feature/num_format
This commit is contained in:
commit
3b03a9cc24
@ -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
|
||||
*
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
41
src/Spout/Common/Singleton.php
Normal file
41
src/Spout/Common/Singleton.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Common;
|
||||
|
||||
/**
|
||||
* Class Singleton
|
||||
* Defines a class as a singleton.
|
||||
*
|
||||
* @package Box\Spout\Common
|
||||
*/
|
||||
trait Singleton
|
||||
{
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
final public static function getInstance()
|
||||
{
|
||||
return isset(static::$instance)
|
||||
? static::$instance
|
||||
: static::$instance = new static;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton constructor.
|
||||
*/
|
||||
final private function __construct()
|
||||
{
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the singleton
|
||||
* @return void
|
||||
*/
|
||||
protected function init() {}
|
||||
|
||||
final private function __wakeup() {}
|
||||
final private function __clone() {}
|
||||
}
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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';
|
||||
@ -49,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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}\".");
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,9 +116,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 +149,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 +158,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, "<sheet>" can also be "<x:sheet>").
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
|
@ -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->openFileInZip($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();
|
||||
$escaper = \Box\Spout\Common\Escaper\XLSX::getInstance();
|
||||
$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->openFileInZip($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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
|
@ -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');
|
||||
|
@ -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,35 +135,70 @@ 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');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function dataProviderForTestIsPositionedOnStartingAndEndingNode()
|
||||
{
|
||||
return [
|
||||
['<test></test>'], // not prefixed
|
||||
['<x:test xmlns:x="foo"></x:test>'], // 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 "<test>"
|
||||
$xmlReader->read();
|
||||
$this->assertTrue($xmlReader->isPositionedOnStartingNode('test'));
|
||||
$this->assertFalse($xmlReader->isPositionedOnEndingNode('test'));
|
||||
|
||||
// the seconds read moves the pointer to "</test>"
|
||||
$xmlReader->read();
|
||||
$this->assertFalse($xmlReader->isPositionedOnStartingNode('test'));
|
||||
$this->assertTrue($xmlReader->isPositionedOnEndingNode('test'));
|
||||
|
||||
$xmlReader->close();
|
||||
}
|
||||
}
|
||||
|
@ -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'],
|
||||
|
@ -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 "<x:sheet>" instead of "<sheet>", 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
|
||||
*/
|
||||
|
@ -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++) {
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
BIN
tests/resources/xlsx/sheet_with_prefixed_xml_files.xlsx
Normal file
BIN
tests/resources/xlsx/sheet_with_prefixed_xml_files.xlsx
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user