diff --git a/CHANGELOG.md b/CHANGELOG.md
index 62694eea..d6f8b2da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ v0.15.0 (?? ??? 2018)
----------------------
### Added
- Parsing of "align" HTML attribute - @troosan #1231
+- Parse formatting inside HTML lists - @troosan @samimussbach #1239 #945 #1215 #508
### Fixed
- fix reading of docx default style - @troosan #1238
diff --git a/README.md b/README.md
index 07b0d439..59fc3c44 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
PHPWord is a library written in pure PHP that provides a set of classes to write to and read from different document file formats. The current version of PHPWord supports Microsoft [Office Open XML](http://en.wikipedia.org/wiki/Office_Open_XML) (OOXML or OpenXML), OASIS [Open Document Format for Office Applications](http://en.wikipedia.org/wiki/OpenDocument) (OpenDocument or ODF), [Rich Text Format](http://en.wikipedia.org/wiki/Rich_Text_Format) (RTF), HTML, and PDF.
-PHPWord is an open source project licensed under the terms of [LGPL version 3](https://github.com/PHPOffice/PHPWord/blob/develop/COPYING.LESSER). PHPWord is aimed to be a high quality software product by incorporating [continuous integration](https://travis-ci.org/PHPOffice/PHPWord) and [unit testing](http://phpoffice.github.io/PHPWord/coverage/develop/). You can learn more about PHPWord by reading the [Developers' Documentation](http://phpword.readthedocs.org/) and the [API Documentation](http://phpoffice.github.io/PHPWord/docs/develop/).
+PHPWord is an open source project licensed under the terms of [LGPL version 3](https://github.com/PHPOffice/PHPWord/blob/develop/COPYING.LESSER). PHPWord is aimed to be a high quality software product by incorporating [continuous integration](https://travis-ci.org/PHPOffice/PHPWord) and [unit testing](http://phpoffice.github.io/PHPWord/coverage/develop/). You can learn more about PHPWord by reading the [Developers' Documentation](http://phpword.readthedocs.org/).
If you have any questions, please ask on [StackOverFlow](https://stackoverflow.com/questions/tagged/phpword)
diff --git a/docs/elements.rst b/docs/elements.rst
index 94ff2667..fe673304 100644
--- a/docs/elements.rst
+++ b/docs/elements.rst
@@ -405,8 +405,10 @@ For instance for the INDEX field, you can do the following (See `Index Field for
$fieldText->addText('My ');
$fieldText->addText('bold index', ['bold' => true]);
$fieldText->addText(' entry');
+ $section->addField('XE', array(), array(), $fieldText);
- $section->addField('INDEX', array(), array('\\e " " \\h "A" \\c "3"'), $fieldText);
+ //this actually adds the index
+ $section->addField('INDEX', array(), array('\\e " " \\h "A" \\c "3"'), 'right click to update index');
Line
----
diff --git a/samples/Sample_26_Html.php b/samples/Sample_26_Html.php
index b993f834..99a35f9c 100644
--- a/samples/Sample_26_Html.php
+++ b/samples/Sample_26_Html.php
@@ -9,25 +9,50 @@ $section = $phpWord->addSection();
$html = '
Adding element via HTML
';
$html .= 'Some well formed HTML snippet needs to be used
';
$html .= 'With for example some1 inline formatting1
';
-$html .= 'Unordered (bulleted) list:
';
-$html .= '';
-$html .= 'Ordered (numbered) list:
';
-$html .= '- Item 1
- Item 2
';
-$html .= 'List with complex content:
';
+$html .= 'Unordered (bulleted) list:
';
+$html .= '';
+
+$html .= 'Ordered (numbered) list:
';
+$html .= '
+ List 1 item 1
+ - List 1 item 2
+
+ - sub list 1
+ - sub list 2
+
+ - List 1 item 3
+
+ A second list, numbering should restart
+
+ - List 2 item 1
+ - List 2 item 2
+
+ - sub list 1
+ - sub list 2
+
+ - List 2 item 3
+
+ - sub list 1, restarts with a
+ - sub list 2
+
+
';
+
+$html .= 'List with formatted content:
';
$html .= '
-
- list item1
+ big list item1
-
- list item2
+ list item2 in bold
';
+$html .= 'A table with formatting:
';
$html .= '
diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php
index b00424b7..28d45672 100644
--- a/src/PhpWord/Element/AbstractContainer.php
+++ b/src/PhpWord/Element/AbstractContainer.php
@@ -33,11 +33,10 @@ namespace PhpOffice\PhpWord\Element;
* @method CheckBox addCheckBox(string $name, $text, mixed $fStyle = null, mixed $pStyle = null)
* @method Title addTitle(string $text, int $depth = 1)
* @method TOC addTOC(mixed $fontStyle = null, mixed $tocStyle = null, int $minDepth = 1, int $maxDepth = 9)
- *
* @method PageBreak addPageBreak()
* @method Table addTable(mixed $style = null)
* @method Image addImage(string $source, mixed $style = null, bool $isWatermark = false)
- * @method \PhpOffice\PhpWord\Element\OLEObject addObject(string $source, mixed $style = null)
+ * @method OLEObject addOLEObject(string $source, mixed $style = null)
* @method TextBox addTextBox(mixed $style = null)
* @method Field addField(string $type = null, array $properties = array(), array $options = array(), mixed $text = null)
* @method Line addLine(mixed $lineStyle = null)
@@ -46,6 +45,8 @@ namespace PhpOffice\PhpWord\Element;
* @method FormField addFormField(string $type, mixed $fStyle = null, mixed $pStyle = null)
* @method SDT addSDT(string $type)
*
+ * @method \PhpOffice\PhpWord\Element\OLEObject addObject(string $source, mixed $style = null) deprecated, use addOLEObject instead
+ *
* @since 0.10.0
*/
abstract class AbstractContainer extends AbstractElement
@@ -200,7 +201,7 @@ abstract class AbstractContainer extends AbstractElement
'FormField' => $generalContainers,
'SDT' => $generalContainers,
'TrackChange' => $generalContainers,
- 'TextRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox', 'TrackChange'),
+ 'TextRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox', 'TrackChange', 'ListItemRun'),
'ListItem' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'),
'ListItemRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'),
'Table' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'),
diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php
index 38d326c1..fd0bd545 100644
--- a/src/PhpWord/Shared/Html.php
+++ b/src/PhpWord/Shared/Html.php
@@ -18,10 +18,10 @@
namespace PhpOffice\PhpWord\Shared;
use PhpOffice\PhpWord\Element\AbstractContainer;
-use PhpOffice\PhpWord\Element\Cell;
use PhpOffice\PhpWord\Element\Row;
use PhpOffice\PhpWord\Element\Table;
use PhpOffice\PhpWord\SimpleType\Jc;
+use PhpOffice\PhpWord\SimpleType\NumberFormat;
/**
* Common Html functions
@@ -30,6 +30,8 @@ use PhpOffice\PhpWord\SimpleType\Jc;
*/
class Html
{
+ private static $listIndex = 0;
+
/**
* Add HTML parts.
*
@@ -135,8 +137,8 @@ class Html
'tr' => array('Row', $node, $element, $styles, null, null, null),
'td' => array('Cell', $node, $element, $styles, null, null, null),
'th' => array('Cell', $node, $element, $styles, null, null, null),
- 'ul' => array('List', null, null, $styles, $data, 3, null),
- 'ol' => array('List', null, null, $styles, $data, 7, null),
+ 'ul' => array('List', $node, $element, $styles, $data, null, null),
+ 'ol' => array('List', $node, $element, $styles, $data, null, null),
'li' => array('ListItem', $node, $element, $styles, $data, null, null),
'img' => array('Image', $node, $element, $styles, null, null, null),
'br' => array('LineBreak', null, $element, $styles, null, null, null),
@@ -330,7 +332,7 @@ class Html
* @param \DOMNode $node
* @param \PhpOffice\PhpWord\Element\Table $element
* @param array &$styles
- * @return Cell $element
+ * @return \PhpOffice\PhpWord\Element\Cell $element
*/
private static function parseCell($node, $element, &$styles)
{
@@ -365,18 +367,56 @@ class Html
/**
* Parse list node
*
+ * @param \DOMNode $node
+ * @param \PhpOffice\PhpWord\Element\AbstractContainer $element
* @param array &$styles
* @param array &$data
- * @param string $argument1 List type
*/
- private static function parseList(&$styles, &$data, $argument1)
+ private static function parseList($node, $element, &$styles, &$data)
{
+ $isOrderedList = $node->nodeName == 'ol';
if (isset($data['listdepth'])) {
$data['listdepth']++;
} else {
$data['listdepth'] = 0;
+ $styles['list'] = 'listStyle_' . self::$listIndex++;
+ $element->getPhpWord()->addNumberingStyle($styles['list'], self::getListStyle($isOrderedList));
}
- $styles['list']['listType'] = $argument1;
+ }
+
+ private static function getListStyle($isOrderedList)
+ {
+ if ($isOrderedList) {
+ return array(
+ 'type' => 'multilevel',
+ 'levels' => array(
+ array('format' => NumberFormat::DECIMAL, 'text' => '%1.', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360),
+ array('format' => NumberFormat::LOWER_LETTER, 'text' => '%2.', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360),
+ array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%3.', 'alignment' => 'right', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 180),
+ array('format' => NumberFormat::DECIMAL, 'text' => '%4.', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360),
+ array('format' => NumberFormat::LOWER_LETTER, 'text' => '%5.', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360),
+ array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%6.', 'alignment' => 'right', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 180),
+ array('format' => NumberFormat::DECIMAL, 'text' => '%7.', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360),
+ array('format' => NumberFormat::LOWER_LETTER, 'text' => '%8.', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360),
+ array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%9.', 'alignment' => 'right', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 180),
+ ),
+ );
+ }
+
+ return array(
+ 'type' => 'hybridMultilevel',
+ 'levels' => array(
+ array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'),
+ array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'),
+ array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'),
+ array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'),
+ array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'),
+ array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'),
+ array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'),
+ array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'),
+ array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'),
+ ),
+ );
}
/**
@@ -394,17 +434,10 @@ class Html
{
$cNodes = $node->childNodes;
if (!empty($cNodes)) {
- $text = '';
+ $listRun = $element->addListItemRun($data['listdepth'], $styles['list'], $styles['paragraph']);
foreach ($cNodes as $cNode) {
- if ($cNode->nodeName == '#text') {
- $text = $cNode->nodeValue;
- }
+ self::parseNode($cNode, $listRun, $styles, $data);
}
- //ideally we should be parsing child nodes for any style, for now just take the text
- if ('' == trim($text) && '' != trim($node->textContent)) {
- $text = trim($node->textContent);
- }
- $element->addListItem($text, $data['listdepth'], $styles['font'], $styles['list'], $styles['paragraph']);
}
}
@@ -462,6 +495,12 @@ class Html
}
$styles['italic'] = $tValue;
break;
+ case 'margin-top':
+ $styles['spaceBefore'] = Converter::cssToPoint($cValue);
+ break;
+ case 'margin-bottom':
+ $styles['spaceAfter'] = Converter::cssToPoint($cValue);
+ break;
case 'border-color':
$styles['color'] = trim($cValue, '#');
break;
@@ -582,14 +621,14 @@ class Html
private static function mapAlign($cssAlignment)
{
switch ($cssAlignment) {
- case 'left':
- return Jc::START;
case 'right':
return Jc::END;
case 'center':
return Jc::CENTER;
case 'justify':
return Jc::BOTH;
+ default:
+ return Jc::START;
}
return null;
diff --git a/tests/PhpWord/Shared/HtmlTest.php b/tests/PhpWord/Shared/HtmlTest.php
index c7d36470..9c4cfd55 100644
--- a/tests/PhpWord/Shared/HtmlTest.php
+++ b/tests/PhpWord/Shared/HtmlTest.php
@@ -35,7 +35,8 @@ class HtmlTest extends \PHPUnit\Framework\TestCase
$content = '';
// Default
- $section = new Section(1);
+ $phpWord = new \PhpOffice\PhpWord\PhpWord();
+ $section = $phpWord->addSection();
$this->assertCount(0, $section->getElements());
// Heading
@@ -57,7 +58,7 @@ class HtmlTest extends \PHPUnit\Framework\TestCase
$this->assertCount(7, $section->getElements());
// Other parts
- $section = new Section(1);
+ $section = $phpWord->addSection();
$content = '';
$content .= '';
$content .= '';
@@ -172,10 +173,11 @@ class HtmlTest extends \PHPUnit\Framework\TestCase
{
$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:pPr/w:jc'));
+ $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:spacing'));
$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'));
}
@@ -224,7 +226,7 @@ class HtmlTest extends \PHPUnit\Framework\TestCase
- list item2
+ list item2
';
@@ -235,6 +237,69 @@ class HtmlTest extends \PHPUnit\Framework\TestCase
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t'));
$this->assertEquals('list item1', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue);
$this->assertEquals('list item2', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->nodeValue);
+ $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:rPr/w:b'));
+ }
+
+ /**
+ * Tests parsing of ul/li
+ */
+ public function tesOrderedListNumbering()
+ {
+ $phpWord = new \PhpOffice\PhpWord\PhpWord();
+ $section = $phpWord->addSection();
+ $html = '
+ - List 1 item 1
+ - List 1 item 2
+
+ Some Text
+
+ - List 2 item 1
+ - List 2 item 2
+
';
+ Html::addHtml($section, $html, false, false);
+
+ $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
+ echo $doc->printXml();
+ $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId'));
+ $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t'));
+
+ $this->assertEquals('List 1 item 1', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->nodeValue);
+ $this->assertEquals('List 2 item 1', $doc->getElement('/w:document/w:body/w:p[4]/w:r/w:t')->nodeValue);
+
+ $firstListnumId = $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:numPr/w:numId', 'w:val');
+ $secondListnumId = $doc->getElementAttribute('/w:document/w:body/w:p[4]/w:pPr/w:numPr/w:numId', 'w:val');
+
+ $this->assertNotEquals($firstListnumId, $secondListnumId);
+ }
+
+ /**
+ * Tests parsing of ul/li
+ */
+ public function testParseListWithFormat()
+ {
+ $phpWord = new \PhpOffice\PhpWord\PhpWord();
+ $section = $phpWord->addSection();
+ $html = preg_replace('/\s+/', ' ', '
+ - Some text before
+
+ list item1 bold with text after bold
+
+ and some after
+
+ -
+
+ list item2
+
+
+
');
+ Html::addHtml($section, $html, false, false);
+
+ $doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
+ $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId'));
+ $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t'));
+ $this->assertEquals('list item2', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->nodeValue);
+ $this->assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r[3]/w:rPr/w:b'));
+ $this->assertEquals('bold', $doc->getElement('/w:document/w:body/w:p[1]/w:r[3]/w:t')->nodeValue);
}
/**
@@ -255,6 +320,9 @@ class HtmlTest extends \PHPUnit\Framework\TestCase
$this->assertEquals('with a linebreak.', $doc->getElement('/w:document/w:body/w:p/w:r[2]/w:t')->nodeValue);
}
+ /**
+ * Test parsing of img
+ */
public function testParseImage()
{
$src = __DIR__ . '/../_files/images/firefox.png';