Skip to content
Open
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
"phpmd/phpmd": "^2.13",
"phpstan/phpstan": "^0.12.88 || ^1.0.0",
"phpstan/phpstan-phpunit": "^1.0 || ^2.0",
"phpunit/phpunit": ">=7.0",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0 || ^10.0",
"symfony/process": "^4.4 || ^5.0",
"tecnickcom/tcpdf": "^6.5"
},
Expand Down
14 changes: 9 additions & 5 deletions src/PhpWord/Shared/Html.php
Original file line number Diff line number Diff line change
Expand Up @@ -345,18 +345,22 @@ protected static function parseInput($node, $element, &$styles): void
/**
* Parse heading node.
*
* @param string $argument1 Name of heading style
*
* @todo Think of a clever way of defining header styles, now it is only based on the assumption, that
* Heading1 - Heading6 are already defined somewhere
*/
protected static function parseHeading(DOMNode $node, AbstractContainer $element, array &$styles, string $argument1): TextRun
protected static function parseHeading(DOMNode $node, AbstractContainer $element, array &$styles, string $headingStyle): TextRun
{
$style = new Paragraph();
$style->setStyleName($argument1);
$style->setStyleName($headingStyle);
$style->setStyleByArray(self::parseInlineStyle($node, $styles['paragraph']));
$textRun = new TextRun($style);

// Create a title with level corresponding to number in heading style
// (Eg, Heading1 = 1)
$element->addTitle($textRun, (int) ltrim($headingStyle, 'Heading'));

return $element->addTextRun($style);
// Return TextRun so children are parsed
return $textRun;
}

/**
Expand Down
28 changes: 26 additions & 2 deletions src/PhpWord/Writer/HTML/Element/Title.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@

namespace PhpOffice\PhpWord\Writer\HTML\Element;

use PhpOffice\PhpWord\Element\Title as ElementTitle;
use PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\Writer\HTML;
use PhpOffice\PhpWord\Writer\HTML\Style\Font;
use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph;

/**
* TextRun element HTML writer.
Expand All @@ -34,21 +38,41 @@ class Title extends AbstractElement
*/
public function write()
{
if (!$this->element instanceof \PhpOffice\PhpWord\Element\Title) {
if (!$this->element instanceof ElementTitle) {
return '';
}

$tag = 'h' . $this->element->getDepth();

$text = $this->element->getText();
$paragraphStyle = null;
if (is_string($text)) {
$text = $this->parentWriter->escapeHTML($text);
} else {
$paragraphStyle = $text->getParagraphStyle();
$writer = new Container($this->parentWriter, $text);
$text = $writer->write();
}
$css = '';
$write1 = $write2 = $write3 = '';
$style = Style::getStyle('Heading_' . $this->element->getDepth());
if ($style !== null) {
$styleWriter = new Font($style);
$write1 = $styleWriter->write();
}
if (is_object($paragraphStyle)) {
$styleWriter = new Paragraph($paragraphStyle);
$write3 = $styleWriter->write();
if ($write1 !== '' && $write3 !== '') {
$write2 = ' ';
}
}
$css = "$write1$write2$write3";
if ($css !== '') {
$css = " style=\"$css\"";
}

$content = "<{$tag}>{$text}</{$tag}>" . PHP_EOL;
$content = "<{$tag}{$css}>{$text}</{$tag}>" . PHP_EOL;

return $content;
}
Expand Down
7 changes: 3 additions & 4 deletions src/PhpWord/Writer/HTML/Part/Head.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,15 @@ private function writeStyles(): string
'font-size' => Settings::getDefaultFontSize() . 'pt',
'color' => "#{$defaultFontColor}",
];
// Mpdf sometimes needs separate tag for body; doesn't harm others.
$bodyarray = $astarray;

$defaultWhiteSpace = $this->getParentWriter()->getDefaultWhiteSpace();
if ($defaultWhiteSpace) {
$astarray['white-space'] = $defaultWhiteSpace;
}
$bodyarray = $astarray;

foreach ([
'body' => $bodyarray,
'*' => $astarray,
'a.NoteRef' => [
'text-decoration' => 'none',
],
Expand Down Expand Up @@ -135,12 +133,13 @@ private function writeStyles(): string
$styleWriter = new FontStyleWriter($style);
if ($style->getStyleType() == 'title') {
$name = str_replace('Heading_', 'h', $name);
$css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL;
$styleParagraph = $style->getParagraph();
$style = $styleParagraph;
} else {
$name = '.' . $name;
$css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL;
}
$css .= "{$name} {" . $styleWriter->write() . '}' . PHP_EOL;
}
if ($style instanceof Paragraph) {
$styleWriter = new ParagraphStyleWriter($style);
Expand Down
76 changes: 76 additions & 0 deletions tests/PhpWordTests/Shared/HtmlHeadingsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/

namespace PhpOffice\PhpWordTests\Shared;

use PhpOffice\PhpWord\Element\TextRun;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Settings;
use PhpOffice\PhpWord\Shared\Html as SharedHtml;
use PhpOffice\PhpWord\Writer\HTML as HtmlWriter;
use PHPUnit\Framework\TestCase;

/**
* Test class for PhpOffice\PhpWord\Shared\Html.
*
* @coversDefaultClass \PhpOffice\PhpWord\Shared\Html
*/
class HtmlHeadingsTest extends TestCase
{
public function testRoundTripHeadings(): void
{
Settings::setOutputEscapingEnabled(true);
$originalDoc = new PhpWord();
$originalDoc->addTitleStyle(1, ['size' => 20]);
$section = $originalDoc->addSection();
$expectedStrings = [];
$section->addTitle('Title 1', 1);
$expectedStrings[] = '<h1 style="font-size: 20pt;">Title 1</h1>';
for ($i = 2; $i <= 6; ++$i) {
$textRun = new TextRun();
$textRun->addText('Title ');
$textRun->addText("$i", ['italic' => true]);
$section->addTitle($textRun, $i);
$expectedStrings[] = "<h$i>Title <span style=\"font-style: italic;\">$i</span></h$i>";
}
$writer = new HtmlWriter($originalDoc);
$content = $writer->getContent();
foreach ($expectedStrings as $expectedString) {
self::assertStringContainsString($expectedString, $content);
}

$newDoc = new PhpWord();
$newSection = $newDoc->addSection();
SharedHtml::addHtml($newSection, $content, true);
$newWriter = new HtmlWriter($newDoc);
$newContent = $newWriter->getContent();
// Reader does not yet support h1 declaration in css.
$content = str_replace('h1 {font-size: 20pt;}' . PHP_EOL, '', $content);

// Reader transforms Text to TextRun,
// but result is functionally the same.
self::assertSame(
$newContent,
str_replace(
'<h1 style="font-size: 20pt;">Title 1</h1>',
'<h1><span style="font-size: 20pt;">Title 1</span></h1>',
$content
)
);
}
}
5 changes: 5 additions & 0 deletions tests/PhpWordTests/Shared/HtmlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use PhpOffice\PhpWord\Element\Table;
use PhpOffice\PhpWord\Element\Text;
use PhpOffice\PhpWord\Element\TextRun;
use PhpOffice\PhpWord\Element\Title;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Shared\Converter;
use PhpOffice\PhpWord\Shared\Html;
Expand Down Expand Up @@ -116,6 +117,8 @@ public function testParseHeader(): void

self::assertCount(1, $section->getElements());
$element = $section->getElement(0);
self::assertInstanceOf(Title::class, $element);
$element = $element->getText();
self::assertInstanceOf(TextRun::class, $element);
self::assertInstanceOf(Paragraph::class, $element->getParagraphStyle());
self::assertEquals('Heading1', $element->getParagraphStyle()->getStyleName());
Expand All @@ -137,6 +140,8 @@ public function testParseHeaderStyle(): void

self::assertCount(1, $section->getElements());
$element = $section->getElement(0);
self::assertInstanceOf(Title::class, $element);
$element = $element->getText();
self::assertInstanceOf(TextRun::class, $element);
self::assertInstanceOf(Paragraph::class, $element->getParagraphStyle());
self::assertEquals('Heading1', $element->getParagraphStyle()->getStyleName());
Expand Down
73 changes: 28 additions & 45 deletions tests/PhpWordTests/Writer/HTML/FontTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,29 +121,24 @@ public function testFontNames1(): void
self::assertEquals('style5', Helper::getTextContent($xpath, '/html/body/div/p[6]/span', 'class'));

$style = Helper::getTextContent($xpath, '/html/head/style');
$prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches);
$prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt; color: #000000;}', $matches[0]);
self::assertSame(1, $prg);
self::assertEquals('body {font-family: \'Courier New\'; font-size: 12pt; color: #000000;}', $matches[0]);
$prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]);
$prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style2 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]);
$prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style3 {font-family: \'hack attempt&#039;}; display:none\'; font-size: 10pt;}', $matches[0]);
$prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style4 {font-family: \'padmaa 1.1\'; font-size: 10pt; font-weight: bold;}', $matches[0]);
$prg = preg_match('/^[.]style5[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style5 {font-family: \'MingLiU-ExtB\'; font-size: 10pt; font-weight: bold;}', $matches[0]);
}

Expand Down Expand Up @@ -177,25 +172,21 @@ public function testFontNames2(): void
self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class'));

$style = Helper::getTextContent($xpath, '/html/head/style');
$prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches);
$prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertEquals('* {font-family: \'Courier New\'; font-size: 12pt; color: #000000;}', $matches[0]);
self::assertSame(1, $prg);
self::assertEquals('body {font-family: \'Courier New\'; font-size: 12pt; color: #000000;}', $matches[0]);
$prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]);
$prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]);
$prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]);
$prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style4 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]);
}

Expand Down Expand Up @@ -229,25 +220,21 @@ public function testFontNames3(): void
self::assertEquals('style4', Helper::getTextContent($xpath, '/html/body/div/p[5]/span', 'class'));

$style = Helper::getTextContent($xpath, '/html/head/style');
$prg = preg_match('/^[*][^\\r\\n]*/m', $style, $matches);
$prg = preg_match('/^body[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertEquals('* {font-family: \'Courier New\', monospace; font-size: 12pt; color: #000000;}', $matches[0]);
self::assertSame(1, $prg);
self::assertEquals('body {font-family: \'Courier New\', monospace; font-size: 12pt; color: #000000;}', $matches[0]);
$prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style1 {font-family: \'Tahoma\'; font-size: 10pt; color: #1B2232; font-weight: bold;}', $matches[0]);
$prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style2 {font-family: \'Arial\', sans-serif; font-size: 10pt;}', $matches[0]);
$prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style3 {font-family: \'DejaVu Sans Monospace\', monospace; font-size: 10pt;}', $matches[0]);
$prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style4 {font-family: \'Arial\'; font-size: 10pt;}', $matches[0]);
}

Expand All @@ -274,23 +261,19 @@ public function testWhiteSpace(): void
$xpath = new DOMXPath($dom);

$style = Helper::getTextContent($xpath, '/html/head/style');
self::assertNotFalse(preg_match('/^[*][^\\r\\n]*/m', $style, $matches));
self::assertEquals('* {font-family: \'Arial\'; font-size: 12pt; color: #000000; white-space: pre-wrap;}', $matches[0]);
self::assertNotFalse(preg_match('/^body[^\\r\\n]*/m', $style, $matches));
self::assertEquals('body {font-family: \'Arial\'; font-size: 12pt; color: #000000; white-space: pre-wrap;}', $matches[0]);
$prg = preg_match('/^[.]style1[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style1 {font-family: \'Courier New\'; font-size: 10pt; white-space: pre-wrap;}', $matches[0]);
$prg = preg_match('/^[.]style2[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style2 {font-family: \'Courier New\'; font-size: 10pt;}', $matches[0]);
$prg = preg_match('/^[.]style3[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style3 {font-family: \'Courier New\'; font-size: 10pt; white-space: normal;}', $matches[0]);
$prg = preg_match('/^[.]style4[^\\r\\n]*/m', $style, $matches);
self::assertNotEmpty($matches);
self::assertNotFalse($prg);
self::assertSame(1, $prg);
self::assertEquals('.style4 {font-family: \'Courier New\'; font-size: 10pt;}', $matches[0]);
}

Expand Down
11 changes: 10 additions & 1 deletion tests/PhpWordTests/Writer/HTML/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public static function getNamedItem(DOMXPath $xpath, string $query, string $name
if ($item2 === null) {
self::fail('Unexpected null return requesting item');
} else {
$returnValue = $item2->attributes->getNamedItem($namedItem);
$returnVal = $item2->attributes->getNamedItem($namedItem);
}
}

Expand Down Expand Up @@ -125,4 +125,13 @@ public static function getAsHTML(PhpWord $phpWord, string $defaultWhiteSpace = '

return $dom;
}

public static function getHtmlString(PhpWord $phpWord, string $defaultWhiteSpace = '', string $defaultGenericFont = ''): string
{
$htmlWriter = new HTML($phpWord);
$htmlWriter->setDefaultWhiteSpace($defaultWhiteSpace);
$htmlWriter->setDefaultGenericFont($defaultGenericFont);

return $htmlWriter->getContent();
}
}
4 changes: 4 additions & 0 deletions tests/PhpWordTests/Writer/HTML/PartTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,9 @@ public function testTitleStyles(): void
self::assertNotFalse(strpos($style, 'h2 {margin-top: 0.25pt; margin-bottom: 0.25pt;}'));
self::assertEquals(1, Helper::getLength($xpath, '/html/body/div/h1'));
self::assertEquals(2, Helper::getLength($xpath, '/html/body/div/h2'));
$html = Helper::getHtmlString($phpWord);
self::assertStringContainsString('<h1 style="font-family: \'Calibri\'; font-weight: bold;">Header 1 #1</h1>', $html);
self::assertStringContainsString('<h2 style="font-family: \'Times New Roman\'; font-style: italic;">Header 2 #1</h2>', $html);
self::assertStringContainsString('<h2 style="font-family: \'Times New Roman\'; font-style: italic;">Header 2 #2</h2>', $html);
}
}