improve HTML parser and add tests

This commit is contained in:
troosan 2017-11-11 23:49:23 +01:00
parent e72446442b
commit a01d22ed67
14 changed files with 187 additions and 28 deletions

View File

@ -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();

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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'];

View File

@ -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;
}

View File

@ -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');

View File

@ -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();

View File

@ -59,6 +59,7 @@ class Font extends AbstractStyle
if (!$style instanceof \PhpOffice\PhpWord\Style\Font) {
return;
}
$xmlWriter = $this->getXmlWriter();
$xmlWriter->startElement('w:rPr');

View File

@ -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));
}
/**

View File

@ -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);
}
}

View File

@ -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, '<span style="font-size: 10pt;">test</span>');
Html::addHtml($section, '<span style="font-size: 10px;">test</span>');
$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, '<span style="font-family: Arial">test</span>');
Html::addHtml($section, '<span style="font-family: Times New Roman;">test</span>');
$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'));
}

View File

@ -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);
}
}

View File

@ -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');

View File

@ -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'));
}
}