Skip to content

Commit 14c6e6f

Browse files
authored
Merge pull request #2104 from simivar/feature/delete-row
Introduce deleteRow() method for TemplateProcessor
2 parents 8521612 + f482f26 commit 14c6e6f

File tree

3 files changed

+123
-0
lines changed

3 files changed

+123
-0
lines changed

src/PhpWord/TemplateProcessor.php

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

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

1146+
/**
1147+
* Find the start position of the nearest table before $offset.
1148+
*/
1149+
protected function findTableStart(int $offset): int
1150+
{
1151+
$rowStart = strrpos(
1152+
$this->tempDocumentMainPart,
1153+
'<w:tbl ',
1154+
((strlen($this->tempDocumentMainPart) - $offset) * -1)
1155+
);
1156+
1157+
if (!$rowStart) {
1158+
$rowStart = strrpos(
1159+
$this->tempDocumentMainPart,
1160+
'<w:tbl>',
1161+
((strlen($this->tempDocumentMainPart) - $offset) * -1)
1162+
);
1163+
}
1164+
if (!$rowStart) {
1165+
throw new Exception('Can not find the start position of the table.');
1166+
}
1167+
1168+
return $rowStart;
1169+
}
1170+
1171+
/**
1172+
* Find the end position of the nearest table row after $offset.
1173+
*/
1174+
protected function findTableEnd(int $offset): int
1175+
{
1176+
return strpos($this->tempDocumentMainPart, '</w:tbl>', $offset) + 7;
1177+
}
1178+
10821179
/**
10831180
* Find the start position of the nearest table row before $offset.
10841181
*

tests/PhpWordTests/TemplateProcessorTest.php

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

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

0 commit comments

Comments
 (0)