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 .= '
  1. Item 1
  2. Item 2
'; -$html .= '

List with complex content:

'; +$html .= '

Unordered (bulleted) list:

'; +$html .= ''; + +$html .= '

Ordered (numbered) list:

'; +$html .= '
    +
  1. List 1 item 1

  2. +
  3. List 1 item 2
  4. +
      +
    1. sub list 1
    2. +
    3. sub list 2
    4. +
    +
  5. List 1 item 3
  6. +
+

A second list, numbering should restart

+
    +
  1. List 2 item 1
  2. +
  3. List 2 item 2
  4. +
      +
    1. sub list 1
    2. +
    3. sub list 2
    4. +
    +
  5. List 2 item 3
  6. +
      +
    1. sub list 1, restarts with a
    2. +
    3. sub list 2
    4. +
    +
'; + +$html .= '

List with formatted content:

'; $html .= ''; +$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 .= '
HeaderContent
'; $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 = '
      +
    1. List 1 item 1
    2. +
    3. List 1 item 2
    4. +
    +

    Some Text

    +
      +
    1. List 2 item 1
    2. +
    3. List 2 item 2
    4. +
    '; + 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+/', ' ', ''); + 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';