diff --git a/src/PhpSpreadsheet/Reader/Xml.php b/src/PhpSpreadsheet/Reader/Xml.php
index 9cce7003fb..7b8697aff2 100644
--- a/src/PhpSpreadsheet/Reader/Xml.php
+++ b/src/PhpSpreadsheet/Reader/Xml.php
@@ -231,6 +231,19 @@ public function listWorksheetInfo($filename)
return $worksheetInfo;
}
+ /**
+ * Loads Spreadsheet from string.
+ */
+ public function loadSpreadsheetFromString(string $contents): Spreadsheet
+ {
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->removeSheetByIndex(0);
+
+ // Load into this instance
+ return $this->loadIntoExisting($contents, $spreadsheet, true);
+ }
+
/**
* Loads Spreadsheet from file.
*/
@@ -245,17 +258,19 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
}
/**
- * Loads from file into Spreadsheet instance.
- *
- * @param string $filename
+ * Loads from file or contents into Spreadsheet instance.
*
- * @return Spreadsheet
+ * @param string $filename file name if useContents is false else file contents
*/
- public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
+ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, bool $useContents = false): Spreadsheet
{
- File::assertFile($filename);
- if (!$this->canRead($filename)) {
- throw new Exception($filename . ' is an Invalid Spreadsheet file.');
+ if ($useContents) {
+ $this->fileContents = $filename;
+ } else {
+ File::assertFile($filename);
+ if (!$this->canRead($filename)) {
+ throw new Exception($filename . ' is an Invalid Spreadsheet file.');
+ }
}
$xml = $this->trySimpleXMLLoadString($filename);
@@ -268,6 +283,9 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
(new Properties($spreadsheet))->readProperties($xml, $namespaces);
$this->styles = (new Style())->parseStyles($xml, $namespaces);
+ if (isset($this->styles['Default'])) {
+ $spreadsheet->getCellXfCollection()[0]->applyFromArray($this->styles['Default']);
+ }
$worksheetID = 0;
$xml_ss = $xml->children($namespaces['ss']);
@@ -295,6 +313,10 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
// the worksheet name in line with the formula, not the reverse
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
}
+ if (isset($worksheet_ss['Protected'])) {
+ $protection = (string) $worksheet_ss['Protected'] === '1';
+ $spreadsheet->getActiveSheet()->getProtection()->setSheet($protection);
+ }
// locally scoped defined names
if (isset($worksheet->Names[0])) {
@@ -314,14 +336,34 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
if (isset($worksheet->Table->Column)) {
foreach ($worksheet->Table->Column as $columnData) {
$columnData_ss = self::getAttributes($columnData, $namespaces['ss']);
+ $colspan = 0;
+ if (isset($columnData_ss['Span'])) {
+ $spanAttr = (string) $columnData_ss['Span'];
+ if (is_numeric($spanAttr)) {
+ $colspan = max(0, (int) $spanAttr);
+ }
+ }
if (isset($columnData_ss['Index'])) {
$columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']);
}
+ $columnWidth = null;
if (isset($columnData_ss['Width'])) {
$columnWidth = $columnData_ss['Width'];
- $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
}
- ++$columnID;
+ $columnVisible = null;
+ if (isset($columnData_ss['Hidden'])) {
+ $columnVisible = ((string) $columnData_ss['Hidden']) !== '1';
+ }
+ while ($colspan >= 0) {
+ if (isset($columnWidth)) {
+ $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
+ }
+ if (isset($columnVisible)) {
+ $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setVisible($columnVisible);
+ }
+ ++$columnID;
+ --$colspan;
+ }
}
}
@@ -334,6 +376,10 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
if (isset($row_ss['Index'])) {
$rowID = (int) $row_ss['Index'];
}
+ if (isset($row_ss['Hidden'])) {
+ $rowVisible = ((string) $row_ss['Hidden']) !== '1';
+ $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setVisible($rowVisible);
+ }
$columnID = 'A';
foreach ($rowData->Cell as $cell) {
@@ -471,6 +517,42 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
$xmlX = $worksheet->children($namespaces['x']);
if (isset($xmlX->WorksheetOptions)) {
(new PageSettings($xmlX, $namespaces))->loadPageSettings($spreadsheet);
+ if (isset($xmlX->WorksheetOptions->TopRowVisible, $xmlX->WorksheetOptions->LeftColumnVisible)) {
+ $leftTopRow = (string) $xmlX->WorksheetOptions->TopRowVisible;
+ $leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnVisible;
+ if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) {
+ $leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1);
+ $spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate);
+ }
+ }
+ $rangeCalculated = false;
+ if (isset($xmlX->WorksheetOptions->Panes->Pane->RangeSelection)) {
+ if (1 === preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $xmlX->WorksheetOptions->Panes->Pane->RangeSelection, $selectionMatches)) {
+ $selectedCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
+ . $selectionMatches[1]
+ . ':'
+ . Coordinate::stringFromColumnIndex((int) $selectionMatches[4])
+ . $selectionMatches[3];
+ $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell);
+ $rangeCalculated = true;
+ }
+ }
+ if (!$rangeCalculated) {
+ if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveRow)) {
+ $activeRow = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveRow;
+ } else {
+ $activeRow = 0;
+ }
+ if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveCol)) {
+ $activeColumn = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveCol;
+ } else {
+ $activeColumn = 0;
+ }
+ if (is_numeric($activeRow) && is_numeric($activeColumn)) {
+ $selectedCell = Coordinate::stringFromColumnIndex((int) $activeColumn + 1) . (string) ($activeRow + 1);
+ $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell);
+ }
+ }
}
}
}
@@ -478,7 +560,11 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
}
// Globally scoped defined names
- $activeWorksheet = $spreadsheet->setActiveSheetIndex(0);
+ $activeSheetIndex = 0;
+ if (isset($xml->ExcelWorkbook->ActiveSheet)) {
+ $activeSheetIndex = (int) (string) $xml->ExcelWorkbook->ActiveSheet;
+ }
+ $activeWorksheet = $spreadsheet->setActiveSheetIndex($activeSheetIndex);
if (isset($xml->Names[0])) {
foreach ($xml->Names[0] as $definedName) {
$definedName_ss = self::getAttributes($definedName, $namespaces['ss']);
diff --git a/src/PhpSpreadsheet/Reader/Xml/Style.php b/src/PhpSpreadsheet/Reader/Xml/Style.php
index 774fffe8b9..698acf6ac0 100644
--- a/src/PhpSpreadsheet/Reader/Xml/Style.php
+++ b/src/PhpSpreadsheet/Reader/Xml/Style.php
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
use SimpleXMLElement;
class Style
@@ -30,7 +31,7 @@ public function parseStyles(SimpleXMLElement $xml, array $namespaces): array
$styleID = (string) $style_ss['ID'];
$this->styles[$styleID] = $this->styles['Default'] ?? [];
- $alignment = $border = $font = $fill = $numberFormat = [];
+ $alignment = $border = $font = $fill = $numberFormat = $protection = [];
foreach ($style as $styleType => $styleDatax) {
$styleData = self::getSxml($styleDatax);
@@ -64,11 +65,31 @@ public function parseStyles(SimpleXMLElement $xml, array $namespaces): array
$numberFormat = $numberFormatStyleParser->parseStyle($styleAttributes);
}
+ break;
+ case 'Protection':
+ $locked = $hidden = null;
+ $styleAttributesP = $styleData->attributes($namespaces['x']);
+ if (isset($styleAttributes['Protected'])) {
+ $locked = ((bool) (string) $styleAttributes['Protected']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED;
+ }
+ if (isset($styleAttributesP['HideFormula'])) {
+ $hidden = ((bool) (string) $styleAttributesP['HideFormula']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED;
+ }
+ if ($locked !== null || $hidden !== null) {
+ $protection['protection'] = [];
+ if ($locked !== null) {
+ $protection['protection']['locked'] = $locked;
+ }
+ if ($hidden !== null) {
+ $protection['protection']['hidden'] = $hidden;
+ }
+ }
+
break;
}
}
- $this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat);
+ $this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat, $protection);
}
return $this->styles;
diff --git a/src/PhpSpreadsheet/Reader/Xml/Style/Font.php b/src/PhpSpreadsheet/Reader/Xml/Style/Font.php
index 16ab44d80d..5f824889a5 100644
--- a/src/PhpSpreadsheet/Reader/Xml/Style/Font.php
+++ b/src/PhpSpreadsheet/Reader/Xml/Style/Font.php
@@ -56,11 +56,11 @@ public function parseStyle(SimpleXMLElement $styleAttributes): array
break;
case 'Bold':
- $style['font']['bold'] = true;
+ $style['font']['bold'] = $styleAttributeValue === '1';
break;
case 'Italic':
- $style['font']['italic'] = true;
+ $style['font']['italic'] = $styleAttributeValue === '1';
break;
case 'Underline':
diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlActiveSheetTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlActiveSheetTest.php
new file mode 100644
index 0000000000..5df4feaa78
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlActiveSheetTest.php
@@ -0,0 +1,116 @@
+
+
+
+
+ 16.00
+
+
+
+
+
+ 6820
+ 19200
+ 32767
+ 32767
+ 1
+ False
+ False
+
+
+
+
+
+
+
+ False
+ False
+
+
+
+
+
+
+
+
+ 3
+ 2
+ 2
+
+
+ False
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+ 3
+ 2
+ R4C3:R4C4
+
+
+ False
+ False
+
+
+
+ EOT;
+ $reader = new Xml();
+ $spreadsheet = $reader->loadSpreadsheetFromString($xmldata);
+ self::assertEquals(3, $spreadsheet->getSheetCount());
+
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame('sheet 2', $sheet->getTitle());
+ self::assertSame('C3', $sheet->getSelectedCells());
+ $sheet = $spreadsheet->getSheetByNameOrThrow('sheet 3');
+ self::assertSame('C4:D4', $sheet->getSelectedCells());
+
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlColSpanTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlColSpanTest.php
new file mode 100644
index 0000000000..a78107fe88
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlColSpanTest.php
@@ -0,0 +1,92 @@
+
+
+
+
+ 16.00
+
+
+
+
+
+ 6820
+ 19200
+ 32767
+ 32767
+ False
+ False
+
+
+
+
+
+
+
+
+
+ wide |
+ thin |
+ thin |
+ thin |
+ thin |
+ normal |
+
+
+
+
+
+
+
+ 3
+ 1
+
+
+ False
+ False
+
+
+
+ EOT;
+ $reader = new Xml();
+ $spreadsheet = $reader->loadSpreadsheetFromString($xmldata);
+ self::assertEquals(1, $spreadsheet->getSheetCount());
+
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame('Tabelle1', $sheet->getTitle());
+ self::assertSame('A2', $sheet->getSelectedCells());
+ $widthMultiplier = 5.4;
+ $expectedWidthBtoE = 25.5 / $widthMultiplier;
+ self::assertEqualsWithDelta($expectedWidthBtoE, $sheet->getColumnDimension('B')->getWidth(), 1E-6, '1st col in span');
+ self::assertEqualsWithDelta($expectedWidthBtoE, $sheet->getColumnDimension('C')->getWidth(), 1E-6, '2nd col in span');
+ self::assertEqualsWithDelta($expectedWidthBtoE, $sheet->getColumnDimension('D')->getWidth(), 1E-6, '3rd col in span');
+ self::assertEqualsWithDelta($expectedWidthBtoE, $sheet->getColumnDimension('E')->getWidth(), 1E-6, '4th col in span');
+ $expectedWidthA = 76.5 / $widthMultiplier;
+ self::assertEqualsWithDelta($expectedWidthA, $sheet->getColumnDimension('A')->getWidth(), 1E-6, 'width without span');
+ self::assertEqualsWithDelta(-1.0, $sheet->getColumnDimension('F')->getWidth(), 1E-6, 'default width');
+
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlColumnRowHiddenTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlColumnRowHiddenTest.php
new file mode 100644
index 0000000000..b02f857e82
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlColumnRowHiddenTest.php
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+ hidden row and hidden column
+
+
+ hidden row and visible column
+
+
+
+
+ visible row and hidden column
+
+
+ visible row and visible column
+
+
+
+
+
+
+ EOT;
+ $reader = new Xml();
+ $spreadsheet = $reader->loadSpreadsheetFromString($xmldata);
+ self::assertEquals(1, $spreadsheet->getSheetCount());
+
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertEquals('1', $sheet->getTitle());
+ self::assertFalse($sheet->getColumnDimension('A')->getVisible());
+ self::assertTrue($sheet->getColumnDimension('B')->getVisible());
+ self::assertTrue($sheet->getColumnDimension('C')->getVisible());
+ self::assertFalse($sheet->getRowDimension(1)->getVisible());
+ self::assertTrue($sheet->getRowDimension(2)->getVisible());
+ self::assertTrue($sheet->getRowDimension(3)->getVisible());
+
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlFontBoldItalicTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlFontBoldItalicTest.php
new file mode 100644
index 0000000000..1465c99244
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlFontBoldItalicTest.php
@@ -0,0 +1,149 @@
+
+
+
+
+ Owen Leibman
+ Owen Leibman
+ 2023-05-15T17:04:33Z
+ 16.00
+
+
+ true
+ 2023-05-15T17:06:02Z
+ Standard
+ defa4170-0d19-0005-0004-bc88714345d2
+ f9465cb1-7889-4d9a-b552-fdd0addf0eb1
+ 55f09022-fb59-44b6-a7c5-9d50704243d3
+ 0
+
+
+
+
+
+ 6820
+ 19200
+ 32767
+ 32767
+ False
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+ normal |
+ normal |
+
+
+ bold |
+ not bold |
+
+
+ italic |
+ not italic |
+
+
+ bold italic |
+ bold italic |
+
+
+
+
+
+
+
+
+
+
+
+ 600
+ 600
+
+
+
+
+ 3
+ 2
+ 1
+
+
+ False
+ False
+
+
+
+ EOT;
+ $reader = new Xml();
+ $spreadsheet = $reader->loadSpreadsheetFromString($xmldata);
+ self::assertEquals(1, $spreadsheet->getSheetCount());
+
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertEquals('Sheet1', $sheet->getTitle());
+ self::assertFalse($sheet->getStyle('A1')->getFont()->getBold());
+ self::assertFalse($sheet->getStyle('A1')->getFont()->getItalic());
+ self::assertTrue($sheet->getStyle('A2')->getFont()->getBold());
+ self::assertFalse($sheet->getStyle('A2')->getFont()->getItalic());
+ self::assertFalse($sheet->getStyle('A3')->getFont()->getBold());
+ self::assertTrue($sheet->getStyle('A3')->getFont()->getItalic());
+ self::assertTrue($sheet->getStyle('A4')->getFont()->getBold());
+ self::assertTrue($sheet->getStyle('A4')->getFont()->getItalic());
+
+ self::assertFalse($sheet->getStyle('B1')->getFont()->getBold());
+ self::assertFalse($sheet->getStyle('B1')->getFont()->getItalic());
+ self::assertFalse($sheet->getStyle('B2')->getFont()->getBold(), 'xml specifies bold=0');
+ self::assertFalse($sheet->getStyle('B2')->getFont()->getItalic());
+ self::assertFalse($sheet->getStyle('B3')->getFont()->getBold());
+ self::assertFalse($sheet->getStyle('B3')->getFont()->getItalic(), 'xml specifies italic=0');
+ self::assertTrue($sheet->getStyle('B4')->getFont()->getBold());
+ self::assertTrue($sheet->getStyle('B4')->getFont()->getItalic());
+
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlOddTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlOddTest.php
index 4126aea613..ace299c2c3 100644
--- a/tests/PhpSpreadsheetTests/Reader/Xml/XmlOddTest.php
+++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlOddTest.php
@@ -69,5 +69,6 @@ public function testWriteThenRead(): void
$props = $spreadsheet->getProperties();
self::assertEquals('Xml2003 Short Workbook', $props->getTitle());
self::assertEquals('2', $props->getCustomPropertyValue('myŚInt'));
+ $spreadsheet->disconnectWorksheets();
}
}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlProtectionTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlProtectionTest.php
new file mode 100644
index 0000000000..acabb90fed
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlProtectionTest.php
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EOT;
+ $reader = new Xml();
+ $spreadsheet = $reader->loadSpreadsheetFromString($xmldata);
+ self::assertEquals(1, $spreadsheet->getSheetCount());
+
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertEquals('sheet 1', $sheet->getTitle());
+ self::assertTrue($sheet->getProtection()->isProtectionEnabled());
+ self::assertSame('protected', $sheet->getCell('A1')->getStyle()->getProtection()->getLocked());
+ self::assertSame('protected', $sheet->getCell('A1')->getStyle()->getProtection()->getHidden());
+ self::assertSame('protected', $sheet->getCell('B1')->getStyle()->getProtection()->getLocked());
+ self::assertSame('unprotected', $sheet->getCell('B1')->getStyle()->getProtection()->getHidden());
+ self::assertSame('unprotected', $sheet->getCell('C1')->getStyle()->getProtection()->getLocked());
+ self::assertSame('protected', $sheet->getCell('C1')->getStyle()->getProtection()->getHidden());
+
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xml/XmlTopLeftTest.php b/tests/PhpSpreadsheetTests/Reader/Xml/XmlTopLeftTest.php
new file mode 100644
index 0000000000..8320e83ec9
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xml/XmlTopLeftTest.php
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.1
+
+
+ 1.2
+
+
+ 1.3
+
+
+ 1.4
+
+
+ 1.5
+
+
+ 1.6
+
+
+
+
+ 2.1
+
+
+ 2.2 (top-left cell)
+
+
+ 2.3
+
+
+ 2.4
+
+
+ 2.5
+
+
+ 2.6
+
+
+
+
+ 3.1
+
+
+ 3.2
+
+
+ 3.3
+
+
+ 3.4
+
+
+ 3.5
+
+
+ 3.6
+
+
+
+
+ 1
+ 1
+
+
+
+ EOT;
+ $reader = new Xml();
+ $spreadsheet = $reader->loadSpreadsheetFromString($xmldata);
+ self::assertEquals(1, $spreadsheet->getSheetCount());
+
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertEquals('sheet 1', $sheet->getTitle());
+ self::assertEquals('B2', $sheet->getTopLeftCell());
+
+ $spreadsheet->disconnectWorksheets();
+ }
+}