Skip to content

Commit 91504df

Browse files
committed
Introduce deleteRow() method for TemplateProcessor
1 parent fab9966 commit 91504df

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

src/PhpWord/TemplateProcessor.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,77 @@ public function cloneRow($search, $numberOfClones): void
759759
$this->tempDocumentMainPart = $result;
760760
}
761761

762+
/**
763+
* Delete a table row in a template document.
764+
*
765+
* @param string $search
766+
*
767+
* @return void
768+
*
769+
* @throws \PhpOffice\PhpWord\Exception\Exception
770+
*/
771+
public function deleteRow($search)
772+
{
773+
if ('${' !== substr($search, 0, 2) && '}' !== substr($search, -1)) {
774+
$search = '${' . $search . '}';
775+
}
776+
777+
$tagPos = strpos($this->tempDocumentMainPart, $search);
778+
if (!$tagPos) {
779+
throw new Exception(sprintf("Can not delete row %s, template variable not found or variable contains markup.", $search));
780+
}
781+
782+
$tableStart = $this->findTableStart($tagPos);
783+
$tableEnd = $this->findTableEnd($tagPos);
784+
$xmlTable = $this->getSlice($tableStart, $tableEnd);
785+
786+
if (substr_count($xmlTable, '<w:tr') === 1) {
787+
$this->tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd);
788+
789+
return;
790+
}
791+
792+
$rowStart = $this->findRowStart($tagPos);
793+
$rowEnd = $this->findRowEnd($tagPos);
794+
$xmlRow = $this->getSlice($rowStart, $rowEnd);
795+
796+
$this->tempDocumentMainPart = $this->getSlice(0, $rowStart) . $this->getSlice($rowEnd);
797+
798+
// Check if there's a cell spanning multiple rows.
799+
if (preg_match('#<w:vMerge w:val="restart"/>#', $xmlRow)) {
800+
// $extraRowStart = $rowEnd;
801+
$extraRowStart = $rowStart;
802+
while (true) {
803+
$extraRowStart = $this->findRowStart($extraRowStart + 1);
804+
$extraRowEnd = $this->findRowEnd($extraRowStart + 1);
805+
806+
// If extraRowEnd is lower then 7, there was no next row found.
807+
if ($extraRowEnd < 7) {
808+
break;
809+
}
810+
811+
// If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row.
812+
$tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd);
813+
if (!preg_match('#<w:vMerge/>#', $tmpXmlRow) &&
814+
!preg_match('#<w:vMerge w:val="continue" />#', $tmpXmlRow)
815+
) {
816+
break;
817+
}
818+
819+
$tableStart = $this->findTableStart($extraRowEnd + 1);
820+
$tableEnd = $this->findTableEnd($extraRowEnd + 1);
821+
$xmlTable = $this->getSlice($tableStart, $tableEnd);
822+
if (substr_count($xmlTable, '<w:tr') === 1) {
823+
$this->tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd);
824+
825+
return;
826+
}
827+
828+
$this->tempDocumentMainPart = $this->getSlice(0, $extraRowStart) . $this->getSlice($extraRowEnd);
829+
}
830+
}
831+
}
832+
762833
/**
763834
* Clones a table row and populates it's values from a two-dimensional array in a template document.
764835
*
@@ -1079,6 +1150,43 @@ protected function getDocumentContentTypesName()
10791150
return '[Content_Types].xml';
10801151
}
10811152

1153+
/**
1154+
* Find the start position of the nearest table before $offset.
1155+
*
1156+
* @param integer $offset
1157+
*
1158+
* @return integer
1159+
*
1160+
* @throws \PhpOffice\PhpWord\Exception\Exception
1161+
*/
1162+
protected function findTableStart($offset)
1163+
{
1164+
$rowStart = strrpos($this->tempDocumentMainPart, '<w:tbl ',
1165+
((strlen($this->tempDocumentMainPart) - $offset) * -1));
1166+
1167+
if (!$rowStart) {
1168+
$rowStart = strrpos($this->tempDocumentMainPart, '<w:tbl>',
1169+
((strlen($this->tempDocumentMainPart) - $offset) * -1));
1170+
}
1171+
if (!$rowStart) {
1172+
throw new Exception('Can not find the start position of the table.');
1173+
}
1174+
1175+
return $rowStart;
1176+
}
1177+
1178+
/**
1179+
* Find the end position of the nearest table row after $offset.
1180+
*
1181+
* @param integer $offset
1182+
*
1183+
* @return integer
1184+
*/
1185+
protected function findTableEnd($offset)
1186+
{
1187+
return strpos($this->tempDocumentMainPart, '</w:tbl>', $offset) + 7;
1188+
}
1189+
10821190
/**
10831191
* Find the start position of the nearest table row before $offset.
10841192
*

tests/PhpWordTests/TemplateProcessorTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,33 @@ public function testXslStyleSheetCanNotBeAppliedOnFailureOfLoadingXmlFromTemplat
182182
@$templateProcessor->applyXslStyleSheet($xslDomDocument);
183183
}
184184

185+
/**
186+
* @covers ::getVariables
187+
* @covers ::deleteRow
188+
* @covers ::saveAs
189+
* @test
190+
*/
191+
public function testDeleteRow()
192+
{
193+
$templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/delete-row.docx');
194+
195+
$this->assertEquals(
196+
array('deleteMe', 'deleteMeToo'),
197+
$templateProcessor->getVariables()
198+
);
199+
200+
$docName = 'delete-row-test-result.docx';
201+
$templateProcessor->deleteRow('deleteMe');
202+
$this->assertEquals(
203+
array(),
204+
$templateProcessor->getVariables()
205+
);
206+
$templateProcessor->saveAs($docName);
207+
$docFound = file_exists($docName);
208+
unlink($docName);
209+
$this->assertTrue($docFound);
210+
}
211+
185212
/**
186213
* @covers ::cloneRow
187214
* @covers ::saveAs
Binary file not shown.

0 commit comments

Comments
 (0)