Skip to content

Commit

Permalink
Improvements to Xml Reader
Browse files Browse the repository at this point in the history
Fix PHPOffice#3999. Fix PHPOffice#4000. Fix PHPOffice#4002. Several bug reports and feature requests for Xml Reader arrived practically simultaneously. They are all small and hit the same code modules, so I have bundled them together in one PR.
- `loadSpreadsheetFromString` might try to open a file with a falsy name (like '0'), which results in an exception with a misleading message (or a completely unexpected result if a file with that name exists). Code will still throw an exception, but the message will no longer be misleading, and no file I/O will be attempted.
- function `trySimpleXmlLoadString` is deprecated. It should never have been implemented with public visibility, and the fact that it was made the fix above a little more difficult than it would otherwise have been. It is replaced with a private equivalent.
- Style reader function `parseStyles` will now use a better namespace-aware method of reading its Xml data. Peculiarly, the Xml for the Style elements can either include or not a namespace prefix. This is probably because the global namespace and the styles namespace are the same. The existing prefix-based code does not recognize their equivalence, but the new namespace-based code does. Xml Reader continues to use prefix-based code in several other places.
- Border line styles with Weight omitted or equal to 0 have been treated as no border, but they should be treated as 'hair' thickness.
- Support for Zoom is added to Xml Reader.
- In support of the above, new properties (and getters and setters) zoomScalePageLayoutView and zoomScaleSheetLayoutView are added to Worksheet/SheetView. (As far as I can tell, Excel does not support Sheet Layout View for Xml spreadsheets).
- Support is added for those new properties in Xlsx Reader and Writer.
- Xls Reader and Writer seem to work okay without changes. There is one test where Xls shows a different value for one of the properties than Xml or Xlsx, but the spreadsheet looks okay and I don't see any practical consequences of the difference.
- PageBreak support is added to Xml Reader.
- Code for writing out Column Page Breaks in Xlsx Writer was wrong (and, unsurprisingly, untested). A one-line change fixes it, and tests are added.
  • Loading branch information
oleibman committed Apr 27, 2024
1 parent 35030fa commit 10ec627
Show file tree
Hide file tree
Showing 11 changed files with 480 additions and 29 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).

### Deprecated

- Nothing
- Reader/Xml trySimpleXMLLoadString should not have had public visibility, and will be removed.

### Removed

Expand All @@ -42,6 +42,9 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Invalid Builtin Defined Name in Xls Reader [Issue #3935](https://github.com/PHPOffice/PhpSpreadsheet/issues/3935) [PR #3942](https://github.com/PHPOffice/PhpSpreadsheet/pull/3942)
- Hidden Rows and Columns Tcpdf/Mpdf [PR #3945](https://github.com/PHPOffice/PhpSpreadsheet/pull/3945)
- Protect Sheet But Allow Sort [Issue #3951](https://github.com/PHPOffice/PhpSpreadsheet/issues/3951) [PR #3956](https://github.com/PHPOffice/PhpSpreadsheet/pull/3956)
- Default Value for Conditional::$text [PR #3946](https://github.com/PHPOffice/PhpSpreadsheet/pull/3946)
- Table Filter Buttons [Issue #3988](https://github.com/PHPOffice/PhpSpreadsheet/issues/3988) [PR #3992](https://github.com/PHPOffice/PhpSpreadsheet/pull/3992)
- Improvements to Xml Reader [Issue #3999](https://github.com/PHPOffice/PhpSpreadsheet/issues/3999) [Issue #4000](https://github.com/PHPOffice/PhpSpreadsheet/issues/4000) [Issue #4002](https://github.com/PHPOffice/PhpSpreadsheet/issues/4002) [PR #4003](https://github.com/PHPOffice/PhpSpreadsheet/pull/4003)

## 2.0.0 - 2024-01-04

Expand Down
14 changes: 14 additions & 0 deletions src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ private function zoomScale(): void

$this->worksheet->getSheetView()->setZoomScaleNormal($zoomScaleNormal);
}

if (isset($this->sheetViewAttributes->zoomScalePageLayoutView)) {
$zoomScaleNormal = (int) ($this->sheetViewAttributes->zoomScalePageLayoutView);
if ($zoomScaleNormal > 0) {
$this->worksheet->getSheetView()->setZoomScalePageLayoutView($zoomScaleNormal);
}
}

if (isset($this->sheetViewAttributes->zoomScaleSheetLayoutView)) {
$zoomScaleNormal = (int) ($this->sheetViewAttributes->zoomScaleSheetLayoutView);
if ($zoomScaleNormal > 0) {
$this->worksheet->getSheetView()->setZoomScaleSheetLayoutView($zoomScaleNormal);
}
}
}

private function view(): void
Expand Down
90 changes: 78 additions & 12 deletions src/PhpSpreadsheet/Reader/Xml.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
use Throwable;

/**
* Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003.
Expand All @@ -45,6 +47,8 @@ public function __construct()

private string $fileContents = '';

private string $xmlFailMessage = '';

public static function xmlMappings(): array
{
return array_merge(
Expand Down Expand Up @@ -106,17 +110,44 @@ public function canRead(string $filename): bool
* Check if the file is a valid SimpleXML.
*
* @return false|SimpleXMLElement
*
* @deprecated 2.0.1 Should never have had public visibility
*
* @codeCoverageIgnore
*/
public function trySimpleXMLLoadString(string $filename): SimpleXMLElement|bool
public function trySimpleXMLLoadString(string $filename, string $fileOrString = 'file'): SimpleXMLElement|bool
{
return $this->trySimpleXMLLoadStringPrivate($filename, $fileOrString);
}

/** @return false|SimpleXMLElement */
private function trySimpleXMLLoadStringPrivate(string $filename, string $fileOrString = 'file'): SimpleXMLElement|bool
{
$this->xmlFailMessage = "Cannot load invalid XML $fileOrString: " . $filename;
$xml = false;

try {
$xml = simplexml_load_string(
$this->getSecurityScannerOrThrow()->scan($this->fileContents ?: file_get_contents($filename)),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
} catch (\Exception $e) {
throw new Exception('Cannot load invalid XML file: ' . $filename, 0, $e);
$data = $this->fileContents;
$continue = true;
if ($data === '' && $fileOrString === 'file') {
if ($filename === '') {
$this->xmlFailMessage = 'Cannot load empty path';
$continue = false;
} else {
$datax = @file_get_contents($filename);
$data = $datax ?: '';
$continue = $datax !== false;
}
}
if ($continue) {
$xml = @simplexml_load_string(
$this->getSecurityScannerOrThrow()->scan($data),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
}
} catch (Throwable $e) {
throw new Exception($this->xmlFailMessage, 0, $e);
}
$this->fileContents = '';

Expand All @@ -135,7 +166,7 @@ public function listWorksheetNames(string $filename): array

$worksheetNames = [];

$xml = $this->trySimpleXMLLoadString($filename);
$xml = $this->trySimpleXMLLoadStringPrivate($filename);
if ($xml === false) {
throw new Exception("Problem reading {$filename}");
}
Expand All @@ -161,7 +192,7 @@ public function listWorksheetInfo(string $filename): array

$worksheetInfo = [];

$xml = $this->trySimpleXMLLoadString($filename);
$xml = $this->trySimpleXMLLoadStringPrivate($filename);
if ($xml === false) {
throw new Exception("Problem reading {$filename}");
}
Expand Down Expand Up @@ -252,16 +283,18 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
{
if ($useContents) {
$this->fileContents = $filename;
$fileOrString = 'string';
} else {
File::assertFile($filename);
if (!$this->canRead($filename)) {
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
}
$fileOrString = 'file';
}

$xml = $this->trySimpleXMLLoadString($filename);
$xml = $this->trySimpleXMLLoadStringPrivate($filename, $fileOrString);
if ($xml === false) {
throw new Exception("Problem reading {$filename}");
throw new Exception($this->xmlFailMessage);
}

$namespaces = $xml->getNamespaces(true);
Expand Down Expand Up @@ -505,6 +538,25 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
$dataValidations->loadDataValidations($worksheet, $spreadsheet);
$xmlX = $worksheet->children(Namespaces::URN_EXCEL);
if (isset($xmlX->WorksheetOptions)) {
if (isset($xmlX->WorksheetOptions->ShowPageBreakZoom)) {
$spreadsheet->getActiveSheet()->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW);
}
if (isset($xmlX->WorksheetOptions->Zoom)) {
$zoomScaleNormal = (int) $xmlX->WorksheetOptions->Zoom;
if ($zoomScaleNormal > 0) {
$spreadsheet->getActiveSheet()->getSheetView()->setZoomScaleNormal($zoomScaleNormal);
$spreadsheet->getActiveSheet()->getSheetView()->setZoomScale($zoomScaleNormal);
}
}
if (isset($xmlX->WorksheetOptions->PageBreakZoom)) {
$zoomScaleNormal = (int) $xmlX->WorksheetOptions->PageBreakZoom;
if ($zoomScaleNormal > 0) {
$spreadsheet->getActiveSheet()->getSheetView()->setZoomScaleSheetLayoutView($zoomScaleNormal);
}
}
if (isset($xmlX->WorksheetOptions->ShowPageBreakZoom)) {
$spreadsheet->getActiveSheet()->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW);
}
if (isset($xmlX->WorksheetOptions->FreezePanes)) {
$freezeRow = $freezeColumn = 1;
if (isset($xmlX->WorksheetOptions->SplitHorizontal)) {
Expand Down Expand Up @@ -590,6 +642,20 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, boo
}
}
}
if (isset($xmlX->PageBreaks)) {
if (isset($xmlX->PageBreaks->ColBreaks)) {
foreach ($xmlX->PageBreaks->ColBreaks->ColBreak as $colBreak) {
$colBreak = (string) $colBreak->Column;
$spreadsheet->getActiveSheet()->setBreak([1 + (int) $colBreak, 1], Worksheet::BREAK_COLUMN);
}
}
if (isset($xmlX->PageBreaks->RowBreaks)) {
foreach ($xmlX->PageBreaks->RowBreaks->RowBreak as $rowBreak) {
$rowBreak = (string) $rowBreak->Row;
$spreadsheet->getActiveSheet()->setBreak([1, (int) $rowBreak], Worksheet::BREAK_ROW);
}
}
}
++$worksheetID;
}

Expand Down
6 changes: 4 additions & 2 deletions src/PhpSpreadsheet/Reader/Xml/Style.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ class Style

public function parseStyles(SimpleXMLElement $xml, array $namespaces): array
{
if (!isset($xml->Styles) || !is_iterable($xml->Styles[0])) {
$children = $xml->children('urn:schemas-microsoft-com:office:spreadsheet');
$stylesXml = $children->Styles[0];
if (!isset($stylesXml) || !is_iterable($stylesXml)) {
return [];
}

Expand All @@ -24,7 +26,7 @@ public function parseStyles(SimpleXMLElement $xml, array $namespaces): array
$fillStyleParser = new Style\Fill();
$numberFormatStyleParser = new Style\NumberFormat();

foreach ($xml->Styles[0] as $style) {
foreach ($stylesXml as $style) {
$style_ss = self::getAttributes($style, $namespaces['ss']);
$styleID = (string) $style_ss['ID'];
$this->styles[$styleID] = $this->styles['Default'] ?? [];
Expand Down
12 changes: 12 additions & 0 deletions src/PhpSpreadsheet/Reader/Xml/Style/Border.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ class Border extends StyleBase
*/
public const BORDER_MAPPINGS = [
'borderStyle' => [
'continuous' => BorderStyle::BORDER_HAIR,
'dash' => BorderStyle::BORDER_DASHED,
'dashdot' => BorderStyle::BORDER_DASHDOT,
'dashdotdot' => BorderStyle::BORDER_DASHDOTDOT,
'dot' => BorderStyle::BORDER_DOTTED,
'double' => BorderStyle::BORDER_DOUBLE,
'0continuous' => BorderStyle::BORDER_HAIR,
'0dash' => BorderStyle::BORDER_DASHED,
'0dashdot' => BorderStyle::BORDER_DASHDOT,
'0dashdotdot' => BorderStyle::BORDER_DASHDOTDOT,
'0dot' => BorderStyle::BORDER_DOTTED,
'0double' => BorderStyle::BORDER_DOUBLE,
'1continuous' => BorderStyle::BORDER_THIN,
'1dash' => BorderStyle::BORDER_DASHED,
'1dashdot' => BorderStyle::BORDER_DASHDOT,
Expand Down
46 changes: 46 additions & 0 deletions src/PhpSpreadsheet/Worksheet/SheetView.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ class SheetView
*/
private ?int $zoomScaleNormal = 100;

/**
* ZoomScalePageLayoutView.
*
* Valid values range from 10 to 400.
*/
private int $zoomScalePageLayoutView = 100;

/**
* ZoomScaleSheetLayoutView.
*
* Valid values range from 10 to 400.
*/
private int $zoomScaleSheetLayoutView = 100;

/**
* ShowZeros.
*
Expand Down Expand Up @@ -105,6 +119,38 @@ public function setZoomScaleNormal(?int $zoomScaleNormal): static
return $this;
}

public function getZoomScalePageLayoutView(): int
{
return $this->zoomScalePageLayoutView;
}

public function setZoomScalePageLayoutView(int $zoomScalePageLayoutView): static
{
if ($zoomScalePageLayoutView >= 1) {
$this->zoomScalePageLayoutView = $zoomScalePageLayoutView;
} else {
throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
}

return $this;
}

public function getZoomScaleSheetLayoutView(): int
{
return $this->zoomScaleSheetLayoutView;
}

public function setZoomScaleSheetLayoutView(int $zoomScaleSheetLayoutView): static
{
if ($zoomScaleSheetLayoutView >= 1) {
$this->zoomScaleSheetLayoutView = $zoomScaleSheetLayoutView;
} else {
throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
}

return $this;
}

/**
* Set ShowZeroes setting.
*/
Expand Down
20 changes: 15 additions & 5 deletions src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,21 @@ private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $
$objWriter->writeAttribute('workbookViewId', '0');

// Zoom scales
if ($worksheet->getSheetView()->getZoomScale() != 100) {
$objWriter->writeAttribute('zoomScale', (string) $worksheet->getSheetView()->getZoomScale());
$zoomScale = $worksheet->getSheetView()->getZoomScale();
if ($zoomScale !== 100 && $zoomScale !== null) {
$objWriter->writeAttribute('zoomScale', (string) $zoomScale);
}
if ($worksheet->getSheetView()->getZoomScaleNormal() != 100) {
$objWriter->writeAttribute('zoomScaleNormal', (string) $worksheet->getSheetView()->getZoomScaleNormal());
$zoomScale = $worksheet->getSheetView()->getZoomScaleNormal();
if ($zoomScale !== 100 && $zoomScale !== null) {
$objWriter->writeAttribute('zoomScaleNormal', (string) $zoomScale);
}
$zoomScale = $worksheet->getSheetView()->getZoomScalePageLayoutView();
if ($zoomScale !== 100) {
$objWriter->writeAttribute('zoomScalePageLayoutView', (string) $zoomScale);
}
$zoomScale = $worksheet->getSheetView()->getZoomScaleSheetLayoutView();
if ($zoomScale !== 100) {
$objWriter->writeAttribute('zoomScaleSheetLayoutView', (string) $zoomScale);
}

// Show zeros (Excel also writes this attribute only if set to false)
Expand Down Expand Up @@ -1210,7 +1220,7 @@ private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $work
$objWriter->writeAttribute('manualBreakCount', (string) count($aColumnBreaks));

foreach ($aColumnBreaks as $cell => $break) {
$coords = Coordinate::coordinateFromString($cell);
$coords = Coordinate::indexesFromString($cell);

$objWriter->startElement('brk');
$objWriter->writeAttribute('id', (string) ((int) $coords[0] - 1));
Expand Down
Loading

0 comments on commit 10ec627

Please sign in to comment.