diff --git a/.gitattributes b/.gitattributes index 91b0574a0..b70296d12 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,7 +3,9 @@ /.gitattributes export-ignore /.github export-ignore /.gitignore export-ignore -/.php_cs.dist export-ignore +/.php-cs-fixer.dist.php export-ignore +/.phpcs.xml.dist export-ignore +/.readthedocs.yaml export-ignore /.scrutinizer.yml export-ignore /CHANGELOG.PHPExcel.md export-ignore /bin export-ignore @@ -11,6 +13,8 @@ /docs export-ignore /infra export-ignore /mkdocs.yml export-ignore +/phpstan-baseline.neon export-ignore +/phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore /samples export-ignore /tests export-ignore diff --git a/CHANGELOG.md b/CHANGELOG.md index c5753337b..f567e658c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com) and this project adheres to [Semantic Versioning](https://semver.org). -## TBD - 3.6.0 +## TBD - 3.7.0 + +### Added + +- Nothing yet. + +### Changed + +- Nothing yet. + +### Moved + +- Nothing yet. + +### Deprecated + +- Drawing::setIsUrl is unneeded. The property is set when setPath determines whether path is a url. + +### Fixed + +- More context options may be needed for http(s) image. [Php issue 17121](https://github.com/php/php-src/issues/17121) [PR #4276](https://github.com/PHPOffice/PhpSpreadsheet/pull/4276) + +## 2024-12-08 - 3.6.0 ### Added @@ -25,9 +47,13 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed +- Html Reader/Writer Better Handling of Booleans. [PR #4257](https://github.com/PHPOffice/PhpSpreadsheet/pull/4257) +- Fill Patterns/Colors When Xml Attributes are Missing. [Issue #4248](https://github.com/PHPOffice/PhpSpreadsheet/issues/4248) [PR #4250](https://github.com/PHPOffice/PhpSpreadsheet/pull/4250) +- Remove Unneccesary files from Composer Package. [PR #4262](https://github.com/PHPOffice/PhpSpreadsheet/pull/4262) - Swapped row and column indexes in ReferenceHelper. [Issue #4246](https://github.com/PHPOffice/PhpSpreadsheet/issues/4246) [PR #4247](https://github.com/PHPOffice/PhpSpreadsheet/pull/4247) - Fix minor break handling drawings. [Issue #4241](https://github.com/PHPOffice/PhpSpreadsheet/issues/4241) [PR #4244](https://github.com/PHPOffice/PhpSpreadsheet/pull/4244) - Ignore cell formatting when the format is a single @. [Issue #4242](https://github.com/PHPOffice/PhpSpreadsheet/issues/4242) [PR #4243](https://github.com/PHPOffice/PhpSpreadsheet/pull/4243) +- Upgrade Dompdf to Php-8.4 compatible version [PR #4267](https://github.com/PHPOffice/PhpSpreadsheet/pull/4267) ## 2024-11-22 - 3.5.0 diff --git a/composer.lock b/composer.lock index 6f3dc1766..6533b316a 100644 --- a/composer.lock +++ b/composer.lock @@ -788,16 +788,16 @@ }, { "name": "dompdf/dompdf", - "version": "v3.0.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "fbc7c5ee5d94f7a910b78b43feb7931b7f971b59" + "reference": "2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/fbc7c5ee5d94f7a910b78b43feb7931b7f971b59", - "reference": "fbc7c5ee5d94f7a910b78b43feb7931b7f971b59", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6", + "reference": "2d622faf9aa1f8f7f24dd094e49b5cf6c0c5d4e6", "shasum": "" }, "require": { @@ -846,9 +846,9 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v3.0.0" + "source": "https://github.com/dompdf/dompdf/tree/v3.0.1" }, - "time": "2024-04-29T14:01:28+00:00" + "time": "2024-12-05T14:59:38+00:00" }, { "name": "dompdf/php-font-lib", diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 0cc64c32e..ab52fb1af 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -3141,6 +3141,53 @@ class Calculation return $localeFileName; } + /** @var array> */ + private static array $falseTrueArray = []; + + /** @return array> */ + public function getFalseTrueArray(): array + { + if (!empty(self::$falseTrueArray)) { + return self::$falseTrueArray; + } + if (count(self::$validLocaleLanguages) == 1) { + self::loadLocales(); + } + $falseTrueArray = [['FALSE'], ['TRUE']]; + foreach (self::$validLocaleLanguages as $language) { + if (str_starts_with($language, 'en')) { + continue; + } + $locale = $language; + if (str_contains($locale, '_')) { + [$language] = explode('_', $locale); + } + $localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__, 'locale', null]); + + try { + $functionNamesFile = $this->getLocaleFile($localeDir, $locale, $language, 'functions'); + } catch (Exception $e) { + continue; + } + // Retrieve the list of locale or language specific function names + $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; + foreach ($localeFunctions as $localeFunction) { + [$localeFunction] = explode('##', $localeFunction); // Strip out comments + if (str_contains($localeFunction, '=')) { + [$fName, $lfName] = array_map('trim', explode('=', $localeFunction)); + if ($fName === 'FALSE') { + $falseTrueArray[0][] = $lfName; + } elseif ($fName === 'TRUE') { + $falseTrueArray[1][] = $lfName; + } + } + } + } + self::$falseTrueArray = $falseTrueArray; + + return $falseTrueArray; + } + /** * Set the locale code. * diff --git a/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php b/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php index b8918c915..58463ebea 100644 --- a/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php +++ b/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php @@ -9,6 +9,11 @@ use Psr\SimpleCache\CacheInterface; * * Alternative implementation should leverage off-memory, non-volatile storage * to reduce overall memory usage. + * + * Either SimpleCache1 or SimpleCache3, but not both, may be used. + * For code coverage testing, it will always be SimpleCache3. + * + * @codeCoverageIgnore */ class SimpleCache1 implements CacheInterface { diff --git a/src/PhpSpreadsheet/Helper/Sample.php b/src/PhpSpreadsheet/Helper/Sample.php index 136c458d4..420e80b7b 100644 --- a/src/PhpSpreadsheet/Helper/Sample.php +++ b/src/PhpSpreadsheet/Helper/Sample.php @@ -9,7 +9,6 @@ use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Writer\IWriter; -use PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RecursiveRegexIterator; @@ -129,11 +128,7 @@ class Sample $writerCallback($writer); } $callStartTime = microtime(true); - if (PHP_VERSION_ID >= 80400 && $writer instanceof Dompdf) { - @$writer->save($path); - } else { - $writer->save($path); - } + $writer->save($path); $this->logWrite($writer, $path, $callStartTime); if ($this->isCli() === false) { // @codeCoverageIgnoreStart diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php index 33bee9cdf..89cbfd5ad 100644 --- a/src/PhpSpreadsheet/Reader/Csv.php +++ b/src/PhpSpreadsheet/Reader/Csv.php @@ -84,10 +84,13 @@ class Csv extends BaseReader */ private static $constructorCallback; + /** Will be changed to false in next major release */ + public const DEFAULT_TEST_AUTODETECT = true; + /** * Attempt autodetect line endings (deprecated after PHP8.1)? */ - private bool $testAutodetect = true; + private bool $testAutodetect = self::DEFAULT_TEST_AUTODETECT; protected bool $castFormattedNumberToNumeric = false; diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index 4ca781a87..431a08fff 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -7,6 +7,7 @@ use DOMDocument; use DOMElement; use DOMNode; use DOMText; +use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Comment; @@ -271,6 +272,12 @@ class Html extends BaseReader ->setQuotePrefix(true); } } + if ($datatype === DataType::TYPE_BOOL) { + $cellContent = self::convertBoolean($cellContent); + if (!is_bool($cellContent)) { + $attributeArray['data-type'] = DataType::TYPE_STRING; + } + } //catching the Exception and ignoring the invalid data types try { @@ -291,6 +298,31 @@ class Html extends BaseReader $cellContent = (string) ''; } + /** @var array> */ + private static array $falseTrueArray = []; + + private static function convertBoolean(?string $cellContent): bool|string + { + if ($cellContent === '1') { + return true; + } + if ($cellContent === '0' || $cellContent === '' || $cellContent === null) { + return false; + } + if (empty(self::$falseTrueArray)) { + $calc = Calculation::getInstance(); + self::$falseTrueArray = $calc->getFalseTrueArray(); + } + if (in_array(mb_strtoupper($cellContent), self::$falseTrueArray[1], true)) { + return true; + } + if (in_array(mb_strtoupper($cellContent), self::$falseTrueArray[0], true)) { + return false; + } + + return $cellContent; + } + private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void { $attributeArray = []; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php index 0672854ad..b02f3d3ff 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -149,14 +149,22 @@ class Styles extends BaseParserClass $fillStyle->getStartColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); //* @phpstan-ignore-line $fillStyle->getEndColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); //* @phpstan-ignore-line } elseif ($fillStyleXml->patternFill) { - $defaultFillStyle = Fill::FILL_NONE; + $defaultFillStyle = ($fillStyle->getFillType() !== null) ? Fill::FILL_NONE : ''; + $fgFound = false; + $bgFound = false; if ($fillStyleXml->patternFill->fgColor) { $fillStyle->getStartColor()->setARGB($this->readColor($fillStyleXml->patternFill->fgColor, true)); - $defaultFillStyle = Fill::FILL_SOLID; + if ($fillStyle->getFillType() !== null) { + $defaultFillStyle = Fill::FILL_SOLID; + } + $fgFound = true; } if ($fillStyleXml->patternFill->bgColor) { $fillStyle->getEndColor()->setARGB($this->readColor($fillStyleXml->patternFill->bgColor, true)); - $defaultFillStyle = Fill::FILL_SOLID; + if ($fillStyle->getFillType() !== null) { + $defaultFillStyle = Fill::FILL_SOLID; + } + $bgFound = true; } $type = ''; @@ -169,6 +177,22 @@ class Styles extends BaseParserClass $patternType = ($type === '') ? $defaultFillStyle : $type; $fillStyle->setFillType($patternType); + if ( + !$fgFound // no foreground color specified + && !in_array($patternType, [Fill::FILL_NONE, Fill::FILL_SOLID], true) // these patterns aren't relevant + && $fillStyle->getStartColor()->getARGB() // not conditional + ) { + $fillStyle->getStartColor() + ->setARGB('', true); + } + if ( + !$bgFound // no background color specified + && !in_array($patternType, [Fill::FILL_NONE, Fill::FILL_SOLID], true) // these patterns aren't relevant + && $fillStyle->getEndColor()->getARGB() // not conditional + ) { + $fillStyle->getEndColor() + ->setARGB('', true); + } } } diff --git a/src/PhpSpreadsheet/Style/Color.php b/src/PhpSpreadsheet/Style/Color.php index 6f9db8a21..8aa4734fc 100644 --- a/src/PhpSpreadsheet/Style/Color.php +++ b/src/PhpSpreadsheet/Style/Color.php @@ -230,12 +230,14 @@ class Color extends Supervisor * * @return $this */ - public function setARGB(?string $colorValue = self::COLOR_BLACK): static + public function setARGB(?string $colorValue = self::COLOR_BLACK, bool $nullStringOkay = false): static { $this->hasChanged = true; - $colorValue = $this->validateColor($colorValue); - if ($colorValue === '') { - return $this; + if (!$nullStringOkay || $colorValue !== '') { + $colorValue = $this->validateColor($colorValue); + if ($colorValue === '') { + return $this; + } } if ($this->isSupervisor) { diff --git a/src/PhpSpreadsheet/Worksheet/Drawing.php b/src/PhpSpreadsheet/Worksheet/Drawing.php index 35933af3f..55450dbfe 100644 --- a/src/PhpSpreadsheet/Worksheet/Drawing.php +++ b/src/PhpSpreadsheet/Worksheet/Drawing.php @@ -111,8 +111,21 @@ class Drawing extends BaseDrawing $this->isUrl = true; $ctx = null; // https://github.com/php/php-src/issues/16023 - if (str_starts_with($path, 'https:')) { - $ctx = stream_context_create(['ssl' => ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]]); + // https://github.com/php/php-src/issues/17121 + if (str_starts_with($path, 'https:') || str_starts_with($path, 'http:')) { + $ctxArray = [ + 'http' => [ + 'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + 'header' => [ + //'Connection: keep-alive', // unacceptable performance + 'Accept: image/*;q=0.9,*/*;q=0.8', + ], + ], + ]; + if (str_starts_with($path, 'https:')) { + $ctxArray['ssl'] = ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT]; + } + $ctx = stream_context_create($ctxArray); } $imageContents = @file_get_contents($path, false, $ctx); if ($imageContents !== false) { @@ -183,6 +196,8 @@ class Drawing extends BaseDrawing * Set isURL. * * @return $this + * + * @deprecated 3.7.0 not needed, property is set by setPath */ public function setIsURL(bool $isUrl): self { diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index 1b2d33abf..df1353ea0 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Chart\Chart; use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\Document\Properties; @@ -141,6 +142,12 @@ class Html extends BaseWriter /** @var Chart[] */ private $sheetCharts; + private bool $betterBoolean = false; + + private string $getTrue = 'TRUE'; + + private string $getFalse = 'FALSE'; + /** * Create a new HTML. */ @@ -148,6 +155,9 @@ class Html extends BaseWriter { $this->spreadsheet = $spreadsheet; $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont(); + $calc = Calculation::getInstance($this->spreadsheet); + $this->getTrue = $calc->getTRUE(); + $this->getFalse = $calc->getFALSE(); } /** @@ -1139,9 +1149,16 @@ class Html extends BaseWriter // Create CSS if ($fill->getFillType() !== Fill::FILL_NONE) { - $value = $fill->getFillType() == Fill::FILL_NONE - ? 'white' : '#' . $fill->getStartColor()->getRGB(); - $css['background-color'] = $value; + if ( + (in_array($fill->getFillType(), ['', Fill::FILL_SOLID], true) || !$fill->getEndColor()->getRGB()) + && $fill->getStartColor()->getRGB() + ) { + $value = '#' . $fill->getStartColor()->getRGB(); + $css['background-color'] = $value; + } elseif ($fill->getEndColor()->getRGB()) { + $value = '#' . $fill->getEndColor()->getRGB(); + $css['background-color'] = $value; + } } return $css; @@ -1346,8 +1363,21 @@ class Html extends BaseWriter if ($cell->getValue() instanceof RichText) { $cellData .= $this->generateRowCellDataValueRich($cell->getValue()); } else { - $origData = $this->preCalculateFormulas ? $cell->getCalculatedValue() : $cell->getValue(); - $origData2 = $this->preCalculateFormulas ? $cell->getCalculatedValueString() : $cell->getValueString(); + if ($this->preCalculateFormulas) { + $origData = $cell->getCalculatedValue(); + if ($this->betterBoolean && is_bool($origData)) { + $origData2 = $origData ? $this->getTrue : $this->getFalse; + } else { + $origData2 = $cell->getCalculatedValueString(); + } + } else { + $origData = $cell->getValue(); + if ($this->betterBoolean && is_bool($origData)) { + $origData2 = $origData ? $this->getTrue : $this->getFalse; + } else { + $origData2 = $cell->getValueString(); + } + } $formatCode = $worksheet->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(); $cellData = NumberFormat::toFormattedString( @@ -1387,9 +1417,20 @@ class Html extends BaseWriter $cellData = nl2br($cellData); // Extend CSS class? + $dataType = $cell->getDataType(); + if ($this->betterBoolean && $this->preCalculateFormulas && $dataType === DataType::TYPE_FORMULA) { + $calculatedValue = $cell->getCalculatedValue(); + if (is_bool($calculatedValue)) { + $dataType = DataType::TYPE_BOOL; + } elseif (is_numeric($calculatedValue)) { + $dataType = DataType::TYPE_NUMERIC; + } elseif (is_string($calculatedValue)) { + $dataType = DataType::TYPE_STRING; + } + } if (!$this->useInlineCss && is_string($cssClass)) { $cssClass .= ' style' . $cell->getXfIndex(); - $cssClass .= ' ' . $cell->getDataType(); + $cssClass .= ' ' . $dataType; } elseif (is_array($cssClass)) { $index = $cell->getXfIndex(); $styleIndex = 'td.style' . $index . ', th.style' . $index; @@ -1403,7 +1444,7 @@ class Html extends BaseWriter $sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL && isset($this->cssStyles['.' . $cell->getDataType()]['text-align']) ) { - $cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align']; + $cssClass['text-align'] = $this->cssStyles['.' . $dataType]['text-align']; } } } else { @@ -1448,6 +1489,16 @@ class Html extends BaseWriter $htmlx .= $this->generateRowIncludeCharts($worksheet, $coordinate); // Column start $html .= ' <' . $cellType; + if ($this->betterBoolean) { + $dataType = $worksheet->getCell($coordinate)->getDataType(); + if ($dataType === DataType::TYPE_BOOL) { + $html .= ' data-type="' . DataType::TYPE_BOOL . '"'; + } elseif ($dataType === DataType::TYPE_FORMULA && is_bool($worksheet->getCell($coordinate)->getCalculatedValue())) { + $html .= ' data-type="' . DataType::TYPE_BOOL . '"'; + } elseif (is_numeric($cellData) && $worksheet->getCell($coordinate)->getDataType() === DataType::TYPE_STRING) { + $html .= ' data-type="' . DataType::TYPE_STRING . '"'; + } + } if (!$this->useInlineCss && !$this->isPdf && is_string($cssClass)) { $html .= ' class="' . $cssClass . '"'; if ($htmlx) { @@ -1903,4 +1954,16 @@ class Html extends BaseWriter return $sheet->getColumnDimension($colStr)->getVisible(); } + + public function getBetterBoolean(): bool + { + return $this->betterBoolean; + } + + public function setBetterBoolean(bool $betterBoolean): self + { + $this->betterBoolean = $betterBoolean; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php index c1c9f32c1..92fc497e7 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php +++ b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php @@ -20,6 +20,7 @@ class Style public const COLUMN_STYLE_PREFIX = 'co'; public const ROW_STYLE_PREFIX = 'ro'; public const TABLE_STYLE_PREFIX = 'ta'; + public const INDENT_TO_INCHES = 0.1043; // undocumented, used trial and error private XMLWriter $writer; @@ -28,12 +29,13 @@ class Style $this->writer = $writer; } - private function mapHorizontalAlignment(string $horizontalAlignment): string + private function mapHorizontalAlignment(?string $horizontalAlignment): string { return match ($horizontalAlignment) { Alignment::HORIZONTAL_CENTER, Alignment::HORIZONTAL_CENTER_CONTINUOUS, Alignment::HORIZONTAL_DISTRIBUTED => 'center', Alignment::HORIZONTAL_RIGHT => 'end', Alignment::HORIZONTAL_FILL, Alignment::HORIZONTAL_JUSTIFY => 'justify', + Alignment::HORIZONTAL_GENERAL, '', null => '', default => 'start', }; } @@ -145,8 +147,10 @@ class Style { // Align $hAlign = $style->getAlignment()->getHorizontal(); + $hAlign = $this->mapHorizontalAlignment($hAlign); $vAlign = $style->getAlignment()->getVertical(); $wrap = $style->getAlignment()->getWrapText(); + $indent = $style->getAlignment()->getIndent(); $this->writer->startElement('style:table-cell-properties'); if (!empty($vAlign) || $wrap) { @@ -168,10 +172,16 @@ class Style $this->writer->endElement(); - if (!empty($hAlign)) { - $hAlign = $this->mapHorizontalAlignment($hAlign); - $this->writer->startElement('style:paragraph-properties'); - $this->writer->writeAttribute('fo:text-align', $hAlign); + if ($hAlign !== '' || !empty($indent)) { + $this->writer + ->startElement('style:paragraph-properties'); + if ($hAlign !== '') { + $this->writer->writeAttribute('fo:text-align', $hAlign); + } + if (!empty($indent)) { + $indentString = sprintf('%.4f', $indent * self::INDENT_TO_INCHES) . 'in'; + $this->writer->writeAttribute('fo:margin-left', $indentString); + } $this->writer->endElement(); } } diff --git a/src/PhpSpreadsheet/Writer/Ods/Content.php b/src/PhpSpreadsheet/Writer/Ods/Content.php index 91ac5d1d2..3b9cc8245 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Content.php +++ b/src/PhpSpreadsheet/Writer/Ods/Content.php @@ -20,9 +20,6 @@ use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Style; */ class Content extends WriterPart { - const NUMBER_COLS_REPEATED_MAX = 1024; - const NUMBER_ROWS_REPEATED_MAX = 1048576; - private Formula $formulaConvertor; /** @@ -142,7 +139,6 @@ class Content extends WriterPart sprintf('%s_%d_%d', Style::COLUMN_STYLE_PREFIX, $sheetIndex, $columnDimension->getColumnNumeric()) ); $objWriter->writeAttribute('table:default-cell-style-name', 'ce0'); -// $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX); $objWriter->endElement(); } $this->writeRows($objWriter, $spreadsheet->getSheet($sheetIndex), $sheetIndex); @@ -155,34 +151,33 @@ class Content extends WriterPart */ private function writeRows(XMLWriter $objWriter, Worksheet $sheet, int $sheetIndex): void { - $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX; - $span_row = 0; + $spanRow = 0; $rows = $sheet->getRowIterator(); foreach ($rows as $row) { - $cellIterator = $row->getCellIterator(); - --$numberRowsRepeated; - if ($cellIterator->valid()) { - $objWriter->startElement('table:table-row'); - if ($span_row) { - if ($span_row > 1) { - $objWriter->writeAttribute('table:number-rows-repeated', (string) $span_row); - } - $objWriter->startElement('table:table-cell'); - $objWriter->writeAttribute('table:number-columns-repeated', (string) self::NUMBER_COLS_REPEATED_MAX); + $cellIterator = $row->getCellIterator(iterateOnlyExistingCells: true); + $cellIterator->rewind(); + $rowStyleExists = $sheet->rowDimensionExists($row->getRowIndex()) && $sheet->getRowDimension($row->getRowIndex())->getRowHeight() > 0; + if ($cellIterator->valid() || $rowStyleExists) { + if ($spanRow) { + $objWriter->startElement('table:table-row'); + $objWriter->writeAttribute( + 'table:number-rows-repeated', + (string) $spanRow + ); $objWriter->endElement(); - $span_row = 0; - } else { - if ($sheet->rowDimensionExists($row->getRowIndex()) && $sheet->getRowDimension($row->getRowIndex())->getRowHeight() > 0) { - $objWriter->writeAttribute( - 'table:style-name', - sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex()) - ); - } - $this->writeCells($objWriter, $cellIterator); + $spanRow = 0; } + $objWriter->startElement('table:table-row'); + if ($rowStyleExists) { + $objWriter->writeAttribute( + 'table:style-name', + sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex()) + ); + } + $this->writeCells($objWriter, $cellIterator); $objWriter->endElement(); } else { - ++$span_row; + ++$spanRow; } } } @@ -192,7 +187,6 @@ class Content extends WriterPart */ private function writeCells(XMLWriter $objWriter, RowCellIterator $cells): void { - $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX; $prevColumn = -1; foreach ($cells as $cell) { /** @var Cell $cell */ @@ -293,17 +287,6 @@ class Content extends WriterPart $objWriter->endElement(); $prevColumn = $column; } - - $numberColsRepeated = $numberColsRepeated - $prevColumn - 1; - if ($numberColsRepeated > 0) { - if ($numberColsRepeated > 1) { - $objWriter->startElement('table:table-cell'); - $objWriter->writeAttribute('table:number-columns-repeated', (string) $numberColsRepeated); - $objWriter->endElement(); - } else { - $objWriter->writeElement('table:table-cell'); - } - } } /** diff --git a/src/PhpSpreadsheet/Writer/Xls/Workbook.php b/src/PhpSpreadsheet/Writer/Xls/Workbook.php index a1a5faf8f..c2b4496a5 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Workbook.php +++ b/src/PhpSpreadsheet/Writer/Xls/Workbook.php @@ -210,8 +210,20 @@ class Workbook extends BIFFwriter $xfWriter->setFontIndex($fontIndex); // Background colors, best to treat these after the font so black will come after white in custom palette - $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB())); - $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB())); + if ($style->getFill()->getStartColor()->getRGB()) { + $xfWriter->setFgColor( + $this->addColor( + $style->getFill()->getStartColor()->getRGB() + ) + ); + } + if ($style->getFill()->getEndColor()->getRGB()) { + $xfWriter->setBgColor( + $this->addColor( + $style->getFill()->getEndColor()->getRGB() + ) + ); + } $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB())); $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB())); $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB())); diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 5258bbad3..5e3aa0b82 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -2875,9 +2875,9 @@ class Worksheet extends BIFFwriter $bFormatBorder = 0; } // Pattern - $bFillStyle = ($conditional->getStyle()->getFill()->getFillType() === null ? 0 : 1); - $bFillColor = ($conditional->getStyle()->getFill()->getStartColor()->getARGB() === null ? 0 : 1); - $bFillColorBg = ($conditional->getStyle()->getFill()->getEndColor()->getARGB() === null ? 0 : 1); + $bFillStyle = $conditional->getStyle()->getFill()->getFillType() ? 1 : 0; + $bFillColor = $conditional->getStyle()->getFill()->getStartColor()->getARGB() ? 1 : 0; + $bFillColorBg = $conditional->getStyle()->getFill()->getEndColor()->getARGB() ? 1 : 0; if ($bFillStyle == 1 || $bFillColor == 1 || $bFillColorBg == 1) { $bFormatFill = 1; } else { diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/src/PhpSpreadsheet/Writer/Xlsx/Style.php index a31b8e249..37c6c3127 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Style.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Style.php @@ -199,7 +199,7 @@ class Style extends WriterPart $objWriter->writeAttribute('position', '0'); // color - if ($fill->getStartColor()->getARGB() !== null) { + if (!empty($fill->getStartColor()->getARGB())) { $objWriter->startElement('color'); $objWriter->writeAttribute('rgb', $fill->getStartColor()->getARGB()); $objWriter->endElement(); @@ -212,7 +212,7 @@ class Style extends WriterPart $objWriter->writeAttribute('position', '1'); // color - if ($fill->getEndColor()->getARGB() !== null) { + if (!empty($fill->getEndColor()->getARGB())) { $objWriter->startElement('color'); $objWriter->writeAttribute('rgb', $fill->getEndColor()->getARGB()); $objWriter->endElement(); @@ -244,7 +244,9 @@ class Style extends WriterPart // patternFill $objWriter->startElement('patternFill'); - $objWriter->writeAttribute('patternType', (string) $fill->getFillType()); + if ($fill->getFillType()) { + $objWriter->writeAttribute('patternType', (string) $fill->getFillType()); + } if (self::writePatternColors($fill)) { // fgColor diff --git a/src/PhpSpreadsheet/Writer/ZipStream2.php b/src/PhpSpreadsheet/Writer/ZipStream2.php index ed21a7e39..e03e1d0cf 100644 --- a/src/PhpSpreadsheet/Writer/ZipStream2.php +++ b/src/PhpSpreadsheet/Writer/ZipStream2.php @@ -5,6 +5,12 @@ namespace PhpOffice\PhpSpreadsheet\Writer; use ZipStream\Option\Archive; use ZipStream\ZipStream; +/** + * Either ZipStream2 or ZipStream3, but not both, may be used. + * For code coverage testing, it will always be ZipStream3. + * + * @codeCoverageIgnore + */ class ZipStream2 { /** diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Logical/XorTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Logical/XorTest.php index ea4a8bb06..990068dde 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Logical/XorTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Logical/XorTest.php @@ -18,7 +18,7 @@ class XorTest extends AllSetupTeardown } #[\PHPUnit\Framework\Attributes\DataProvider('providerXORLiteral')] - public function xtestXORLiteral(mixed $expectedResult, string $formula): void + public function testXORLiteral(mixed $expectedResult, float|string $formula): void { $sheet = $this->getSheet(); $sheet->getCell('A1')->setValue("=XOR($formula)"); diff --git a/tests/PhpSpreadsheetTests/Functional/StreamTest.php b/tests/PhpSpreadsheetTests/Functional/StreamTest.php index 4a43f77c5..9d062d2b8 100644 --- a/tests/PhpSpreadsheetTests/Functional/StreamTest.php +++ b/tests/PhpSpreadsheetTests/Functional/StreamTest.php @@ -8,11 +8,6 @@ use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; -/** - * Not clear that Dompdf will be Php8.4 compatible in time. - * Run in separate process and add version test till it is ready. - */ -#[\PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses] class StreamTest extends TestCase { public static function providerFormats(): array @@ -45,11 +40,7 @@ class StreamTest extends TestCase } else { self::assertSame(0, $stat['size']); - if ($format === 'Dompdf' && PHP_VERSION_ID >= 80400) { - @$writer->save($stream); - } else { - $writer->save($stream); - } + $writer->save($stream); self::assertIsResource($stream, 'should not close the stream for further usage out of PhpSpreadsheet'); $stat = fstat($stream); diff --git a/tests/PhpSpreadsheetTests/Reader/Csv/CsvLineEndingTest.php b/tests/PhpSpreadsheetTests/Reader/Csv/CsvLineEndingTest.php index 9ff77268a..3fc1d3031 100644 --- a/tests/PhpSpreadsheetTests/Reader/Csv/CsvLineEndingTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Csv/CsvLineEndingTest.php @@ -12,6 +12,8 @@ class CsvLineEndingTest extends TestCase { private string $tempFile = ''; + private static bool $alwaysFalse = false; + protected function tearDown(): void { if ($this->tempFile !== '') { @@ -30,6 +32,9 @@ class CsvLineEndingTest extends TestCase $data = ['123', '456', '789']; file_put_contents($filename, implode($ending, $data)); $reader = new Csv(); + if (Csv::DEFAULT_TEST_AUTODETECT === self::$alwaysFalse) { + $reader->setTestAutoDetect(true); + } $spreadsheet = $reader->load($filename); $sheet = $spreadsheet->getActiveSheet(); self::assertEquals($data[0], $sheet->getCell('A1')->getValue()); diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/RepeatEmptyCellsAndRowsTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/RepeatEmptyCellsAndRowsTest.php new file mode 100644 index 000000000..b64f8fd1d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Ods/RepeatEmptyCellsAndRowsTest.php @@ -0,0 +1,48 @@ +getActiveSheet(); + $oldSheet->setCellValue('C1', 'xx'); + $oldSheet->setCellValue('G1', 'aa'); + $oldSheet->setCellValue('BB1', 'bb'); + $oldSheet->setCellValue('A6', 'aaa'); + $oldSheet->setCellValue('B7', 'bbb'); + $oldSheet->getRowDimension(10)->setRowHeight(12); + $oldSheet->setCellValue('A12', 'this is A12'); + $style = $oldSheet->getStyle('B14:D14'); + $style->getFont()->setBold(true); + $oldSheet->getCell('E15')->setValue('X'); + $oldSheet->mergeCells('E15:G16'); + $oldSheet->getCell('J15')->setValue('j15'); + $oldSheet->getCell('J16')->setValue('j16'); + $oldSheet->getCell('A19')->setValue('lastrow'); + $spreadsheet = $this->writeAndReload($spreadsheetOld, 'Ods'); + $spreadsheetOld->disconnectWorksheets(); + + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('xx', $sheet->getCell('C1')->getValue()); + self::assertSame('aa', $sheet->getCell('G1')->getValue()); + self::assertSame('bb', $sheet->getCell('BB1')->getValue()); + self::assertSame('aaa', $sheet->getCell('A6')->getValue()); + self::assertSame('bbb', $sheet->getCell('B7')->getValue()); + self::assertSame('this is A12', $sheet->getCell('A12')->getValue()); + // Read styles, including row height, not yet implemented for ODS + self::assertSame('j15', $sheet->getCell('J15')->getValue()); + self::assertSame('j16', $sheet->getCell('J16')->getValue()); + self::assertSame(['E15:G16' => 'E15:G16'], $sheet->getMergeCells()); + self::assertSame('lastrow', $sheet->getCell('A19')->getValue()); + + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFillTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFillTest.php index 44fb8e57d..7bdfecdd0 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFillTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/DefaultFillTest.php @@ -40,6 +40,6 @@ class DefaultFillTest extends TestCase $spreadsheet = $reader->load($filename); $style = $spreadsheet->getActiveSheet()->getConditionalStyles('A1')[0]->getStyle(); - self::assertSame('solid', $style->getFill()->getFillType()); + self::assertSame('', $style->getFill()->getFillType()); } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4248Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4248Test.php new file mode 100644 index 000000000..06b0b7f31 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4248Test.php @@ -0,0 +1,104 @@ +outfile !== '') { + unlink($this->outfile); + $this->outfile = ''; + } + } + + public function testStyles(): void + { + $file = 'tests/data/Reader/XLSX/issue.4248.xlsx'; + $reader = new XlsxReader(); + $spreadsheet = $reader->load($file); + $writer = new XlsxWriter($spreadsheet); + $this->outfile = File::temporaryFilename(); + $writer->save($this->outfile); + $spreadsheet->disconnectWorksheets(); + + $file = 'zip://'; + $file .= $this->outfile; + $file .= '#xl/styles.xml'; + $data = file_get_contents($file) ?: ''; + $expected = '' + . '' + . ''; + self::assertStringContainsString($expected, $data, 'neither fgColor nor bgColor'); + $expected = '' + . '' + . '' + . ''; + self::assertStringContainsString($expected, $data, 'bgColor but no fgColor'); + $expected = '' + . '' // dxfId 1 - fill color for Oui + . '' + . '' + . '' + . '' + . '' + . '' // dxfId 2 - fill color for Non + . '' + . '' + . '' + . '' + . '' + . ''; + self::assertStringContainsString($expected, $data, 'conditional fill styles'); + + $file = 'zip://'; + $file .= $this->outfile; + $file .= '#xl/worksheets/sheet1.xml'; + $data = file_get_contents($file) ?: ''; + $expected = '' + . '' + . 'NOT(ISERROR(SEARCH("Oui",C16)))' + . '' + . ''; + self::assertStringContainsString($expected, $data, 'first condition for D18'); + $expected = '' + . '' + . 'NOT(ISERROR(SEARCH("Non",C16)))' + . '' + . ''; + self::assertStringContainsString($expected, $data, 'second condition for D18'); + } + + public function testHtml(): void + { + $file = 'tests/data/Reader/XLSX/issue.4248.xlsx'; + $reader = new XlsxReader(); + $spreadsheet = $reader->load($file); + $writer = new HtmlWriter($spreadsheet); + + $file = 'zip://'; + $file .= $this->outfile; + $file .= '#xl/styles.xml'; + $data = str_replace(["\r", "\n"], '', $writer->generateHtmlAll()); + $expected = ' ' // Cell D18 + . '  ' + . ' ' + . ' Eligible ' + . ' Non'; + self::assertStringContainsString($expected, $data, 'Cell D18 style'); + $expected = ' td.style70, th.style70 { vertical-align:middle; text-align:center; border-bottom:1px solid #000000 !important; border-top:2px solid #000000 !important; border-left:2px solid #000000 !important; border-right:1px solid #000000 !important; font-weight:bold; color:#000000; font-family:\'Calibri\'; font-size:16pt; background-color:#BDD7EE }'; + self::assertStringContainsString($expected, $data, 'background color'); + + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Dompdf/PaperSizeArrayTest.php b/tests/PhpSpreadsheetTests/Writer/Dompdf/PaperSizeArrayTest.php index 3abf56110..4c0b86b63 100644 --- a/tests/PhpSpreadsheetTests/Writer/Dompdf/PaperSizeArrayTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Dompdf/PaperSizeArrayTest.php @@ -10,11 +10,6 @@ use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; use PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf; use PHPUnit\Framework\TestCase; -/** - * Not clear that Dompdf will be Php8.4 compatible in time. - * Run in separate process and add version test till it is ready. - */ -#[\PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses] class PaperSizeArrayTest extends TestCase { private string $outfile = ''; @@ -41,11 +36,7 @@ class PaperSizeArrayTest extends TestCase $sheet->setCellValue('A7', 'Lorem Ipsum'); $writer = new Dompdf($spreadsheet); $this->outfile = File::temporaryFilename(); - if (PHP_VERSION_ID >= 80400) { // hopefully temporary - @$writer->save($this->outfile); - } else { - $writer->save($this->outfile); - } + $writer->save($this->outfile); $spreadsheet->disconnectWorksheets(); unset($spreadsheet); $contents = file_get_contents($this->outfile); @@ -65,11 +56,7 @@ class PaperSizeArrayTest extends TestCase $sheet->setCellValue('A7', 'Lorem Ipsum'); $writer = new Dompdf($spreadsheet); $this->outfile = File::temporaryFilename(); - if (PHP_VERSION_ID >= 80400) { // hopefully temporary - @$writer->save($this->outfile); - } else { - $writer->save($this->outfile); - } + $writer->save($this->outfile); $spreadsheet->disconnectWorksheets(); unset($spreadsheet); $contents = file_get_contents($this->outfile); diff --git a/tests/PhpSpreadsheetTests/Writer/Html/BetterBooleanTest.php b/tests/PhpSpreadsheetTests/Writer/Html/BetterBooleanTest.php new file mode 100644 index 000000000..0eac757d4 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Html/BetterBooleanTest.php @@ -0,0 +1,211 @@ +locale = $calculation->getLocale(); + } + + protected function tearDown(): void + { + $calculation = Calculation::getInstance(); + $calculation->setLocale($this->locale); + } + + public function testDefault(): void + { + $spreadsheet = new Spreadsheet(); + $writer = new HtmlWriter($spreadsheet); + // Default will change with next PhpSpreadsheet release + self::assertFalse($writer->getBetterBoolean()); + $spreadsheet->disconnectWorksheets(); + } + + public function setBetter(HtmlWriter $writer): void + { + $writer->setBetterBoolean(true); + } + + public function setNotBetter(HtmlWriter $writer): void + { + $writer->setBetterBoolean(false); + } + + public function testBetterBoolean(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $sheet->getCell('B1')->setValue('Hello'); + $sheet->getCell('C1')->setValue(true); + $sheet->getCell('D1')->setValue('=IF(1>2, TRUE, FALSE)'); + $sheet->getCell('E1')->setValueExplicit(1, DataType::TYPE_STRING); + $sheet->getCell('F1')->setValue('="A"&"B"'); + $sheet->getCell('G1')->setValue('=1+2'); + + /** @var callable */ + $callableWriter = [$this, 'setBetter']; + $reloaded = $this->writeAndReload($spreadsheet, 'Html', null, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $rsheet = $reloaded->getActiveSheet(); + self::assertSame(1, $rsheet->getCell('A1')->getValue()); + self::assertSame('Hello', $rsheet->getCell('B1')->getValue()); + self::assertTrue($rsheet->getCell('C1')->getValue()); + self::assertFalse($rsheet->getCell('D1')->getValue()); + self::assertSame('1', $rsheet->getCell('E1')->getValue()); + self::assertSame('AB', $rsheet->getCell('F1')->getValue()); + self::assertSame(3, $rsheet->getCell('G1')->getValue()); + $reloaded->disconnectWorksheets(); + } + + public function testNotBetterBoolean(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $sheet->getCell('B1')->setValue('Hello'); + $sheet->getCell('C1')->setValue(true); + $sheet->getCell('D1')->setValue('=IF(1>2, TRUE, FALSE)'); + $sheet->getCell('E1')->setValueExplicit(1, DataType::TYPE_STRING); + $sheet->getCell('F1')->setValue('="A"&"B"'); + $sheet->getCell('G1')->setValue('=1+2'); + + /** @var callable */ + $callableWriter = [$this, 'setNotBetter']; + $reloaded = $this->writeAndReload($spreadsheet, 'Html', null, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $rsheet = $reloaded->getActiveSheet(); + self::assertSame(1, $rsheet->getCell('A1')->getValue()); + self::assertSame('Hello', $rsheet->getCell('B1')->getValue()); + self::assertSame(1, $rsheet->getCell('C1')->getValue()); + self::assertNull($rsheet->getCell('D1')->getValue()); + self::assertSame(1, $rsheet->getCell('E1')->getValue()); + self::assertSame('AB', $rsheet->getCell('F1')->getValue()); + self::assertSame(3, $rsheet->getCell('G1')->getValue()); + $reloaded->disconnectWorksheets(); + } + + public function testLocale(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $sheet->getCell('B1')->setValue('Hello'); + $sheet->getCell('C1')->setValue(true); + $sheet->getCell('D1')->setValue('=IF(1>2, TRUE, FALSE)'); + $sheet->getCell('E1')->setValueExplicit(1, DataType::TYPE_STRING); + $sheet->getCell('F1')->setValue('="A"&"B"'); + $sheet->getCell('G1')->setValue('=1+2'); + $calc = Calculation::getInstance(); + $calc->setLocale('fr'); + $writer = new HtmlWriter($spreadsheet); + $writer->setBetterBoolean(true); + $html = $writer->generateHtmlAll(); + self::assertStringNotContainsString('TRUE', $html); + self::assertStringContainsString('VRAI', $html); + self::assertStringContainsString('FAUX', $html); + self::assertStringContainsString('1', $html); + self::assertStringContainsString('AB', $html); + self::assertStringContainsString('3', $html); + + /** @var callable */ + $callableWriter = [$this, 'setBetter']; + $reloaded = $this->writeAndReload($spreadsheet, 'Html', null, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $rsheet = $reloaded->getActiveSheet(); + self::assertSame(1, $rsheet->getCell('A1')->getValue()); + self::assertSame('Hello', $rsheet->getCell('B1')->getValue()); + self::assertTrue($rsheet->getCell('C1')->getValue()); + self::assertFalse($rsheet->getCell('D1')->getValue()); + self::assertSame('1', $rsheet->getCell('E1')->getValue()); + self::assertSame('AB', $rsheet->getCell('F1')->getValue()); + self::assertSame(3, $rsheet->getCell('G1')->getValue()); + $reloaded->disconnectWorksheets(); + } + + public function testInline(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $sheet->getCell('B1')->setValue('Hello'); + $sheet->getCell('C1')->setValue(true); + $sheet->getCell('D1')->setValue('=IF(1>2, TRUE, FALSE)'); + $sheet->getCell('E1')->setValueExplicit(1, DataType::TYPE_STRING); + $sheet->getCell('F1')->setValue('="A"&"B"'); + $sheet->getCell('G1')->setValue('=1+2'); + $calc = Calculation::getInstance(); + $calc->setLocale('fr'); + $writer = new HtmlWriter($spreadsheet); + $writer->setBetterBoolean(true); + $writer->setUseInlineCss(true); + $html = $writer->generateHtmlAll(); + $html = str_replace('vertical-align:bottom; color:#000000; font-family:\'Calibri\'; font-size:11pt; ', '', $html); + $html = str_replace(' width:42pt" class="gridlines gridlinesp"', '"', $html); + self::assertStringNotContainsString('TRUE', $html); + self::assertStringContainsString('1', $html); + self::assertStringContainsString('Hello', $html); + self::assertStringContainsString('VRAI', $html); + self::assertStringContainsString('FAUX', $html); + self::assertStringContainsString('1', $html); + self::assertStringContainsString('AB', $html); + self::assertStringContainsString('3', $html); + + /** @var callable */ + $callableWriter = [$this, 'setBetter']; + $reloaded = $this->writeAndReload($spreadsheet, 'Html', null, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $rsheet = $reloaded->getActiveSheet(); + self::assertSame(1, $rsheet->getCell('A1')->getValue()); + self::assertSame('Hello', $rsheet->getCell('B1')->getValue()); + self::assertTrue($rsheet->getCell('C1')->getValue()); + self::assertFalse($rsheet->getCell('D1')->getValue()); + self::assertSame('1', $rsheet->getCell('E1')->getValue()); + self::assertSame('AB', $rsheet->getCell('F1')->getValue()); + self::assertSame(3, $rsheet->getCell('G1')->getValue()); + $reloaded->disconnectWorksheets(); + } + + public function testForeignNoLocale(): void + { + $fragment = '' + . '' + . '' + . '' // Bulgarian TRUE + . '' // Finnish FALSE + . '' + . '' + . '' + . '
1HelloИСТИНАEPÄTOSIwhatevertRuE1
'; + $reader = new HtmlReader(); + $spreadsheet = $reader->loadFromString($fragment); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame(1, $sheet->getCell('A1')->getValue()); + self::assertSame('Hello', $sheet->getCell('B1')->getValue()); + self::assertTrue($sheet->getCell('C1')->getValue()); + self::assertFalse($sheet->getCell('D1')->getValue()); + self::assertSame('whatever', $sheet->getCell('E1')->getValue()); + self::assertTrue($sheet->getCell('F1')->getValue()); + self::assertSame('1', $sheet->getCell('G1')->getValue()); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php b/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php new file mode 100644 index 000000000..7b6a62baa --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php @@ -0,0 +1,62 @@ +compatibilityMode = Functions::getCompatibilityMode(); + Functions::setCompatibilityMode( + Functions::COMPATIBILITY_OPENOFFICE + ); + } + + protected function tearDown(): void + { + parent::tearDown(); + Functions::setCompatibilityMode($this->compatibilityMode); + } + + public function testWriteSpreadsheet(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A1', 'aa'); + $sheet->setCellValue('B1', 'bb'); + $sheet->setCellValue('A2', 'cc'); + $sheet->setCellValue('B2', 'dd'); + $sheet->getStyle('A1')->getAlignment()->setIndent(2); + $writer = new Ods($spreadsheet); + $content = new Content($writer); + $xml = $content->write(); + self::assertStringContainsString( + '' + . '' + . '' + . '', + $xml + ); + self::assertStringContainsString( + '' + . '' + . '' // fo:margin-left is what we're looking for + . '' + . '', + $xml + ); + self::assertSame(3, substr_count($xml, 'table:style-name="ce0"')); + self::assertSame(1, substr_count($xml, 'table:style-name="ce1"')); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/XLSX/issue.4248.xlsx b/tests/data/Reader/XLSX/issue.4248.xlsx new file mode 100644 index 000000000..2804c40ca Binary files /dev/null and b/tests/data/Reader/XLSX/issue.4248.xlsx differ diff --git a/tests/data/Writer/Ods/content-arrays.xml b/tests/data/Writer/Ods/content-arrays.xml index 231036ee2..8e72601a5 100644 --- a/tests/data/Writer/Ods/content-arrays.xml +++ b/tests/data/Writer/Ods/content-arrays.xml @@ -8,7 +8,6 @@ - @@ -24,7 +23,6 @@ 1 - @@ -33,14 +31,11 @@ 3 - 3 - - diff --git a/tests/data/Writer/Ods/content-empty.xml b/tests/data/Writer/Ods/content-empty.xml index cb14ce7e5..e9015af89 100644 --- a/tests/data/Writer/Ods/content-empty.xml +++ b/tests/data/Writer/Ods/content-empty.xml @@ -8,7 +8,6 @@ - @@ -17,10 +16,6 @@ - - - - diff --git a/tests/data/Writer/Ods/content-hidden-worksheet.xml b/tests/data/Writer/Ods/content-hidden-worksheet.xml index 1d8956050..b58346670 100644 --- a/tests/data/Writer/Ods/content-hidden-worksheet.xml +++ b/tests/data/Writer/Ods/content-hidden-worksheet.xml @@ -11,7 +11,6 @@ - @@ -24,7 +23,6 @@ 1 - @@ -33,7 +31,6 @@ 2 - diff --git a/tests/data/Writer/Ods/content-with-data.xml b/tests/data/Writer/Ods/content-with-data.xml index 82209609f..bde23e29d 100644 --- a/tests/data/Writer/Ods/content-with-data.xml +++ b/tests/data/Writer/Ods/content-with-data.xml @@ -11,57 +11,46 @@ - - - - - - - - - - - @@ -92,7 +81,6 @@ Lorem ipsum - @@ -107,10 +95,6 @@ 42798.572060185 - - - - @@ -119,7 +103,6 @@ 2 -