diff --git a/src/PhpWord/TemplateProcessor.php b/src/PhpWord/TemplateProcessor.php index 8aee40c546..cc20c73543 100644 --- a/src/PhpWord/TemplateProcessor.php +++ b/src/PhpWord/TemplateProcessor.php @@ -24,9 +24,12 @@ use PhpOffice\PhpWord\Exception\CopyFileException; use PhpOffice\PhpWord\Exception\CreateTemporaryFileException; use PhpOffice\PhpWord\Exception\Exception; +use PhpOffice\PhpWord\Shared\Html; use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\Shared\XMLWriter; use PhpOffice\PhpWord\Shared\ZipArchive; +use PhpOffice\PhpWord\Writer\Word2007; +use ReflectionClass; use Throwable; use XSLTProcessor; @@ -315,6 +318,123 @@ public function setComplexBlock($search, Element\AbstractElement $complexType): $this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:p'); } + /** + * @param string $search + * @param string $htmlContent + * @param bool $fullHtml + */ + public function setHtmlBlock($search, $htmlContent, $fullHtml = false): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + //deal remote load Image + $pattern = '/]+src\s*=\s*["\']([^"\']+)["\'][^>]*>/i'; + preg_match_all($pattern, $htmlContent, $matches); + $imageSrcList = $matches[1]; + if (!empty($imageSrcList)) { + foreach ($imageSrcList as $imageSrc) { + try { + $content = file_get_contents($imageSrc); + } catch (\Exception $e) { + $localImg = __DIR__ . '/resources/doc.png'; + $htmlContent = str_replace($imageSrc, $localImg, $htmlContent); + } + } + } + Html::addHtml($section, $htmlContent, $fullHtml); + $zip = $this->zip(); + $obj = new Word2007($phpWord); + $refClass = new ReflectionClass(Word2007::class); + $addFilesToPackage = $refClass->getMethod('addFilesToPackage'); + $addFilesToPackage->setAccessible(true); + $sectionMedia = Media::getElements('section'); + //add image to zip + if (!empty($sectionMedia)) { + //insert image to zip + $res = $addFilesToPackage->invoke($obj, $zip, $sectionMedia); + $registerContentTypes = $refClass->getMethod('registerContentTypes'); + $registerContentTypes->setAccessible(true); + $registerContentTypes->invoke($obj, $sectionMedia); + + $relationships = $refClass->getProperty('relationships'); + $relationships->setAccessible(true); + $tmpRelationships = []; + foreach ($sectionMedia as $element) { + $tmpRelationships[] = $element; + } + $relationships->setValue($obj, $tmpRelationships); + } + $documentWriterPart = $obj->getWriterPart('Document'); + $relsDocumentWriterPart = $obj->getWriterPart('RelsDocument'); + $documentXml = $documentWriterPart->write(); + $relsDocumentXml = $relsDocumentWriterPart->write(); + // Load the XML string into a SimpleXMLElement + $xml = simplexml_load_string($documentXml); + // Extract content between tags + if ($xml === false) { + return; + } + $bodyContent = $xml->xpath('//w:body/*'); + // Output the extracted content + $documentBodyStr = ''; + if ($bodyContent) { + foreach ($bodyContent as $element) { + $documentBodyStr .= $element->asXML(); + } + } + + //replace html content r:id vaule avoid rid conflict + $rIdsElement = $xml->xpath('//*[@r:id]'); + $rIdValuesMap = []; + if ($rIdsElement) { + foreach ($rIdsElement as $idEle) { + $rid = (string) $idEle->attributes('r', true)->id; + $rIdValuesMap[$rid] = $rid; + } + } + if (!empty($rIdValuesMap)) { + foreach ($rIdValuesMap as $rid => $value) { + $replactVulue = $rid . '-1'; + $rIdValuesMap[$rid] = $replactVulue; + $documentBodyStr = str_replace($rid, $replactVulue, $documentBodyStr); + } + } + //replace document.xml + $this->replaceXmlBlock($search, $documentBodyStr, 'w:p'); + + $xml = simplexml_load_string($relsDocumentXml); + if ($xml === false) { + return; + } + // Register the namespace + $xml->registerXPathNamespace('ns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + // Use XPath to find all Relationship nodes + $RelationshipXmls = $xml->xpath('//ns:Relationship'); + $RelationshipStr = ''; + if ($RelationshipXmls) { + foreach ($RelationshipXmls as $relationshipXml) { + $rid = (string) $relationshipXml->attributes(); + if (isset($rIdValuesMap[$rid])) { + $tmpStr = $relationshipXml->asXML(); + if ($tmpStr != false) { + $tmpStr = str_replace($rid, $rIdValuesMap[$rid], $tmpStr); + $RelationshipStr .= $tmpStr; + } + } + } + } + + //add relation to document.xml.rels + if ($RelationshipStr) { + $relsFileName = $this->getRelationsName($this->getMainPartName()); + $content = $this->tempDocumentRelations[$this->getMainPartName()]; + $endStr = ''; + $replaceValue = $RelationshipStr . $endStr; + $content = str_replace($endStr, $replaceValue, $content); + $this->tempDocumentRelations[$this->getMainPartName()] = $content; + } + } + /** * @param mixed $search * @param mixed $replace diff --git a/tests/PhpWordTests/TemplateProcessorTest.php b/tests/PhpWordTests/TemplateProcessorTest.php index 49e88d1b5b..bea023c918 100644 --- a/tests/PhpWordTests/TemplateProcessorTest.php +++ b/tests/PhpWordTests/TemplateProcessorTest.php @@ -1630,4 +1630,21 @@ public function testShouldMakeFieldsUpdateOnOpenWithCustomMacro(): void $templateProcessor->setUpdateFields(false); self::assertStringContainsString('', $templateProcessor->getSettingsPart()); } + + public function testSetHtml(): void + { + Settings::setOutputEscapingEnabled(true); + $image1 = __DIR__ . '/_files/images/earth.jpg'; + $image2 = __DIR__ . '/_files/images/mars.jpg'; + $content = '

+

+

HPJ LDAP(Lightweight Directory Access Protocol),轻量级目录访问协议,是一种在线目录访问协议,主要用于目录中资源的搜索和查询。如果在用户可控制的输入中没有对 LDAP 语法进行除去或引用,那么生成的 LDAP 查询可能会导致

'; + $templateProcessor = new TemplateProcessor(__DIR__ . '/_files/templates/template_to_html.docx'); + $templateProcessor->setHtmlBlock('html_content', $content); + $docName = 'html-to-template-test.docx'; + $templateProcessor->saveAs($docName); + $docFound = file_exists($docName); + unlink($docName); + self::assertTrue($docFound); + } } diff --git a/tests/PhpWordTests/_files/templates/template_to_html.docx b/tests/PhpWordTests/_files/templates/template_to_html.docx new file mode 100644 index 0000000000..dd2e6a8ebd Binary files /dev/null and b/tests/PhpWordTests/_files/templates/template_to_html.docx differ