From 1e711541f19b51b586eedd350073426e86b38e12 Mon Sep 17 00:00:00 2001 From: Mark Baker Date: Sun, 30 Jun 2019 23:42:25 +0200 Subject: [PATCH] Refactoring xlsx reader (#1033) Start work on breaking up monolithic Reader and Writer classes into dedicated subclasses to make maintenance work easier --- composer.json | 2 +- src/PhpSpreadsheet/Document/Properties.php | 2 +- src/PhpSpreadsheet/Reader/Ods.php | 98 +-- src/PhpSpreadsheet/Reader/Ods/Properties.php | 136 ++++ src/PhpSpreadsheet/Reader/Xlsx.php | 619 ++---------------- src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 144 ++++ .../Reader/Xlsx/BaseParserClass.php | 19 + .../Reader/Xlsx/ColumnAndRowAttributes.php | 204 ++++++ .../Reader/Xlsx/ConditionalStyles.php | 92 +++ .../Reader/Xlsx/DataValidations.php | 50 ++ src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php | 58 ++ src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php | 150 +++++ .../Reader/Xlsx/SheetViewOptions.php | 124 ++++ src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php | 127 ++++ src/PhpSpreadsheet/Reader/Xlsx/Styles.php | 265 ++++++++ .../PhpSpreadsheetTests/Helper/SampleTest.php | 1 + tests/PhpSpreadsheetTests/Reader/OdsTest.php | 43 ++ tests/PhpSpreadsheetTests/Reader/XlsxTest.php | 147 ++++- tests/data/Reader/Ods/propertyTest.ods | Bin 0 -> 8874 bytes tests/data/Reader/XLSX/autofilterTest.xlsx | Bin 0 -> 9985 bytes .../XLSX/conditionalFormattingTest.xlsx | Bin 0 -> 9686 bytes tests/data/Reader/XLSX/pageSetupTest.xlsx | Bin 0 -> 11418 bytes tests/data/Reader/XLSX/propertyTest.xlsx | Bin 9076 -> 9111 bytes .../Reader/XLSX/rowColumnAttributeTest.xlsx | Bin 0 -> 9061 bytes tests/data/Reader/XLSX/stylesTest.xlsx | Bin 0 -> 10876 bytes 25 files changed, 1597 insertions(+), 684 deletions(-) create mode 100644 src/PhpSpreadsheet/Reader/Ods/Properties.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php create mode 100644 src/PhpSpreadsheet/Reader/Xlsx/Styles.php create mode 100644 tests/data/Reader/Ods/propertyTest.ods create mode 100644 tests/data/Reader/XLSX/autofilterTest.xlsx create mode 100644 tests/data/Reader/XLSX/conditionalFormattingTest.xlsx create mode 100644 tests/data/Reader/XLSX/pageSetupTest.xlsx create mode 100644 tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx create mode 100644 tests/data/Reader/XLSX/stylesTest.xlsx diff --git a/composer.json b/composer.json index b0dcb0b7..6f9fd9c3 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ ] }, "require": { - "php": "5.6|^7.0", + "php": "^5.6|^7.0", "ext-ctype": "*", "ext-dom": "*", "ext-gd": "*", diff --git a/src/PhpSpreadsheet/Document/Properties.php b/src/PhpSpreadsheet/Document/Properties.php index bbac96d9..1a432db0 100644 --- a/src/PhpSpreadsheet/Document/Properties.php +++ b/src/PhpSpreadsheet/Document/Properties.php @@ -418,7 +418,7 @@ class Properties * * @param string $propertyName * - * @return string + * @return mixed */ public function getCustomPropertyValue($propertyName) { diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php index cbbcf7a0..51462c20 100644 --- a/src/PhpSpreadsheet/Reader/Ods.php +++ b/src/PhpSpreadsheet/Reader/Ods.php @@ -7,7 +7,7 @@ use DateTimeZone; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; -use PhpOffice\PhpSpreadsheet\Document\Properties; +use PhpOffice\PhpSpreadsheet\Reader\Ods\Properties as DocumentProperties; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Settings; @@ -265,7 +265,7 @@ class Ods extends BaseReader $zip = new ZipArchive(); if (!$zip->open($pFilename)) { - throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.'); + throw new Exception("Could not open {$pFilename} for reading! Error opening file."); } // Meta @@ -275,97 +275,13 @@ class Ods extends BaseReader 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); + if ($xml === false) { + throw new Exception('Unable to read data from {$pFilename}'); + } + $namespacesMeta = $xml->getNamespaces(true); - $docProps = $spreadsheet->getProperties(); - $officeProperty = $xml->children($namespacesMeta['office']); - foreach ($officeProperty as $officePropertyData) { - $officePropertyDC = []; - if (isset($namespacesMeta['dc'])) { - $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']); - } - foreach ($officePropertyDC as $propertyName => $propertyValue) { - $propertyValue = (string) $propertyValue; - switch ($propertyName) { - case 'title': - $docProps->setTitle($propertyValue); - - break; - case 'subject': - $docProps->setSubject($propertyValue); - - break; - case 'creator': - $docProps->setCreator($propertyValue); - $docProps->setLastModifiedBy($propertyValue); - - break; - case 'date': - $creationDate = strtotime($propertyValue); - $docProps->setCreated($creationDate); - $docProps->setModified($creationDate); - - break; - case 'description': - $docProps->setDescription($propertyValue); - - break; - } - } - $officePropertyMeta = []; - if (isset($namespacesMeta['dc'])) { - $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); - } - foreach ($officePropertyMeta as $propertyName => $propertyValue) { - $propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']); - $propertyValue = (string) $propertyValue; - switch ($propertyName) { - case 'initial-creator': - $docProps->setCreator($propertyValue); - - break; - case 'keyword': - $docProps->setKeywords($propertyValue); - - break; - case 'creation-date': - $creationDate = strtotime($propertyValue); - $docProps->setCreated($creationDate); - - break; - case 'user-defined': - $propertyValueType = Properties::PROPERTY_TYPE_STRING; - foreach ($propertyValueAttributes as $key => $value) { - if ($key == 'name') { - $propertyValueName = (string) $value; - } elseif ($key == 'value-type') { - switch ($value) { - case 'date': - $propertyValue = Properties::convertProperty($propertyValue, 'date'); - $propertyValueType = Properties::PROPERTY_TYPE_DATE; - - break; - case 'boolean': - $propertyValue = Properties::convertProperty($propertyValue, 'bool'); - $propertyValueType = Properties::PROPERTY_TYPE_BOOLEAN; - - break; - case 'float': - $propertyValue = Properties::convertProperty($propertyValue, 'r4'); - $propertyValueType = Properties::PROPERTY_TYPE_FLOAT; - - break; - default: - $propertyValueType = Properties::PROPERTY_TYPE_STRING; - } - } - } - $docProps->setCustomProperty($propertyValueName, $propertyValue, $propertyValueType); - - break; - } - } - } + (new DocumentProperties($spreadsheet))->load($xml, $namespacesMeta); // Content diff --git a/src/PhpSpreadsheet/Reader/Ods/Properties.php b/src/PhpSpreadsheet/Reader/Ods/Properties.php new file mode 100644 index 00000000..85596b32 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Ods/Properties.php @@ -0,0 +1,136 @@ +spreadsheet = $spreadsheet; + } + + public function load(\SimpleXMLElement $xml, $namespacesMeta) + { + $docProps = $this->spreadsheet->getProperties(); + $officeProperty = $xml->children($namespacesMeta['office']); + foreach ($officeProperty as $officePropertyData) { + /** @var \SimpleXMLElement $officePropertyData */ + $officePropertiesDC = []; + if (isset($namespacesMeta['dc'])) { + $officePropertiesDC = $officePropertyData->children($namespacesMeta['dc']); + } + $this->setCoreProperties($docProps, $officePropertiesDC); + + $officePropertyMeta = []; + if (isset($namespacesMeta['dc'])) { + $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); + } + foreach ($officePropertyMeta as $propertyName => $propertyValue) { + $this->setMetaProperties($namespacesMeta, $propertyValue, $propertyName, $docProps); + } + } + } + + private function setCoreProperties(DocumentProperties $docProps, \SimpleXMLElement $officePropertyDC) + { + foreach ($officePropertyDC as $propertyName => $propertyValue) { + $propertyValue = (string) $propertyValue; + switch ($propertyName) { + case 'title': + $docProps->setTitle($propertyValue); + + break; + case 'subject': + $docProps->setSubject($propertyValue); + + break; + case 'creator': + $docProps->setCreator($propertyValue); + $docProps->setLastModifiedBy($propertyValue); + + break; + case 'creation-date': + $creationDate = strtotime($propertyValue); + $docProps->setCreated($creationDate); + $docProps->setModified($creationDate); + + break; + case 'keyword': + $docProps->setKeywords($propertyValue); + + break; + case 'description': + $docProps->setDescription($propertyValue); + + break; + } + } + } + + private function setMetaProperties( + $namespacesMeta, + \SimpleXMLElement $propertyValue, + $propertyName, + DocumentProperties $docProps + ) { + $propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']); + $propertyValue = (string) $propertyValue; + switch ($propertyName) { + case 'initial-creator': + $docProps->setCreator($propertyValue); + + break; + case 'keyword': + $docProps->setKeywords($propertyValue); + + break; + case 'creation-date': + $creationDate = strtotime($propertyValue); + $docProps->setCreated($creationDate); + + break; + case 'user-defined': + $this->setUserDefinedProperty($propertyValueAttributes, $propertyValue, $docProps); + + break; + } + } + + private function setUserDefinedProperty($propertyValueAttributes, $propertyValue, DocumentProperties $docProps) + { + $propertyValueName = ''; + $propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING; + foreach ($propertyValueAttributes as $key => $value) { + if ($key == 'name') { + $propertyValueName = (string) $value; + } elseif ($key == 'value-type') { + switch ($value) { + case 'date': + $propertyValue = DocumentProperties::convertProperty($propertyValue, 'date'); + $propertyValueType = DocumentProperties::PROPERTY_TYPE_DATE; + + break; + case 'boolean': + $propertyValue = DocumentProperties::convertProperty($propertyValue, 'bool'); + $propertyValueType = DocumentProperties::PROPERTY_TYPE_BOOLEAN; + + break; + case 'float': + $propertyValue = DocumentProperties::convertProperty($propertyValue, 'r4'); + $propertyValueType = DocumentProperties::PROPERTY_TYPE_FLOAT; + + break; + default: + $propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING; + } + } + } + + $docProps->setCustomProperty($propertyValueName, $propertyValue, $propertyValueType); + } +} diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index b5ca97a9..9cb287ef 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -4,11 +4,19 @@ namespace PhpOffice\PhpSpreadsheet\Reader; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\Hyperlink; -use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\NamedRange; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ColumnAndRowAttributes; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Settings; @@ -21,7 +29,6 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Borders; use PhpOffice\PhpSpreadsheet\Style\Color; -use PhpOffice\PhpSpreadsheet\Style\Conditional; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Protection; use PhpOffice\PhpSpreadsheet\Style\Style; @@ -324,60 +331,6 @@ class Xlsx extends BaseReader return $contents; } - /** - * Set Worksheet column attributes by attributes array passed. - * - * @param Worksheet $docSheet - * @param string $column A, B, ... DX, ... - * @param array $columnAttributes array of attributes (indexes are attribute name, values are value) - * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ? - */ - private function setColumnAttributes(Worksheet $docSheet, $column, array $columnAttributes) - { - if (isset($columnAttributes['xfIndex'])) { - $docSheet->getColumnDimension($column)->setXfIndex($columnAttributes['xfIndex']); - } - if (isset($columnAttributes['visible'])) { - $docSheet->getColumnDimension($column)->setVisible($columnAttributes['visible']); - } - if (isset($columnAttributes['collapsed'])) { - $docSheet->getColumnDimension($column)->setCollapsed($columnAttributes['collapsed']); - } - if (isset($columnAttributes['outlineLevel'])) { - $docSheet->getColumnDimension($column)->setOutlineLevel($columnAttributes['outlineLevel']); - } - if (isset($columnAttributes['width'])) { - $docSheet->getColumnDimension($column)->setWidth($columnAttributes['width']); - } - } - - /** - * Set Worksheet row attributes by attributes array passed. - * - * @param Worksheet $docSheet - * @param int $row 1, 2, 3, ... 99, ... - * @param array $rowAttributes array of attributes (indexes are attribute name, values are value) - * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ? - */ - private function setRowAttributes(Worksheet $docSheet, $row, array $rowAttributes) - { - if (isset($rowAttributes['xfIndex'])) { - $docSheet->getRowDimension($row)->setXfIndex($rowAttributes['xfIndex']); - } - if (isset($rowAttributes['visible'])) { - $docSheet->getRowDimension($row)->setVisible($rowAttributes['visible']); - } - if (isset($rowAttributes['collapsed'])) { - $docSheet->getRowDimension($row)->setCollapsed($rowAttributes['collapsed']); - } - if (isset($rowAttributes['outlineLevel'])) { - $docSheet->getRowDimension($row)->setOutlineLevel($rowAttributes['outlineLevel']); - } - if (isset($rowAttributes['rowHeight'])) { - $docSheet->getRowDimension($row)->setRowHeight($rowAttributes['rowHeight']); - } - } - /** * Loads Spreadsheet from file. * @@ -537,8 +490,7 @@ class Xlsx extends BaseReader } } } - $styles = []; - $cellStyles = []; + $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']")); //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" $xmlStyles = simplexml_load_string( @@ -546,6 +498,9 @@ class Xlsx extends BaseReader 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); + + $styles = []; + $cellStyles = []; $numFmts = null; if ($xmlStyles && $xmlStyles->numFmts[0]) { $numFmts = $xmlStyles->numFmts[0]; @@ -625,31 +580,10 @@ class Xlsx extends BaseReader } } - $dxfs = []; - if (!$this->readDataOnly && $xmlStyles) { - // Conditional Styles - if ($xmlStyles->dxfs) { - foreach ($xmlStyles->dxfs->dxf as $dxf) { - $style = new Style(false, true); - self::readStyle($style, $dxf); - $dxfs[] = $style; - } - } - // Cell Styles - if ($xmlStyles->cellStyles) { - foreach ($xmlStyles->cellStyles->cellStyle as $cellStyle) { - if ((int) ($cellStyle['builtinId']) == 0) { - if (isset($cellStyles[(int) ($cellStyle['xfId'])])) { - // Set default style - $style = new Style(); - self::readStyle($style, $cellStyles[(int) ($cellStyle['xfId'])]); - - // normal style, currently not using it for anything - } - } - } - } - } + $styleReader = new Styles($xmlStyles); + $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles); + $dxfs = $styleReader->dxfs($this->readDataOnly); + $styles = $styleReader->styles(); //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" $xmlWorkbook = simplexml_load_string( @@ -716,134 +650,19 @@ class Xlsx extends BaseReader $docSheet->setSheetState((string) $eleSheet['state']); } - if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) { - if (isset($xmlSheet->sheetViews->sheetView['zoomScale'])) { - $zoomScale = (int) ($xmlSheet->sheetViews->sheetView['zoomScale']); - if ($zoomScale <= 0) { - // setZoomScale will throw an Exception if the scale is less than or equals 0 - // that is OK when manually creating documents, but we should be able to read all documents - $zoomScale = 100; - } - - $docSheet->getSheetView()->setZoomScale($zoomScale); - } - if (isset($xmlSheet->sheetViews->sheetView['zoomScaleNormal'])) { - $zoomScaleNormal = (int) ($xmlSheet->sheetViews->sheetView['zoomScaleNormal']); - if ($zoomScaleNormal <= 0) { - // setZoomScaleNormal will throw an Exception if the scale is less than or equals 0 - // that is OK when manually creating documents, but we should be able to read all documents - $zoomScaleNormal = 100; - } - - $docSheet->getSheetView()->setZoomScaleNormal($zoomScaleNormal); - } - if (isset($xmlSheet->sheetViews->sheetView['view'])) { - $docSheet->getSheetView()->setView((string) $xmlSheet->sheetViews->sheetView['view']); - } - if (isset($xmlSheet->sheetViews->sheetView['showGridLines'])) { - $docSheet->setShowGridLines(self::boolean((string) $xmlSheet->sheetViews->sheetView['showGridLines'])); - } - if (isset($xmlSheet->sheetViews->sheetView['showRowColHeaders'])) { - $docSheet->setShowRowColHeaders(self::boolean((string) $xmlSheet->sheetViews->sheetView['showRowColHeaders'])); - } - if (isset($xmlSheet->sheetViews->sheetView['rightToLeft'])) { - $docSheet->setRightToLeft(self::boolean((string) $xmlSheet->sheetViews->sheetView['rightToLeft'])); - } - if (isset($xmlSheet->sheetViews->sheetView->pane)) { - $xSplit = 0; - $ySplit = 0; - $topLeftCell = null; - - if (isset($xmlSheet->sheetViews->sheetView->pane['xSplit'])) { - $xSplit = (int) ($xmlSheet->sheetViews->sheetView->pane['xSplit']); - } - - if (isset($xmlSheet->sheetViews->sheetView->pane['ySplit'])) { - $ySplit = (int) ($xmlSheet->sheetViews->sheetView->pane['ySplit']); - } - - if (isset($xmlSheet->sheetViews->sheetView->pane['topLeftCell'])) { - $topLeftCell = (string) $xmlSheet->sheetViews->sheetView->pane['topLeftCell']; - } - - $docSheet->freezePane(Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1), $topLeftCell); + if ($xmlSheet) { + if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) { + $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet); + $sheetViews->load(); } - if (isset($xmlSheet->sheetViews->sheetView->selection)) { - if (isset($xmlSheet->sheetViews->sheetView->selection['sqref'])) { - $sqref = (string) $xmlSheet->sheetViews->sheetView->selection['sqref']; - $sqref = explode(' ', $sqref); - $sqref = $sqref[0]; - $docSheet->setSelectedCells($sqref); - } - } + $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheet); + $sheetViewOptions->load($this->getReadDataOnly()); + + (new ColumnAndRowAttributes($docSheet, $xmlSheet)) + ->load($this->getReadFilter(), $this->getReadDataOnly()); } - if (isset($xmlSheet->sheetPr, $xmlSheet->sheetPr->tabColor)) { - if (isset($xmlSheet->sheetPr->tabColor['rgb'])) { - $docSheet->getTabColor()->setARGB((string) $xmlSheet->sheetPr->tabColor['rgb']); - } - } - if (isset($xmlSheet->sheetPr, $xmlSheet->sheetPr['codeName'])) { - $docSheet->setCodeName((string) $xmlSheet->sheetPr['codeName'], false); - } - if (isset($xmlSheet->sheetPr, $xmlSheet->sheetPr->outlinePr)) { - if (isset($xmlSheet->sheetPr->outlinePr['summaryRight']) && - !self::boolean((string) $xmlSheet->sheetPr->outlinePr['summaryRight'])) { - $docSheet->setShowSummaryRight(false); - } else { - $docSheet->setShowSummaryRight(true); - } - - if (isset($xmlSheet->sheetPr->outlinePr['summaryBelow']) && - !self::boolean((string) $xmlSheet->sheetPr->outlinePr['summaryBelow'])) { - $docSheet->setShowSummaryBelow(false); - } else { - $docSheet->setShowSummaryBelow(true); - } - } - - if (isset($xmlSheet->sheetPr, $xmlSheet->sheetPr->pageSetUpPr)) { - if (isset($xmlSheet->sheetPr->pageSetUpPr['fitToPage']) && - !self::boolean((string) $xmlSheet->sheetPr->pageSetUpPr['fitToPage'])) { - $docSheet->getPageSetup()->setFitToPage(false); - } else { - $docSheet->getPageSetup()->setFitToPage(true); - } - } - - if (isset($xmlSheet->sheetFormatPr)) { - if (isset($xmlSheet->sheetFormatPr['customHeight']) && - self::boolean((string) $xmlSheet->sheetFormatPr['customHeight']) && - isset($xmlSheet->sheetFormatPr['defaultRowHeight'])) { - $docSheet->getDefaultRowDimension()->setRowHeight((float) $xmlSheet->sheetFormatPr['defaultRowHeight']); - } - if (isset($xmlSheet->sheetFormatPr['defaultColWidth'])) { - $docSheet->getDefaultColumnDimension()->setWidth((float) $xmlSheet->sheetFormatPr['defaultColWidth']); - } - if (isset($xmlSheet->sheetFormatPr['zeroHeight']) && - ((string) $xmlSheet->sheetFormatPr['zeroHeight'] == '1')) { - $docSheet->getDefaultRowDimension()->setZeroHeight(true); - } - } - - if (isset($xmlSheet->printOptions) && !$this->readDataOnly) { - if (self::boolean((string) $xmlSheet->printOptions['gridLinesSet'])) { - $docSheet->setShowGridlines(true); - } - if (self::boolean((string) $xmlSheet->printOptions['gridLines'])) { - $docSheet->setPrintGridlines(true); - } - if (self::boolean((string) $xmlSheet->printOptions['horizontalCentered'])) { - $docSheet->getPageSetup()->setHorizontalCentered(true); - } - if (self::boolean((string) $xmlSheet->printOptions['verticalCentered'])) { - $docSheet->getPageSetup()->setVerticalCentered(true); - } - } - - $this->readColumnsAndRowsAttributes($xmlSheet, $docSheet); - if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) { $cIndex = 1; // Cell Start from 1 foreach ($xmlSheet->sheetData->row as $row) { @@ -963,49 +782,8 @@ class Xlsx extends BaseReader } } - $conditionals = []; if (!$this->readDataOnly && $xmlSheet && $xmlSheet->conditionalFormatting) { - foreach ($xmlSheet->conditionalFormatting as $conditional) { - foreach ($conditional->cfRule as $cfRule) { - if (((string) $cfRule['type'] == Conditional::CONDITION_NONE || (string) $cfRule['type'] == Conditional::CONDITION_CELLIS || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT || (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION) && isset($dxfs[(int) ($cfRule['dxfId'])])) { - $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; - } - } - } - - foreach ($conditionals as $ref => $cfRules) { - ksort($cfRules); - $conditionalStyles = []; - foreach ($cfRules as $cfRule) { - $objConditional = new Conditional(); - $objConditional->setConditionType((string) $cfRule['type']); - $objConditional->setOperatorType((string) $cfRule['operator']); - - if ((string) $cfRule['text'] != '') { - $objConditional->setText((string) $cfRule['text']); - } - - if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) { - $objConditional->setStopIfTrue(true); - } - - if (count($cfRule->formula) > 1) { - foreach ($cfRule->formula as $formula) { - $objConditional->addCondition((string) $formula); - } - } else { - $objConditional->addCondition((string) $cfRule->formula); - } - $objConditional->setStyle(clone $dxfs[(int) ($cfRule['dxfId'])]); - $conditionalStyles[] = $objConditional; - } - - // Extract all cell references in $ref - $cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref))); - foreach ($cellBlocks as $cellBlock) { - $docSheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles); - } - } + (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load(); } $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells']; @@ -1026,103 +804,7 @@ class Xlsx extends BaseReader } if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) { - $autoFilterRange = (string) $xmlSheet->autoFilter['ref']; - if (strpos($autoFilterRange, ':') !== false) { - $autoFilter = $docSheet->getAutoFilter(); - $autoFilter->setRange($autoFilterRange); - - foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) { - $column = $autoFilter->getColumnByOffset((int) $filterColumn['colId']); - // Check for standard filters - if ($filterColumn->filters) { - $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER); - $filters = $filterColumn->filters; - if ((isset($filters['blank'])) && ($filters['blank'] == 1)) { - // Operator is undefined, but always treated as EQUAL - $column->createRule()->setRule(null, '')->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_FILTER); - } - // Standard filters are always an OR join, so no join rule needs to be set - // Entries can be either filter elements - foreach ($filters->filter as $filterRule) { - // Operator is undefined, but always treated as EQUAL - $column->createRule()->setRule(null, (string) $filterRule['val'])->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_FILTER); - } - // Or Date Group elements - foreach ($filters->dateGroupItem as $dateGroupItem) { - // Operator is undefined, but always treated as EQUAL - $column->createRule()->setRule( - null, - [ - 'year' => (string) $dateGroupItem['year'], - 'month' => (string) $dateGroupItem['month'], - 'day' => (string) $dateGroupItem['day'], - 'hour' => (string) $dateGroupItem['hour'], - 'minute' => (string) $dateGroupItem['minute'], - 'second' => (string) $dateGroupItem['second'], - ], - (string) $dateGroupItem['dateTimeGrouping'] - ) - ->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP); - } - } - // Check for custom filters - if ($filterColumn->customFilters) { - $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER); - $customFilters = $filterColumn->customFilters; - // Custom filters can an AND or an OR join; - // and there should only ever be one or two entries - if ((isset($customFilters['and'])) && ($customFilters['and'] == 1)) { - $column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND); - } - foreach ($customFilters->customFilter as $filterRule) { - $column->createRule()->setRule( - (string) $filterRule['operator'], - (string) $filterRule['val'] - ) - ->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER); - } - } - // Check for dynamic filters - if ($filterColumn->dynamicFilter) { - $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); - // We should only ever have one dynamic filter - foreach ($filterColumn->dynamicFilter as $filterRule) { - // Operator is undefined, but always treated as EQUAL - $column->createRule()->setRule( - null, - (string) $filterRule['val'], - (string) $filterRule['type'] - ) - ->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); - if (isset($filterRule['val'])) { - $column->setAttribute('val', (string) $filterRule['val']); - } - if (isset($filterRule['maxVal'])) { - $column->setAttribute('maxVal', (string) $filterRule['maxVal']); - } - } - } - // Check for dynamic filters - if ($filterColumn->top10) { - $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER); - // We should only ever have one top10 filter - foreach ($filterColumn->top10 as $filterRule) { - $column->createRule()->setRule( - (((isset($filterRule['percent'])) && ($filterRule['percent'] == 1)) - ? Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT - : Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE - ), - (string) $filterRule['val'], - (((isset($filterRule['top'])) && ($filterRule['top'] == 1)) - ? Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP - : Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM - ) - ) - ->setRuleType(Column\Rule::AUTOFILTER_RULETYPE_TOPTENFILTER); - } - } - } - } + (new AutoFilter($docSheet, $xmlSheet))->load(); } if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) { @@ -1134,124 +816,12 @@ class Xlsx extends BaseReader } } - if ($xmlSheet && $xmlSheet->pageMargins && !$this->readDataOnly) { - $docPageMargins = $docSheet->getPageMargins(); - $docPageMargins->setLeft((float) ($xmlSheet->pageMargins['left'])); - $docPageMargins->setRight((float) ($xmlSheet->pageMargins['right'])); - $docPageMargins->setTop((float) ($xmlSheet->pageMargins['top'])); - $docPageMargins->setBottom((float) ($xmlSheet->pageMargins['bottom'])); - $docPageMargins->setHeader((float) ($xmlSheet->pageMargins['header'])); - $docPageMargins->setFooter((float) ($xmlSheet->pageMargins['footer'])); - } - - if ($xmlSheet && $xmlSheet->pageSetup && !$this->readDataOnly) { - $docPageSetup = $docSheet->getPageSetup(); - - if (isset($xmlSheet->pageSetup['orientation'])) { - $docPageSetup->setOrientation((string) $xmlSheet->pageSetup['orientation']); - } - if (isset($xmlSheet->pageSetup['paperSize'])) { - $docPageSetup->setPaperSize((int) ($xmlSheet->pageSetup['paperSize'])); - } - if (isset($xmlSheet->pageSetup['scale'])) { - $docPageSetup->setScale((int) ($xmlSheet->pageSetup['scale']), false); - } - if (isset($xmlSheet->pageSetup['fitToHeight']) && (int) ($xmlSheet->pageSetup['fitToHeight']) >= 0) { - $docPageSetup->setFitToHeight((int) ($xmlSheet->pageSetup['fitToHeight']), false); - } - if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) { - $docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false); - } - if (isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) && - self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])) { - $docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber'])); - } - - $relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); - if (isset($relAttributes['id'])) { - $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id']; - } - } - - if ($xmlSheet && $xmlSheet->headerFooter && !$this->readDataOnly) { - $docHeaderFooter = $docSheet->getHeaderFooter(); - - if (isset($xmlSheet->headerFooter['differentOddEven']) && - self::boolean((string) $xmlSheet->headerFooter['differentOddEven'])) { - $docHeaderFooter->setDifferentOddEven(true); - } else { - $docHeaderFooter->setDifferentOddEven(false); - } - if (isset($xmlSheet->headerFooter['differentFirst']) && - self::boolean((string) $xmlSheet->headerFooter['differentFirst'])) { - $docHeaderFooter->setDifferentFirst(true); - } else { - $docHeaderFooter->setDifferentFirst(false); - } - if (isset($xmlSheet->headerFooter['scaleWithDoc']) && - !self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc'])) { - $docHeaderFooter->setScaleWithDocument(false); - } else { - $docHeaderFooter->setScaleWithDocument(true); - } - if (isset($xmlSheet->headerFooter['alignWithMargins']) && - !self::boolean((string) $xmlSheet->headerFooter['alignWithMargins'])) { - $docHeaderFooter->setAlignWithMargins(false); - } else { - $docHeaderFooter->setAlignWithMargins(true); - } - - $docHeaderFooter->setOddHeader((string) $xmlSheet->headerFooter->oddHeader); - $docHeaderFooter->setOddFooter((string) $xmlSheet->headerFooter->oddFooter); - $docHeaderFooter->setEvenHeader((string) $xmlSheet->headerFooter->evenHeader); - $docHeaderFooter->setEvenFooter((string) $xmlSheet->headerFooter->evenFooter); - $docHeaderFooter->setFirstHeader((string) $xmlSheet->headerFooter->firstHeader); - $docHeaderFooter->setFirstFooter((string) $xmlSheet->headerFooter->firstFooter); - } - - if ($xmlSheet && $xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk && !$this->readDataOnly) { - foreach ($xmlSheet->rowBreaks->brk as $brk) { - if ($brk['man']) { - $docSheet->setBreak("A$brk[id]", Worksheet::BREAK_ROW); - } - } - } - if ($xmlSheet && $xmlSheet->colBreaks && $xmlSheet->colBreaks->brk && !$this->readDataOnly) { - foreach ($xmlSheet->colBreaks->brk as $brk) { - if ($brk['man']) { - $docSheet->setBreak(Coordinate::stringFromColumnIndex((string) $brk['id'] + 1) . '1', Worksheet::BREAK_COLUMN); - } - } + if ($xmlSheet && !$this->readDataOnly) { + $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData); } if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) { - foreach ($xmlSheet->dataValidations->dataValidation as $dataValidation) { - // Uppercase coordinate - $range = strtoupper($dataValidation['sqref']); - $rangeSet = explode(' ', $range); - foreach ($rangeSet as $range) { - $stRange = $docSheet->shrinkRangeToFit($range); - - // Extract all cell references in $range - foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $reference) { - // Create validation - $docValidation = $docSheet->getCell($reference)->getDataValidation(); - $docValidation->setType((string) $dataValidation['type']); - $docValidation->setErrorStyle((string) $dataValidation['errorStyle']); - $docValidation->setOperator((string) $dataValidation['operator']); - $docValidation->setAllowBlank($dataValidation['allowBlank'] != 0); - $docValidation->setShowDropDown($dataValidation['showDropDown'] == 0); - $docValidation->setShowInputMessage($dataValidation['showInputMessage'] != 0); - $docValidation->setShowErrorMessage($dataValidation['showErrorMessage'] != 0); - $docValidation->setErrorTitle((string) $dataValidation['errorTitle']); - $docValidation->setError((string) $dataValidation['error']); - $docValidation->setPromptTitle((string) $dataValidation['promptTitle']); - $docValidation->setPrompt((string) $dataValidation['prompt']); - $docValidation->setFormula1((string) $dataValidation->formula1); - $docValidation->setFormula2((string) $dataValidation->formula2); - } - } - } + (new DataValidations($docSheet, $xmlSheet))->load(); } // unparsed sheet AlternateContent @@ -1265,50 +835,25 @@ class Xlsx extends BaseReader } // Add hyperlinks - $hyperlinks = []; if (!$this->readDataOnly) { + $hyperlinkReader = new Hyperlinks($docSheet); // Locate hyperlink relations - if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { + $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + if ($zip->locateName($relationsFileName)) { //~ http://schemas.openxmlformats.org/package/2006/relationships" $relsWorksheet = simplexml_load_string( $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') + $this->getFromZipArchive($zip, $relationsFileName) ), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); - foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') { - $hyperlinks[(string) $ele['Id']] = (string) $ele['Target']; - } - } + $hyperlinkReader->readHyperlinks($relsWorksheet); } // Loop through hyperlinks if ($xmlSheet && $xmlSheet->hyperlinks) { - /** @var SimpleXMLElement $hyperlink */ - foreach ($xmlSheet->hyperlinks->hyperlink as $hyperlink) { - // Link url - $linkRel = $hyperlink->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); - - foreach (Coordinate::extractAllCellReferencesInRange($hyperlink['ref']) as $cellReference) { - $cell = $docSheet->getCell($cellReference); - if (isset($linkRel['id'])) { - $hyperlinkUrl = $hyperlinks[(string) $linkRel['id']]; - if (isset($hyperlink['location'])) { - $hyperlinkUrl .= '#' . (string) $hyperlink['location']; - } - $cell->getHyperlink()->setUrl($hyperlinkUrl); - } elseif (isset($hyperlink['location'])) { - $cell->getHyperlink()->setUrl('sheet://' . (string) $hyperlink['location']); - } - - // Tooltip - if (isset($hyperlink['tooltip'])) { - $cell->getHyperlink()->setTooltip((string) $hyperlink['tooltip']); - } - } - } + $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks); } } @@ -2482,94 +2027,4 @@ class Xlsx extends BaseReader return (bool) $xsdBoolean; } - - /** - * Read columns and rows attributes from XML and set them on the worksheet. - * - * @param SimpleXMLElement $xmlSheet - * @param Worksheet $docSheet - */ - private function readColumnsAndRowsAttributes(SimpleXMLElement $xmlSheet, Worksheet $docSheet) - { - $columnsAttributes = []; - $rowsAttributes = []; - if (isset($xmlSheet->cols) && !$this->readDataOnly) { - foreach ($xmlSheet->cols->col as $col) { - for ($i = (int) ($col['min']); $i <= (int) ($col['max']); ++$i) { - if ($col['style'] && !$this->readDataOnly) { - $columnsAttributes[Coordinate::stringFromColumnIndex($i)]['xfIndex'] = (int) $col['style']; - } - if (self::boolean($col['hidden'])) { - $columnsAttributes[Coordinate::stringFromColumnIndex($i)]['visible'] = false; - } - if (self::boolean($col['collapsed'])) { - $columnsAttributes[Coordinate::stringFromColumnIndex($i)]['collapsed'] = true; - } - if ($col['outlineLevel'] > 0) { - $columnsAttributes[Coordinate::stringFromColumnIndex($i)]['outlineLevel'] = (int) $col['outlineLevel']; - } - $columnsAttributes[Coordinate::stringFromColumnIndex($i)]['width'] = (float) $col['width']; - - if ((int) ($col['max']) == 16384) { - break; - } - } - } - } - - if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) { - foreach ($xmlSheet->sheetData->row as $row) { - if ($row['ht'] && !$this->readDataOnly) { - $rowsAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht']; - } - if (self::boolean($row['hidden']) && !$this->readDataOnly) { - $rowsAttributes[(int) $row['r']]['visible'] = false; - } - if (self::boolean($row['collapsed'])) { - $rowsAttributes[(int) $row['r']]['collapsed'] = true; - } - if ($row['outlineLevel'] > 0) { - $rowsAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel']; - } - if ($row['s'] && !$this->readDataOnly) { - $rowsAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s']; - } - } - } - - $readFilter = (\get_class($this->getReadFilter()) !== DefaultReadFilter::class ? $this->getReadFilter() : null); - - // set columns/rows attributes - $columnsAttributesSet = []; - $rowsAttributesSet = []; - foreach ($columnsAttributes as $coordColumn => $columnAttributes) { - if ($readFilter !== null) { - foreach ($rowsAttributes as $coordRow => $rowAttributes) { - if (!$readFilter->readCell($coordColumn, $coordRow, $docSheet->getTitle())) { - continue 2; - } - } - } - - if (!isset($columnsAttributesSet[$coordColumn])) { - $this->setColumnAttributes($docSheet, $coordColumn, $columnAttributes); - $columnsAttributesSet[$coordColumn] = true; - } - } - - foreach ($rowsAttributes as $coordRow => $rowAttributes) { - if ($readFilter !== null) { - foreach ($columnsAttributes as $coordColumn => $columnAttributes) { - if (!$readFilter->readCell($coordColumn, $coordRow, $docSheet->getTitle())) { - continue 2; - } - } - } - - if (!isset($rowsAttributesSet[$coordRow])) { - $this->setRowAttributes($docSheet, $coordRow, $rowAttributes); - $rowsAttributesSet[$coordRow] = true; - } - } - } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php new file mode 100644 index 00000000..6929758d --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php @@ -0,0 +1,144 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + } + + public function load() + { + $autoFilterRange = (string) $this->worksheetXml->autoFilter['ref']; + if (strpos($autoFilterRange, ':') !== false) { + $this->readAutoFilter($autoFilterRange, $this->worksheetXml); + } + } + + private function readAutoFilter($autoFilterRange, $xmlSheet) + { + $autoFilter = $this->worksheet->getAutoFilter(); + $autoFilter->setRange($autoFilterRange); + + foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) { + $column = $autoFilter->getColumnByOffset((int) $filterColumn['colId']); + // Check for standard filters + if ($filterColumn->filters) { + $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER); + $filters = $filterColumn->filters; + if ((isset($filters['blank'])) && ($filters['blank'] == 1)) { + // Operator is undefined, but always treated as EQUAL + $column->createRule()->setRule(null, '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); + } + // Standard filters are always an OR join, so no join rule needs to be set + // Entries can be either filter elements + foreach ($filters->filter as $filterRule) { + // Operator is undefined, but always treated as EQUAL + $column->createRule()->setRule(null, (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); + } + + // Or Date Group elements + $this->readDateRangeAutoFilter($filters, $column); + } + + // Check for custom filters + $this->readCustomAutoFilter($filterColumn, $column); + // Check for dynamic filters + $this->readDynamicAutoFilter($filterColumn, $column); + // Check for dynamic filters + $this->readTopTenAutoFilter($filterColumn, $column); + } + } + + private function readDateRangeAutoFilter(\SimpleXMLElement $filters, Column $column) + { + foreach ($filters->dateGroupItem as $dateGroupItem) { + // Operator is undefined, but always treated as EQUAL + $column->createRule()->setRule( + null, + [ + 'year' => (string) $dateGroupItem['year'], + 'month' => (string) $dateGroupItem['month'], + 'day' => (string) $dateGroupItem['day'], + 'hour' => (string) $dateGroupItem['hour'], + 'minute' => (string) $dateGroupItem['minute'], + 'second' => (string) $dateGroupItem['second'], + ], + (string) $dateGroupItem['dateTimeGrouping'] + )->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP); + } + } + + private function readCustomAutoFilter(\SimpleXMLElement $filterColumn, Column $column) + { + if ($filterColumn->customFilters) { + $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER); + $customFilters = $filterColumn->customFilters; + // Custom filters can an AND or an OR join; + // and there should only ever be one or two entries + if ((isset($customFilters['and'])) && ($customFilters['and'] == 1)) { + $column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND); + } + foreach ($customFilters->customFilter as $filterRule) { + $column->createRule()->setRule( + (string) $filterRule['operator'], + (string) $filterRule['val'] + )->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER); + } + } + } + + private function readDynamicAutoFilter(\SimpleXMLElement $filterColumn, Column $column) + { + if ($filterColumn->dynamicFilter) { + $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); + // We should only ever have one dynamic filter + foreach ($filterColumn->dynamicFilter as $filterRule) { + // Operator is undefined, but always treated as EQUAL + $column->createRule()->setRule( + null, + (string) $filterRule['val'], + (string) $filterRule['type'] + )->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); + if (isset($filterRule['val'])) { + $column->setAttribute('val', (string) $filterRule['val']); + } + if (isset($filterRule['maxVal'])) { + $column->setAttribute('maxVal', (string) $filterRule['maxVal']); + } + } + } + } + + private function readTopTenAutoFilter(\SimpleXMLElement $filterColumn, Column $column) + { + if ($filterColumn->top10) { + $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER); + // We should only ever have one top10 filter + foreach ($filterColumn->top10 as $filterRule) { + $column->createRule()->setRule( + (((isset($filterRule['percent'])) && ($filterRule['percent'] == 1)) + ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT + : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE + ), + (string) $filterRule['val'], + (((isset($filterRule['top'])) && ($filterRule['top'] == 1)) + ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP + : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM + ) + )->setRuleType(Rule::AUTOFILTER_RULETYPE_TOPTENFILTER); + } + } + } +} diff --git a/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php b/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php new file mode 100644 index 00000000..1679f01f --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php @@ -0,0 +1,19 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + } + + /** + * Set Worksheet column attributes by attributes array passed. + * + * @param string $columnAddress A, B, ... DX, ... + * @param array $columnAttributes array of attributes (indexes are attribute name, values are value) + * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ? + */ + private function setColumnAttributes($columnAddress, array $columnAttributes) + { + if (isset($columnAttributes['xfIndex'])) { + $this->worksheet->getColumnDimension($columnAddress)->setXfIndex($columnAttributes['xfIndex']); + } + if (isset($columnAttributes['visible'])) { + $this->worksheet->getColumnDimension($columnAddress)->setVisible($columnAttributes['visible']); + } + if (isset($columnAttributes['collapsed'])) { + $this->worksheet->getColumnDimension($columnAddress)->setCollapsed($columnAttributes['collapsed']); + } + if (isset($columnAttributes['outlineLevel'])) { + $this->worksheet->getColumnDimension($columnAddress)->setOutlineLevel($columnAttributes['outlineLevel']); + } + if (isset($columnAttributes['width'])) { + $this->worksheet->getColumnDimension($columnAddress)->setWidth($columnAttributes['width']); + } + } + + /** + * Set Worksheet row attributes by attributes array passed. + * + * @param int $rowNumber 1, 2, 3, ... 99, ... + * @param array $rowAttributes array of attributes (indexes are attribute name, values are value) + * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ? + */ + private function setRowAttributes($rowNumber, array $rowAttributes) + { + if (isset($rowAttributes['xfIndex'])) { + $this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']); + } + if (isset($rowAttributes['visible'])) { + $this->worksheet->getRowDimension($rowNumber)->setVisible($rowAttributes['visible']); + } + if (isset($rowAttributes['collapsed'])) { + $this->worksheet->getRowDimension($rowNumber)->setCollapsed($rowAttributes['collapsed']); + } + if (isset($rowAttributes['outlineLevel'])) { + $this->worksheet->getRowDimension($rowNumber)->setOutlineLevel($rowAttributes['outlineLevel']); + } + if (isset($rowAttributes['rowHeight'])) { + $this->worksheet->getRowDimension($rowNumber)->setRowHeight($rowAttributes['rowHeight']); + } + } + + /** + * @param IReadFilter $readFilter + * @param bool $readDataOnly + */ + public function load(IReadFilter $readFilter = null, $readDataOnly = false) + { + if ($this->worksheetXml === null) { + return; + } + + $columnsAttributes = []; + $rowsAttributes = []; + if (isset($this->worksheetXml->cols)) { + $columnsAttributes = $this->readColumnAttributes($this->worksheetXml->cols, $readDataOnly); + } + + if ($this->worksheetXml->sheetData && $this->worksheetXml->sheetData->row) { + $rowsAttributes = $this->readRowAttributes($this->worksheetXml->sheetData->row, $readDataOnly); + } + + // set columns/rows attributes + $columnsAttributesAreSet = []; + foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) { + if ($readFilter === null || + !$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes)) { + if (!isset($columnsAttributesAreSet[$columnCoordinate])) { + $this->setColumnAttributes($columnCoordinate, $columnAttributes); + $columnsAttributesAreSet[$columnCoordinate] = true; + } + } + } + + $rowsAttributesAreSet = []; + foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) { + if ($readFilter === null || + !$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes)) { + if (!isset($rowsAttributesAreSet[$rowCoordinate])) { + $this->setRowAttributes($rowCoordinate, $rowAttributes); + $rowsAttributesAreSet[$rowCoordinate] = true; + } + } + } + } + + private function isFilteredColumn(IReadFilter $readFilter, $columnCoordinate, array $rowsAttributes) + { + foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) { + if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) { + return true; + } + } + + return false; + } + + private function readColumnAttributes(\SimpleXMLElement $worksheetCols, $readDataOnly) + { + $columnAttributes = []; + + foreach ($worksheetCols->col as $column) { + $startColumn = Coordinate::stringFromColumnIndex((int) $column['min']); + $endColumn = Coordinate::stringFromColumnIndex((int) $column['max']); + ++$endColumn; + for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) { + $columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly); + + if ((int) ($column['max']) == 16384) { + break; + } + } + } + + return $columnAttributes; + } + + private function readColumnRangeAttributes(\SimpleXMLElement $column, $readDataOnly) + { + $columnAttributes = []; + + if ($column['style'] && !$readDataOnly) { + $columnAttributes['xfIndex'] = (int) $column['style']; + } + if (self::boolean($column['hidden'])) { + $columnAttributes['visible'] = false; + } + if (self::boolean($column['collapsed'])) { + $columnAttributes['collapsed'] = true; + } + if (((int) $column['outlineLevel']) > 0) { + $columnAttributes['outlineLevel'] = (int) $column['outlineLevel']; + } + $columnAttributes['width'] = (float) $column['width']; + + return $columnAttributes; + } + + private function isFilteredRow(IReadFilter $readFilter, $rowCoordinate, array $columnsAttributes) + { + foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) { + if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) { + return true; + } + } + + return false; + } + + private function readRowAttributes(\SimpleXMLElement $worksheetRow, $readDataOnly) + { + $rowAttributes = []; + + foreach ($worksheetRow as $row) { + if ($row['ht'] && !$readDataOnly) { + $rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht']; + } + if (self::boolean($row['hidden'])) { + $rowAttributes[(int) $row['r']]['visible'] = false; + } + if (self::boolean($row['collapsed'])) { + $rowAttributes[(int) $row['r']]['collapsed'] = true; + } + if ((int) $row['outlineLevel'] > 0) { + $rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel']; + } + if ($row['s'] && !$readDataOnly) { + $rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s']; + } + } + + return $rowAttributes; + } +} diff --git a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php new file mode 100644 index 00000000..b3de5d1c --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php @@ -0,0 +1,92 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + $this->dxfs = $dxfs; + } + + public function load() + { + $this->setConditionalStyles( + $this->worksheet, + $this->readConditionalStyles($this->worksheetXml) + ); + } + + private function readConditionalStyles($xmlSheet) + { + $conditionals = []; + foreach ($xmlSheet->conditionalFormatting as $conditional) { + foreach ($conditional->cfRule as $cfRule) { + if (((string) $cfRule['type'] == Conditional::CONDITION_NONE + || (string) $cfRule['type'] == Conditional::CONDITION_CELLIS + || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT + || (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION) + && isset($this->dxfs[(int) ($cfRule['dxfId'])])) { + $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; + } + } + } + + return $conditionals; + } + + private function setConditionalStyles(Worksheet $worksheet, array $conditionals) + { + foreach ($conditionals as $ref => $cfRules) { + ksort($cfRules); + $conditionalStyles = $this->readStyleRules($cfRules); + + // Extract all cell references in $ref + $cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref))); + foreach ($cellBlocks as $cellBlock) { + $worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles); + } + } + } + + private function readStyleRules($cfRules) + { + $conditionalStyles = []; + foreach ($cfRules as $cfRule) { + $objConditional = new Conditional(); + $objConditional->setConditionType((string) $cfRule['type']); + $objConditional->setOperatorType((string) $cfRule['operator']); + + if ((string) $cfRule['text'] != '') { + $objConditional->setText((string) $cfRule['text']); + } + + if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) { + $objConditional->setStopIfTrue(true); + } + + if (count($cfRule->formula) > 1) { + foreach ($cfRule->formula as $formula) { + $objConditional->addCondition((string) $formula); + } + } else { + $objConditional->addCondition((string) $cfRule->formula); + } + $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); + $conditionalStyles[] = $objConditional; + } + + return $conditionalStyles; + } +} diff --git a/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php b/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php new file mode 100644 index 00000000..fab702b1 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php @@ -0,0 +1,50 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + } + + public function load() + { + foreach ($this->worksheetXml->dataValidation as $dataValidation) { + // Uppercase coordinate + $range = strtoupper($dataValidation['sqref']); + $rangeSet = explode(' ', $range); + foreach ($rangeSet as $range) { + $stRange = $this->worksheet->shrinkRangeToFit($range); + + // Extract all cell references in $range + foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $reference) { + // Create validation + $docValidation = $this->worksheet->getCell($reference)->getDataValidation(); + $docValidation->setType((string) $dataValidation['type']); + $docValidation->setErrorStyle((string) $dataValidation['errorStyle']); + $docValidation->setOperator((string) $dataValidation['operator']); + $docValidation->setAllowBlank($dataValidation['allowBlank'] != 0); + $docValidation->setShowDropDown($dataValidation['showDropDown'] == 0); + $docValidation->setShowInputMessage($dataValidation['showInputMessage'] != 0); + $docValidation->setShowErrorMessage($dataValidation['showErrorMessage'] != 0); + $docValidation->setErrorTitle((string) $dataValidation['errorTitle']); + $docValidation->setError((string) $dataValidation['error']); + $docValidation->setPromptTitle((string) $dataValidation['promptTitle']); + $docValidation->setPrompt((string) $dataValidation['prompt']); + $docValidation->setFormula1((string) $dataValidation->formula1); + $docValidation->setFormula2((string) $dataValidation->formula2); + } + } + } + } +} diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php new file mode 100644 index 00000000..400b2725 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php @@ -0,0 +1,58 @@ +worksheet = $workSheet; + } + + public function readHyperlinks(\SimpleXMLElement $relsWorksheet) + { + foreach ($relsWorksheet->Relationship as $element) { + if ($element['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') { + $this->hyperlinks[(string) $element['Id']] = (string) $element['Target']; + } + } + } + + public function setHyperlinks(\SimpleXMLElement $worksheetXml) + { + foreach ($worksheetXml->hyperlink as $hyperlink) { + $this->setHyperlink($hyperlink, $this->worksheet); + } + } + + private function setHyperlink(\SimpleXMLElement $hyperlink, Worksheet $worksheet) + { + // Link url + $linkRel = $hyperlink->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + + foreach (Coordinate::extractAllCellReferencesInRange($hyperlink['ref']) as $cellReference) { + $cell = $worksheet->getCell($cellReference); + if (isset($linkRel['id'])) { + $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']]; + if (isset($hyperlink['location'])) { + $hyperlinkUrl .= '#' . (string) $hyperlink['location']; + } + $cell->getHyperlink()->setUrl($hyperlinkUrl); + } elseif (isset($hyperlink['location'])) { + $cell->getHyperlink()->setUrl('sheet://' . (string) $hyperlink['location']); + } + + // Tooltip + if (isset($hyperlink['tooltip'])) { + $cell->getHyperlink()->setTooltip((string) $hyperlink['tooltip']); + } + } + } +} diff --git a/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php b/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php new file mode 100644 index 00000000..6f286769 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php @@ -0,0 +1,150 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + } + + public function load(array $unparsedLoadedData) + { + if (!$this->worksheetXml) { + return $unparsedLoadedData; + } + + $this->margins($this->worksheetXml, $this->worksheet); + $unparsedLoadedData = $this->pageSetup($this->worksheetXml, $this->worksheet, $unparsedLoadedData); + $this->headerFooter($this->worksheetXml, $this->worksheet); + $this->pageBreaks($this->worksheetXml, $this->worksheet); + + return $unparsedLoadedData; + } + + private function margins(\SimpleXMLElement $xmlSheet, Worksheet $worksheet) + { + if ($xmlSheet->pageMargins) { + $docPageMargins = $worksheet->getPageMargins(); + $docPageMargins->setLeft((float) ($xmlSheet->pageMargins['left'])); + $docPageMargins->setRight((float) ($xmlSheet->pageMargins['right'])); + $docPageMargins->setTop((float) ($xmlSheet->pageMargins['top'])); + $docPageMargins->setBottom((float) ($xmlSheet->pageMargins['bottom'])); + $docPageMargins->setHeader((float) ($xmlSheet->pageMargins['header'])); + $docPageMargins->setFooter((float) ($xmlSheet->pageMargins['footer'])); + } + } + + private function pageSetup(\SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData) + { + if ($xmlSheet->pageSetup) { + $docPageSetup = $worksheet->getPageSetup(); + + if (isset($xmlSheet->pageSetup['orientation'])) { + $docPageSetup->setOrientation((string) $xmlSheet->pageSetup['orientation']); + } + if (isset($xmlSheet->pageSetup['paperSize'])) { + $docPageSetup->setPaperSize((int) ($xmlSheet->pageSetup['paperSize'])); + } + if (isset($xmlSheet->pageSetup['scale'])) { + $docPageSetup->setScale((int) ($xmlSheet->pageSetup['scale']), false); + } + if (isset($xmlSheet->pageSetup['fitToHeight']) && (int) ($xmlSheet->pageSetup['fitToHeight']) >= 0) { + $docPageSetup->setFitToHeight((int) ($xmlSheet->pageSetup['fitToHeight']), false); + } + if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) { + $docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false); + } + if (isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) && + self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])) { + $docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber'])); + } + + $relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + if (isset($relAttributes['id'])) { + $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id']; + } + } + + return $unparsedLoadedData; + } + + private function headerFooter(\SimpleXMLElement $xmlSheet, Worksheet $worksheet) + { + if ($xmlSheet->headerFooter) { + $docHeaderFooter = $worksheet->getHeaderFooter(); + + if (isset($xmlSheet->headerFooter['differentOddEven']) && + self::boolean((string) $xmlSheet->headerFooter['differentOddEven'])) { + $docHeaderFooter->setDifferentOddEven(true); + } else { + $docHeaderFooter->setDifferentOddEven(false); + } + if (isset($xmlSheet->headerFooter['differentFirst']) && + self::boolean((string) $xmlSheet->headerFooter['differentFirst'])) { + $docHeaderFooter->setDifferentFirst(true); + } else { + $docHeaderFooter->setDifferentFirst(false); + } + if (isset($xmlSheet->headerFooter['scaleWithDoc']) && + !self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc'])) { + $docHeaderFooter->setScaleWithDocument(false); + } else { + $docHeaderFooter->setScaleWithDocument(true); + } + if (isset($xmlSheet->headerFooter['alignWithMargins']) && + !self::boolean((string) $xmlSheet->headerFooter['alignWithMargins'])) { + $docHeaderFooter->setAlignWithMargins(false); + } else { + $docHeaderFooter->setAlignWithMargins(true); + } + + $docHeaderFooter->setOddHeader((string) $xmlSheet->headerFooter->oddHeader); + $docHeaderFooter->setOddFooter((string) $xmlSheet->headerFooter->oddFooter); + $docHeaderFooter->setEvenHeader((string) $xmlSheet->headerFooter->evenHeader); + $docHeaderFooter->setEvenFooter((string) $xmlSheet->headerFooter->evenFooter); + $docHeaderFooter->setFirstHeader((string) $xmlSheet->headerFooter->firstHeader); + $docHeaderFooter->setFirstFooter((string) $xmlSheet->headerFooter->firstFooter); + } + } + + private function pageBreaks(\SimpleXMLElement $xmlSheet, Worksheet $worksheet) + { + if ($xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk) { + $this->rowBreaks($xmlSheet, $worksheet); + } + if ($xmlSheet->colBreaks && $xmlSheet->colBreaks->brk) { + $this->columnBreaks($xmlSheet, $worksheet); + } + } + + private function rowBreaks(\SimpleXMLElement $xmlSheet, Worksheet $worksheet) + { + foreach ($xmlSheet->rowBreaks->brk as $brk) { + if ($brk['man']) { + $worksheet->setBreak("A{$brk['id']}", Worksheet::BREAK_ROW); + } + } + } + + private function columnBreaks(\SimpleXMLElement $xmlSheet, Worksheet $worksheet) + { + foreach ($xmlSheet->colBreaks->brk as $brk) { + if ($brk['man']) { + $worksheet->setBreak( + Coordinate::stringFromColumnIndex(((int) $brk['id']) + 1) . '1', + Worksheet::BREAK_COLUMN + ); + } + } + } +} diff --git a/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php b/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php new file mode 100644 index 00000000..eb61a5d3 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php @@ -0,0 +1,124 @@ +worksheet = $workSheet; + $this->worksheetXml = $worksheetXml; + } + + /** + * @param bool $readDataOnly + */ + public function load($readDataOnly = false) + { + if ($this->worksheetXml === null) { + return; + } + + if (isset($this->worksheetXml->sheetPr)) { + $this->tabColor($this->worksheetXml->sheetPr); + $this->codeName($this->worksheetXml->sheetPr); + $this->outlines($this->worksheetXml->sheetPr); + $this->pageSetup($this->worksheetXml->sheetPr); + } + + if (isset($this->worksheetXml->sheetFormatPr)) { + $this->sheetFormat($this->worksheetXml->sheetFormatPr); + } + + if (!$readDataOnly && isset($this->worksheetXml->printOptions)) { + $this->printOptions($this->worksheetXml->printOptions); + } + } + + private function tabColor(\SimpleXMLElement $sheetPr) + { + if (isset($sheetPr->tabColor, $sheetPr->tabColor['rgb'])) { + $this->worksheet->getTabColor()->setARGB((string) $sheetPr->tabColor['rgb']); + } + } + + private function codeName(\SimpleXMLElement $sheetPr) + { + if (isset($sheetPr['codeName'])) { + $this->worksheet->setCodeName((string) $sheetPr['codeName'], false); + } + } + + private function outlines(\SimpleXMLElement $sheetPr) + { + if (isset($sheetPr->outlinePr)) { + if (isset($sheetPr->outlinePr['summaryRight']) && + !self::boolean((string) $sheetPr->outlinePr['summaryRight'])) { + $this->worksheet->setShowSummaryRight(false); + } else { + $this->worksheet->setShowSummaryRight(true); + } + + if (isset($sheetPr->outlinePr['summaryBelow']) && + !self::boolean((string) $sheetPr->outlinePr['summaryBelow'])) { + $this->worksheet->setShowSummaryBelow(false); + } else { + $this->worksheet->setShowSummaryBelow(true); + } + } + } + + private function pageSetup(\SimpleXMLElement $sheetPr) + { + if (isset($sheetPr->pageSetUpPr)) { + if (isset($sheetPr->pageSetUpPr['fitToPage']) && + !self::boolean((string) $sheetPr->pageSetUpPr['fitToPage'])) { + $this->worksheet->getPageSetup()->setFitToPage(false); + } else { + $this->worksheet->getPageSetup()->setFitToPage(true); + } + } + } + + private function sheetFormat(\SimpleXMLElement $sheetFormatPr) + { + if (isset($sheetFormatPr['customHeight']) && + self::boolean((string) $sheetFormatPr['customHeight']) && + isset($sheetFormatPr['defaultRowHeight'])) { + $this->worksheet->getDefaultRowDimension() + ->setRowHeight((float) $sheetFormatPr['defaultRowHeight']); + } + + if (isset($sheetFormatPr['defaultColWidth'])) { + $this->worksheet->getDefaultColumnDimension() + ->setWidth((float) $sheetFormatPr['defaultColWidth']); + } + + if (isset($sheetFormatPr['zeroHeight']) && + ((string) $sheetFormatPr['zeroHeight'] === '1')) { + $this->worksheet->getDefaultRowDimension()->setZeroHeight(true); + } + } + + private function printOptions(\SimpleXMLElement $printOptions) + { + if (self::boolean((string) $printOptions['gridLinesSet'])) { + $this->worksheet->setShowGridlines(true); + } + if (self::boolean((string) $printOptions['gridLines'])) { + $this->worksheet->setPrintGridlines(true); + } + if (self::boolean((string) $printOptions['horizontalCentered'])) { + $this->worksheet->getPageSetup()->setHorizontalCentered(true); + } + if (self::boolean((string) $printOptions['verticalCentered'])) { + $this->worksheet->getPageSetup()->setVerticalCentered(true); + } + } +} diff --git a/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php b/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php new file mode 100644 index 00000000..2caaec31 --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php @@ -0,0 +1,127 @@ +sheetViewXml = $sheetViewXml; + $this->worksheet = $workSheet; + } + + public function load() + { + $this->zoomScale(); + $this->view(); + $this->gridLines(); + $this->headers(); + $this->direction(); + + if (isset($this->sheetViewXml->pane)) { + $this->pane(); + } + if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection['sqref'])) { + $this->selection(); + } + } + + private function zoomScale() + { + if (isset($this->sheetViewXml['zoomScale'])) { + $zoomScale = (int) ($this->sheetViewXml['zoomScale']); + if ($zoomScale <= 0) { + // setZoomScale will throw an Exception if the scale is less than or equals 0 + // that is OK when manually creating documents, but we should be able to read all documents + $zoomScale = 100; + } + + $this->worksheet->getSheetView()->setZoomScale($zoomScale); + } + + if (isset($this->sheetViewXml['zoomScaleNormal'])) { + $zoomScaleNormal = (int) ($this->sheetViewXml['zoomScaleNormal']); + if ($zoomScaleNormal <= 0) { + // setZoomScaleNormal will throw an Exception if the scale is less than or equals 0 + // that is OK when manually creating documents, but we should be able to read all documents + $zoomScaleNormal = 100; + } + + $this->worksheet->getSheetView()->setZoomScaleNormal($zoomScaleNormal); + } + } + + private function view() + { + if (isset($this->sheetViewXml['view'])) { + $this->worksheet->getSheetView()->setView((string) $this->sheetViewXml['view']); + } + } + + private function gridLines() + { + if (isset($this->sheetViewXml['showGridLines'])) { + $this->worksheet->setShowGridLines( + self::boolean((string) $this->sheetViewXml['showGridLines']) + ); + } + } + + private function headers() + { + if (isset($this->sheetViewXml['showRowColHeaders'])) { + $this->worksheet->setShowRowColHeaders( + self::boolean((string) $this->sheetViewXml['showRowColHeaders']) + ); + } + } + + private function direction() + { + if (isset($this->sheetViewXml['rightToLeft'])) { + $this->worksheet->setRightToLeft( + self::boolean((string) $this->sheetViewXml['rightToLeft']) + ); + } + } + + private function pane() + { + $xSplit = 0; + $ySplit = 0; + $topLeftCell = null; + + if (isset($this->sheetViewXml->pane['xSplit'])) { + $xSplit = (int) ($this->sheetViewXml->pane['xSplit']); + } + + if (isset($this->sheetViewXml->pane['ySplit'])) { + $ySplit = (int) ($this->sheetViewXml->pane['ySplit']); + } + + if (isset($this->sheetViewXml->pane['topLeftCell'])) { + $topLeftCell = (string) $this->sheetViewXml->pane['topLeftCell']; + } + + $this->worksheet->freezePane( + Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1), + $topLeftCell + ); + } + + private function selection() + { + $sqref = (string) $this->sheetViewXml->selection['sqref']; + $sqref = explode(' ', $sqref); + $sqref = $sqref[0]; + + $this->worksheet->setSelectedCells($sqref); + } +} diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php new file mode 100644 index 00000000..3cc4054d --- /dev/null +++ b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -0,0 +1,265 @@ +styleXml = $styleXml; + } + + public function setStyleBaseData(Theme $theme, $styles, $cellStyles) + { + self::$theme = $theme; + $this->styles = $styles; + $this->cellStyles = $cellStyles; + } + + private static function readFontStyle(Font $fontStyle, \SimpleXMLElement $fontStyleXml) + { + $fontStyle->setName((string) $fontStyleXml->name['val']); + $fontStyle->setSize((float) $fontStyleXml->sz['val']); + + if (isset($fontStyleXml->b)) { + $fontStyle->setBold(!isset($fontStyleXml->b['val']) || self::boolean((string) $fontStyleXml->b['val'])); + } + if (isset($fontStyleXml->i)) { + $fontStyle->setItalic(!isset($fontStyleXml->i['val']) || self::boolean((string) $fontStyleXml->i['val'])); + } + if (isset($fontStyleXml->strike)) { + $fontStyle->setStrikethrough(!isset($fontStyleXml->strike['val']) || self::boolean((string) $fontStyleXml->strike['val'])); + } + $fontStyle->getColor()->setARGB(self::readColor($fontStyleXml->color)); + + if (isset($fontStyleXml->u) && !isset($fontStyleXml->u['val'])) { + $fontStyle->setUnderline(Font::UNDERLINE_SINGLE); + } elseif (isset($fontStyleXml->u, $fontStyleXml->u['val'])) { + $fontStyle->setUnderline((string) $fontStyleXml->u['val']); + } + + if (isset($fontStyleXml->vertAlign, $fontStyleXml->vertAlign['val'])) { + $verticalAlign = strtolower((string) $fontStyleXml->vertAlign['val']); + if ($verticalAlign === 'superscript') { + $fontStyle->setSuperscript(true); + } + if ($verticalAlign === 'subscript') { + $fontStyle->setSubscript(true); + } + } + } + + private static function readFillStyle(Fill $fillStyle, \SimpleXMLElement $fillStyleXml) + { + if ($fillStyleXml->gradientFill) { + /** @var \SimpleXMLElement $gradientFill */ + $gradientFill = $fillStyleXml->gradientFill[0]; + if (!empty($gradientFill['type'])) { + $fillStyle->setFillType((string) $gradientFill['type']); + } + $fillStyle->setRotation((float) ($gradientFill['degree'])); + $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $fillStyle->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); + $fillStyle->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); + } elseif ($fillStyleXml->patternFill) { + $patternType = (string) $fillStyleXml->patternFill['patternType'] != '' ? (string) $fillStyleXml->patternFill['patternType'] : 'solid'; + $fillStyle->setFillType($patternType); + if ($fillStyleXml->patternFill->fgColor) { + $fillStyle->getStartColor()->setARGB(self::readColor($fillStyleXml->patternFill->fgColor, true)); + } else { + $fillStyle->getStartColor()->setARGB('FF000000'); + } + if ($fillStyleXml->patternFill->bgColor) { + $fillStyle->getEndColor()->setARGB(self::readColor($fillStyleXml->patternFill->bgColor, true)); + } + } + } + + private static function readBorderStyle(Borders $borderStyle, \SimpleXMLElement $borderStyleXml) + { + $diagonalUp = self::boolean((string) $borderStyleXml['diagonalUp']); + $diagonalDown = self::boolean((string) $borderStyleXml['diagonalDown']); + if (!$diagonalUp && !$diagonalDown) { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE); + } elseif ($diagonalUp && !$diagonalDown) { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP); + } elseif (!$diagonalUp && $diagonalDown) { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN); + } else { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH); + } + + self::readBorder($borderStyle->getLeft(), $borderStyleXml->left); + self::readBorder($borderStyle->getRight(), $borderStyleXml->right); + self::readBorder($borderStyle->getTop(), $borderStyleXml->top); + self::readBorder($borderStyle->getBottom(), $borderStyleXml->bottom); + self::readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal); + } + + private static function readBorder(Border $border, \SimpleXMLElement $borderXml) + { + if (isset($borderXml['style'])) { + $border->setBorderStyle((string) $borderXml['style']); + } + if (isset($borderXml->color)) { + $border->getColor()->setARGB(self::readColor($borderXml->color)); + } + } + + private static function readAlignmentStyle(Alignment $alignment, \SimpleXMLElement $alignmentXml) + { + $alignment->setHorizontal((string) $alignmentXml->alignment['horizontal']); + $alignment->setVertical((string) $alignmentXml->alignment['vertical']); + + $textRotation = 0; + if ((int) $alignmentXml->alignment['textRotation'] <= 90) { + $textRotation = (int) $alignmentXml->alignment['textRotation']; + } elseif ((int) $alignmentXml->alignment['textRotation'] > 90) { + $textRotation = 90 - (int) $alignmentXml->alignment['textRotation']; + } + + $alignment->setTextRotation((int) $textRotation); + $alignment->setWrapText(self::boolean((string) $alignmentXml->alignment['wrapText'])); + $alignment->setShrinkToFit(self::boolean((string) $alignmentXml->alignment['shrinkToFit'])); + $alignment->setIndent((int) ((string) $alignmentXml->alignment['indent']) > 0 ? (int) ((string) $alignmentXml->alignment['indent']) : 0); + $alignment->setReadOrder((int) ((string) $alignmentXml->alignment['readingOrder']) > 0 ? (int) ((string) $alignmentXml->alignment['readingOrder']) : 0); + } + + private function readStyle(Style $docStyle, $style) + { + $docStyle->getNumberFormat()->setFormatCode($style->numFmt); + + if (isset($style->font)) { + self::readFontStyle($docStyle->getFont(), $style->font); + } + + if (isset($style->fill)) { + self::readFillStyle($docStyle->getFill(), $style->fill); + } + + if (isset($style->border)) { + self::readBorderStyle($docStyle->getBorders(), $style->border); + } + + if (isset($style->alignment)) { + self::readAlignmentStyle($docStyle->getAlignment(), $style->alignment); + } + + // protection + if (isset($style->protection)) { + $this->readProtectionLocked($docStyle, $style); + $this->readProtectionHidden($docStyle, $style); + } + + // top-level style settings + if (isset($style->quotePrefix)) { + $docStyle->setQuotePrefix(true); + } + } + + private function readProtectionLocked(Style $docStyle, $style) + { + if (isset($style->protection['locked'])) { + if (self::boolean((string) $style->protection['locked'])) { + $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED); + } else { + $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED); + } + } + } + + private function readProtectionHidden(Style $docStyle, $style) + { + if (isset($style->protection['hidden'])) { + if (self::boolean((string) $style->protection['hidden'])) { + $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED); + } else { + $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED); + } + } + } + + private static function readColor($color, $background = false) + { + if (isset($color['rgb'])) { + return (string) $color['rgb']; + } elseif (isset($color['indexed'])) { + return Color::indexedColor($color['indexed'] - 7, $background)->getARGB(); + } elseif (isset($color['theme'])) { + if (self::$theme !== null) { + $returnColour = self::$theme->getColourByIndex((int) $color['theme']); + if (isset($color['tint'])) { + $tintAdjust = (float) $color['tint']; + $returnColour = Color::changeBrightness($returnColour, $tintAdjust); + } + + return 'FF' . $returnColour; + } + } + + return ($background) ? 'FFFFFFFF' : 'FF000000'; + } + + public function dxfs($readDataOnly = false) + { + $dxfs = []; + if (!$readDataOnly && $this->styleXml) { + // Conditional Styles + if ($this->styleXml->dxfs) { + foreach ($this->styleXml->dxfs->dxf as $dxf) { + $style = new Style(false, true); + $this->readStyle($style, $dxf); + $dxfs[] = $style; + } + } + // Cell Styles + if ($this->styleXml->cellStyles) { + foreach ($this->styleXml->cellStyles->cellStyle as $cellStyle) { + if ((int) ($cellStyle['builtinId']) == 0) { + if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) { + // Set default style + $style = new Style(); + $this->readStyle($style, $this->cellStyles[(int) ($cellStyle['xfId'])]); + + // normal style, currently not using it for anything + } + } + } + } + } + + return $dxfs; + } + + public function styles() + { + return $this->styles; + } + + private static function getArrayItem($array, $key = 0) + { + return isset($array[$key]) ? $array[$key] : null; + } +} diff --git a/tests/PhpSpreadsheetTests/Helper/SampleTest.php b/tests/PhpSpreadsheetTests/Helper/SampleTest.php index 1db16464..a56d7813 100644 --- a/tests/PhpSpreadsheetTests/Helper/SampleTest.php +++ b/tests/PhpSpreadsheetTests/Helper/SampleTest.php @@ -39,6 +39,7 @@ class SampleTest extends TestCase if (version_compare(PHP_VERSION, '7.2.99') >= 0) { $skipped[] = 'Basic/26_Utf8.php'; $skipped[] = 'Pdf/21_Pdf_Domdf.php'; + $skipped[] = 'Pdf/21_Pdf_mPDF.php'; } // Unfortunately some tests are too long be ran with code-coverage diff --git a/tests/PhpSpreadsheetTests/Reader/OdsTest.php b/tests/PhpSpreadsheetTests/Reader/OdsTest.php index a6edaee2..59ae006b 100644 --- a/tests/PhpSpreadsheetTests/Reader/OdsTest.php +++ b/tests/PhpSpreadsheetTests/Reader/OdsTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader; use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\Reader\Ods; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Font; @@ -222,4 +223,46 @@ class OdsTest extends TestCase self::assertTrue($style->getFont()->getBold()); self::assertTrue($style->getFont()->getItalic()); } + + public function testLoadOdsWorkbookProperties() + { + $customPropertySet = [ + 'Owner' => ['type' => Properties::PROPERTY_TYPE_STRING, 'value' => 'PHPOffice'], + 'Tested' => ['type' => Properties::PROPERTY_TYPE_BOOLEAN, 'value' => true], + 'Counter' => ['type' => Properties::PROPERTY_TYPE_FLOAT, 'value' => 10.0], + 'TestDate' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-30'], + 'HereAndNow' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-30'], + ]; + + $filename = './data/Reader/Ods/propertyTest.ods'; + $reader = new Ods(); + $spreadsheet = $reader->load($filename); + + $properties = $spreadsheet->getProperties(); + // Core Properties +// $this->assertSame('Mark Baker', $properties->getCreator()); + $this->assertSame('Property Test File', $properties->getTitle()); + $this->assertSame('Testing for Properties', $properties->getSubject()); + $this->assertSame('TEST ODS PHPSpreadsheet', $properties->getKeywords()); + + // Extended Properties +// $this->assertSame('PHPOffice', $properties->getCompany()); +// $this->assertSame('The Big Boss', $properties->getManager()); + + // Custom Properties + $customProperties = $properties->getCustomProperties(); + $this->assertInternalType('array', $customProperties); + $customProperties = array_flip($customProperties); + $this->assertArrayHasKey('TestDate', $customProperties); + + foreach ($customPropertySet as $propertyName => $testData) { + $this->assertTrue($properties->isCustomPropertySet($propertyName)); + $this->assertSame($testData['type'], $properties->getCustomPropertyType($propertyName)); + if ($properties->getCustomPropertyType($propertyName) == Properties::PROPERTY_TYPE_DATE) { + $this->assertSame($testData['value'], date('Y-m-d', $properties->getCustomPropertyValue($propertyName))); + } else { + $this->assertSame($testData['value'], $properties->getCustomPropertyValue($propertyName)); + } + } + } } diff --git a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php index f9f8090c..e9ab52de 100644 --- a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php @@ -2,39 +2,166 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Document\Properties; use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Shared\File; +use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Style\Style; +use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter; +use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; use PHPUnit\Framework\TestCase; class XlsxTest extends TestCase { - public function testLoadWorkbookProperties() + public function testLoadXlsxWorkbookProperties() { + $customPropertySet = [ + 'Publisher' => ['type' => Properties::PROPERTY_TYPE_STRING, 'value' => 'PHPOffice Suite'], + 'Tested' => ['type' => Properties::PROPERTY_TYPE_BOOLEAN, 'value' => true], + 'Counter' => ['type' => Properties::PROPERTY_TYPE_INTEGER, 'value' => 15], + 'Rate' => ['type' => Properties::PROPERTY_TYPE_FLOAT, 'value' => 1.15], + 'Refactor Date' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-10'], + ]; + $filename = './data/Reader/XLSX/propertyTest.xlsx'; $reader = new Xlsx(); $spreadsheet = $reader->load($filename); $properties = $spreadsheet->getProperties(); // Core Properties - $this->assertEquals('Mark Baker', $properties->getCreator()); - $this->assertEquals('Unit Testing', $properties->getTitle()); - $this->assertEquals('Property Test', $properties->getSubject()); + $this->assertSame('Mark Baker', $properties->getCreator()); + $this->assertSame('Unit Testing', $properties->getTitle()); + $this->assertSame('Property Test', $properties->getSubject()); + // Extended Properties - $this->assertEquals('PHPOffice', $properties->getCompany()); - $this->assertEquals('The Big Boss', $properties->getManager()); + $this->assertSame('PHPOffice', $properties->getCompany()); + $this->assertSame('The Big Boss', $properties->getManager()); + // Custom Properties $customProperties = $properties->getCustomProperties(); $this->assertInternalType('array', $customProperties); $customProperties = array_flip($customProperties); $this->assertArrayHasKey('Publisher', $customProperties); - $this->assertTrue($properties->isCustomPropertySet('Publisher')); - $this->assertEquals(Properties::PROPERTY_TYPE_STRING, $properties->getCustomPropertyType('Publisher')); - $this->assertEquals('PHPOffice Suite', $properties->getCustomPropertyValue('Publisher')); + + foreach ($customPropertySet as $propertyName => $testData) { + $this->assertTrue($properties->isCustomPropertySet($propertyName)); + $this->assertSame($testData['type'], $properties->getCustomPropertyType($propertyName)); + if ($properties->getCustomPropertyType($propertyName) == Properties::PROPERTY_TYPE_DATE) { + $this->assertSame($testData['value'], date('Y-m-d', $properties->getCustomPropertyValue($propertyName))); + } else { + $this->assertSame($testData['value'], $properties->getCustomPropertyValue($propertyName)); + } + } + } + + public function testLoadXlsxRowColumnAttributes() + { + $filename = './data/Reader/XLSX/rowColumnAttributeTest.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + for ($row = 1; $row <= 4; ++$row) { + $this->assertEquals($row * 5 + 10, floor($worksheet->getRowDimension($row)->getRowHeight())); + } + + $this->assertFalse($worksheet->getRowDimension(5)->getVisible()); + + for ($column = 1; $column <= 4; ++$column) { + $columnAddress = Coordinate::stringFromColumnIndex($column); + $this->assertEquals( + $column * 2 + 2, + floor($worksheet->getColumnDimension($columnAddress)->getWidth()) + ); + } + + $this->assertFalse($worksheet->getColumnDimension('E')->getVisible()); + } + + public function testLoadXlsxWithStyles() + { + $expectedColours = [ + 1 => ['A' => 'C00000', 'C' => 'FF0000', 'E' => 'FFC000'], + 3 => ['A' => '7030A0', 'C' => '000000', 'E' => 'FFFF00'], + 5 => ['A' => '002060', 'C' => '000000', 'E' => '92D050'], + 7 => ['A' => '0070C0', 'C' => '00B0F0', 'E' => '00B050'], + ]; + + $filename = './data/Reader/XLSX/stylesTest.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + for ($row = 1; $row <= 8; $row += 2) { + for ($column = 'A'; $column !== 'G'; ++$column, ++$column) { + $this->assertEquals( + $expectedColours[$row][$column], + $worksheet->getStyle($column . $row)->getFill()->getStartColor()->getRGB() + ); + } + } + } + + public function testLoadXlsxAutofilter() + { + $filename = './data/Reader/XLSX/autofilterTest.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + + $autofilter = $worksheet->getAutoFilter(); + $this->assertInstanceOf(AutoFilter::class, $autofilter); + $this->assertEquals('A1:D57', $autofilter->getRange()); + $this->assertEquals( + AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER, + $autofilter->getColumn('A')->getFilterType() + ); + } + + public function testLoadXlsxPageSetup() + { + $filename = './data/Reader/XLSX/pageSetupTest.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + + $pageMargins = $worksheet->getPageMargins(); + // Convert from inches to cm for testing + $this->assertEquals(2.5, $pageMargins->getTop() * 2.54); + $this->assertEquals(3.3, $pageMargins->getLeft() * 2.54); + $this->assertEquals(3.3, $pageMargins->getRight() * 2.54); + $this->assertEquals(1.3, $pageMargins->getHeader() * 2.54); + + $this->assertEquals(PageSetup::PAPERSIZE_A4, $worksheet->getPageSetup()->getPaperSize()); + $this->assertEquals(['A10', 'A20', 'A30', 'A40', 'A50'], array_keys($worksheet->getBreaks())); + } + + public function testLoadXlsxConditionalFormatting() + { + $filename = './data/Reader/XLSX/conditionalFormattingTest.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + + $worksheet = $spreadsheet->getActiveSheet(); + + $conditionalStyle = $worksheet->getCell('B2')->getStyle()->getConditionalStyles(); + + $this->assertNotEmpty($conditionalStyle); + $conditionalRule = $conditionalStyle[0]; + $this->assertNotEmpty($conditionalRule->getConditions()); + $this->assertEquals(Conditional::CONDITION_CELLIS, $conditionalRule->getConditionType()); + $this->assertEquals(Conditional::OPERATOR_BETWEEN, $conditionalRule->getOperatorType()); + $this->assertEquals(['200', '400'], $conditionalRule->getConditions()); + $this->assertInstanceOf(Style::class, $conditionalRule->getStyle()); } /** * Test load Xlsx file without cell reference. + * + * @doesNotPerformAssertions */ public function testLoadXlsxWithoutCellReference() { @@ -61,6 +188,8 @@ class XlsxTest extends TestCase /** * Test load Xlsx file with drawing having double attributes. + * + * @doesNotPerformAssertions */ public function testLoadXlsxWithDoubleAttrDrawing() { diff --git a/tests/data/Reader/Ods/propertyTest.ods b/tests/data/Reader/Ods/propertyTest.ods new file mode 100644 index 0000000000000000000000000000000000000000..7c691a5af1e0e9e802c839cc1f533970e2b4cbaa GIT binary patch literal 8874 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 WAj9<-000;Mg23k=DtLkw0QeuCwJSaV literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/autofilterTest.xlsx b/tests/data/Reader/XLSX/autofilterTest.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1a0b2f3fcb4a2958306944f923a7f898ab0b1a89 GIT binary patch literal 9985 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 WIXKAZ0szR6&s)eg!iMJOxBmm~SAOvT literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/conditionalFormattingTest.xlsx b/tests/data/Reader/XLSX/conditionalFormattingTest.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..801bad1f6316f73ea2a61b72596ad5a33f89aba0 GIT binary patch literal 9686 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 literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/pageSetupTest.xlsx b/tests/data/Reader/XLSX/pageSetupTest.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0f8f2540dfffdbb1281f123036c657cef9f6d863 GIT binary patch literal 11418 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 AEC2ui literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/propertyTest.xlsx b/tests/data/Reader/XLSX/propertyTest.xlsx index 3f3ac114954cf57bddfcb3380fd2d964d97aa268..de4b521f139049e802e7b77364d3d9e33ffc4277 100644 GIT binary patch delta 1835 zcmV+`2h{lVMwdsh(gp>|#QR+ylh+0ve{Y*G6n&qx|A72X!2}4PlBx+rt<-H%+qHd& zRJq_3uYj3tl2ldy`>vt%a}Qm+h+xcx<8$vl$L#KHtI}9gAsMe@gm-LY&^qHetFL49 z^K@10Un&;`=R6MCqWqdrJ|xHiq3egK!*=`eO7ZF_N?HbaO0T%e{PweU@XuV z-Xr|Ou(vt`CzVXx9l@L-Rl)`GdC) zp*whVS!W``mv{Sax*rBEe>UeyFg4wDj?HNpI%eqO>D*0|X=3AhNJjWk#!0J+2VH3B z7`darguapO5W#lTviyn0o&E4L_4w*E#O`&%dIvmXbR(Y>q^HLAoYgts#K^-AEPgvo z$G(S*O&2?7xhg<`PB4PB`+EnJT^B$YkDL(*7mT8b#puoson)MNf3E4+Ddg{sLUZb+ zV{<;6hkoF>ft`*!Le?`z-Bt_Cy3wdRdz|R)7sH;e$F9c65RqO(9Ok&|;qY2U$_(tF zhwi}u+r3xlRvk$-*@Y35$bcSp?ftPGx~83uJkt%JGl4sD%$b`wsW(oObn5B$=ubuT zKQ_$H5Yg}r(ku%i8`P;F*$sTTj_GPjBF&0au+p~8snM zkGz>Tnm1#z$4gU_GZ-sXy9we|6cA{+60)r}!E3P%-v`7ZYXz%S3!9(^8$9N3-(*~; zTp4&VN`pZOmN=!>rd)4=1ENkTwfq1Lvn$7F9at*Ue=u~PrlOkhBddTWQFKomKqeS6 zItYe(Q4A(Y!52w=GPM^FoI(u^v}kF(is>INf@$nO7d&#sY$W#D-89*~))ssO7xu-< zh1hl7s#|*-&VNk5@4oK+siAC#tT+UDCU}YxYsg<)iKGCFF4BxH@qO>pg#V1(Cfv8Mi7eVMM?gYNdyGiEHPegsN55eutax(7|i(d|vJP_hDr95gW!LWgvVSdDC3r&}l!-7&|)oa9oK37S@Ie{7lO zJcU@MbqNA(m$AN`)*5ALRjnL7>5X>yTggnO2Y32;xvz}fC^(DnNnRq)Li!PV(>V5+ zXSp+fYB|iASbow@bof2g16hvSu!r%~vh+O9Q7VDd5vUJ#p9oEay zNjq^hKZDj!YeNkqr*n>O>hCD~@&}_Y00030{{sL}O928c02BZQ00;m8lk6aU1hb^DJr#Y6UR|`$!eIaCm2rdxDte3ELHPd3MDpr z51^fa^o#rnb_n^c1iP0tcpZ}Oe@J%AO%ESg$?zXpg^yU_6Bxm7Mk64tUMmR{?;p{E zM{X{f?r(U-z7Ep0Xxo0Gwdquatcog~63y3tMnNNd4vaNgyF;u%#$FD!%8#RP|fC|(V)YJyxyw09(!I2P`Gu%f4+BIGB6hC z3vz`s%@ca1R#cZ(C#w|All4l9Z!Fhpz51~F+*`w3ojw_DU9ekLiFU1_0%~1QP1h&j zLMGQ2$EO$TUqnn_I_58AOmkBk2Z}Q!FkC+`6yzb3QK~B~Ex0~f97DTK=`A!5BvL$h z`v|&2yvrt+3Awxv7l-~lf6fBibHj=4d+ywh$>Ge7!YIb`Xtu;l?*}9!!z359)8&IM z40MWo?+>AmbT>u_PC8yZ(YVJCKiiDYPGjtcN!aXwUpd>VCk2_QwL9lcA+{+BhzpC~ zjngO$khSe&=e*D*DA0{PNPD>Vf${4S2oulsK)7HOLo7x2c;YUie?{QiF3x7QA9xdc z7G#mVoG&L~9QZNLqMnfR3!{Fk1!nzdHN8Df&Gt)RPd8&BG;1SN8yM|^x3;fZhK%nNKkp15`lO|s|y!p(vxTV%7qw8wlZ zlK-(`_J&BtZ;)YG98#&zB+YN&%XPvwGpayOLqfn4@~3O9$OWvEy4gg@I*tf5TnpJ$o9OkhUA>QpL)HkUbOW2H0~b9NZ{OtH zWL#VLYPA825*+bLjmx;%L`OuEQR?^+YUb9S(Ri@bwtr^mKW#+~<0n=DP2>2U)__bf zWV9DtnOQL!B?X@)&Dp9TAUFjDH8kjGvQFq9E`qJyKNkYBVAc{l<8K=6F0}<8z?pq@ zawfKIyKe7-4evjp-*;d3;nbCE`m8uaMJ{-T5)~A$jYM()hd$Dr&f}io&Od(v$6^ez z3$(zQkbj=VEilx!*fDz|&+G&?P^NL;#0k1qTj7eAvXmUi-uNBijsQqLUpc6pE?K>* z0LQH~=zA|-d_iQd&(^O>M;H-s0i{hJFY)ahwa`je-3PL@;MH}BEwdxnX)c)}wZ4Qp zg14o3?@uv@JO8Lk3F5iCx#Vxuy9-SXhl3zK`$ueOYC^uR z6tj#Q*#Ze>9k?S>0RR9{vu++F0e?|X!Y~wt-%b1vP2b15!H|({L^g(gu^)x*}UpnoAK_xk-xGq6`M@!!yDkYCCNB9>DgU-SdG(vvlucfRaRP7OY*j+5Mcj{ZV z0=vX~5INoKkp*VE08}57 zks(q95DEYQlPwAwlK~E|b=bB}4njHBSG6Tkm!(>R@z!)*J~}>LUDR)*FBR`w*)c8j z8MJc*14)8r766LY(e+pr%yyH${Q{T;vSX*c#H$Y!o9Ouc>v$%uIdSnO@;Icn^cx?C zazqulIB0|XiiSs;IlC2(jPZT!3a4LugBH7e#<)h^YrW_e299)-ko$%oZ4B@6cppw} zYX={nrSADNKfwY3&(BZ*#lO+AK@~)H3C=ZHFzXP&wA6PpwQ*)*{B8d~I{p`P@GrMs zo+zi#2SN-xk$emvzL{H%2MWu$3rV(-yzvW=T0v=uEg;8R?V`d5zIhc4CE?%U_c*$| z${V{sOme-&S{{vt!9&*QUJ;gd@96r1meMIz+_8MKAJuK{YVJBiLfVtsts{=MthqQ( zW@MdA{PUS;4RDfC{S`d=NBl4>zKlSvK{?GequWY|IboHfim>V?j@-4~*nUFbi28052tl}TCA zZkZ<~hA48c{I%*?&&IMn`#IqI%7#xzf})9v*T4$(Pm&yN_T-vF0RSs6000#5lku=& za<_M~HnO+3{!O>aRdwwPI5B+nEuW#zd0;LugQUfQf+{6-cnzx=o)N50Js9>7b%U%W zrFSRZNF%gs;23 zF?_{JY^?g22Hf$x|lI%jMu<_QSt?_7jO3 z?%@!&!@!`<2j3fEB_$?9u6v;tgpA_&qx8G@q3eLq<`U))bAzCu8bN&^T|GIsX?RN0`m=QAhJ*2UW}F zq?AuOZh7e$BJy3Y5EMof{+5od2Iy``7^TdgQofBQZ+I$^~ zSqllH)rn-N;7ml*Ru%wiWWWnS5LfCKzN~M=XOhR)*XPOk$W!=!FX8Cp{mf7#5yc|u z#7aW^SSo#r zZ>5j1PbL~yKuSvVkYn!BA;>$6z9>>8TTg?jS$c5v@zT{8QK7+DDTa%t%>Q3G8OWcU zb^+#MD_FQ*0pK9Oocxb={gs)2>KFvLtprE;@7~IlH!MeNIlImB?*h) zQv{>nIzCqaE#N*x<**{CzNv}$9gW%cbBoCGHSGH!Pon7yTzDy7Bu~Kf?kox2ZI9SB z+nVjc@bx*-P;x>Mn3}Go7pHG+zarm$Vkv~nM^LBnlo9fA?i}1rJP1o`{{n+J+ z*|(9QxpnYN|FewepJ-#6g3EZ$O8@`|e8wMT+}Xm^)Ww$`Rx)2=vy`G zAMq&92<#0arV|!C68TsnII1Kz~l*8~(K1;o9f zTlsy~?R#Psak2?$nGf*8YN{9UPb=@VIj!Qu9#4WCb)u74RH1D02JPuaGT1e~V%eOF zgQ_4@j<%xFW7T(W?CLV)ddIz3;a5h6SC`hPHFz!(uwj=C+S~@K&tKe)rT2zl(gi1( zgnpSY+94U!Prbhr8WD3jMvA=Amy#xxA`TBmq?qJ0Rv0}aqhAc5mVOZ$K_)e6A~NF= z>bAk)cjc@hhvvVyHV^Oqu~5L7j1r#z<2gc`5Amf#a{MCOkP30xY;DAQ5mM54nhzYS zCA9oTG`ZbX2}%v?f@Zf`n6k#6IwbR16Xt8PD%8_blBqw+5hobhUxw@@2Rd}TaXaZ0 zOc%w4o7djx^9?*^09G~$4s|l2r6<#DdPRqbe3$&1{)PZ&roNOtMFg&<|0%I98sb4D zwzPY{-Vuv1p!h9Etd-em)sx6q%werOYI=Xq>+FxNVYm_;+EWd&4~=s3?0d_-mE%Xt zjSX_+`k0EemE=%Y(WNn#=*AH9VW3ayRJ>uwstj$J)xst4O_8HoL6zg5;oNOmVjbqM zb5Sd8kv6@0Ea02u_EBMVk>M2N9|jv77R98_4s=0;aUS**mM?yzs)+@hK5>Mb679lllt2cjAa5S&g0@;@vCWu@(xk zX>ki)(Pf-s9rC!l2zb6Ev)GOIy_I!SbZGMVb_vf_vRpUe`A6jjyLm@d;5aHCoRL2YHP~$`~G)U z9%_z9<8fegVc!cNI=erzeT}17+nBUTS|vBhPR4I&JXBI+VNPtvLx-f`x>Bb8@G(u; zo-cVu5DKZ|G7g@+evfFHYJ7pLVw*hEMq0}aDO%#Gqp0X6$It7+s+Yz{-1*khwo4iyaDXwK& ztO2;NgywED&cJobm?6ntviDd+_yoag=~#3hl`C+_5^wc_j1;|nnMueFky51ny-V5^ zHeV+QBgIg!tC!YL*;j=jAL(>>a4fF>+xJ|t1(T`M_xyY>347i5YDXcCU}+hjApMji zvgWjM)A)ok+At9L;S53`yB{RnpD2jB>sEX`HVmn!O}yXhT7A^JjIS{W)6Uyvz1T{0 z)8V4G>}t|QOJrN*RD$&}qjWjoW``wLeM@;Uw%YVN zQWRQbA!pBwN>`0Y3_)3&NW}I$AM=)Mb>!tSuqrs>Glhtg0fjPSy@*l7Sg$E@$kO@v znr&5IvGp9Ej|rI}_&Pv-5hdG$V-}1`hss={uh|GXqfNdsJNfrWP*yULi+V-TT-+Pe zhsb>A4!?RO)b$eaEAj)OZa;gXjMMH+OEeBk0-xJLtRk=Tl5FB|zaJ$siCBVruoSjW z+#-)Cp9sEU?dz2$_{QEJW~9w9->lvxQ$^gHP7u_z$7FO6VY>uXN1HD3HZC-bN3!s6 zUzDUQwc)6^#0=tiH}N}bOc}C_?X*m*lNzKD97>nd4Ns_EWf?%2CYD-%p>axsqcgoZ zOSY{xw|5Nkz@m;sUH@?RN_16PmvcMojaT+tWVsDC$Zeu;(VuDNVRk`@ZCz3av2dE^ zXsKqc!SUc)fBmx$P_K@pfwI-=QSS~kbY znG2chC{P&UY8~iF-(m+QBWtN1-A4)CA)H}h;)>yQ=&ijha#p$> z63d;xc&a+<&D}mfl$w3kC~JnNLzaw0?AD~Y8f147Y=iG?`MMP;$o(bLM+vb00&0|Qx%OI<8z1{8$7 zNzXH$`k*=3nh`$Btk7cAg6G7tAB%!&Ek;V!97sv2GS$yxch_JA*F;OkYJG>ki_BKXer2S?J*q6=vCQZC%AV`OYe-1ctLeX}Jp0 zA4q54-jA-d&zZe&59IZ7@aHU2-c=N~Pa~tgpTQeWW;cjlNz#mkTW{y0VtDqWv3ap7 zy}*$C%CHOgCRjNSwXrPJ4M%P4N43trD=o@~L4uMF^5=tl2pz*IF*nPqp?SvRuiFgc zp{UW(vihH}8#eE^I&~I(98XY*vmY8pb2V+fL<&@6tDBF$6Wg!d!&2Qi(o!8VPrNhjdOs;%+) zUtgv$mpmuGfM28*!5$&ff8^KM#nZ;r`L}lXgX*%~G6>O^sM;TRc|7X&l^)&(TTTTE zrqNK4$Dk^TflAo*H93Vd_u;z4qWo5VALEgXtn%#UwxX=poOIyzCyh$gu!3kHS)sJ9 zW5dYld^l;kp$UO1CZ<*mB2oH)`rU(jiR+7=M7Qizo;mOIxXgMR!XzKSTw6iYlFX;jpr(MTou9;W4GSIbh5&(Cul1jq8x0**4 zo)j}7IE_0w{8>c}Y7_a&C&^IEBueW+0|hhds+)1`H8-Ow4kKS*YF}Ijc+xY`$IWRF(072u!U*#@adnAxyLol3ReDU`KRB{(ml!kziz2Pt{FvW^^_ySf zG7wOpljqP<+LYN7l7~!IVJ12Lap_<7-mr;HnMYI7UELICEy2E5DMT2yX6OJjPC)#i zMxwe?^BE<2=|1E+Y&n8GmQx3zQz`KZDy0bYvLmW->6D6kdQuU72QdeS9&KnWVL*N1 zi%5pzLb3Oxm-{~CxpCU&q@Oh$&PEiF#RP~|ynlz8yIbRt(e5(1h`-1Y_Jx;kR5rZt3&-BD8;&ZdEUwV~YG-rC~ zm?o&jL<*#kT1K3)La$zCL^!xPJ~>V3)rYnJh4Pa)tEL7-+bH4yGR4|^TUuDU2)cNviks_-V2Z zuHW{Drxsq42i}L14ky4nJt>U-yPF`;C22VXPW)OhJuvx|% z5|V*>4TRZfn|cO*x2yA_CS6Q!Tv}^TUDjU6c7w_sX=S0iKZ%8V>Fl#^jN@TtI5H+9wrVYQtR&5jNWx26* zb*Wl>0T#U$MiU3_eDXZ3o@_-=Gn&nY)TnZ$bG^H~N({q#unGG)^7eqOExGfPg_{P8 zp8-#|6#is2^boW&qI<*t zp6o68#UPZeUH&B}R(3gbHcO(u9jaU7ACF7wNHTLrQBbtZJv1EMI<`@4Ea3(t$Og>3 zxbAf2>8{pYN4D@ri+Y?@s*{Iwhz7tw6tVD^=G%O}K1=0_3H>I7gLNPhJmw-t>CfNl zydSsxu$^#*RYLsHPmV2lDRLmh!m9Yi&%l<;Kg}3JDR^KcIPp-yM}dD$JVOVE|K%Mx z;r`gN5{2x+>Kl3jbx(x2ipRC8h!|LIB&mK`Z3vOrXf9W1oL5U8QJ!!(AJ;fNyXdta z`_0?qUQ}uopIiSjetJMLayuUb1{%*A`#QyAVO7@;6DAS9;q0{_#oK@_aeW` zem{571>$}Bdf9h%?xp0%c&BU5Lb3i-`-Jdk`a(c5fW_h8KWO-Ky8ayhe$U=+^+hcCJ4IWP^<+@a_EUV}FJIs`)>mIj{Z${j~%5 zHNxM;^CuoWgLnYIKcw_m_}@$5U*SN)zrg<}h>9{W;MoNLUV?uCV6~nk{_WrY0on_p AzyJUM literal 0 HcmV?d00001 diff --git a/tests/data/Reader/XLSX/stylesTest.xlsx b/tests/data/Reader/XLSX/stylesTest.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..047a9df9a29cd1626c4dd7ea14757f0e48f4dc7d GIT binary patch literal 10876 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 SK>z>^`k{e_vDh!d2l#(