-
-
Save KKSzymanowski/acf892616805aed9900289b9bb1ede6d to your computer and use it in GitHub Desktop.
Fill in text form fields in DOCX
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Word; | |
use DOMAttr; | |
use DOMElement; | |
use DOMXPath; | |
use Exception; | |
use Illuminate\Support\Arr; | |
use PhpOffice\PhpWord\Shared\XMLReader; | |
use Ramsey\Uuid\Uuid; | |
use ZipArchive; | |
class FormFieldHelper | |
{ | |
protected $templatePath; | |
protected $dom; | |
protected $xpath; | |
protected $formFields; | |
public function __construct($templatePath) | |
{ | |
$this->templatePath = $templatePath; | |
$this->open(); | |
} | |
protected function open() | |
{ | |
$xmlReader = new XMLReader(); | |
$this->dom = $xmlReader->getDomFromZip($this->templatePath, 'word/document.xml'); | |
$this->xpath = new DOMXpath($this->dom); | |
$this->formFields = []; | |
/** @var DOMElement $element */ | |
foreach ($this->xpath->query('//w:fldChar/w:ffData') as $element) { | |
$nameElements = $this->xpath->query('w:name', $element); | |
if (count($nameElements) != 1) { | |
continue; | |
} | |
$name = $nameElements[0]->getAttribute('w:val'); | |
/** @var DOMElement $current */ | |
$current = $start = $element->parentNode->parentNode; | |
$emptyTextElements = []; | |
while ($current = $current->nextSibling) { | |
$textElements = $this->xpath->query('w:t', $current); | |
/** @var DOMElement $textElement */ | |
foreach ($textElements as $textElement) { | |
if ($textElement->nodeValue == mb_chr(hexdec('2002'))) { | |
$emptyTextElements[] = $current; | |
break; | |
} | |
} | |
$fldCharElements = $this->xpath->query('w:fldChar/@w:fldCharType', $current); | |
/** @var DOMAttr $fldCharElement */ | |
foreach ($fldCharElements as $fldCharElement) { | |
if ($fldCharElement->value == 'end') { | |
break 2; | |
} | |
} | |
} | |
if (count($emptyTextElements) == 0) { | |
continue; | |
} | |
$this->formFields[$name] = [ | |
'mainNode' => $start, | |
'emptyTextElements' => $emptyTextElements, | |
]; | |
} | |
} | |
public function getFieldNames() | |
{ | |
return array_keys($this->formFields); | |
} | |
public function setFieldValues($fieldValues) | |
{ | |
$fieldsKeys = array_keys($this->formFields); | |
sort($fieldsKeys); | |
$fieldValuesKeys = array_keys($fieldValues); | |
sort($fieldValuesKeys); | |
if ($fieldsKeys != $fieldValuesKeys) { | |
throw new Exception('Form field values do not match for template ' . $this->templatePath . '. Expected: ' . implode(',', array_keys($this->formFields)) . ', provided: ' . implode(',', array_keys($fieldValues))); | |
} | |
foreach ($this->formFields as $name => $field) { | |
/** @var DOMElement[] $emptyTextElements */ | |
$emptyTextElements = $field['emptyTextElements']; | |
/** @var DOMElement $firstAfterDeleted */ | |
$firstAfterDeleted = Arr::last($emptyTextElements)->nextSibling; | |
foreach ($emptyTextElements as $emptyTextElement) { | |
$field['mainNode']->parentNode->removeChild($emptyTextElement); | |
} | |
/* | |
Create this structure: | |
<w:r w:rsidR="00F545D9"> | |
<w:rPr> | |
<w:noProof/> | |
<w:lang w:val="pl-PL"/> | |
</w:rPr> | |
<w:t><CONTENT HERE>></w:t> | |
</w:r> | |
*/ | |
$wr = $this->dom->createElement('w:r'); | |
$wrpr = $this->dom->createElement('w:rPr'); | |
$wlang = $this->dom->createElement('w:lang'); | |
$wlang->setAttribute('w:val', 'pl-PL'); | |
$wrpr->appendChild($this->dom->createElement('w:noProof')); | |
$wrpr->appendChild($wlang); | |
$wt = $this->dom->createElement('w:t'); | |
$wt->nodeValue = $fieldValues[$name]; | |
$wr->appendChild($wrpr); | |
$wr->appendChild($wt); | |
$firstAfterDeleted->parentNode->insertBefore($wr, $firstAfterDeleted); | |
} | |
return $this; | |
} | |
public function save($outputDir) | |
{ | |
$tempFile = tmpfile(); | |
$tmpFilename = stream_get_meta_data($tempFile)['uri']; | |
copy($this->templatePath, $tmpFilename); | |
$zip = new ZipArchive(); | |
if (!$zip->open($tmpFilename)) { | |
throw new Exception('Could not open ' . $tmpFilename); | |
} | |
$zip->addFromString('word/document.xml', $this->dom->saveXML()); | |
$zip->close(); | |
$fileName = Uuid::uuid4() . '.docx'; | |
copy($tmpFilename, $outputDir . '/' . $fileName); | |
return $fileName; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment