From ca5f08130230066ffc9ac2feee797f08b6bc2faa Mon Sep 17 00:00:00 2001 From: lubosdz Date: Sat, 11 Jul 2020 15:42:28 +0200 Subject: [PATCH] Html parser (addHtml) - support horizontal rule
--- src/PhpWord/Shared/Html.php | 61 +++++++++++++++++++++++++++++-- tests/PhpWord/Shared/HtmlTest.php | 48 ++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 8 deletions(-) diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 08004b7a..9e5d84b1 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -183,6 +183,7 @@ class Html 'img' => array('Image', $node, $element, $styles, null, null, null), 'br' => array('LineBreak', null, $element, $styles, null, null, null), 'a' => array('Link', $node, $element, $styles, null, null, null), + 'hr' => array('HorizRule', $node, $element, $styles, null, null, null), ); $newElement = null; @@ -630,10 +631,27 @@ class Html } break; case 'border': - if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+)\s+([a-z]+)/', $cValue, $matches)) { - $styles['borderSize'] = Converter::cssToPoint($matches[1]); - $styles['borderColor'] = trim($matches[2], '#'); - $styles['borderStyle'] = self::mapBorderStyle($matches[3]); + case 'border-top': + case 'border-bottom': + case 'border-right': + case 'border-left': + // must have exact order [width color style], e.g. "1px #0011CC solid" or "2pt green solid" + // Word does not accept shortened hex colors e.g. #CCC, only full e.g. #CCCCCC + if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+|[a-zA-Z]+)\s+([a-z]+)/', $cValue, $matches)) { + if(false !== strpos($cKey, '-')){ + $which = explode('-', $cKey)[1]; + $which = ucfirst($which); // e.g. bottom -> Bottom + }else{ + $which = ''; + } + // normalization: in HTML 1px means tinest possible line width, so we cannot convert 1px -> 15 twips, coz line'd be bold, we use smallest twip instead + $size = strtolower(trim($matches[1])); + // (!) BC change: up to ver. 0.17.0 Converter was incorrectly converting to points - Converter::cssToPoint($matches[1]) + $size = ($size == '1px') ? 1 : Converter::cssToTwip($size); + // valid variants may be e.g. borderSize, borderTopSize, borderLeftColor, etc .. + $styles["border{$which}Size"] = $size; // twips + $styles["border{$which}Color"] = trim($matches[2], '#'); + $styles["border{$which}Style"] = self::mapBorderStyle($matches[3]); } break; } @@ -835,4 +853,39 @@ class Html return $element->addLink($target, $node->textContent, $styles['font'], $styles['paragraph']); } + + /** + * Render horizontal rule + * Note: Word rule is not the same as HTML's
since it does not support width and thus neither alignment + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + */ + protected static function parseHorizRule($node, $element) + { + $styles = self::parseInlineStyle($node); + + //
is implemented as an empty paragraph - extending 100% inside the section + // Some properties may be controlled, e.g.
+ + $fontStyle = $styles + ['size' => 3]; + + $paragraphStyle = $styles + [ + 'lineHeight' => 0.25, // multiply default line height - e.g. 1, 1.5 etc + 'spacing' => 0, // twip + 'spaceBefore' => 120, // twip, 240/2 (default line height) + 'spaceAfter' => 120, // twip + 'borderBottomSize' => empty($styles['line-height']) ? 1 : $styles['line-height'], + 'borderBottomColor' => empty($styles['color']) ? '000000' : $styles['color'], + 'borderBottomStyle' => 'single', // same as "solid" + ]; + + $element->addText("", $fontStyle, $paragraphStyle); + + // Notes:
cannot be: + // - table - throws error "cannot be inside textruns", e.g. lists + // - line - that is a shape, has different behaviour + // - repeated text, e.g. underline "_", because of unpredictable line wrapping + } + } diff --git a/tests/PhpWord/Shared/HtmlTest.php b/tests/PhpWord/Shared/HtmlTest.php index 61ebd535..52d641af 100644 --- a/tests/PhpWord/Shared/HtmlTest.php +++ b/tests/PhpWord/Shared/HtmlTest.php @@ -702,6 +702,9 @@ HTML; $this->assertEquals('dxa', $doc->getElement($xpath)->getAttribute('w:type')); } + /** + * Test parsing background color for table rows and table cellspacing + */ public function testParseCellspacingRowBgColor() { $phpWord = new \PhpOffice\PhpWord\PhpWord(); @@ -726,10 +729,6 @@ HTML; Html::addHtml($section, $html); $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); - // uncomment to see results - file_put_contents('./table_src.html', $html); - file_put_contents('./table_result_'.time().'.docx', file_get_contents( TestHelperDOCX::getFile() ) ); - $xpath = '/w:document/w:body/w:tbl/w:tblPr/w:tblCellSpacing'; $this->assertTrue($doc->elementExists($xpath)); $this->assertEquals(3 * 15, $doc->getElement($xpath)->getAttribute('w:w')); @@ -744,4 +743,45 @@ HTML; $this->assertEquals('FF0000', $doc->getElement($xpath)->getAttribute('w:fill')); } + /** + * Parse horizontal rule + */ + public function testParseHorizRule() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + + // borders & backgrounds are here just for better visual comparison + $html = <<Simple default rule:

+
+

Custom style rule:

+
+

END

+HTML; + + Html::addHtml($section, $html); + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + + // default rule + $xpath = '/w:document/w:body/w:p[2]/w:pPr/w:pBdr/w:bottom'; + $this->assertTrue($doc->elementExists($xpath)); + $this->assertEquals('single', $doc->getElement($xpath)->getAttribute('w:val')); // solid + $this->assertEquals('1', $doc->getElement($xpath)->getAttribute('w:sz')); // 1 twip + $this->assertEquals('000000', $doc->getElement($xpath)->getAttribute('w:color')); // black + + // custom style rule + $xpath = '/w:document/w:body/w:p[4]/w:pPr/w:pBdr/w:bottom'; + $this->assertTrue($doc->elementExists($xpath)); + $this->assertEquals('single', $doc->getElement($xpath)->getAttribute('w:val')); + $this->assertEquals(5 * 15, $doc->getElement($xpath)->getAttribute('w:sz')); + $this->assertEquals('lightblue', $doc->getElement($xpath)->getAttribute('w:color')); + + $xpath = '/w:document/w:body/w:p[4]/w:pPr/w:spacing'; + $this->assertTrue($doc->elementExists($xpath)); + $this->assertEquals(22.5, $doc->getElement($xpath)->getAttribute('w:before')); + $this->assertEquals(0, $doc->getElement($xpath)->getAttribute('w:after')); + $this->assertEquals(240, $doc->getElement($xpath)->getAttribute('w:line')); + } + }