diff --git a/composer.json b/composer.json
index 5055105d16..d85f27e9b1 100644
--- a/composer.json
+++ b/composer.json
@@ -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"
},
diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php
index 170dc5dff3..7a965467b8 100644
--- a/src/PhpWord/Shared/Html.php
+++ b/src/PhpWord/Shared/Html.php
@@ -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;
}
/**
diff --git a/src/PhpWord/Writer/HTML/Element/Title.php b/src/PhpWord/Writer/HTML/Element/Title.php
index 84b0b199fc..ba9675b051 100644
--- a/src/PhpWord/Writer/HTML/Element/Title.php
+++ b/src/PhpWord/Writer/HTML/Element/Title.php
@@ -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.
@@ -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;
}
diff --git a/src/PhpWord/Writer/HTML/Part/Head.php b/src/PhpWord/Writer/HTML/Part/Head.php
index 79235e1c4a..2bcb70e5f6 100644
--- a/src/PhpWord/Writer/HTML/Part/Head.php
+++ b/src/PhpWord/Writer/HTML/Part/Head.php
@@ -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',
],
@@ -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);
diff --git a/tests/PhpWordTests/Shared/HtmlHeadingsTest.php b/tests/PhpWordTests/Shared/HtmlHeadingsTest.php
new file mode 100644
index 0000000000..331935fbae
--- /dev/null
+++ b/tests/PhpWordTests/Shared/HtmlHeadingsTest.php
@@ -0,0 +1,76 @@
+addTitleStyle(1, ['size' => 20]);
+ $section = $originalDoc->addSection();
+ $expectedStrings = [];
+ $section->addTitle('Title 1', 1);
+ $expectedStrings[] = '
Title 1
';
+ for ($i = 2; $i <= 6; ++$i) {
+ $textRun = new TextRun();
+ $textRun->addText('Title ');
+ $textRun->addText("$i", ['italic' => true]);
+ $section->addTitle($textRun, $i);
+ $expectedStrings[] = "Title $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(
+ 'Title 1
',
+ 'Title 1
',
+ $content
+ )
+ );
+ }
+}
diff --git a/tests/PhpWordTests/Shared/HtmlTest.php b/tests/PhpWordTests/Shared/HtmlTest.php
index 42d8aa598b..11be8c7f65 100644
--- a/tests/PhpWordTests/Shared/HtmlTest.php
+++ b/tests/PhpWordTests/Shared/HtmlTest.php
@@ -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;
@@ -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());
@@ -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());
diff --git a/tests/PhpWordTests/Writer/HTML/FontTest.php b/tests/PhpWordTests/Writer/HTML/FontTest.php
index 0a203b7237..50a239377d 100644
--- a/tests/PhpWordTests/Writer/HTML/FontTest.php
+++ b/tests/PhpWordTests/Writer/HTML/FontTest.php
@@ -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'}; 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]);
}
@@ -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]);
}
@@ -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]);
}
@@ -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]);
}
diff --git a/tests/PhpWordTests/Writer/HTML/Helper.php b/tests/PhpWordTests/Writer/HTML/Helper.php
index 37f640d28a..2e7ee343c3 100644
--- a/tests/PhpWordTests/Writer/HTML/Helper.php
+++ b/tests/PhpWordTests/Writer/HTML/Helper.php
@@ -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);
}
}
@@ -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();
+ }
}
diff --git a/tests/PhpWordTests/Writer/HTML/PartTest.php b/tests/PhpWordTests/Writer/HTML/PartTest.php
index b6748a58c5..0fe43c2350 100644
--- a/tests/PhpWordTests/Writer/HTML/PartTest.php
+++ b/tests/PhpWordTests/Writer/HTML/PartTest.php
@@ -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('Header 1 #1
', $html);
+ self::assertStringContainsString('Header 2 #1
', $html);
+ self::assertStringContainsString('Header 2 #2
', $html);
}
}