From a01d22ed67b4ac43ff99ea932a1ed9bc4c63c5e8 Mon Sep 17 00:00:00 2001 From: troosan Date: Sat, 11 Nov 2017 23:49:23 +0100 Subject: [PATCH] improve HTML parser and add tests --- src/PhpWord/Element/Image.php | 2 +- src/PhpWord/Shared/Converter.php | 42 ++++++++++++++++++ src/PhpWord/Shared/Html.php | 15 ++++--- src/PhpWord/Shared/OLERead.php | 8 +++- src/PhpWord/Writer/HTML/Style/Paragraph.php | 7 +-- .../Word2007/Element/AbstractElement.php | 12 +++--- src/PhpWord/Writer/Word2007/Part/Comments.php | 6 ++- src/PhpWord/Writer/Word2007/Style/Font.php | 1 + tests/PhpWord/Element/ImageTest.php | 3 ++ tests/PhpWord/Shared/ConverterTest.php | 17 ++++++++ tests/PhpWord/Shared/HtmlTest.php | 42 +++++++++++++++--- tests/PhpWord/Writer/HTMLTest.php | 16 ++++++- tests/PhpWord/Writer/PDF/MPDFTest.php | 1 + tests/PhpWord/Writer/Word2007/ElementTest.php | 43 +++++++++++++++++++ 14 files changed, 187 insertions(+), 28 deletions(-) diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index c9620b6b..a5bd7283 100644 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -449,7 +449,7 @@ class Image extends AbstractElement $tempFilename = tempnam(Settings::getTempDir(), 'PHPWordImage'); if (false === $tempFilename) { - throw new CreateTemporaryFileException(); + throw new CreateTemporaryFileException(); // @codeCoverageIgnore } $zip = new ZipArchive(); diff --git a/src/PhpWord/Shared/Converter.php b/src/PhpWord/Shared/Converter.php index 6ba2b567..43c2f299 100644 --- a/src/PhpWord/Shared/Converter.php +++ b/src/PhpWord/Shared/Converter.php @@ -26,6 +26,7 @@ class Converter const INCH_TO_TWIP = 1440; const INCH_TO_PIXEL = 96; const INCH_TO_POINT = 72; + const INCH_TO_PICA = 6; const PIXEL_TO_EMU = 9525; const DEGREE_TO_ANGLE = 60000; @@ -227,6 +228,17 @@ class Converter return round($emu / self::PIXEL_TO_EMU); } + /** + * Convert pica to point + * + * @param int $pica + * @return float + */ + public static function picaToPoint($pica = 1) + { + return $pica / self::INCH_TO_PICA * self::INCH_TO_POINT; + } + /** * Convert degree to angle * @@ -275,4 +287,34 @@ class Converter return array($red, $green, $blue); } + + /** + * Transforms a size in CSS format (eg. 10px, 10px, ...) to points + * + * @param string $value + * @return float + */ + public static function cssToPoint($value) + { + preg_match('/^[+-]?([0-9]+.?[0-9]+)?(px|em|ex|%|in|cm|mm|pt|pc)$/i', $value, $matches); + $size = $matches[1]; + $unit = $matches[2]; + + switch ($unit) { + case 'pt': + return $size; + case 'px': + return self::pixelToPoint($size); + case 'cm': + return self::cmToPoint($size); + case 'mm': + return self::cmToPoint($size / 10); + case 'in': + return self::inchToPoint($size); + case 'pc': + return self::picaToPoint($size); + default: + return null; + } + } } diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index 620839b4..c4292eb4 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -233,11 +233,9 @@ class Html { $styles['font'] = self::parseInlineStyle($node, $styles['font']); - // Commented as source of bug #257. `method_exists` doesn't seems to work properly in this case. - // @todo Find better error checking for this one - // if (method_exists($element, 'addText')) { - $element->addText($node->nodeValue, $styles['font'], $styles['paragraph']); - // } + if (is_callable(array($element, 'addText'))) { + $element->addText($node->nodeValue, $styles['font'], $styles['paragraph']); + } } /** @@ -375,6 +373,13 @@ class Html break; } break; + case 'font-size': + $styles['size'] = Converter::cssToPoint($cValue); + break; + case 'font-family': + $cValue = array_map('trim', explode(',', $cValue)); + $styles['name'] = ucwords($cValue[0]); + break; case 'color': $styles['color'] = trim($cValue, '#'); break; diff --git a/src/PhpWord/Shared/OLERead.php b/src/PhpWord/Shared/OLERead.php index e4efd7da..1321b8da 100644 --- a/src/PhpWord/Shared/OLERead.php +++ b/src/PhpWord/Shared/OLERead.php @@ -110,15 +110,18 @@ class OLERead $bbdBlocks = $this->numBigBlockDepotBlocks; + // @codeCoverageIgnoreStart if ($this->numExtensionBlocks != 0) { $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS)/4; } - + // @codeCoverageIgnoreEnd + for ($i = 0; $i < $bbdBlocks; ++$i) { $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); $pos += 4; } + // @codeCoverageIgnoreStart for ($j = 0; $j < $this->numExtensionBlocks; ++$j) { $pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE; $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1); @@ -133,6 +136,7 @@ class OLERead $this->extensionBlock = self::getInt4d($this->data, $pos); } } + // @codeCoverageIgnoreEnd $pos = 0; $this->bigBlockChain = ''; @@ -196,7 +200,7 @@ class OLERead } if ($numBlocks == 0) { - return ''; + return '';// @codeCoverageIgnore } $block = $this->props[$stream]['startBlock']; diff --git a/src/PhpWord/Writer/HTML/Style/Paragraph.php b/src/PhpWord/Writer/HTML/Style/Paragraph.php index af551dc5..57e44e85 100644 --- a/src/PhpWord/Writer/HTML/Style/Paragraph.php +++ b/src/PhpWord/Writer/HTML/Style/Paragraph.php @@ -44,11 +44,6 @@ class Paragraph extends AbstractStyle $textAlign = ''; switch ($style->getAlignment()) { - case Jc::START: - case Jc::NUM_TAB: - case Jc::LEFT: - $textAlign = 'left'; - break; case Jc::CENTER: $textAlign = 'center'; break; @@ -65,7 +60,7 @@ class Paragraph extends AbstractStyle case Jc::JUSTIFY: $textAlign = 'justify'; break; - default: + default: //all others, align left $textAlign = 'left'; break; } diff --git a/src/PhpWord/Writer/Word2007/Element/AbstractElement.php b/src/PhpWord/Writer/Word2007/Element/AbstractElement.php index 07ffc286..86018fd2 100644 --- a/src/PhpWord/Writer/Word2007/Element/AbstractElement.php +++ b/src/PhpWord/Writer/Word2007/Element/AbstractElement.php @@ -139,10 +139,10 @@ abstract class AbstractElement { if ($this->element->getCommentRangeEnd() != null) { $comment = $this->element->getCommentRangeEnd(); - //only set the ID if it is not yet set, otherwise it will overwrite it + //only set the ID if it is not yet set, otherwise it will overwrite it, this should normally not happen if ($comment->getElementId() == null) { - $comment->setElementId(); - } + $comment->setElementId(); // @codeCoverageIgnore + } // @codeCoverageIgnore $this->xmlWriter->writeElementBlock('w:commentRangeEnd', array('w:id' => $comment->getElementId())); $this->xmlWriter->startElement('w:r'); @@ -150,10 +150,10 @@ abstract class AbstractElement $this->xmlWriter->endElement(); } elseif ($this->element->getCommentRangeStart() != null && $this->element->getCommentRangeStart()->getEndElement() == null) { $comment = $this->element->getCommentRangeStart(); - //only set the ID if it is not yet set, otherwise it will overwrite it + //only set the ID if it is not yet set, otherwise it will overwrite it, this should normally not happen if ($comment->getElementId() == null) { - $comment->setElementId(); - } + $comment->setElementId(); // @codeCoverageIgnore + } // @codeCoverageIgnore $this->xmlWriter->writeElementBlock('w:commentRangeEnd', array('w:id' => $comment->getElementId())); $this->xmlWriter->startElement('w:r'); diff --git a/src/PhpWord/Writer/Word2007/Part/Comments.php b/src/PhpWord/Writer/Word2007/Part/Comments.php index b2b49864..4551ca92 100644 --- a/src/PhpWord/Writer/Word2007/Part/Comments.php +++ b/src/PhpWord/Writer/Word2007/Part/Comments.php @@ -78,8 +78,10 @@ class Comments extends AbstractPart $xmlWriter->startElement('w:comment'); $xmlWriter->writeAttribute('w:id', $comment->getElementId()); $xmlWriter->writeAttribute('w:author', $comment->getAuthor()); - $xmlWriter->writeAttribute('w:date', $comment->getDate()->format($this->dateFormat)); - $xmlWriter->writeAttribute('w:initials', $comment->getInitials()); + if ($comment->getDate() != null) { + $xmlWriter->writeAttribute('w:date', $comment->getDate()->format($this->dateFormat)); + } + $xmlWriter->writeAttributeIf($comment->getInitials() != null, 'w:initials', $comment->getInitials()); $containerWriter = new Container($xmlWriter, $comment); $containerWriter->write(); diff --git a/src/PhpWord/Writer/Word2007/Style/Font.php b/src/PhpWord/Writer/Word2007/Style/Font.php index 0cb3209f..3fbff63d 100644 --- a/src/PhpWord/Writer/Word2007/Style/Font.php +++ b/src/PhpWord/Writer/Word2007/Style/Font.php @@ -59,6 +59,7 @@ class Font extends AbstractStyle if (!$style instanceof \PhpOffice\PhpWord\Style\Font) { return; } + $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('w:rPr'); diff --git a/tests/PhpWord/Element/ImageTest.php b/tests/PhpWord/Element/ImageTest.php index 00449e1f..381b9086 100644 --- a/tests/PhpWord/Element/ImageTest.php +++ b/tests/PhpWord/Element/ImageTest.php @@ -206,6 +206,9 @@ class ImageTest extends \PHPUnit\Framework\TestCase $this->assertEquals('imagecreatefromstring', $image->getImageCreateFunction()); $this->assertEquals('imagejpeg', $image->getImageFunction()); $this->assertTrue($image->isMemImage()); + + $this->assertNotNull($image->getImageStringData()); + $this->assertNotNull($image->getImageStringData(true)); } /** diff --git a/tests/PhpWord/Shared/ConverterTest.php b/tests/PhpWord/Shared/ConverterTest.php index a71046aa..28d68e17 100644 --- a/tests/PhpWord/Shared/ConverterTest.php +++ b/tests/PhpWord/Shared/ConverterTest.php @@ -88,6 +88,9 @@ class ConverterTest extends \PHPUnit\Framework\TestCase $result = Converter::emuToPixel($value); $this->assertEquals(round($value / 9525), $result); + $result = Converter::picaToPoint($value); + $this->assertEquals($value / 6 * 72, $result, '', 0.00001); + $result = Converter::degreeToAngle($value); $this->assertEquals((int) round($value * 60000), $result); @@ -112,4 +115,18 @@ class ConverterTest extends \PHPUnit\Framework\TestCase $this->assertEquals($value[1], $result); } } + + /** + * Test css size to point + */ + public function testCssSizeParser() + { + $this->assertEquals(null, Converter::cssToPoint('10em')); + $this->assertEquals(10, Converter::cssToPoint('10pt')); + $this->assertEquals(7.5, Converter::cssToPoint('10px')); + $this->assertEquals(720, Converter::cssToPoint('10in')); + $this->assertEquals(120, Converter::cssToPoint('10pc')); + $this->assertEquals(28.346457, Converter::cssToPoint('10mm'), '', 0.000001); + $this->assertEquals(283.464567, Converter::cssToPoint('10cm'), '', 0.000001); + } } diff --git a/tests/PhpWord/Shared/HtmlTest.php b/tests/PhpWord/Shared/HtmlTest.php index 8ec6840f..c50df5af 100644 --- a/tests/PhpWord/Shared/HtmlTest.php +++ b/tests/PhpWord/Shared/HtmlTest.php @@ -127,10 +127,42 @@ class HtmlTest extends \PHPUnit\Framework\TestCase $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:jc')); - $this->assertEquals('start', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:jc', 'w:val')); - $this->assertEquals('end', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:jc', 'w:val')); - $this->assertEquals('center', $doc->getElementAttribute('/w:document/w:body/w:p[3]/w:pPr/w:jc', 'w:val')); - $this->assertEquals('both', $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:jc', 'w:val')); + $this->assertEquals(Jc::START, $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:jc', 'w:val')); + $this->assertEquals(Jc::END, $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:jc', 'w:val')); + $this->assertEquals(Jc::CENTER, $doc->getElementAttribute('/w:document/w:body/w:p[3]/w:pPr/w:jc', 'w:val')); + $this->assertEquals(Jc::BOTH, $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:jc', 'w:val')); + } + + /** + * Test font-size style + */ + public function testParseFontSize() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, 'test'); + Html::addHtml($section, 'test'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:sz')); + $this->assertEquals('20', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:r/w:rPr/w:sz', 'w:val')); + $this->assertEquals('15', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:r/w:rPr/w:sz', 'w:val')); + } + + /** + * Test font-family style + */ + public function testParseFontFamily() + { + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + $section = $phpWord->addSection(); + Html::addHtml($section, 'test'); + Html::addHtml($section, 'test'); + + $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:rFonts')); + $this->assertEquals('Arial', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:r/w:rPr/w:rFonts', 'w:ascii')); + $this->assertEquals('Times New Roman', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:r/w:rPr/w:rFonts', 'w:ascii')); } /** @@ -144,7 +176,7 @@ class HtmlTest extends \PHPUnit\Framework\TestCase $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007'); $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:jc')); - $this->assertEquals('center', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:jc', 'w:val')); + $this->assertEquals(Jc::CENTER, $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:jc', 'w:val')); $this->assertEquals('single', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:r/w:rPr/w:u', 'w:val')); } diff --git a/tests/PhpWord/Writer/HTMLTest.php b/tests/PhpWord/Writer/HTMLTest.php index 4d75ea5a..bdfc44e3 100644 --- a/tests/PhpWord/Writer/HTMLTest.php +++ b/tests/PhpWord/Writer/HTMLTest.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Writer; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\Jc; /** @@ -95,6 +96,15 @@ class HTMLTest extends \PHPUnit\Framework\TestCase $textrun->addText(htmlspecialchars('Test 3', ENT_COMPAT, 'UTF-8')); $textrun->addTextBreak(); + $textrun = $section->addTextRun(array('alignment' => Jc::START)); + $textrun->addText(htmlspecialchars('Text left aligned', ENT_COMPAT, 'UTF-8')); + + $textrun = $section->addTextRun(array('alignment' => Jc::BOTH)); + $textrun->addText(htmlspecialchars('Text justified', ENT_COMPAT, 'UTF-8')); + + $textrun = $section->addTextRun(array('alignment' => Jc::END)); + $textrun->addText(htmlspecialchars('Text right aligned', ENT_COMPAT, 'UTF-8')); + $textrun = $section->addTextRun('Paragraph'); $textrun->addLink('https://github.com/PHPOffice/PHPWord'); $textrun->addImage($localImage); @@ -120,10 +130,14 @@ class HTMLTest extends \PHPUnit\Framework\TestCase $cell = $table->addRow()->addCell(); $writer = new HTML($phpWord); + $writer->save($file); - $this->assertFileExists($file); + unlink($file); + Settings::setOutputEscapingEnabled(true); + $writer->save($file); + $this->assertFileExists($file); unlink($file); } } diff --git a/tests/PhpWord/Writer/PDF/MPDFTest.php b/tests/PhpWord/Writer/PDF/MPDFTest.php index 0e6cf308..330125fb 100644 --- a/tests/PhpWord/Writer/PDF/MPDFTest.php +++ b/tests/PhpWord/Writer/PDF/MPDFTest.php @@ -38,6 +38,7 @@ class MPDFTest extends \PHPUnit\Framework\TestCase $phpWord = new PhpWord(); $section = $phpWord->addSection(); $section->addText('Test 1'); + $section->addPageBreak(); $rendererName = Settings::PDF_RENDERER_MPDF; $rendererLibraryPath = realpath(PHPWORD_TESTS_BASE_DIR . '/../vendor/mpdf/mpdf'); diff --git a/tests/PhpWord/Writer/Word2007/ElementTest.php b/tests/PhpWord/Writer/Word2007/ElementTest.php index b31b223a..0f0b323a 100644 --- a/tests/PhpWord/Writer/Word2007/ElementTest.php +++ b/tests/PhpWord/Writer/Word2007/ElementTest.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord\Writer\Word2007; use PhpOffice\Common\XMLWriter; +use PhpOffice\PhpWord\Element\Comment; +use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\TestHelperDOCX; @@ -351,4 +353,45 @@ class ElementTest extends \PHPUnit\Framework\TestCase $this->assertTrue($doc->elementExists($path . '[3]/w:sdt/w:sdtPr/w:alias')); $this->assertTrue($doc->elementExists($path . '[3]/w:sdt/w:sdtPr/w:tag')); } + + /** + * Test Comment element + */ + public function testCommentWithoutEndElement() + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $comment = new Comment('tester'); + $phpWord->addComment($comment); + + $element = $section->addText('this is a test'); + $element->setCommentRangeStart($comment); + + $doc = TestHelperDOCX::getDocument($phpWord); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:commentRangeStart')); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:commentRangeEnd')); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:commentReference')); + } + + /** + * Test Comment element + */ + public function testCommentWithEndElement() + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $comment = new Comment('tester'); + $phpWord->addComment($comment); + + $element = $section->addText('this is a test'); + $element->setCommentRangeStart($comment); + $element->setCommentRangeEnd($comment); + + $doc = TestHelperDOCX::getDocument($phpWord); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:commentRangeStart')); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:commentRangeEnd')); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:commentReference')); + } }