diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 1cf32429..6f1ba922 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -759,6 +759,70 @@ class TemplateProcessor $this->tempDocumentMainPart = $result; } + /** + * Delete a table row in a template document. + */ + public function deleteRow(string $search): void + { + if ('${' !== substr($search, 0, 2) && '}' !== substr($search, -1)) { + $search = '${' . $search . '}'; + } + + $tagPos = strpos($this->tempDocumentMainPart, $search); + if (!$tagPos) { + throw new Exception(sprintf('Can not delete row %s, template variable not found or variable contains markup.', $search)); + } + + $tableStart = $this->findTableStart($tagPos); + $tableEnd = $this->findTableEnd($tagPos); + $xmlTable = $this->getSlice($tableStart, $tableEnd); + + if (substr_count($xmlTable, 'tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd); + + return; + } + + $rowStart = $this->findRowStart($tagPos); + $rowEnd = $this->findRowEnd($tagPos); + $xmlRow = $this->getSlice($rowStart, $rowEnd); + + $this->tempDocumentMainPart = $this->getSlice(0, $rowStart) . $this->getSlice($rowEnd); + + // Check if there's a cell spanning multiple rows. + if (preg_match('##', $xmlRow)) { + $extraRowStart = $rowStart; + while (true) { + $extraRowStart = $this->findRowStart($extraRowStart + 1); + $extraRowEnd = $this->findRowEnd($extraRowStart + 1); + + // If extraRowEnd is lower then 7, there was no next row found. + if ($extraRowEnd < 7) { + break; + } + + // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row. + $tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd); + if (!preg_match('##', $tmpXmlRow) && + !preg_match('##', $tmpXmlRow) + ) { + break; + } + + $tableStart = $this->findTableStart($extraRowEnd + 1); + $tableEnd = $this->findTableEnd($extraRowEnd + 1); + $xmlTable = $this->getSlice($tableStart, $tableEnd); + if (substr_count($xmlTable, 'tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd); + + return; + } + + $this->tempDocumentMainPart = $this->getSlice(0, $extraRowStart) . $this->getSlice($extraRowEnd); + } + } + } + /** * Clones a table row and populates it's values from a two-dimensional array in a template document. * @@ -1079,6 +1143,39 @@ class TemplateProcessor return '[Content_Types].xml'; } + /** + * Find the start position of the nearest table before $offset. + */ + protected function findTableStart(int $offset): int + { + $rowStart = strrpos( + $this->tempDocumentMainPart, + 'tempDocumentMainPart) - $offset) * -1) + ); + + if (!$rowStart) { + $rowStart = strrpos( + $this->tempDocumentMainPart, + '', + ((strlen($this->tempDocumentMainPart) - $offset) * -1) + ); + } + if (!$rowStart) { + throw new Exception('Can not find the start position of the table.'); + } + + return $rowStart; + } + + /** + * Find the end position of the nearest table row after $offset. + */ + protected function findTableEnd(int $offset): int + { + return strpos($this->tempDocumentMainPart, '', $offset) + 7; + } + /** * Find the start position of the nearest table row before $offset. * diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index fc975fab..e57e5bf1 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -182,6 +182,32 @@ final class TemplateProcessorTest extends \PHPUnit\Framework\TestCase @$templateProcessor->applyXslStyleSheet($xslDomDocument); } + /** + * @covers ::deleteRow + * @covers ::getVariables + * @covers ::saveAs + */ + public function testDeleteRow(): void + { + $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/delete-row.docx'); + + self::assertEquals( + ['deleteMe', 'deleteMeToo'], + $templateProcessor->getVariables() + ); + + $docName = 'delete-row-test-result.docx'; + $templateProcessor->deleteRow('deleteMe'); + self::assertEquals( + [], + $templateProcessor->getVariables() + ); + $templateProcessor->saveAs($docName); + $docFound = file_exists($docName); + unlink($docName); + self::assertTrue($docFound); + } + /** * @covers ::cloneRow * @covers ::saveAs diff --git a/tests/PhpWordTests/_files/templates/delete-row.docx b/tests/PhpWordTests/_files/templates/delete-row.docx new file mode 100644 index 00000000..dd8d8a31 Binary files /dev/null and b/tests/PhpWordTests/_files/templates/delete-row.docx differ