From 33383a2a08a84cb3e8332ac4e5e4c2f33b9689a3 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 30 Nov 2023 07:37:18 -0800 Subject: [PATCH 1/2] Generate Table Cell if Row Doesn't Have Any Fix #2505. Word treats file as corrupt if a table row does not contain a cell (documentation for why this is so is included in the issue). Person reporting the issue suggests that dropping such a row from the output file is preferred. However, I think generating an empty cell instead is closer to the user's expectation. For example, as demonstrated in the unit tests added with this PR, if a table has row 1 and 3 which contain cells, but row 2 does not, the table as written to the file will have 3 rows, with the second containing an empty cell. --- src/PhpWord/Writer/Word2007/Element/Table.php | 10 +- .../Writer/Word2007/Element/TableTest.php | 218 ++++++++++++++++++ 2 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 tests/PhpWordTests/Writer/Word2007/Element/TableTest.php diff --git a/src/PhpWord/Writer/Word2007/Element/Table.php b/src/PhpWord/Writer/Word2007/Element/Table.php index 9364fe45c1..a32cc19639 100644 --- a/src/PhpWord/Writer/Word2007/Element/Table.php +++ b/src/PhpWord/Writer/Word2007/Element/Table.php @@ -103,8 +103,14 @@ private function writeRow(XMLWriter $xmlWriter, RowElement $row): void } // Write cells - foreach ($row->getCells() as $cell) { - $this->writeCell($xmlWriter, $cell); + $cells = $row->getCells(); + if (count($cells) === 0) { + // issue 2505 - Word treats doc as corrupt if row without cell + $this->writeCell($xmlWriter, new CellElement()); + } else { + foreach ($cells as $cell) { + $this->writeCell($xmlWriter, $cell); + } } $xmlWriter->endElement(); // w:tr diff --git a/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php new file mode 100644 index 0000000000..cacedbc0d1 --- /dev/null +++ b/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php @@ -0,0 +1,218 @@ +addSection(); + $section->addText('Before table (normal).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R1C1'); + $tc = $table->addCell(); + $tc->addText('R1C2'); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R2C1'); + $tc = $table->addCell(); + $tc->addText('R2C2'); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R3C1'); + $tc = $table->addCell(); + $tc->addText('R3C2'); + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[2]'), 'should be only 1 table'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[3]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]')); + } + + public static function testSomeRowWithNoCells(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Before table (row 2 has no cells).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R1C1'); + $tc = $table->addCell(); + $tc->addText('R1C2'); + $row = $table->addRow(); + /* + $tc = $table->addCell(); + $tc->addText('R2C1'); + $tc = $table->addCell(); + $tc->addText('R2C2'); + */ + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R3C1'); + $tc = $table->addCell(); + $tc->addText('R3C2'); + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[2]'), 'should be only 1 table'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); + //self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[3]')); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]')); + } + + public static function testOnly1RowWithNoCells(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Before table (only 1 row and it has no cells).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + $row = $table->addRow(); + /* + $tc = $table->addCell(); + $tc->addText('R1C1'); + $tc = $table->addCell(); + $tc->addText('R1C2'); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R2C1'); + $tc = $table->addCell(); + $tc->addText('R2C2'); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R3C1'); + $tc = $table->addCell(); + $tc->addText('R3C2'); + */ + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[2]'), 'only 1 table should be written'); + + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); + //self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]')); + + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); + /* + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[3]')); + + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]')); + */ + } + + public static function testNoRows(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Before table (no rows therefore omitted).'); + $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); + /* + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R1C1'); + $tc = $table->addCell(); + $tc->addText('R1C2'); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R2C1'); + $tc = $table->addCell(); + $tc->addText('R2C2'); + $row = $table->addRow(); + $tc = $table->addCell(); + $tc->addText('R3C1'); + $tc = $table->addCell(); + $tc->addText('R3C2'); + */ + $section->addText('After table.'); + + $doc = TestHelperDOCX::getDocument($phpWord); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[1]'), 'no table should be written'); + /* + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]')); + */ + + /* + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[3]')); + + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); + self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[2]')); + self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]')); + */ + } +} From 3abb0b4f92f6cad2a50876349552aa0619aaa9b0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 7 Jan 2024 10:40:18 -0800 Subject: [PATCH 2/2] Remove Commented-Out Code in Tests --- docs/changes/2.x/2.0.0.md | 24 +++++++ .../Writer/Word2007/Element/TableTest.php | 71 ------------------- 2 files changed, 24 insertions(+), 71 deletions(-) create mode 100644 docs/changes/2.x/2.0.0.md diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/2.x/2.0.0.md new file mode 100644 index 0000000000..26977dd006 --- /dev/null +++ b/docs/changes/2.x/2.0.0.md @@ -0,0 +1,24 @@ +# [2.0.0](https://github.com/PHPOffice/PHPWord/tree/2.0.0) (WIP) + +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.2.0...2.0.0) + +## Enhancements + +### Bug fixes + +- MsDoc Reader : Correct Font Size Calculation by [@oleibman](https://github.com/oleibman) fixing [#2526](https://github.com/PHPOffice/PHPWord/issues/2526) in [#2531](https://github.com/PHPOffice/PHPWord/pull/2531) +- TemplateProcessor Persist File After Destruct [@oleibman](https://github.com/oleibman) fixing [#2539](https://github.com/PHPOffice/PHPWord/issues/2539) in [#2542](https://github.com/PHPOffice/PHPWord/pull/2531) +- Html Reader : Process Titles as Headings not Paragraphs [@0b10011](https://github.com/0b10011) and [@oleibman](https://github.com/oleibman) Issue [#1692](https://github.com/PHPOffice/PHPWord/issues/1692) PR [#2533](https://github.com/PHPOffice/PHPWord/pull/2533) +- Generate Table Cell if Row Doesn't Have Any [@oleibman](https://github.com/oleibman) fixing [#2505](https://github.com/PHPOffice/PHPWord/issues/2505) in [#2516](https://github.com/PHPOffice/PHPWord/pull/2516) + +### Miscellaneous + +- Bump dompdf/dompdf from 2.0.3 to 2.0.4 by [@dependabot](https://github.com/dependabot) in [#2530](https://github.com/PHPOffice/PHPWord/pull/2530) +- Bump phpunit/phpunit from 9.6.13 to 9.6.14 by [@dependabot](https://github.com/dependabot) in [#2519](https://github.com/PHPOffice/PHPWord/pull/2519) +- Bump mpdf/mpdf from 8.2.0 to 8.2.2 by [@dependabot](https://github.com/dependabot) in [#2518](https://github.com/PHPOffice/PHPWord/pull/2518) +- Bump phpmd/phpmd from 2.14.1 to 2.15.0 by [@dependabot](https://github.com/dependabot) in [#2538](https://github.com/PHPOffice/PHPWord/pull/2538) +- Bump phpunit/phpunit from 9.6.14 to 9.6.15 by [@dependabot](https://github.com/dependabot) in [#2537](https://github.com/PHPOffice/PHPWord/pull/2537) +- Bump symfony/process from 5.4.28 to 5.4.34 by [@dependabot](https://github.com/dependabot) in [#2536](https://github.com/PHPOffice/PHPWord/pull/2536) +- Allow rgb() when converting Html [@oleibman](https://github.com/oleibman) fixing [#2508](https://github.com/PHPOffice/PHPWord/issues/2508) in [#2512](https://github.com/PHPOffice/PHPWord/pull/2512) + +### BC Breaks diff --git a/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php b/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php index cacedbc0d1..57010893ae 100644 --- a/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php +++ b/tests/PhpWordTests/Writer/Word2007/Element/TableTest.php @@ -89,12 +89,6 @@ public static function testSomeRowWithNoCells(): void $tc = $table->addCell(); $tc->addText('R1C2'); $row = $table->addRow(); - /* - $tc = $table->addCell(); - $tc->addText('R2C1'); - $tc = $table->addCell(); - $tc->addText('R2C2'); - */ $row = $table->addRow(); $tc = $table->addCell(); $tc->addText('R3C1'); @@ -113,7 +107,6 @@ public static function testSomeRowWithNoCells(): void self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); - //self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[3]')); self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); @@ -128,22 +121,6 @@ public static function testOnly1RowWithNoCells(): void $section->addText('Before table (only 1 row and it has no cells).'); $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); $row = $table->addRow(); - /* - $tc = $table->addCell(); - $tc->addText('R1C1'); - $tc = $table->addCell(); - $tc->addText('R1C2'); - $row = $table->addRow(); - $tc = $table->addCell(); - $tc->addText('R2C1'); - $tc = $table->addCell(); - $tc->addText('R2C2'); - $row = $table->addRow(); - $tc = $table->addCell(); - $tc->addText('R3C1'); - $tc = $table->addCell(); - $tc->addText('R3C2'); - */ $section->addText('After table.'); $doc = TestHelperDOCX::getDocument($phpWord); @@ -152,20 +129,8 @@ public static function testOnly1RowWithNoCells(): void self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); - //self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]')); self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); - /* - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); - self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[3]')); - - self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[2]')); - self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]')); - */ } public static function testNoRows(): void @@ -174,45 +139,9 @@ public static function testNoRows(): void $section = $phpWord->addSection(); $section->addText('Before table (no rows therefore omitted).'); $table = $section->addTable(['width' => 5000, 'unit' => TblWidth::PERCENT]); - /* - $row = $table->addRow(); - $tc = $table->addCell(); - $tc->addText('R1C1'); - $tc = $table->addCell(); - $tc->addText('R1C2'); - $row = $table->addRow(); - $tc = $table->addCell(); - $tc->addText('R2C1'); - $tc = $table->addCell(); - $tc->addText('R2C2'); - $row = $table->addRow(); - $tc = $table->addCell(); - $tc->addText('R3C1'); - $tc = $table->addCell(); - $tc->addText('R3C2'); - */ $section->addText('After table.'); $doc = TestHelperDOCX::getDocument($phpWord); self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl[1]'), 'no table should be written'); - /* - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[2]')); - self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[1]/w:tc[3]')); - */ - - /* - self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[2]')); - self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[2]/w:tc[3]')); - - self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc')); - self::assertTrue($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[2]')); - self::assertFalse($doc->elementExists('/w:document/w:body/w:tbl/w:tr[3]/w:tc[3]')); - */ } }