Support XML file without styles

Closes #331
Closes https://github.com/PHPOffice/PHPExcel/pull/559
Fixes https://github.com/PHPOffice/PHPExcel/issues/558
This commit is contained in:
Adrien Crivelli 2018-01-14 16:59:34 +09:00
parent bf2dbbaf10
commit 481fc4a7c6
No known key found for this signature in database
GPG Key ID: B182FD79DC6DE92E
13 changed files with 280 additions and 189 deletions

View File

@ -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) - 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) - 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) - `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 ## [1.0.0] - 2017-12-25

View File

@ -14,6 +14,7 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Font; use PhpOffice\PhpSpreadsheet\Style\Font;
use SimpleXMLElement;
/** /**
* Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003. * Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003.
@ -300,31 +301,6 @@ class Xml extends BaseReader
*/ */
public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) 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); File::assertFile($pFilename);
if (!$this->canRead($pFilename)) { if (!$this->canRead($pFilename)) {
throw new Exception($pFilename . ' is an Invalid Spreadsheet file.'); throw new Exception($pFilename . ' is an Invalid Spreadsheet file.');
@ -423,140 +399,7 @@ class Xml extends BaseReader
} }
} }
foreach ($xml->Styles[0] as $style) { $this->parseStyles($xml, $namespaces);
$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;
}
}
}
$worksheetID = 0; $worksheetID = 0;
$xml_ss = $xml->children($namespaces['ss']); $xml_ss = $xml->children($namespaces['ss']);
@ -816,4 +659,218 @@ class Xml extends BaseReader
return $value; 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;
}
}
}
} }

View File

@ -5,32 +5,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader;
use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Reader\BaseReader; use PhpOffice\PhpSpreadsheet\Reader\BaseReader;
use PhpOffice\PhpSpreadsheet\Reader\Xml; use PhpOffice\PhpSpreadsheet\Reader\Xml;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PHPUnit\Framework\TestCase; 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 * @dataProvider providerInvalidXML
* @expectedException \PhpOffice\PhpSpreadsheet\Reader\Exception * @expectedException \PhpOffice\PhpSpreadsheet\Reader\Exception
@ -48,7 +26,7 @@ class XEEValidatorTest extends TestCase
public function providerInvalidXML() public function providerInvalidXML()
{ {
$tests = []; $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)]; $tests[basename($file)] = [realpath($file)];
} }
@ -70,7 +48,7 @@ class XEEValidatorTest extends TestCase
public function providerInvalidSimpleXML() public function providerInvalidSimpleXML()
{ {
$tests = []; $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)]; $tests[basename($file)] = [realpath($file)];
} }
@ -93,7 +71,7 @@ class XEEValidatorTest extends TestCase
public function providerValidXML() public function providerValidXML()
{ {
$tests = []; $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)]; $tests[basename($file)] = [realpath($file), file_get_contents($file)];
} }
@ -105,7 +83,8 @@ class XEEValidatorTest extends TestCase
*/ */
public function testReadHyperlinks() public function testReadHyperlinks()
{ {
$spreadsheet = $this->loadXEETestFile(); $reader = new Xml();
$spreadsheet = $reader->load('../samples/templates/Excel2003XMLTest.xml');
$firstSheet = $spreadsheet->getSheet(0); $firstSheet = $spreadsheet->getSheet(0);
$hyperlink = $firstSheet->getCell('L1'); $hyperlink = $firstSheet->getCell('L1');
@ -114,4 +93,11 @@ class XEEValidatorTest extends TestCase
self::assertEquals('PhpSpreadsheet', $hyperlink->getValue()); self::assertEquals('PhpSpreadsheet', $hyperlink->getValue());
self::assertEquals('http://phpspreadsheet.readthedocs.io/', $hyperlink->getHyperlink()->getUrl()); 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());
}
} }

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?><?mso-application progid="Excel.Sheet"?>
<Workbook xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40"
xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:x2="http://schemas.microsoft.com/office/excel/2003/xml"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel">
<OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office">
<Colors>
<Color>
<Index>3</Index>
<RGB>#000000</RGB>
</Color>
</Colors>
</OfficeDocumentSettings>
<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
<WindowHeight>9000</WindowHeight>
<WindowWidth>13860</WindowWidth>
<WindowTopX>240</WindowTopX>
<WindowTopY>75</WindowTopY>
<ProtectStructure>False</ProtectStructure>
<ProtectWindows>False</ProtectWindows>
</ExcelWorkbook>
<ss:Worksheet ss:Name="Sample Data">
<Table>
<Column ss:Width="96.4913"/>
<Column ss:Span="1" ss:Width="48.3874"/>
<Column ss:Index="4" ss:Width="35.8866"/>
<Column ss:Span="6" ss:Width="48.3874"/>
<Column ss:Index="12" ss:Width="50.2583"/>
<Column ss:Span="1011" ss:Width="48.3874"/>
<Row ss:AutoFitHeight="0" ss:Height="14.9953">
<Cell>
<ss:Data xmlns="http://www.w3.org/TR/REC-html40" ss:Type="String">Test String 1</ss:Data>
<Comment>
<ss:Data xmlns="http://www.w3.org/TR/REC-html40">
<Font html:Face="Sans" html:Size="10">Test for a simple colour-formatted string</Font>
</ss:Data>
</Comment>
</Cell>
<Cell>
<Data ss:Type="Number">1</Data>
</Cell>
</Row>
</Table>
<x:WorksheetOptions/>
</ss:Worksheet>
</Workbook>