Html Writer Conditional Formatting Inline Css

Fix #4539. Conditional Formatting was recently added to Html Writer. It works fine when not using inline Css. However, when using inline Css, the code inadvertently added 2 different `style` attributes (one for the unconditional style and one for the conditional style) to the same cell. This is not valid html, and results in losing the conditional styling. This PR combines the two `style` attributes into one, which will now come after the `class`, `colspan`, and `rowspan` attributes.

Aside from the new tests, this PR changes an unusually large number of existing tests. While this might normally be considered a red flag, it is not a problem here. All of the changes involve merely changing the order of attributes within html tags; none of them affect how the generated html would appear in a browser.
This commit is contained in:
oleibman
2025-07-15 22:22:34 -07:00
parent 747ccd1b44
commit a15091737f
10 changed files with 78 additions and 33 deletions
+12 -3
View File
@@ -1330,7 +1330,11 @@ class Html extends BaseWriter
$style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $row])
? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $row]) : '';
$html .= ' <tr style="' . $style . '">' . PHP_EOL;
if ($style === '') {
$html .= ' <tr>' . PHP_EOL;
} else {
$html .= ' <tr style="' . $style . '">' . PHP_EOL;
}
}
return $html;
@@ -1542,6 +1546,7 @@ class Html extends BaseWriter
$html .= ' data-type="' . DataType::TYPE_STRING . '"';
}
}
$holdCss = '';
if (!$this->useInlineCss && !$this->isPdf && is_string($cssClass)) {
$html .= ' class="' . $cssClass . '"';
if ($htmlx) {
@@ -1587,7 +1592,7 @@ class Html extends BaseWriter
$xcssClass['position'] = 'relative';
}
/** @var string[] $xcssClass */
$html .= ' style="' . $this->assembleCSS($xcssClass) . '"';
$holdCss = $this->assembleCSS($xcssClass);
if ($this->useInlineCss) {
$html .= ' class="gridlines gridlinesp"';
}
@@ -1638,13 +1643,17 @@ class Html extends BaseWriter
}
if ($matched) {
$styles = $this->createCSSStyle($styleMerger->getStyle());
$html .= ' style="';
$html .= ' style="' . $holdCss . ' ';
$holdCss = '';
foreach ($styles as $key => $value) {
$html .= $key . ':' . $value . ';';
}
$html .= '"';
}
}
if ($holdCss !== '') {
$html .= ' style="' . $holdCss . '"';
}
$html .= '>';
$html .= $htmlx;
@@ -99,7 +99,7 @@ class HideMergeTest extends TestCase
self::assertStringContainsString(
'<tr class="row1">'
. '<td class="column0 style0" style="width:42pt; height:17pt">&nbsp;</td>'
. '<td class="column1 style1 s style1" style="width:84pt; height:17pt" colspan="2" rowspan="2">Hello World Headline</td>'
. '<td class="column1 style1 s style1" colspan="2" rowspan="2" style="width:84pt; height:17pt">Hello World Headline</td>'
. '</tr>',
$html
);
@@ -112,8 +112,8 @@ class HideMergeTest extends TestCase
self::assertStringContainsString(
'<tr class="row3">'
. '<td class="column0 style0" style="width:42pt; height:17pt">&nbsp;</td>'
. '<td class="column1 style2 s style2" style="width:42pt; height:17pt" rowspan="2">Label 1</td>'
. '<td class="column2 style3 s style3" style="width:42pt; height:17pt" rowspan="2">Text 1</td>'
. '<td class="column1 style2 s style2" rowspan="2" style="width:42pt; height:17pt">Label 1</td>'
. '<td class="column2 style3 s style3" rowspan="2" style="width:42pt; height:17pt">Text 1</td>'
. '</tr>',
$html
);
@@ -126,8 +126,8 @@ class HideMergeTest extends TestCase
self::assertStringContainsString(
'<tr class="row5">'
. '<td class="column0 style0" style="width:42pt; height:17pt">&nbsp;</td>'
. '<td class="column1 style2 s style2" style="width:42pt; height:17pt" rowspan="2">Label 2</td>'
. '<td class="column2 style3 s style3" style="width:42pt; height:17pt" rowspan="2">Text 2</td>'
. '<td class="column1 style2 s style2" rowspan="2" style="width:42pt; height:17pt">Label 2</td>'
. '<td class="column2 style3 s style3" rowspan="2" style="width:42pt; height:17pt">Text 2</td>'
. '</tr>',
$html
);
@@ -153,15 +153,15 @@ class BetterBooleanTest extends Functional\AbstractFunctional
$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);
$html = str_replace(' width:42pt"', '"', $html);
self::assertStringNotContainsString('TRUE', $html);
self::assertStringContainsString('<td style="text-align:right;">1</td>', $html);
self::assertStringContainsString('<td style="text-align:left;">Hello</td>', $html);
self::assertStringContainsString('<td data-type="b" style="text-align:center;">VRAI</td>', $html);
self::assertStringContainsString('<td data-type="b" style="text-align:center;">FAUX</td>', $html);
self::assertStringContainsString('<td data-type="s" style="text-align:left;">1</td>', $html);
self::assertStringContainsString('<td style="text-align:left;">AB</td>', $html);
self::assertStringContainsString('<td style="text-align:right;">3</td>', $html);
self::assertStringContainsString('<td class="gridlines gridlinesp" style="text-align:right;">1</td>', $html);
self::assertStringContainsString('<td class="gridlines gridlinesp" style="text-align:left;">Hello</td>', $html);
self::assertStringContainsString('<td data-type="b" class="gridlines gridlinesp" style="text-align:center;">VRAI</td>', $html);
self::assertStringContainsString('<td data-type="b" class="gridlines gridlinesp" style="text-align:center;">FAUX</td>', $html);
self::assertStringContainsString('<td data-type="s" class="gridlines gridlinesp" style="text-align:left;">1</td>', $html);
self::assertStringContainsString('<td class="gridlines gridlinesp" style="text-align:left;">AB</td>', $html);
self::assertStringContainsString('<td class="gridlines gridlinesp" style="text-align:right;">3</td>', $html);
$reloaded = $this->writeAndReload($spreadsheet, 'Html', null, $this->setBetter(...));
$spreadsheet->disconnectWorksheets();
@@ -34,7 +34,7 @@ class Issue3678Test extends TestCase
self::assertStringContainsString('.n { text-align:right }', $html);
$writer->setUseInlineCss(true);
$html = $writer->generateHtmlAll();
self::assertStringContainsString('<td style="' . $style2 . '" class="gridlines gridlinesp">1</td>', $html);
self::assertStringContainsString('<td class="gridlines gridlinesp" style="' . $style2 . '">1</td>', $html);
$spreadsheet->disconnectWorksheets();
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Writer\Html;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PhpOffice\PhpSpreadsheet\Writer\Html;
use PHPUnit\Framework\TestCase;
class Issue4539Test extends TestCase
{
public function testInlineAndNot(): void
{
$infile = 'tests/data/Reader/XLSX/issue.4539.xlsx';
$reader = new XlsxReader();
$spreadsheet = $reader->load($infile);
$writer = new Html($spreadsheet);
$writer->setConditionalFormatting(true);
$writer->setUseInlineCss(true);
$html = $writer->generateHtmlAll();
$expected = '<td class="gridlines gridlinesp" style="vertical-align:bottom; color:#000000; font-family:\'Aptos Narrow\'; font-size:12pt; text-align:right; width:102pt vertical-align:bottom;border-bottom:none #000000;border-top:none #000000;border-left:none #000000;border-right:none #000000;color:#000000;font-family:\'Aptos Narrow\';font-size:12pt;background-color:#5A8AC6;">5</td>';
self::assertStringContainsString($expected, $html, 'inline conditional style');
$expected = '<td class="gridlines gridlinesp" style="vertical-align:bottom; font-weight:bold; color:#000000; font-family:\'Aptos Narrow\'; font-size:12pt; text-align:left; width:102pt">Column Heading</td>';
self::assertStringContainsString($expected, $html, 'inline no conditional style');
$writer->setUseInlineCss(false);
$html = $writer->generateHtmlAll();
$expected = '<td class="column0 style2 n" style=" vertical-align:bottom;border-bottom:none #000000;border-top:none #000000;border-left:none #000000;border-right:none #000000;color:#000000;font-family:\'Aptos Narrow\';font-size:12pt;background-color:#5A8AC6;">5</td>';
self::assertStringContainsString($expected, $html, 'notinline conditional style');
$expected = '<td class="column0 style1 s">Column Heading</td>';
self::assertStringContainsString($expected, $html, 'notinline no conditional style');
$spreadsheet->disconnectWorksheets();
}
}
@@ -23,7 +23,7 @@ class NoTitleTest extends TestCase
$writer->setUseInlineCss(true);
$html = $writer->generateHTMLAll();
self::assertStringContainsString('<title>Sheet1</title>', $html);
self::assertStringContainsString('<td style="vertical-align:bottom; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt" class="gridlines gridlinesp">C1</td>', $html);
self::assertStringContainsString('<td class="gridlines gridlinesp" style="vertical-align:bottom; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt">C1</td>', $html);
$writer->setUseInlineCss(false);
$html = $writer->generateHTMLAll();
self::assertStringContainsString('<td class="column2 style0 s">C1</td>', $html);
@@ -55,8 +55,8 @@ class NoTitleTest extends TestCase
$writer = new Html($spreadsheet);
$writer->setUseInlineCss(true);
$html = $writer->generateHTMLAll();
self::assertStringContainsString('<td style="vertical-align:bottom; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:right; width:42pt" class="gridlines gridlinesp">7</td>', $html);
self::assertStringContainsString('<td style="vertical-align:bottom; border-bottom:none #808080; border-top:none #808080; border-left:none #808080; border-right:none #808080; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:right; width:42pt" class="gridlines gridlinesp">19</td>', $html);
self::assertStringContainsString('<td class="gridlines gridlinesp" style="vertical-align:bottom; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:right; width:42pt">7</td>', $html);
self::assertStringContainsString('<td class="gridlines gridlinesp" style="vertical-align:bottom; border-bottom:none #808080; border-top:none #808080; border-left:none #808080; border-right:none #808080; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:right; width:42pt">19</td>', $html);
$spreadsheet->disconnectWorksheets();
}
}
@@ -93,7 +93,7 @@ class HideMergeTest extends TestCase
);
self::assertStringContainsString(
'<tr class="row1">'
. '<td class="column1 style1 s style1" style="width:84pt; height:17pt" colspan="2" rowspan="2">Hello World Headline</td>'
. '<td class="column1 style1 s style1" colspan="2" rowspan="2" style="width:84pt; height:17pt">Hello World Headline</td>'
. '</tr>',
$html
);
@@ -104,8 +104,8 @@ class HideMergeTest extends TestCase
);
self::assertStringContainsString(
'<tr class="row3">'
. '<td class="column1 style2 s style2" style="width:42pt; height:17pt" rowspan="2">Label 1</td>'
. '<td class="column2 style3 s style3" style="width:42pt; height:17pt" rowspan="2">Text 1</td>'
. '<td class="column1 style2 s style2" rowspan="2" style="width:42pt; height:17pt">Label 1</td>'
. '<td class="column2 style3 s style3" rowspan="2" style="width:42pt; height:17pt">Text 1</td>'
. '</tr>',
$html
);
@@ -116,8 +116,8 @@ class HideMergeTest extends TestCase
);
self::assertStringContainsString(
'<tr class="row5">'
. '<td class="column1 style2 s style2" style="width:42pt; height:17pt" rowspan="2">Label 2</td>'
. '<td class="column2 style3 s style3" style="width:42pt; height:17pt" rowspan="2">Text 2</td>'
. '<td class="column1 style2 s style2" rowspan="2" style="width:42pt; height:17pt">Label 2</td>'
. '<td class="column2 style3 s style3" rowspan="2" style="width:42pt; height:17pt">Text 2</td>'
. '</tr>',
$html
);
@@ -87,15 +87,15 @@ class HideMergeTest extends TestCase
self::assertStringContainsString(
'<tbody><tr style="height:17pt">'
. '<td></td>'
. '<td style="vertical-align:bottom; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt" class="gridlines gridlinesp">B</td>'
. '<td style="vertical-align:bottom; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt" class="gridlines gridlinesp">C</td>'
. '<td class="gridlines gridlinesp" style="vertical-align:bottom; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt">B</td>'
. '<td class="gridlines gridlinesp" style="vertical-align:bottom; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt">C</td>'
. '</tr>',
$html
);
self::assertStringContainsString(
'<tr style="height:17pt">'
. '<td></td>'
. '<td style="vertical-align:bottom; text-align:center; border-bottom:1px solid #000000 !important; border-top:1px solid #000000 !important; border-left:1px solid #000000 !important; border-right:1px solid #000000 !important; font-weight:bold; color:#000000; font-family:\'Calibri\'; font-size:11pt; width:84pt; height:17pt" class="gridlines gridlinesp" colspan="2" rowspan="2">Hello World Headline</td>'
. '<td class="gridlines gridlinesp" colspan="2" rowspan="2" style="vertical-align:bottom; text-align:center; border-bottom:1px solid #000000 !important; border-top:1px solid #000000 !important; border-left:1px solid #000000 !important; border-right:1px solid #000000 !important; font-weight:bold; color:#000000; font-family:\'Calibri\'; font-size:11pt; width:84pt; height:17pt">Hello World Headline</td>'
. '</tr>',
$html
);
@@ -109,16 +109,16 @@ class HideMergeTest extends TestCase
self::assertStringContainsString(
'<tr style="height:17pt">'
. '<td></td>'
. '<td style="vertical-align:middle; border-bottom:1px solid #000000 !important; border-top:1px solid #000000 !important; border-left:1px solid #000000 !important; border-right:1px solid #000000 !important; font-weight:bold; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt" class="gridlines gridlinesp" rowspan="2">Label 1</td>'
. '<td style="vertical-align:middle; border-bottom:1px solid #000000 !important; border-top:1px solid #000000 !important; border-left:1px solid #000000 !important; border-right:1px solid #000000 !important; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt" class="gridlines gridlinesp" rowspan="2">Text 1</td>'
. '<td class="gridlines gridlinesp" rowspan="2" style="vertical-align:middle; border-bottom:1px solid #000000 !important; border-top:1px solid #000000 !important; border-left:1px solid #000000 !important; border-right:1px solid #000000 !important; font-weight:bold; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt">Label 1</td>'
. '<td class="gridlines gridlinesp" rowspan="2" style="vertical-align:middle; border-bottom:1px solid #000000 !important; border-top:1px solid #000000 !important; border-left:1px solid #000000 !important; border-right:1px solid #000000 !important; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt">Text 1</td>'
. '</tr>',
$html
);
self::assertStringContainsString(
'<tr style="height:17pt">'
. '<td></td>'
. '<td style="vertical-align:middle; border-bottom:1px solid #000000 !important; border-top:1px solid #000000 !important; border-left:1px solid #000000 !important; border-right:1px solid #000000 !important; font-weight:bold; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt" class="gridlines gridlinesp" rowspan="2">Label 2</td>'
. '<td style="vertical-align:middle; border-bottom:1px solid #000000 !important; border-top:1px solid #000000 !important; border-left:1px solid #000000 !important; border-right:1px solid #000000 !important; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt" class="gridlines gridlinesp" rowspan="2">Text 2</td>'
. '<td class="gridlines gridlinesp" rowspan="2" style="vertical-align:middle; border-bottom:1px solid #000000 !important; border-top:1px solid #000000 !important; border-left:1px solid #000000 !important; border-right:1px solid #000000 !important; font-weight:bold; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt">Label 2</td>'
. '<td class="gridlines gridlinesp" rowspan="2" style="vertical-align:middle; border-bottom:1px solid #000000 !important; border-top:1px solid #000000 !important; border-left:1px solid #000000 !important; border-right:1px solid #000000 !important; color:#000000; font-family:\'Calibri\'; font-size:11pt; text-align:left; width:42pt; height:17pt">Text 2</td>'
. '</tr>',
$html
);
@@ -32,7 +32,7 @@ class MergedBorderTest extends TestCase
$sheet->setShowGridlines(false);
$writer = new Tcpdf($spreadsheet);
$html = $writer->generateHtmlAll();
self::assertSame(1, preg_match('/border-bottom:1px solid #FF0000 !important; border-top:1px solid #FF0000 !important; border-left:1px solid #FF0000 !important; border-right:1px solid #FF0000 !important; color:#000000;[^>]+ colspan="2" rowspan="4"/', $html));
self::assertSame(1, preg_match('/ colspan="2" rowspan="4" style="vertical-align:bottom; border-bottom:1px solid #FF0000 !important; border-top:1px solid #FF0000 !important; border-left:1px solid #FF0000 !important; border-right:1px solid #FF0000 !important; color:#000000;[^>]+/', $html));
$spreadsheet->disconnectWorksheets();
}
}
Binary file not shown.