Merge branch 'master' into Feature_Images-from-string-or-streams

This commit is contained in:
Mark Baker
2022-11-06 16:03:30 +01:00
committed by GitHub
16 changed files with 731 additions and 194 deletions
-64
View File
@@ -312,70 +312,6 @@ parameters:
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Address\\:\\:sheetName\\(\\) has no return type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/Address.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchFirstValue\\(\\) has no return type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchFirstValue\\(\\) has parameter \\$lookupArray with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchFirstValue\\(\\) has parameter \\$lookupValue with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has no return type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has parameter \\$keySet with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has parameter \\$lookupArray with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchLargestValue\\(\\) has parameter \\$lookupValue with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchSmallestValue\\(\\) has no return type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchSmallestValue\\(\\) has parameter \\$lookupArray with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:matchSmallestValue\\(\\) has parameter \\$lookupValue with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:prepareLookupArray\\(\\) has no return type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:prepareLookupArray\\(\\) has parameter \\$lookupArray with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:prepareLookupArray\\(\\) has parameter \\$matchType with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:validateLookupArray\\(\\) has parameter \\$lookupArray with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:validateLookupValue\\(\\) has parameter \\$lookupValue with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\ExcelMatch\\:\\:validateMatchType\\(\\) has parameter \\$matchType with no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\LookupRef\\\\Lookup\\:\\:verifyResultVector\\(\\) has no return type specified\\.$#"
count: 1
+13
View File
@@ -0,0 +1,13 @@
<?php
use PhpOffice\PhpSpreadsheet\IOFactory;
require __DIR__ . '/../Header.php';
// Read from Xlsx (.xls) template
$helper->log('Load Xlsx template file');
$reader = IOFactory::createReader('Xlsx');
$spreadsheet = $reader->load(__DIR__ . '/../templates/27template.xlsx');
// Save
$helper->write($spreadsheet, __FILE__);
Binary file not shown.
@@ -39,12 +39,11 @@ class ExcelMatch
}
$lookupArray = Functions::flattenArray($lookupArray);
$matchType = (int) ($matchType ?? self::MATCHTYPE_LARGEST_VALUE);
try {
// Input validation
self::validateLookupValue($lookupValue);
self::validateMatchType($matchType);
$matchType = self::validateMatchType($matchType);
self::validateLookupArray($lookupArray);
$keySet = array_keys($lookupArray);
@@ -87,36 +86,78 @@ class ExcelMatch
return ExcelError::NA();
}
private static function matchFirstValue($lookupArray, $lookupValue)
/**
* @param mixed $lookupValue
*
* @return mixed
*/
private static function matchFirstValue(array $lookupArray, $lookupValue)
{
$wildcardLookup = ((bool) preg_match('/([\?\*])/', $lookupValue));
$wildcard = WildcardMatch::wildcard($lookupValue);
if (is_string($lookupValue)) {
$valueIsString = true;
$wildcard = WildcardMatch::wildcard($lookupValue);
} else {
$valueIsString = false;
$wildcard = '';
}
$valueIsNumeric = is_int($lookupValue) || is_float($lookupValue);
foreach ($lookupArray as $i => $lookupArrayValue) {
$typeMatch = ((gettype($lookupValue) === gettype($lookupArrayValue)) ||
(is_numeric($lookupValue) && is_numeric($lookupArrayValue)));
if (
$typeMatch && is_string($lookupValue) &&
$wildcardLookup && WildcardMatch::compare($lookupArrayValue, $wildcard)
$valueIsString
&& is_string($lookupArrayValue)
) {
// wildcard match
return $i;
} elseif ($lookupArrayValue === $lookupValue) {
// exact match
return $i;
if (WildcardMatch::compare($lookupArrayValue, $wildcard)) {
return $i; // wildcard match
}
} else {
if ($lookupArrayValue === $lookupValue) {
return $i; // exact match
}
if (
$valueIsNumeric
&& (is_float($lookupArrayValue) || is_int($lookupArrayValue))
&& $lookupArrayValue == $lookupValue
) {
return $i; // exact match
}
}
}
return null;
}
private static function matchLargestValue($lookupArray, $lookupValue, $keySet)
/**
* @param mixed $lookupValue
*
* @return mixed
*/
private static function matchLargestValue(array $lookupArray, $lookupValue, array $keySet)
{
if (is_string($lookupValue)) {
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
$wildcard = WildcardMatch::wildcard($lookupValue);
foreach (array_reverse($lookupArray) as $i => $lookupArrayValue) {
if (is_string($lookupArrayValue) && WildcardMatch::compare($lookupArrayValue, $wildcard)) {
return $i;
}
}
} else {
foreach ($lookupArray as $i => $lookupArrayValue) {
if ($lookupArrayValue === $lookupValue) {
return $keySet[$i];
}
}
}
}
$valueIsNumeric = is_int($lookupValue) || is_float($lookupValue);
foreach ($lookupArray as $i => $lookupArrayValue) {
$typeMatch = ((gettype($lookupValue) === gettype($lookupArrayValue)) ||
(is_numeric($lookupValue) && is_numeric($lookupArrayValue)));
if ($valueIsNumeric && (is_int($lookupArrayValue) || is_float($lookupArrayValue))) {
if ($lookupArrayValue <= $lookupValue) {
return array_search($i, $keySet);
}
}
$typeMatch = gettype($lookupValue) === gettype($lookupArrayValue);
if ($typeMatch && ($lookupArrayValue <= $lookupValue)) {
return array_search($i, $keySet);
}
@@ -125,21 +166,42 @@ class ExcelMatch
return null;
}
private static function matchSmallestValue($lookupArray, $lookupValue)
/**
* @param mixed $lookupValue
*
* @return mixed
*/
private static function matchSmallestValue(array $lookupArray, $lookupValue)
{
$valueKey = null;
if (is_string($lookupValue)) {
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
$wildcard = WildcardMatch::wildcard($lookupValue);
foreach ($lookupArray as $i => $lookupArrayValue) {
if (is_string($lookupArrayValue) && WildcardMatch::compare($lookupArrayValue, $wildcard)) {
return $i;
}
}
}
}
$valueIsNumeric = is_int($lookupValue) || is_float($lookupValue);
// The basic algorithm is:
// Iterate and keep the highest match until the next element is smaller than the searched value.
// Return immediately if perfect match is found
foreach ($lookupArray as $i => $lookupArrayValue) {
$typeMatch = gettype($lookupValue) === gettype($lookupArrayValue);
$bothNumeric = $valueIsNumeric && (is_int($lookupArrayValue) || is_float($lookupArrayValue));
if ($lookupArrayValue === $lookupValue) {
// Another "special" case. If a perfect match is found,
// the algorithm gives up immediately
return $i;
} elseif ($typeMatch && $lookupArrayValue >= $lookupValue) {
}
if ($bothNumeric && $lookupValue == $lookupArrayValue) {
return $i; // exact match, as above
}
if (($typeMatch || $bothNumeric) && $lookupArrayValue >= $lookupValue) {
$valueKey = $i;
} elseif ($typeMatch && $lookupArrayValue < $lookupValue) {
//Excel algorithm gives up immediately if the first element is smaller than the searched value
@@ -150,6 +212,9 @@ class ExcelMatch
return $valueKey;
}
/**
* @param mixed $lookupValue
*/
private static function validateLookupValue($lookupValue): void
{
// Lookup_value type has to be number, text, or logical values
@@ -158,18 +223,29 @@ class ExcelMatch
}
}
private static function validateMatchType($matchType): void
/**
* @param mixed $matchType
*/
private static function validateMatchType($matchType): int
{
// Match_type is 0, 1 or -1
if (
($matchType !== self::MATCHTYPE_FIRST_VALUE) &&
($matchType !== self::MATCHTYPE_LARGEST_VALUE) && ($matchType !== self::MATCHTYPE_SMALLEST_VALUE)
) {
throw new Exception(ExcelError::NA());
// However Excel accepts other numeric values,
// including numeric strings and floats.
// It seems to just be interested in the sign.
if (!is_numeric($matchType)) {
throw new Exception(ExcelError::Value());
}
if ($matchType > 0) {
return self::MATCHTYPE_LARGEST_VALUE;
}
if ($matchType < 0) {
return self::MATCHTYPE_SMALLEST_VALUE;
}
return self::MATCHTYPE_FIRST_VALUE;
}
private static function validateLookupArray($lookupArray): void
private static function validateLookupArray(array $lookupArray): void
{
// Lookup_array should not be empty
$lookupArraySize = count($lookupArray);
@@ -178,7 +254,10 @@ class ExcelMatch
}
}
private static function prepareLookupArray($lookupArray, $matchType)
/**
* @param mixed $matchType
*/
private static function prepareLookupArray(array $lookupArray, $matchType): array
{
// Lookup_array should contain only number, text, or logical values, or empty (null) cells
foreach ($lookupArray as $i => $value) {
+51 -24
View File
@@ -763,7 +763,7 @@ class Xlsx extends BaseReader
$sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheet);
$sheetViewOptions->load($this->getReadDataOnly(), $this->styleReader);
(new ColumnAndRowAttributes($docSheet, $xmlSheet))
(new ColumnAndRowAttributes($docSheet, $xmlSheetNS))
->load($this->getReadFilter(), $this->getReadDataOnly());
}
@@ -913,11 +913,13 @@ class Xlsx extends BaseReader
$this->readTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip);
}
if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) {
foreach ($xmlSheet->mergeCells->mergeCell as $mergeCell) {
$mergeRef = (string) $mergeCell['ref'];
if ($xmlSheetNS && $xmlSheetNS->mergeCells && $xmlSheetNS->mergeCells->mergeCell && !$this->readDataOnly) {
foreach ($xmlSheetNS->mergeCells->mergeCell as $mergeCellx) {
/** @scrutinizer ignore-call */
$mergeCell = $mergeCellx->attributes();
$mergeRef = (string) ($mergeCell['ref'] ?? '');
if (strpos($mergeRef, ':') !== false) {
$docSheet->mergeCells((string) $mergeCell['ref'], Worksheet::MERGE_CELL_CONTENT_HIDE);
$docSheet->mergeCells($mergeRef, Worksheet::MERGE_CELL_CONTENT_HIDE);
}
}
}
@@ -940,7 +942,10 @@ class Xlsx extends BaseReader
}
$node->addAttribute('sqref', $item->children(Namespaces::DATA_VALIDATIONS2)->sqref);
if (isset($item->formula1)) {
$node->addChild('formula1', $item->formula1->children(Namespaces::DATA_VALIDATIONS2)->f);
$childNode = $node->addChild('formula1');
if ($childNode !== null) { // null should never happen
$childNode[0] = (string) $item->formula1->children(Namespaces::DATA_VALIDATIONS2)->f; // @phpstan-ignore-line
}
}
}
}
@@ -1157,14 +1162,21 @@ class Xlsx extends BaseReader
}
// Header/footer images
if ($xmlSheet && $xmlSheet->legacyDrawingHF) {
if ($xmlSheetNS && $xmlSheetNS->legacyDrawingHF) {
$vmlHfRid = '';
$vmlHfRidAttr = $xmlSheetNS->legacyDrawingHF->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT);
if ($vmlHfRidAttr !== null && isset($vmlHfRidAttr['id'])) {
$vmlHfRid = (string) $vmlHfRidAttr['id'][0];
}
if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
$relsWorksheet = $this->loadZipNoNamespace(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels', Namespaces::RELATIONSHIPS);
$vmlRelationship = '';
foreach ($relsWorksheet->Relationship as $ele) {
if ($ele['Type'] == Namespaces::VML) {
if ((string) $ele['Type'] == Namespaces::VML && (string) $ele['Id'] === $vmlHfRid) {
$vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
break;
}
}
@@ -1199,20 +1211,23 @@ class Xlsx extends BaseReader
$imageData = self::getAttributes($imageData, Namespaces::URN_MSOFFICE);
$style = self::toCSSArray((string) $shape['style']);
$hfImages[(string) $shape['id']] = new HeaderFooterDrawing();
if (isset($imageData['title'])) {
$hfImages[(string) $shape['id']]->setName((string) $imageData['title']);
}
if (array_key_exists((string) $imageData['relid'], $drawings)) {
$shapeId = (string) $shape['id'];
$hfImages[$shapeId] = new HeaderFooterDrawing();
if (isset($imageData['title'])) {
$hfImages[$shapeId]->setName((string) $imageData['title']);
}
$hfImages[(string) $shape['id']]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false);
$hfImages[(string) $shape['id']]->setResizeProportional(false);
$hfImages[(string) $shape['id']]->setWidth($style['width']);
$hfImages[(string) $shape['id']]->setHeight($style['height']);
if (isset($style['margin-left'])) {
$hfImages[(string) $shape['id']]->setOffsetX($style['margin-left']);
$hfImages[$shapeId]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false);
$hfImages[$shapeId]->setResizeProportional(false);
$hfImages[$shapeId]->setWidth($style['width']);
$hfImages[$shapeId]->setHeight($style['height']);
if (isset($style['margin-left'])) {
$hfImages[$shapeId]->setOffsetX($style['margin-left']);
}
$hfImages[$shapeId]->setOffsetY($style['margin-top']);
$hfImages[$shapeId]->setResizeProportional(true);
}
$hfImages[(string) $shape['id']]->setOffsetY($style['margin-top']);
$hfImages[(string) $shape['id']]->setResizeProportional(true);
}
$docSheet->getHeaderFooter()->setImages($hfImages);
@@ -1229,6 +1244,9 @@ class Xlsx extends BaseReader
if (substr($drawingFilename, 0, 7) === 'xl//xl/') {
$drawingFilename = substr($drawingFilename, 4);
}
if (substr($drawingFilename, 0, 8) === '/xl//xl/') {
$drawingFilename = substr($drawingFilename, 5);
}
if ($zip->locateName($drawingFilename)) {
$relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS);
$drawings = [];
@@ -1243,10 +1261,10 @@ class Xlsx extends BaseReader
}
}
if ($xmlSheet->drawing && !$this->readDataOnly) {
if ($xmlSheetNS->drawing && !$this->readDataOnly) {
$unparsedDrawings = [];
$fileDrawing = null;
foreach ($xmlSheet->drawing as $drawing) {
foreach ($xmlSheetNS->drawing as $drawing) {
$drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id');
$fileDrawing = $drawings[$drawingRelId];
$drawingFilename = dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels';
@@ -1261,7 +1279,13 @@ class Xlsx extends BaseReader
$hyperlinks[(string) $ele['Id']] = (string) $ele['Target'];
}
if ($eleType === "$xmlNamespaceBase/image") {
$images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);
$eleTarget = (string) $ele['Target'];
if (substr($eleTarget, 0, 4) === '/xl/') {
$eleTarget = substr($eleTarget, 1);
$images[(string) $ele['Id']] = $eleTarget;
} else {
$images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $eleTarget);
}
} elseif ($eleType === "$xmlNamespaceBase/chart") {
if ($this->includeCharts) {
$eleTarget = (string) $ele['Target'];
@@ -2030,7 +2054,10 @@ class Xlsx extends BaseReader
$unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings'];
foreach ($sheetPrinterSettings as $rId => $printerSettings) {
$rId = substr($rId, 3) . 'ps'; // rIdXXX, add 'ps' suffix to avoid identical resource identifier collision with unparsed vmlDrawing
$rId = substr($rId, 3); // rIdXXX
if (substr($rId, -2) !== 'ps') {
$rId = $rId . 'ps'; // rIdXXX, add 'ps' suffix to avoid identical resource identifier collision with unparsed vmlDrawing
}
$unparsedPrinterSettings[$rId] = [];
$unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']);
$unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target'];
@@ -137,15 +137,19 @@ class ColumnAndRowAttributes extends BaseParserClass
{
$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);
foreach ($worksheetCols->col as $columnx) {
/** @scrutinizer ignore-call */
$column = $columnx->attributes();
if ($column !== null) {
$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;
if ((int) ($column['max']) == 16384) {
break;
}
}
}
}
@@ -153,23 +157,26 @@ class ColumnAndRowAttributes extends BaseParserClass
return $columnAttributes;
}
private function readColumnRangeAttributes(SimpleXMLElement $column, bool $readDataOnly): array
private function readColumnRangeAttributes(?SimpleXMLElement $column, bool $readDataOnly): array
{
$columnAttributes = [];
if ($column['style'] && !$readDataOnly) {
$columnAttributes['xfIndex'] = (int) $column['style'];
if ($column !== null) {
if (isset($column['style']) && !$readDataOnly) {
$columnAttributes['xfIndex'] = (int) $column['style'];
}
if (isset($column['hidden']) && self::boolean($column['hidden'])) {
$columnAttributes['visible'] = false;
}
if (isset($column['collapsed']) && self::boolean($column['collapsed'])) {
$columnAttributes['collapsed'] = true;
}
if (isset($column['outlineLevel']) && ((int) $column['outlineLevel']) > 0) {
$columnAttributes['outlineLevel'] = (int) $column['outlineLevel'];
}
if (isset($column['width'])) {
$columnAttributes['width'] = (float) $column['width'];
}
}
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;
}
@@ -189,21 +196,25 @@ class ColumnAndRowAttributes extends BaseParserClass
{
$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'];
foreach ($worksheetRow as $rowx) {
/** @scrutinizer ignore-call */
$row = $rowx->attributes();
if ($row !== null) {
if (isset($row['ht']) && !$readDataOnly) {
$rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
}
if (isset($row['hidden']) && self::boolean($row['hidden'])) {
$rowAttributes[(int) $row['r']]['visible'] = false;
}
if (isset($row['collapsed']) && self::boolean($row['collapsed'])) {
$rowAttributes[(int) $row['r']]['collapsed'] = true;
}
if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) {
$rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
}
if (isset($row['s']) && !$readDataOnly) {
$rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
}
}
}
+5 -1
View File
@@ -80,7 +80,11 @@ class PageSetup extends BaseParserClass
$relAttributes = $xmlSheet->pageSetup->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT);
if (isset($relAttributes['id'])) {
$unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id'];
$relid = (string) $relAttributes['id'];
if (substr($relid, -2) !== 'ps') {
$relid .= 'ps';
}
$unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = $relid;
}
}
+8 -6
View File
@@ -294,12 +294,14 @@ class Rels extends WriterPart
}
foreach ($unparsedLoadedData['sheets'][$worksheet->getCodeName()][$relationship] as $rId => $value) {
$this->writeRelationship(
$objWriter,
$rId,
$type,
$value['relFilePath']
);
if (substr($rId, 0, 17) !== '_headerfooter_vml') {
$this->writeRelationship(
$objWriter,
$rId,
$type,
$value['relFilePath']
);
}
}
}
@@ -3,25 +3,77 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PHPUnit\Framework\TestCase;
class MatchTest extends TestCase
class MatchTest extends AllSetupTeardown
{
protected function setUp(): void
/**
* @dataProvider providerMATCH
*
* @param mixed $expectedResult
* @param mixed $input
* @param mixed $type
*/
public function testMATCH($expectedResult, $input, array $array, $type = null): void
{
Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL);
if (is_array($expectedResult)) {
$expectedResult = $expectedResult[0];
}
if ($expectedResult === 'incomplete') {
self::markTestIncomplete('Undefined behavior with different results in Excel and PhpSpreadsheet');
}
$this->mightHaveException($expectedResult);
$sheet = $this->getSheet();
$maxRow = count($array);
$formulaArray = '';
for ($row = 1; $row <= $maxRow; ++$row) {
$this->setCell("A$row", $array[$row - 1]);
$formulaArray = "A1:A$row";
}
$this->setCell('B1', $input);
if ($type === null) {
$formula = "=MATCH(B1,$formulaArray)";
} else {
$formula = "=MATCH(B1, $formulaArray, $type)";
}
$sheet->getCell('D1')->setValue($formula);
$result = $sheet->getCell('D1')->getCalculatedValue();
self::assertEquals($expectedResult, $result);
}
/**
* @dataProvider providerMATCH
*
* @param mixed $expectedResult
* @param mixed $input
* @param mixed $type
*/
public function testMATCH($expectedResult, ...$args): void
public function testMATCHLibre($expectedResult, $input, array $array, $type = null): void
{
$result = LookupRef::MATCH(...$args);
$this->setOpenOffice();
if (is_array($expectedResult)) {
$expectedResult = $expectedResult[1];
}
if ($expectedResult === 'incomplete') {
self::markTestIncomplete('Undefined behavior with different results in Excel and PhpSpreadsheet');
}
$this->mightHaveException($expectedResult);
$sheet = $this->getSheet();
$maxRow = count($array);
$formulaArray = '';
for ($row = 1; $row <= $maxRow; ++$row) {
$this->setCell("A$row", $array[$row - 1]);
$formulaArray = "A1:A$row";
}
$this->setCell('B1', $input);
if ($type === null) {
$formula = "=MATCH(B1,$formulaArray)";
} else {
$formula = "=MATCH(B1, $formulaArray, $type)";
}
$sheet->getCell('D1')->setValue($formula);
$result = $sheet->getCell('D1')->getCalculatedValue();
self::assertEquals($expectedResult, $result);
}
@@ -0,0 +1,163 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
class Issue1482Test extends \PHPUnit\Framework\TestCase
{
/**
* @var string
*/
private static $testbook = 'tests/data/Reader/XLSX/issue.1482.xlsx';
public function testPreliminaries(): void
{
$file = 'zip://';
$file .= self::$testbook;
$file .= '#xl/worksheets/sheet.xml';
$data = file_get_contents($file);
// confirm that file contains expected namespaced xml tag
if ($data === false) {
self::fail('Unable to read file');
} else {
self::assertStringContainsString('<x:drawing r:id="rId1" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" />', $data);
}
}
public function testLoadXlsx(): void
{
$reader = new Xlsx();
$spreadsheet = $reader->load(self::$testbook);
$sheet = $spreadsheet->getSheet(0);
$expectedColWidths = [
0.95,
7.45,
0.6,
4.4,
5.91,
3.92,
7.71,
2.12,
2.56,
6.94,
9.6,
1.82,
3.01,
8.63,
10.37,
2.09,
];
$columnDimensions = $sheet->getColumnDimensions();
$actualColWidths = [];
foreach ($columnDimensions as $columnDimension) {
$actualColWidths[] = $columnDimension->getWidth();
}
self::assertSame($expectedColWidths, $actualColWidths);
$expectedRowHeights = [
14.0,
7.0,
14.0,
80.0,
234.0,
63.0,
13.0,
19.0,
18.0,
18.0,
18.0,
18.0,
18.0,
18.0,
18.0,
18.0,
];
$rowHeights = $sheet->getRowDimensions();
$actualRowHeights = [];
foreach ($rowHeights as $rowDimension) {
$actualRowHeights[] = $rowDimension->getRowHeight();
}
self::assertSame($expectedRowHeights, $actualRowHeights);
$expectedMergeCells = [
'B1:E1',
'F1:G1',
'J1:L1',
'M1:N1',
'B3:E3',
'F3:G3',
'B5:O5',
'A7:C7',
'D7:M7',
'A8:B8',
'C8:D8',
'E8:F8',
'G8:H8',
'I8:J8',
'L8:P8',
'A9:B9',
'C9:D9',
'E9:F9',
'G9:H9',
'I9:J9',
'L9:P9',
'A10:B10',
'C10:D10',
'E10:F10',
'G10:H10',
'I10:J10',
'L10:P10',
'A11:B11',
'C11:D11',
'E11:F11',
'G11:H11',
'I11:J11',
'L11:P11',
'A12:B12',
'C12:D12',
'E12:F12',
'G12:H12',
'I12:J12',
'L12:P12',
'A13:B13',
'C13:D13',
'E13:F13',
'G13:H13',
'I13:J13',
'L13:P13',
'A14:B14',
'C14:D14',
'E14:F14',
'G14:H14',
'I14:J14',
'L14:P14',
'A15:B15',
'C15:D15',
'E15:F15',
'G15:H15',
'I15:J15',
'L15:P15',
'A16:B16',
'C16:D16',
'E16:F16',
'G16:H16',
'I16:J16',
'L16:P16',
];
self::assertSame($expectedMergeCells, array_keys($sheet->getMergeCells()));
$drawingCollection = $sheet->getDrawingCollection();
self::assertCount(1, $drawingCollection);
$drawing = $drawingCollection[0];
if ($drawing === null) {
self::fail('Unexpected null instead of drawing');
} else {
self::assertSame('B5', $drawing->getCoordinates());
self::assertSame('', $drawing->getCoordinates2());
self::assertFalse($drawing->getResizeProportional());
self::assertSame(600, $drawing->getWidth());
self::assertSame(312, $drawing->getHeight());
}
// no name, no description, no type
$spreadsheet->disconnectWorksheets();
}
}
@@ -0,0 +1,66 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
use PHPUnit\Framework\TestCase;
class Issue3126Test extends TestCase
{
public function testReloadXlsxWorkbookProperties(): void
{
$filename = 'tests/data/Reader/XLSX/issue.3126.xlsx';
$reader = new XlsxReader();
$generation1 = $reader->load($filename);
$gen1sheet = $generation1->getActiveSheet();
$gen1hf = $gen1sheet->getHeaderFooter();
$gen1Images = $gen1hf->getImages();
self::assertCount(1, $gen1Images);
$gen1hfName = array_key_exists('LF', $gen1Images) ? $gen1Images['LF']->getName() : '';
self::assertSame('fleche-verte-up-right', $gen1hfName);
$pageSetupRel = $generation1->getUnparsedLoadedData()['sheets']['Worksheet']['pageSetupRelId'] ?? '';
self::assertSame('rId1ps', $pageSetupRel);
$pageSetupPath = $generation1->getUnparsedLoadedData()['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? '';
self::assertSame('xl/printerSettings/printerSettings1.bin', $pageSetupPath);
$generation2Name = File::temporaryFilename();
$writer = new XlsxWriter($generation1);
$writer->save($generation2Name);
$generation1->disconnectWorksheets();
$reader2 = new XlsxReader();
$generation2 = $reader2->load($generation2Name);
$gen2sheet = $generation2->getActiveSheet();
$gen2hf = $gen2sheet->getHeaderFooter();
$gen2Images = $gen2hf->getImages();
self::assertCount(1, $gen2Images);
$gen2hfName = array_key_exists('LF', $gen2Images) ? $gen2Images['LF']->getName() : '';
self::assertSame('fleche-verte-up-right', $gen2hfName);
$pageSetupRel = $generation2->getUnparsedLoadedData()['sheets']['Worksheet']['pageSetupRelId'] ?? '';
self::assertSame('rId1ps', $pageSetupRel);
$pageSetupPath = $generation2->getUnparsedLoadedData()['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? '';
self::assertSame('xl/printerSettings/printerSettings1.bin', $pageSetupPath);
$generation3Name = File::temporaryFilename();
$writer = new XlsxWriter($generation2);
$writer->save($generation3Name);
$generation2->disconnectWorksheets();
$reader3 = new XlsxReader();
$generation3 = $reader3->load($generation3Name);
$gen3sheet = $generation3->getActiveSheet();
$gen3hf = $gen3sheet->getHeaderFooter();
$gen3Images = $gen3hf->getImages();
self::assertCount(1, $gen3Images);
$gen3hfName = array_key_exists('LF', $gen3Images) ? $gen3Images['LF']->getName() : '';
self::assertSame('fleche-verte-up-right', $gen3hfName);
$pageSetupRel = $generation3->getUnparsedLoadedData()['sheets']['Worksheet']['pageSetupRelId'] ?? '';
self::assertSame('rId1ps', $pageSetupRel);
$pageSetupPath = $generation3->getUnparsedLoadedData()['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? '';
self::assertSame('xl/printerSettings/printerSettings1.bin', $pageSetupPath);
unlink($generation2Name);
unlink($generation3Name);
$generation3->disconnectWorksheets();
}
}
@@ -0,0 +1,23 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PHPUnit\Framework\TestCase;
class Issue3145Test extends TestCase
{
public function testIssue3145(): void
{
$filename = 'tests/data/Reader/XLSX/issue.3145.xlsx';
$reader = IOFactory::createReader('Xlsx');
$spreadsheet = $reader->load($filename);
$sheet = $spreadsheet->getActiveSheet();
self::assertEquals('Headline A', $sheet->getCell('A1')->getValue());
self::assertEquals('Configdential B', $sheet->getCell('A2')->getValue());
self::assertSame('OFFSET(INDIRECT(SUBSTITUTE($A2," ","")),0,0,COUNTA(INDIRECT(SUBSTITUTE($A2," ","")&"Col")),1)', $sheet->getCell('B2')->getDataValidation()->getFormula1());
$spreadsheet->disconnectWorksheets();
}
}
+186 -25
View File
@@ -1,6 +1,18 @@
<?php
return [
'unsorted numeric array still finds match with type 1?' => [
2, // Expected
2, // Input
[2, 0, 4, 3],
1,
],
'unsorted array still finds match with type 1?' => [
6,
'Amplitude',
['Aardvark', 'Apple', 'A~*e', 'A*e', 'A[solve', 'Amplitude', 'Adverse', 'Apartment'],
1,
],
// Third argument = 0
[
1, // Expected
@@ -33,12 +45,6 @@ return [
[2, 3, 4, 3],
1,
],
[
2, // Expected
2, // Input
[2, 0, 4, 3],
1,
],
[
3, // Expected
2, // Input
@@ -114,10 +120,10 @@ return [
1,
],
// 0s are causing errors, because things like 0 == 'x' is true. Thanks PHP!
[
'0 does not match x' => [
3,
'x',
[[0], [0], ['x'], ['x'], ['x']],
[0, 0, 'x', 'x', 'x'],
0,
],
[
@@ -201,12 +207,42 @@ return [
[true, false, 'a', 'z', 2, 888],
-1,
],
[
'number compared to string type -1' => [
'#N/A', // Expected
6,
['6'],
-1,
],
'number compared to string type 0' => [
'#N/A', // Expected
6,
['6'],
0,
],
'number compared to string type 1' => [
'#N/A', // Expected
6,
['6'],
1,
],
'string compared to number type -1' => [
'#N/A', // Expected
'6',
[6],
-1,
],
'string compared to number type 0' => [
'#N/A', // Expected
'6',
[6],
0,
],
'string compared to number type 1' => [
'#N/A', // Expected
'6',
[6],
1,
],
// expression match
[
2, // Expected
@@ -286,20 +322,38 @@ return [
[1, 22, 'aaa'],
1,
],
[
'#N/A', // Expected (Invalid match type)
'int match type other than 0/1/-1 is accepted' => [
3, // Expected (Invalid match type)
'abc',
[1, 22, 'aaa'],
123,
],
[
'float match type is accepted' => [
3, // Expected (Invalid match type)
'abc',
[1, 22, 'aaa'],
123.5,
],
'numeric string match type is accepted' => [
3, // Expected (Invalid match type)
'abc',
[1, 22, 'aaa'],
'89.7',
],
'non-numeric string match type is not accepted' => [
'#VALUE!', // Expected (Invalid match type)
'abc',
[1, 22, 'aaa'],
'"x"',
],
'empty lookup array' => [
'#N/A', // Expected (Empty lookup array)
'abc',
[],
1,
],
[
8,
'wildcard match 1 with type -1' => [
[8, 2], // LibreOffice matches wildcard but shouldn't
'A*e',
['Aardvark', 'Apple', 'Armadillo', 'Acre', 'Absolve', 'Amplitude', 'Adverse', 'Apartment'],
-1,
@@ -310,14 +364,22 @@ return [
['Aardvark', 'Apple', 'Armadillo', 'Acre', 'Absolve', 'Amplitude', 'Adverse', 'Apartment'],
0,
],
[
'#N/A',
'A*e',
['Aardvark', 'Apple', 'Armadillo', 'Acre', 'Absolve', 'Amplitude', 'Adverse', 'Apartment'],
1,
'wildcard match with tilde' => [
4,
'A~*e',
['Aardvark', 'Apple', 'A~*e', 'A*e', 'Absolve', 'Amplitude', 'Adverse', 'Apartment'],
0,
],
[
8,
'string with preg_quote escaped character' => [
5,
'A[solve',
['Aardvark', 'Apple', 'A~*e', 'A*e', 'A[solve', 'Amplitude', 'Adverse', 'Apartment'],
0,
],
// duplicate test deleted - see 'wildcard with type 1' below
// end of deletions
'wildcard match 2 with type -1' => [
[8, 5],
'A?s*e',
['Aardvark', 'Apple', 'Armadillo', 'Acre', 'Absolve', 'Amplitude', 'Adverse', 'Apartment'],
-1,
@@ -328,14 +390,14 @@ return [
['Aardvark', 'Apple', 'Armadillo', 'Acre', 'Absolve', 'Amplitude', 'Adverse', 'Apartment'],
0,
],
[
'#N/A',
'wildcard with type 1' => [
['#N/A', 2], // LibreOffice matches wildcard but shouldn't
'A*e',
['Aardvark', 'Apple', 'Armadillo', 'Acre', 'Absolve', 'Amplitude', 'Adverse', 'Apartment'],
1,
],
[
8,
'wildcard match 3 with type -1' => [
[8, 3], // LibreOffice matches wildcard but shouldn't
'*verse',
['Obtuse', 'Amuse', 'Obverse', 'Inverse', 'Assurance', 'Amplitude', 'Adverse', 'Apartment'],
-1,
@@ -358,4 +420,103 @@ return [
['abc123fff', 'abc/123fff'],
0,
],
'float lookup int array type0' => [
1, // Expected
2.0, // Input
[2, 3, 4, 5],
0,
],
'int lookup float array type0' => [
2, // Expected
3, // Input
[2, 3.0, 4, 5],
0,
],
'int lookup float array type0 not equal' => [
'#N/A', // Expected
3, // Input
[2, 3.1, 4, 5],
0,
],
'float lookup int array type0 not equal' => [
'#N/A', // Expected
3.1, // Input
[2, 3, 4, 5],
0,
],
'float lookup int array type1 equal' => [
1, // Expected
2.0, // Input
[2, 3, 4, 5],
1,
],
'int lookup float array type1 equal' => [
2, // Expected
3, // Input
[2, 3.0, 4, 5],
1,
],
'float lookup int array type1 less' => [
1, // Expected
2.5, // Input
[2, 3, 4, 5],
1,
],
'int lookup float array type1 less' => [
2, // Expected
3, // Input
[2, 2.9, 4, 5],
1,
],
'float lookup int array type -1 equal' => [
4, // Expected
2.0, // Input
[5, 4, 3, 2],
-1,
],
'int lookup float array type -1 equal' => [
3, // Expected
3, // Input
[5, 4, 3.0, 2],
-1,
],
'float lookup int array type -1 greater' => [
2, // Expected
3.5, // Input
[5, 4, 3, 2],
-1,
],
'int lookup float array type -1 greater' => [
2, // Expected
3, // Input
[5, 4, 2.9, 2],
-1,
],
'default type is type1' => [
3, // Expected
4, // Input
[1, 2, 3, 5],
],
'same as previous but explicit type1' => [
3, // Expected
4, // Input
[1, 2, 3, 5],
],
'same as previous but type0 so different result' => [
'#N/A', // Expected
4, // Input
[1, 2, 3, 5],
0,
],
'undefined behavior N/A in Excel 4 in PhpSpreadsheet' => [
'incomplete', // Expected
4, // Input
[8, 6, 3, 1],
],
'same as previous but type -1 so match' => [
2, // Expected
4, // Input
[8, 6, 3, 1],
-1,
],
];
Binary file not shown.
Binary file not shown.
Binary file not shown.