From 481fc4a7c6aefda8b36dfeeebc52ac90395da106 Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Sun, 14 Jan 2018 16:59:34 +0900 Subject: [PATCH] Support XML file without styles Closes #331 Closes https://github.com/PHPOffice/PHPExcel/pull/559 Fixes https://github.com/PHPOffice/PHPExcel/issues/558 --- CHANGELOG.md | 1 + src/PhpSpreadsheet/Reader/Xml.php | 381 ++++++++++-------- .../{XEEValidatorTest.php => XmlTest.php} | 40 +- tests/data/Reader/Xml/WithoutStyle.xml | 47 +++ .../{XEE => Xml}/XEETestInvalidSimpleXML.xml | 0 .../{XEE => Xml}/XEETestInvalidUTF-16.xml | Bin .../{XEE => Xml}/XEETestInvalidUTF-16BE.xml | Bin .../{XEE => Xml}/XEETestInvalidUTF-16LE.xml | Bin .../{XEE => Xml}/XEETestInvalidUTF-8.xml | 0 .../{XEE => Xml}/XEETestValidUTF-16.xml | Bin .../{XEE => Xml}/XEETestValidUTF-16BE.xml | Bin .../{XEE => Xml}/XEETestValidUTF-16LE.xml | Bin .../Reader/{XEE => Xml}/XEETestValidUTF-8.xml | 0 13 files changed, 280 insertions(+), 189 deletions(-) rename tests/PhpSpreadsheetTests/Reader/{XEEValidatorTest.php => XmlTest.php} (76%) create mode 100644 tests/data/Reader/Xml/WithoutStyle.xml rename tests/data/Reader/{XEE => Xml}/XEETestInvalidSimpleXML.xml (100%) rename tests/data/Reader/{XEE => Xml}/XEETestInvalidUTF-16.xml (100%) rename tests/data/Reader/{XEE => Xml}/XEETestInvalidUTF-16BE.xml (100%) rename tests/data/Reader/{XEE => Xml}/XEETestInvalidUTF-16LE.xml (100%) rename tests/data/Reader/{XEE => Xml}/XEETestInvalidUTF-8.xml (100%) rename tests/data/Reader/{XEE => Xml}/XEETestValidUTF-16.xml (100%) rename tests/data/Reader/{XEE => Xml}/XEETestValidUTF-16BE.xml (100%) rename tests/data/Reader/{XEE => Xml}/XEETestValidUTF-16LE.xml (100%) rename tests/data/Reader/{XEE => Xml}/XEETestValidUTF-8.xml (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d73781d..746db5d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Support for shape style ending with `;` - [#304](https://github.com/PHPOffice/PhpSpreadsheet/issues/304) - Freeze Panes takes wrong coordinates for XLSX - [#322](https://github.com/PHPOffice/PhpSpreadsheet/issues/322) - `COLUMNS` and `ROWS` functions crashed in some cases - [#336](https://github.com/PHPOffice/PhpSpreadsheet/issues/336) +- Support XML file without styles - [#331](https://github.com/PHPOffice/PhpSpreadsheet/pull/331) ## [1.0.0] - 2017-12-25 diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php index 65b81824..0a80a4ba 100644 --- a/src/PhpSpreadsheet/Reader/Xml.php +++ b/src/PhpSpreadsheet/Reader/Xml.php @@ -14,6 +14,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Font; +use SimpleXMLElement; /** * Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003. @@ -64,9 +65,9 @@ class Xml extends BaseReader // $signature = [ - '', - ]; + '', + ]; // Open file $this->openFile($pFilename); @@ -300,31 +301,6 @@ class Xml extends BaseReader */ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) { - $fromFormats = ['\-', '\ ']; - $toFormats = ['-', ' ']; - - $underlineStyles = [ - Font::UNDERLINE_NONE, - Font::UNDERLINE_DOUBLE, - Font::UNDERLINE_DOUBLEACCOUNTING, - Font::UNDERLINE_SINGLE, - Font::UNDERLINE_SINGLEACCOUNTING, - ]; - $verticalAlignmentStyles = [ - Alignment::VERTICAL_BOTTOM, - Alignment::VERTICAL_TOP, - Alignment::VERTICAL_CENTER, - Alignment::VERTICAL_JUSTIFY, - ]; - $horizontalAlignmentStyles = [ - Alignment::HORIZONTAL_GENERAL, - Alignment::HORIZONTAL_LEFT, - Alignment::HORIZONTAL_RIGHT, - Alignment::HORIZONTAL_CENTER, - Alignment::HORIZONTAL_CENTER_CONTINUOUS, - Alignment::HORIZONTAL_JUSTIFY, - ]; - File::assertFile($pFilename); if (!$this->canRead($pFilename)) { throw new Exception($pFilename . ' is an Invalid Spreadsheet file.'); @@ -423,140 +399,7 @@ class Xml extends BaseReader } } - foreach ($xml->Styles[0] as $style) { - $style_ss = $style->attributes($namespaces['ss']); - $styleID = (string) $style_ss['ID']; - $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : []; - foreach ($style as $styleType => $styleData) { - $styleAttributes = $styleData->attributes($namespaces['ss']); - switch ($styleType) { - case 'Alignment': - foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { - $styleAttributeValue = (string) $styleAttributeValue; - switch ($styleAttributeKey) { - case 'Vertical': - if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) { - $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue; - } - - break; - case 'Horizontal': - if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) { - $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue; - } - - break; - case 'WrapText': - $this->styles[$styleID]['alignment']['wrapText'] = true; - - break; - } - } - - break; - case 'Borders': - foreach ($styleData->Border as $borderStyle) { - $borderAttributes = $borderStyle->attributes($namespaces['ss']); - $thisBorder = []; - foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) { - switch ($borderStyleKey) { - case 'LineStyle': - $thisBorder['borderStyle'] = Border::BORDER_MEDIUM; - - break; - case 'Weight': - break; - case 'Position': - $borderPosition = strtolower($borderStyleValue); - - break; - case 'Color': - $borderColour = substr($borderStyleValue, 1); - $thisBorder['color']['rgb'] = $borderColour; - - break; - } - } - if (!empty($thisBorder)) { - if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) { - $this->styles[$styleID]['borders'][$borderPosition] = $thisBorder; - } - } - } - - break; - case 'Font': - foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { - $styleAttributeValue = (string) $styleAttributeValue; - switch ($styleAttributeKey) { - case 'FontName': - $this->styles[$styleID]['font']['name'] = $styleAttributeValue; - - break; - case 'Size': - $this->styles[$styleID]['font']['size'] = $styleAttributeValue; - - break; - case 'Color': - $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1); - - break; - case 'Bold': - $this->styles[$styleID]['font']['bold'] = true; - - break; - case 'Italic': - $this->styles[$styleID]['font']['italic'] = true; - - break; - case 'Underline': - if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) { - $this->styles[$styleID]['font']['underline'] = $styleAttributeValue; - } - - break; - } - } - - break; - case 'Interior': - foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { - switch ($styleAttributeKey) { - case 'Color': - $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1); - - break; - case 'Pattern': - $this->styles[$styleID]['fill']['fillType'] = strtolower($styleAttributeValue); - - break; - } - } - - break; - case 'NumberFormat': - foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { - $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue); - switch ($styleAttributeValue) { - case 'Short Date': - $styleAttributeValue = 'dd/mm/yyyy'; - - break; - } - if ($styleAttributeValue > '') { - $this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue; - } - } - - break; - case 'Protection': - foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { - } - - break; - } - } - } + $this->parseStyles($xml, $namespaces); $worksheetID = 0; $xml_ss = $xml->children($namespaces['ss']); @@ -816,4 +659,218 @@ class Xml extends BaseReader return $value; } + + /** + * @param SimpleXMLElement $xml + * @param array $namespaces + */ + private function parseStyles(SimpleXMLElement $xml, array $namespaces) + { + if (!isset($xml->Styles)) { + return; + } + + foreach ($xml->Styles[0] as $style) { + $style_ss = $style->attributes($namespaces['ss']); + $styleID = (string) $style_ss['ID']; + $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : []; + foreach ($style as $styleType => $styleData) { + $styleAttributes = $styleData->attributes($namespaces['ss']); + switch ($styleType) { + case 'Alignment': + $this->parseStyleAlignment($styleID, $styleAttributes); + + break; + case 'Borders': + $this->parseStyleBorders($styleID, $styleData, $namespaces); + + break; + case 'Font': + $this->parseStyleFont($styleID, $styleAttributes); + + break; + case 'Interior': + $this->parseStyleInterior($styleID, $styleAttributes); + + break; + case 'NumberFormat': + $this->parseStyleNumberFormat($styleID, $styleAttributes); + + break; + } + } + } + } + + /** + * @param string $styleID + * @param SimpleXMLElement $styleAttributes + */ + private function parseStyleAlignment($styleID, SimpleXMLElement $styleAttributes) + { + $verticalAlignmentStyles = [ + Alignment::VERTICAL_BOTTOM, + Alignment::VERTICAL_TOP, + Alignment::VERTICAL_CENTER, + Alignment::VERTICAL_JUSTIFY, + ]; + $horizontalAlignmentStyles = [ + Alignment::HORIZONTAL_GENERAL, + Alignment::HORIZONTAL_LEFT, + Alignment::HORIZONTAL_RIGHT, + Alignment::HORIZONTAL_CENTER, + Alignment::HORIZONTAL_CENTER_CONTINUOUS, + Alignment::HORIZONTAL_JUSTIFY, + ]; + + foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { + $styleAttributeValue = (string) $styleAttributeValue; + switch ($styleAttributeKey) { + case 'Vertical': + if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) { + $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue; + } + + break; + case 'Horizontal': + if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) { + $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue; + } + + break; + case 'WrapText': + $this->styles[$styleID]['alignment']['wrapText'] = true; + + break; + } + } + } + + /** + * @param $styleID + * @param SimpleXMLElement $styleData + * @param array $namespaces + */ + private function parseStyleBorders($styleID, SimpleXMLElement $styleData, array $namespaces) + { + foreach ($styleData->Border as $borderStyle) { + $borderAttributes = $borderStyle->attributes($namespaces['ss']); + $thisBorder = []; + foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) { + switch ($borderStyleKey) { + case 'LineStyle': + $thisBorder['borderStyle'] = Border::BORDER_MEDIUM; + + break; + case 'Weight': + break; + case 'Position': + $borderPosition = strtolower($borderStyleValue); + + break; + case 'Color': + $borderColour = substr($borderStyleValue, 1); + $thisBorder['color']['rgb'] = $borderColour; + + break; + } + } + if (!empty($thisBorder)) { + if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) { + $this->styles[$styleID]['borders'][$borderPosition] = $thisBorder; + } + } + } + } + + /** + * @param $styleID + * @param SimpleXMLElement $styleAttributes + */ + private function parseStyleFont($styleID, SimpleXMLElement $styleAttributes) + { + $underlineStyles = [ + Font::UNDERLINE_NONE, + Font::UNDERLINE_DOUBLE, + Font::UNDERLINE_DOUBLEACCOUNTING, + Font::UNDERLINE_SINGLE, + Font::UNDERLINE_SINGLEACCOUNTING, + ]; + + foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { + $styleAttributeValue = (string) $styleAttributeValue; + switch ($styleAttributeKey) { + case 'FontName': + $this->styles[$styleID]['font']['name'] = $styleAttributeValue; + + break; + case 'Size': + $this->styles[$styleID]['font']['size'] = $styleAttributeValue; + + break; + case 'Color': + $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1); + + break; + case 'Bold': + $this->styles[$styleID]['font']['bold'] = true; + + break; + case 'Italic': + $this->styles[$styleID]['font']['italic'] = true; + + break; + case 'Underline': + if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) { + $this->styles[$styleID]['font']['underline'] = $styleAttributeValue; + } + + break; + } + } + } + + /** + * @param $styleID + * @param SimpleXMLElement $styleAttributes + */ + private function parseStyleInterior($styleID, SimpleXMLElement $styleAttributes) + { + foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { + switch ($styleAttributeKey) { + case 'Color': + $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1); + + break; + case 'Pattern': + $this->styles[$styleID]['fill']['fillType'] = strtolower($styleAttributeValue); + + break; + } + } + } + + /** + * @param $styleID + * @param SimpleXMLElement $styleAttributes + */ + private function parseStyleNumberFormat($styleID, SimpleXMLElement $styleAttributes) + { + $fromFormats = ['\-', '\ ']; + $toFormats = ['-', ' ']; + + foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { + $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue); + switch ($styleAttributeValue) { + case 'Short Date': + $styleAttributeValue = 'dd/mm/yyyy'; + + break; + } + + if ($styleAttributeValue > '') { + $this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue; + } + } + } } diff --git a/tests/PhpSpreadsheetTests/Reader/XEEValidatorTest.php b/tests/PhpSpreadsheetTests/Reader/XmlTest.php similarity index 76% rename from tests/PhpSpreadsheetTests/Reader/XEEValidatorTest.php rename to tests/PhpSpreadsheetTests/Reader/XmlTest.php index bee83788..066c6889 100644 --- a/tests/PhpSpreadsheetTests/Reader/XEEValidatorTest.php +++ b/tests/PhpSpreadsheetTests/Reader/XmlTest.php @@ -5,32 +5,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Reader\BaseReader; use PhpOffice\PhpSpreadsheet\Reader\Xml; -use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; -class XEEValidatorTest extends TestCase +class XmlTest extends TestCase { - /** - * @var Spreadsheet - */ - private $spreadsheetXEETest; - - /** - * @return Spreadsheet - */ - protected function loadXEETestFile() - { - if (!$this->spreadsheetXEETest) { - $filename = '../samples/templates/Excel2003XMLTest.xml'; - - // Load into this instance - $reader = new Xml(); - $this->spreadsheetXEETest = $reader->load($filename); - } - - return $this->spreadsheetXEETest; - } - /** * @dataProvider providerInvalidXML * @expectedException \PhpOffice\PhpSpreadsheet\Reader\Exception @@ -48,7 +26,7 @@ class XEEValidatorTest extends TestCase public function providerInvalidXML() { $tests = []; - foreach (glob(__DIR__ . '/../../data/Reader/XEE/XEETestInvalidUTF*.xml') as $file) { + foreach (glob(__DIR__ . '/../../data/Reader/Xml/XEETestInvalidUTF*.xml') as $file) { $tests[basename($file)] = [realpath($file)]; } @@ -70,7 +48,7 @@ class XEEValidatorTest extends TestCase public function providerInvalidSimpleXML() { $tests = []; - foreach (glob(__DIR__ . '/../../data/Reader/XEE/XEETestInvalidSimpleXML*.xml') as $file) { + foreach (glob(__DIR__ . '/../../data/Reader/Xml/XEETestInvalidSimpleXML*.xml') as $file) { $tests[basename($file)] = [realpath($file)]; } @@ -93,7 +71,7 @@ class XEEValidatorTest extends TestCase public function providerValidXML() { $tests = []; - foreach (glob(__DIR__ . '/../../data/Reader/XEE/XEETestValid*.xml') as $file) { + foreach (glob(__DIR__ . '/../../data/Reader/Xml/XEETestValid*.xml') as $file) { $tests[basename($file)] = [realpath($file), file_get_contents($file)]; } @@ -105,7 +83,8 @@ class XEEValidatorTest extends TestCase */ public function testReadHyperlinks() { - $spreadsheet = $this->loadXEETestFile(); + $reader = new Xml(); + $spreadsheet = $reader->load('../samples/templates/Excel2003XMLTest.xml'); $firstSheet = $spreadsheet->getSheet(0); $hyperlink = $firstSheet->getCell('L1'); @@ -114,4 +93,11 @@ class XEEValidatorTest extends TestCase self::assertEquals('PhpSpreadsheet', $hyperlink->getValue()); self::assertEquals('http://phpspreadsheet.readthedocs.io/', $hyperlink->getHyperlink()->getUrl()); } + + public function testReadWithoutStyle() + { + $reader = new Xml(); + $spreadsheet = $reader->load(__DIR__ . '/../../data/Reader/Xml/WithoutStyle.xml'); + self::assertSame('Test String 1', $spreadsheet->getActiveSheet()->getCell('A1')->getValue()); + } } diff --git a/tests/data/Reader/Xml/WithoutStyle.xml b/tests/data/Reader/Xml/WithoutStyle.xml new file mode 100644 index 00000000..b8698b04 --- /dev/null +++ b/tests/data/Reader/Xml/WithoutStyle.xml @@ -0,0 +1,47 @@ + + + + + + 3 + #000000 + + + + + 9000 + 13860 + 240 + 75 + False + False + + + + + + + + + + + + Test String 1 + + + Test for a simple colour-formatted string + + + + + 1 + + +
+ +
+
diff --git a/tests/data/Reader/XEE/XEETestInvalidSimpleXML.xml b/tests/data/Reader/Xml/XEETestInvalidSimpleXML.xml similarity index 100% rename from tests/data/Reader/XEE/XEETestInvalidSimpleXML.xml rename to tests/data/Reader/Xml/XEETestInvalidSimpleXML.xml diff --git a/tests/data/Reader/XEE/XEETestInvalidUTF-16.xml b/tests/data/Reader/Xml/XEETestInvalidUTF-16.xml similarity index 100% rename from tests/data/Reader/XEE/XEETestInvalidUTF-16.xml rename to tests/data/Reader/Xml/XEETestInvalidUTF-16.xml diff --git a/tests/data/Reader/XEE/XEETestInvalidUTF-16BE.xml b/tests/data/Reader/Xml/XEETestInvalidUTF-16BE.xml similarity index 100% rename from tests/data/Reader/XEE/XEETestInvalidUTF-16BE.xml rename to tests/data/Reader/Xml/XEETestInvalidUTF-16BE.xml diff --git a/tests/data/Reader/XEE/XEETestInvalidUTF-16LE.xml b/tests/data/Reader/Xml/XEETestInvalidUTF-16LE.xml similarity index 100% rename from tests/data/Reader/XEE/XEETestInvalidUTF-16LE.xml rename to tests/data/Reader/Xml/XEETestInvalidUTF-16LE.xml diff --git a/tests/data/Reader/XEE/XEETestInvalidUTF-8.xml b/tests/data/Reader/Xml/XEETestInvalidUTF-8.xml similarity index 100% rename from tests/data/Reader/XEE/XEETestInvalidUTF-8.xml rename to tests/data/Reader/Xml/XEETestInvalidUTF-8.xml diff --git a/tests/data/Reader/XEE/XEETestValidUTF-16.xml b/tests/data/Reader/Xml/XEETestValidUTF-16.xml similarity index 100% rename from tests/data/Reader/XEE/XEETestValidUTF-16.xml rename to tests/data/Reader/Xml/XEETestValidUTF-16.xml diff --git a/tests/data/Reader/XEE/XEETestValidUTF-16BE.xml b/tests/data/Reader/Xml/XEETestValidUTF-16BE.xml similarity index 100% rename from tests/data/Reader/XEE/XEETestValidUTF-16BE.xml rename to tests/data/Reader/Xml/XEETestValidUTF-16BE.xml diff --git a/tests/data/Reader/XEE/XEETestValidUTF-16LE.xml b/tests/data/Reader/Xml/XEETestValidUTF-16LE.xml similarity index 100% rename from tests/data/Reader/XEE/XEETestValidUTF-16LE.xml rename to tests/data/Reader/Xml/XEETestValidUTF-16LE.xml diff --git a/tests/data/Reader/XEE/XEETestValidUTF-8.xml b/tests/data/Reader/Xml/XEETestValidUTF-8.xml similarity index 100% rename from tests/data/Reader/XEE/XEETestValidUTF-8.xml rename to tests/data/Reader/Xml/XEETestValidUTF-8.xml