From 9e63c411f8a5f8fba2b50c88327266f712800981 Mon Sep 17 00:00:00 2001 From: Vincent Kool Date: Sat, 20 Aug 2022 23:24:54 +0200 Subject: [PATCH 1/3] Added code to read FormFields (text input, dropdown and checkbox) from a Word file --- src/PhpWord/Reader/Word2007/AbstractPart.php | 141 +++++++++++++++++- tests/PhpWord/Reader/Word2007/ElementTest.php | 141 +++++++++++++++++- 2 files changed, 279 insertions(+), 3 deletions(-) diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 729c0b95b0..94548a87c9 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -19,6 +19,7 @@ use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; use PhpOffice\PhpWord\Element\AbstractContainer; +use PhpOffice\PhpWord\Element\FormField; use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; @@ -112,8 +113,48 @@ protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $pa $headingDepth = $this->getHeadingDepth($paragraphStyle); } - // PreserveText - if ($xmlReader->elementExists('w:r/w:instrText', $domNode)) { + if ($xmlReader->elementExists('w:r/w:fldChar/w:ffData', $domNode)) { + // FormField + $partOfFormField = false; + $formNodes = array(); + $formType = null; +// $field = new FormField("type", "forntsyle", "paragraphstyle"): + $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag', $domNode); + if (0 === $textRunContainers) { + $parent->addTextBreak(null, $paragraphStyle); + } else { + $nodes = $xmlReader->getElements('*', $domNode); + $paragraph = $parent->addTextRun($paragraphStyle); + foreach ($nodes as $node) { + if($xmlReader->elementExists('w:fldChar/w:ffData', $node)) { + $partOfFormField = true; + $formNodes[] = $node; + if($xmlReader->elementExists('w:fldChar/w:ffData/w:ddList', $node)) { + $formType = "dropdown"; + } elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:textInput', $node)) { + $formType = "textinput"; + } elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:checkBox', $node)) { + $formType = "checkbox"; + } + } elseif ( + $partOfFormField && + $xmlReader->elementExists('w:fldChar', $node) && + "end" == $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar') + ) { + $formNodes[] = $node; + $partOfFormField = false; + // Process the form fields + $this->readFormField($xmlReader, $formNodes, $paragraph, $docPart, $paragraphStyle, $formType); + } elseif ($partOfFormField){ + $formNodes[] = $node; + } else { + // normal runs + $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle); + } + } + } + } elseif ($xmlReader->elementExists('w:r/w:instrText', $domNode)) { + // PreserveText $ignoreText = false; $textContent = ''; $fontStyle = $this->readFontStyle($xmlReader, $domNode); @@ -176,6 +217,102 @@ protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $pa } } + /** + * @param XMLReader $xmlReader + * @param \DOMElement[] $domNodes + * @param AbstractContainer $parent + * @param string $docPart + * @param null $paragraphStyle + * @param string $formType + * @return void + */ + private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $docPart = 'document', $paragraphStyle = null, $formType) + { + if(!in_array($formType, array("textinput", "checkbox", "dropdown"))) return; + + $formField = $parent->addFormField($formType, null, $paragraphStyle); + $ffData = $xmlReader->getElement("w:fldChar/w:ffData", $domNodes[0]); + + foreach ($xmlReader->getElements("*", $ffData) as $node) { + /** @var \DOMElement $node */ + switch ($node->localName) { + case "name": + $formField->setName($node->getAttribute("w:val")); + break; + case "ddList": + $listEntries = array(); + foreach ($xmlReader->getElements("*", $node) as $ddListNode) { + switch ($ddListNode->localName) { + case "result": + $formField->setValue($xmlReader->getAttribute("w:val", $ddListNode)); + break; + case "default": + $formField->setDefault($xmlReader->getAttribute("w:val", $ddListNode)); + break; + case "listEntry": + $listEntries[] = $xmlReader->getAttribute("w:val", $ddListNode); + break; + } + } + $formField->setEntries($listEntries); + if (!is_null($formField->getValue())) { + $formField->setText($listEntries[$formField->getValue()]); + } + break; + case "textInput": + foreach ($xmlReader->getElements("*", $node) as $ddListNode) { + switch ($ddListNode->localName) { + case "default": + $formField->setDefault($xmlReader->getAttribute("w:val", $ddListNode)); + break; + case "format": + case "maxLength": + break; + } + } + break; + case "checkBox": + foreach ($xmlReader->getElements("*", $node) as $ddListNode) { + + switch ($ddListNode->localName) { + case "default": + $formField->setDefault($xmlReader->getAttribute("w:val", $ddListNode)); + break; + case "checked": + $formField->setValue($xmlReader->getAttribute("w:val", $ddListNode)); + break; + case "size": + case "sizeAuto": + break; + } + } + break; + } + } + + if ("textinput" == $formType) { + $ignoreText = true; + $textContent = ""; + foreach ($domNodes as $node) { + if ($xmlReader->elementExists('w:fldChar', $node)) { + $fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar'); + if ('separate' == $fldCharType) { + $ignoreText = false; + } elseif ('end' == $fldCharType) { + $ignoreText = true; + } + } + + if (false === $ignoreText) { + $textContent .= $xmlReader->getValue('w:t', $node); + } + } + $formField->setValue(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8')); + $formField->setText(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8')); + } + } + + /** * Returns the depth of the Heading, returns 0 for a Title * diff --git a/tests/PhpWord/Reader/Word2007/ElementTest.php b/tests/PhpWord/Reader/Word2007/ElementTest.php index 08b72418ac..be812b10dd 100644 --- a/tests/PhpWord/Reader/Word2007/ElementTest.php +++ b/tests/PhpWord/Reader/Word2007/ElementTest.php @@ -312,4 +312,143 @@ public function testReadDrawing() $elements = $phpWord->getSection(0)->getElements(); $this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); } -} + + + /** + * Test reading FormField - DROPDOWN + */ + public function testReadFormFieldDropdown() + { + $documentXml = ' + + Reference + + + + + + + + + + + + + + + + + + + + FORMDROPDOWN + + + + + + + + + + + + + + + + + + + '; + + $phpWord = $this->getDocumentFromString(array('document' => $documentXml)); + + $elements = $phpWord->getSection(0)->getElements(); + $this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + + $subElements = $elements[0]->getElements(); + + $this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); + $this->assertEquals("Reference", $subElements[0]->getText()); + + $this->assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[1]); + $this->assertEquals("dropdown", $subElements[1]->getType()); + $this->assertEquals("DropDownList1", $subElements[1]->getName()); + $this->assertEquals("2", $subElements[1]->getValue()); + $this->assertEquals("Option Two", $subElements[1]->getText()); + $this->assertEquals(array("TBD", "Option One", "Option Two", "Option Three", "Other"), $subElements[1]->getEntries()); + + } + + + + /** + * Test reading FormField - textinput + */ + public function testReadFormFieldTextinput() + { + $documentXml = ' + + Fieldname + + + + + + + + + + + + + + + + FORMTEXT + + + + + + + + + + + + + + + + + + This is some sample text + + + + + + + + '; + + $phpWord = $this->getDocumentFromString(array('document' => $documentXml)); + + $elements = $phpWord->getSection(0)->getElements(); + $this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + + $subElements = $elements[0]->getElements(); + + $this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); + $this->assertEquals("Fieldname", $subElements[0]->getText()); + + $this->assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[1]); + $this->assertEquals("textinput", $subElements[1]->getType()); + $this->assertEquals("TextInput2", $subElements[1]->getName()); + $this->assertEquals("This is some sample text", $subElements[1]->getValue()); + $this->assertEquals("This is some sample text", $subElements[1]->getText()); + } + +} \ No newline at end of file From 3aa08f7f17861b4db5252c56267574c276160251 Mon Sep 17 00:00:00 2001 From: Vincent Kool Date: Sat, 20 Aug 2022 23:44:03 +0200 Subject: [PATCH 2/3] Fixed code style issues and added a testcase for reading a FormField of type checkbox --- src/PhpWord/Reader/Word2007/AbstractPart.php | 82 +++++++++---------- tests/PhpWord/Reader/Word2007/ElementTest.php | 81 ++++++++++++++---- 2 files changed, 105 insertions(+), 58 deletions(-) diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 94548a87c9..14cdb89e0b 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -126,26 +126,25 @@ protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $pa $nodes = $xmlReader->getElements('*', $domNode); $paragraph = $parent->addTextRun($paragraphStyle); foreach ($nodes as $node) { - if($xmlReader->elementExists('w:fldChar/w:ffData', $node)) { + if ($xmlReader->elementExists('w:fldChar/w:ffData', $node)) { $partOfFormField = true; $formNodes[] = $node; - if($xmlReader->elementExists('w:fldChar/w:ffData/w:ddList', $node)) { - $formType = "dropdown"; + if ($xmlReader->elementExists('w:fldChar/w:ffData/w:ddList', $node)) { + $formType = 'dropdown'; } elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:textInput', $node)) { - $formType = "textinput"; + $formType = 'textinput'; } elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:checkBox', $node)) { - $formType = "checkbox"; + $formType = 'checkbox'; } - } elseif ( - $partOfFormField && + } elseif ($partOfFormField && $xmlReader->elementExists('w:fldChar', $node) && - "end" == $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar') + 'end' == $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar') ) { $formNodes[] = $node; $partOfFormField = false; // Process the form fields $this->readFormField($xmlReader, $formNodes, $paragraph, $docPart, $paragraphStyle, $formType); - } elseif ($partOfFormField){ + } elseif ($partOfFormField) { $formNodes[] = $node; } else { // normal runs @@ -224,33 +223,34 @@ protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $pa * @param string $docPart * @param null $paragraphStyle * @param string $formType - * @return void */ - private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $docPart = 'document', $paragraphStyle = null, $formType) + private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $docPart, $paragraphStyle, $formType) { - if(!in_array($formType, array("textinput", "checkbox", "dropdown"))) return; + if (!in_array($formType, array('textinput', 'checkbox', 'dropdown'))) { + return; + } $formField = $parent->addFormField($formType, null, $paragraphStyle); - $ffData = $xmlReader->getElement("w:fldChar/w:ffData", $domNodes[0]); + $ffData = $xmlReader->getElement('w:fldChar/w:ffData', $domNodes[0]); - foreach ($xmlReader->getElements("*", $ffData) as $node) { + foreach ($xmlReader->getElements('*', $ffData) as $node) { /** @var \DOMElement $node */ switch ($node->localName) { - case "name": - $formField->setName($node->getAttribute("w:val")); + case 'name': + $formField->setName($node->getAttribute('w:val')); break; - case "ddList": + case 'ddList': $listEntries = array(); - foreach ($xmlReader->getElements("*", $node) as $ddListNode) { + foreach ($xmlReader->getElements('*', $node) as $ddListNode) { switch ($ddListNode->localName) { - case "result": - $formField->setValue($xmlReader->getAttribute("w:val", $ddListNode)); + case 'result': + $formField->setValue($xmlReader->getAttribute('w:val', $ddListNode)); break; - case "default": - $formField->setDefault($xmlReader->getAttribute("w:val", $ddListNode)); + case 'default': + $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode)); break; - case "listEntry": - $listEntries[] = $xmlReader->getAttribute("w:val", $ddListNode); + case 'listEntry': + $listEntries[] = $xmlReader->getAttribute('w:val', $ddListNode); break; } } @@ -259,30 +259,29 @@ private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $ $formField->setText($listEntries[$formField->getValue()]); } break; - case "textInput": - foreach ($xmlReader->getElements("*", $node) as $ddListNode) { + case 'textInput': + foreach ($xmlReader->getElements('*', $node) as $ddListNode) { switch ($ddListNode->localName) { - case "default": - $formField->setDefault($xmlReader->getAttribute("w:val", $ddListNode)); + case 'default': + $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode)); break; - case "format": - case "maxLength": + case 'format': + case 'maxLength': break; } } break; - case "checkBox": - foreach ($xmlReader->getElements("*", $node) as $ddListNode) { - + case 'checkBox': + foreach ($xmlReader->getElements('*', $node) as $ddListNode) { switch ($ddListNode->localName) { - case "default": - $formField->setDefault($xmlReader->getAttribute("w:val", $ddListNode)); + case 'default': + $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode)); break; - case "checked": - $formField->setValue($xmlReader->getAttribute("w:val", $ddListNode)); + case 'checked': + $formField->setValue($xmlReader->getAttribute('w:val', $ddListNode)); break; - case "size": - case "sizeAuto": + case 'size': + case 'sizeAuto': break; } } @@ -290,9 +289,9 @@ private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $ } } - if ("textinput" == $formType) { + if ('textinput' == $formType) { $ignoreText = true; - $textContent = ""; + $textContent = ''; foreach ($domNodes as $node) { if ($xmlReader->elementExists('w:fldChar', $node)) { $fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar'); @@ -312,7 +311,6 @@ private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $ } } - /** * Returns the depth of the Heading, returns 0 for a Title * diff --git a/tests/PhpWord/Reader/Word2007/ElementTest.php b/tests/PhpWord/Reader/Word2007/ElementTest.php index be812b10dd..18e195eb16 100644 --- a/tests/PhpWord/Reader/Word2007/ElementTest.php +++ b/tests/PhpWord/Reader/Word2007/ElementTest.php @@ -313,7 +313,6 @@ public function testReadDrawing() $this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); } - /** * Test reading FormField - DROPDOWN */ @@ -370,19 +369,16 @@ public function testReadFormFieldDropdown() $subElements = $elements[0]->getElements(); $this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); - $this->assertEquals("Reference", $subElements[0]->getText()); + $this->assertEquals('Reference', $subElements[0]->getText()); $this->assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[1]); - $this->assertEquals("dropdown", $subElements[1]->getType()); - $this->assertEquals("DropDownList1", $subElements[1]->getName()); - $this->assertEquals("2", $subElements[1]->getValue()); - $this->assertEquals("Option Two", $subElements[1]->getText()); - $this->assertEquals(array("TBD", "Option One", "Option Two", "Option Three", "Other"), $subElements[1]->getEntries()); - + $this->assertEquals('dropdown', $subElements[1]->getType()); + $this->assertEquals('DropDownList1', $subElements[1]->getName()); + $this->assertEquals('2', $subElements[1]->getValue()); + $this->assertEquals('Option Two', $subElements[1]->getText()); + $this->assertEquals(array('TBD', 'Option One', 'Option Two', 'Option Three', 'Other'), $subElements[1]->getEntries()); } - - /** * Test reading FormField - textinput */ @@ -442,13 +438,66 @@ public function testReadFormFieldTextinput() $subElements = $elements[0]->getElements(); $this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); - $this->assertEquals("Fieldname", $subElements[0]->getText()); + $this->assertEquals('Fieldname', $subElements[0]->getText()); $this->assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[1]); - $this->assertEquals("textinput", $subElements[1]->getType()); - $this->assertEquals("TextInput2", $subElements[1]->getName()); - $this->assertEquals("This is some sample text", $subElements[1]->getValue()); - $this->assertEquals("This is some sample text", $subElements[1]->getText()); + $this->assertEquals('textinput', $subElements[1]->getType()); + $this->assertEquals('TextInput2', $subElements[1]->getName()); + $this->assertEquals('This is some sample text', $subElements[1]->getValue()); + $this->assertEquals('This is some sample text', $subElements[1]->getText()); } -} \ No newline at end of file + /** + * Test reading FormField - checkbox + */ + public function testReadFormFieldCheckbox() + { + $documentXml = ' + + + + + + + + + + + + + + + + + + FORMCHECKBOX + + + + + + + + + + + + + + '; + + $phpWord = $this->getDocumentFromString(array('document' => $documentXml)); + + $elements = $phpWord->getSection(0)->getElements(); + $this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]); + + $subElements = $elements[0]->getElements(); + +// $this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $subElements[0]); +// $this->assertEquals('Fieldname', $subElements[0]->getText()); + + $this->assertInstanceOf('PhpOffice\PhpWord\Element\FormField', $subElements[0]); + $this->assertEquals('checkbox', $subElements[0]->getType()); + $this->assertEquals('SomeCheckbox', $subElements[0]->getName()); + } +} From 94e46ba2d81c598e6eb6df8794a95a48735b6544 Mon Sep 17 00:00:00 2001 From: Vincent Kool Date: Sat, 20 Aug 2022 23:58:48 +0200 Subject: [PATCH 3/3] Fixed minor issue found by Scrutinizer --- src/PhpWord/Reader/Word2007/AbstractPart.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 14cdb89e0b..8be917e6d6 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -143,7 +143,7 @@ protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $pa $formNodes[] = $node; $partOfFormField = false; // Process the form fields - $this->readFormField($xmlReader, $formNodes, $paragraph, $docPart, $paragraphStyle, $formType); + $this->readFormField($xmlReader, $formNodes, $paragraph, $paragraphStyle, $formType); } elseif ($partOfFormField) { $formNodes[] = $node; } else { @@ -220,11 +220,10 @@ protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $pa * @param XMLReader $xmlReader * @param \DOMElement[] $domNodes * @param AbstractContainer $parent - * @param string $docPart - * @param null $paragraphStyle + * @param mixed $paragraphStyle * @param string $formType */ - private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $docPart, $paragraphStyle, $formType) + private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $paragraphStyle, $formType) { if (!in_array($formType, array('textinput', 'checkbox', 'dropdown'))) { return;