diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php
index e7a8d039c4..5e8530fdb7 100644
--- a/src/PhpWord/TemplateProcessor.php
+++ b/src/PhpWord/TemplateProcessor.php
@@ -27,6 +27,7 @@
class TemplateProcessor
{
+
const MAXIMUM_REPLACEMENTS_DEFAULT = -1;
/**
@@ -147,7 +148,7 @@ protected function transformXml($xml, $xsltProcessor)
/**
* Applies XSL style sheet to template's parts.
- *
+ *
* Note: since the method doesn't make any guess on logic of the provided XSL style sheet,
* make sure that output is correctly escaped. Otherwise you may get broken document.
*
@@ -274,7 +275,7 @@ public function cloneRow($search, $numberOfClones)
$tagPos = strpos($this->tempDocumentMainPart, $search);
if (!$tagPos) {
- throw new Exception("Can not clone row, template variable not found or variable contains markup.");
+ throw new Exception(sprintf("Can not clone row %s, template variable not found or variable contains markup.", $search));
}
$rowStart = $this->findRowStart($tagPos);
@@ -297,7 +298,8 @@ public function cloneRow($search, $numberOfClones)
// 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)) {
+ !preg_match('##', $tmpXmlRow)
+ ) {
break;
}
// This row was a spanned row, update $rowEnd and search for the next row.
@@ -377,6 +379,77 @@ public function replaceBlock($blockname, $replacement)
}
}
+ /**
+ * Delete a table row in a template document.
+ *
+ * @param string $search
+ *
+ * @return void
+ *
+ * @throws \PhpOffice\PhpWord\Exception\Exception
+ */
+ public function deleteRow($search)
+ {
+ 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 = $rowEnd;
+ $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;
+ } else {
+ $this->tempDocumentMainPart = $this->getSlice(0, $extraRowStart) . $this->getSlice($extraRowEnd);
+ }
+ }
+ }
+ }
+
/**
* Delete a block of text.
*
@@ -483,6 +556,7 @@ protected function setValueForPart($search, $replace, $documentPartXML, $limit)
return str_replace($search, $replace, $documentPartXML);
} else {
$regExpEscaper = new RegExp();
+
return preg_replace($regExpEscaper->escape($search), $replace, $documentPartXML, $limit);
}
}
@@ -533,6 +607,31 @@ protected function getFooterName($index)
return sprintf('word/footer%d.xml', $index);
}
+ /**
+ * Find the start position of the nearest table before $offset.
+ *
+ * @param integer $offset
+ *
+ * @return integer
+ *
+ * @throws \PhpOffice\PhpWord\Exception\Exception
+ */
+ protected function findTableStart($offset)
+ {
+ $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 start position of the nearest table row before $offset.
*
@@ -544,10 +643,12 @@ protected function getFooterName($index)
*/
protected function findRowStart($offset)
{
- $rowStart = strrpos($this->tempDocumentMainPart, 'tempDocumentMainPart) - $offset) * -1));
+ $rowStart = strrpos($this->tempDocumentMainPart, 'tempDocumentMainPart) - $offset) * -1));
if (!$rowStart) {
- $rowStart = strrpos($this->tempDocumentMainPart, '', ((strlen($this->tempDocumentMainPart) - $offset) * -1));
+ $rowStart = strrpos($this->tempDocumentMainPart, '',
+ ((strlen($this->tempDocumentMainPart) - $offset) * -1));
}
if (!$rowStart) {
throw new Exception('Can not find the start position of the row to clone.');
@@ -556,6 +657,18 @@ protected function findRowStart($offset)
return $rowStart;
}
+ /**
+ * Find the end position of the nearest table row after $offset.
+ *
+ * @param integer $offset
+ *
+ * @return integer
+ */
+ protected function findTableEnd($offset)
+ {
+ return strpos($this->tempDocumentMainPart, '', $offset) + 7;
+ }
+
/**
* Find the end position of the nearest table row after $offset.
*
diff --git a/tests/PhpWord/TemplateProcessorTest.php b/tests/PhpWord/TemplateProcessorTest.php
index 11b43cf454..26cf76f262 100644
--- a/tests/PhpWord/TemplateProcessorTest.php
+++ b/tests/PhpWord/TemplateProcessorTest.php
@@ -177,6 +177,33 @@ public function testCloneRow()
unlink($docName);
$this->assertTrue($docFound);
}
+
+ /**
+ * @covers ::getVariables
+ * @covers ::deleteRow
+ * @covers ::saveAs
+ * @test
+ */
+ public function testDeleteRow()
+ {
+ $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/delete-row.docx');
+
+ $this->assertEquals(
+ array('deleteMe', 'deleteMeToo'),
+ $templateProcessor->getVariables()
+ );
+
+ $docName = 'delete-row-test-result.docx';
+ $templateProcessor->deleteRow('deleteMe');
+ $this->assertEquals(
+ array(),
+ $templateProcessor->getVariables()
+ );
+ $templateProcessor->saveAs($docName);
+ $docFound = file_exists($docName);
+ unlink($docName);
+ $this->assertTrue($docFound);
+ }
/**
* @covers ::setValue
diff --git a/tests/PhpWord/_files/templates/delete-row.docx b/tests/PhpWord/_files/templates/delete-row.docx
new file mode 100644
index 0000000000..dd8d8a3188
Binary files /dev/null and b/tests/PhpWord/_files/templates/delete-row.docx differ