diff --git a/.travis.yml b/.travis.yml index b689506c..8c3cd86a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ matrix: env: COVERAGE=1 - php: 5.3 dist: precise - env: COMPOSER_MEMORY_LIMIT=2G + env: COMPOSER_MEMORY_LIMIT=3G - php: 7.3 env: DEPENDENCIES="--ignore-platform-reqs" exclude: @@ -38,6 +38,7 @@ env: before_install: ## Packages + - sudo rm -f /etc/apt/sources.list.d/mongodb.list # Makes apt crash on Precise, and we don't need MongoDB - sudo apt-get update -qq - sudo apt-get install -y graphviz diff --git a/README.md b/README.md index 68092a48..b15f83d7 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Develop: 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/). +PHPWord is an open source project licensed under the terms of [LGPL version 3](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) @@ -174,7 +174,7 @@ You can also read the [Developers' Documentation](http://phpword.readthedocs.org We welcome everyone to contribute to PHPWord. Below are some of the things that you can do to contribute. -- Read [our contributing guide](https://github.com/PHPOffice/PHPWord/blob/master/CONTRIBUTING.md). +- Read [our contributing guide](CONTRIBUTING.md). - [Fork us](https://github.com/PHPOffice/PHPWord/fork) and [request a pull](https://github.com/PHPOffice/PHPWord/pulls) to the [develop](https://github.com/PHPOffice/PHPWord/tree/develop) branch. - Submit [bug reports or feature requests](https://github.com/PHPOffice/PHPWord/issues) to GitHub. - Follow [@PHPWord](https://twitter.com/PHPWord) and [@PHPOffice](https://twitter.com/PHPOffice) on Twitter. diff --git a/phpmd.xml.dist b/phpmd.xml.dist index 44b3efdf..2077e02b 100644 --- a/phpmd.xml.dist +++ b/phpmd.xml.dist @@ -19,7 +19,7 @@ - + diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index abf23c6e..0c773dbe 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -254,7 +254,7 @@ abstract class AbstractContainer extends AbstractElement // Special condition, e.g. preservetext can only exists in cell when // the cell is located in header or footer $validSubcontainers = array( - 'PreserveText' => array(array('Cell'), array('Header', 'Footer')), + 'PreserveText' => array(array('Cell'), array('Header', 'Footer', 'Section')), 'Footnote' => array(array('Cell', 'TextRun'), array('Section')), 'Endnote' => array(array('Cell', 'TextRun'), array('Section')), ); diff --git a/src/PhpWord/Shared/Html.php b/src/PhpWord/Shared/Html.php index c484e08f..54e9509e 100644 --- a/src/PhpWord/Shared/Html.php +++ b/src/PhpWord/Shared/Html.php @@ -32,9 +32,9 @@ use PhpOffice\PhpWord\Style\Paragraph; */ class Html { - private static $listIndex = 0; - private static $xpath; - private static $options; + protected static $listIndex = 0; + protected static $xpath; + protected static $options; /** * Add HTML parts. @@ -203,7 +203,7 @@ class Html * @param array $styles * @param array $data */ - private static function parseChildNodes($node, $element, $styles, $data) + protected static function parseChildNodes($node, $element, $styles, $data) { if ('li' != $node->nodeName) { $cNodes = $node->childNodes; @@ -225,7 +225,7 @@ class Html * @param array &$styles * @return \PhpOffice\PhpWord\Element\TextRun */ - private static function parseParagraph($node, $element, &$styles) + protected static function parseParagraph($node, $element, &$styles) { $styles['paragraph'] = self::recursiveParseStylesInHierarchy($node, $styles['paragraph']); $newElement = $element->addTextRun($styles['paragraph']); @@ -244,7 +244,7 @@ class Html * @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 */ - private static function parseHeading($element, &$styles, $argument1) + protected static function parseHeading($element, &$styles, $argument1) { $styles['paragraph'] = $argument1; $newElement = $element->addTextRun($styles['paragraph']); @@ -259,7 +259,7 @@ class Html * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array &$styles */ - private static function parseText($node, $element, &$styles) + protected static function parseText($node, $element, &$styles) { $styles['font'] = self::recursiveParseStylesInHierarchy($node, $styles['font']); @@ -280,7 +280,7 @@ class Html * @param string $argument1 Style name * @param string $argument2 Style value */ - private static function parseProperty(&$styles, $argument1, $argument2) + protected static function parseProperty(&$styles, $argument1, $argument2) { $styles['font'][$argument1] = $argument2; } @@ -291,7 +291,7 @@ class Html * @param \DOMNode $node * @param array &$styles */ - private static function parseSpan($node, &$styles) + protected static function parseSpan($node, &$styles) { self::parseInlineStyle($node, $styles['font']); } @@ -306,7 +306,7 @@ class Html * * @todo As soon as TableItem, RowItem and CellItem support relative width and height */ - private static function parseTable($node, $element, &$styles) + protected static function parseTable($node, $element, &$styles) { $elementStyles = self::parseInlineStyle($node, $styles['table']); @@ -335,7 +335,7 @@ class Html * @param array &$styles * @return Row $element */ - private static function parseRow($node, $element, &$styles) + protected static function parseRow($node, $element, &$styles) { $rowStyles = self::parseInlineStyle($node, $styles['row']); if ($node->parentNode->nodeName == 'thead') { @@ -353,7 +353,7 @@ class Html * @param array &$styles * @return \PhpOffice\PhpWord\Element\Cell|\PhpOffice\PhpWord\Element\TextRun $element */ - private static function parseCell($node, $element, &$styles) + protected static function parseCell($node, $element, &$styles) { $cellStyles = self::recursiveParseStylesInHierarchy($node, $styles['cell']); @@ -376,7 +376,7 @@ class Html * @param \DOMNode $node * @return bool Returns true if the node contains an HTML element that cannot be added to TextRun */ - private static function shouldAddTextRun(\DOMNode $node) + protected static function shouldAddTextRun(\DOMNode $node) { $containsBlockElement = self::$xpath->query('.//table|./p|./ul|./ol', $node)->length > 0; if ($containsBlockElement) { @@ -393,7 +393,7 @@ class Html * @param \DOMNode $node * @param array &$styles */ - private static function recursiveParseStylesInHierarchy(\DOMNode $node, array $style) + protected static function recursiveParseStylesInHierarchy(\DOMNode $node, array $style) { $parentStyle = self::parseInlineStyle($node, array()); $style = array_merge($parentStyle, $style); @@ -412,7 +412,7 @@ class Html * @param array &$styles * @param array &$data */ - private static function parseList($node, $element, &$styles, &$data) + protected static function parseList($node, $element, &$styles, &$data) { $isOrderedList = $node->nodeName === 'ol'; if (isset($data['listdepth'])) { @@ -431,7 +431,7 @@ class Html * @param bool $isOrderedList * @return array */ - private static function getListStyle($isOrderedList) + protected static function getListStyle($isOrderedList) { if ($isOrderedList) { return array( @@ -477,7 +477,7 @@ class Html * @todo This function is almost the same like `parseChildNodes`. Merged? * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete parsing part of childNodes */ - private static function parseListItem($node, $element, &$styles, $data) + protected static function parseListItem($node, $element, &$styles, $data) { $cNodes = $node->childNodes; if (!empty($cNodes)) { @@ -495,7 +495,7 @@ class Html * @param array $styles * @return array */ - private static function parseStyle($attribute, $styles) + protected static function parseStyle($attribute, $styles) { $properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;")); @@ -623,7 +623,7 @@ class Html * * @return \PhpOffice\PhpWord\Element\Image **/ - private static function parseImage($node, $element) + protected static function parseImage($node, $element) { $style = array(); $src = null; @@ -726,7 +726,7 @@ class Html * @param string $cssBorderStyle * @return null|string */ - private static function mapBorderStyle($cssBorderStyle) + protected static function mapBorderStyle($cssBorderStyle) { switch ($cssBorderStyle) { case 'none': @@ -739,7 +739,7 @@ class Html } } - private static function mapBorderColor(&$styles, $cssBorderColor) + protected static function mapBorderColor(&$styles, $cssBorderColor) { $numColors = substr_count($cssBorderColor, '#'); if ($numColors === 1) { @@ -759,7 +759,7 @@ class Html * @param string $cssAlignment * @return string|null */ - private static function mapAlign($cssAlignment) + protected static function mapAlign($cssAlignment) { switch ($cssAlignment) { case 'right': @@ -778,7 +778,7 @@ class Html * * @param \PhpOffice\PhpWord\Element\AbstractContainer $element */ - private static function parseLineBreak($element) + protected static function parseLineBreak($element) { $element->addTextBreak(); } @@ -790,7 +790,7 @@ class Html * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array $styles */ - private static function parseLink($node, $element, &$styles) + protected static function parseLink($node, $element, &$styles) { $target = null; foreach ($node->attributes as $attribute) { diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index 306ecff3..4293940f 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -139,6 +139,16 @@ class ListItem extends AbstractStyle return $this->numId; } + /** + * Set numbering Id. Same numId means same list + * @param mixed $numInt + */ + public function setNumId($numInt) + { + $this->numId = $numInt; + $this->getListTypeStyle(); + } + /** * Get legacy numbering definition * @@ -148,7 +158,12 @@ class ListItem extends AbstractStyle private function getListTypeStyle() { // Check if legacy style already registered in global Style collection - $numStyle = "PHPWordList{$this->listType}"; + $numStyle = 'PHPWordListType' . $this->listType; + + if ($this->numId) { + $numStyle .= 'NumId' . $this->numId; + } + if (Style::getStyle($numStyle) !== null) { $this->setNumStyle($numStyle); diff --git a/src/PhpWord/Writer/Word2007/Element/ListItemRun.php b/src/PhpWord/Writer/Word2007/Element/ListItemRun.php index 765d2ee0..fda2b078 100644 --- a/src/PhpWord/Writer/Word2007/Element/ListItemRun.php +++ b/src/PhpWord/Writer/Word2007/Element/ListItemRun.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Writer\Word2007\Element; +use PhpOffice\PhpWord\Element\ListItemRun as ListItemRunElement; use PhpOffice\PhpWord\Writer\Word2007\Style\Paragraph as ParagraphStyleWriter; /** @@ -31,34 +32,56 @@ class ListItemRun extends AbstractElement */ public function write() { - $xmlWriter = $this->getXmlWriter(); $element = $this->getElement(); - if (!$element instanceof \PhpOffice\PhpWord\Element\ListItemRun) { + + if (!$element instanceof ListItemRunElement) { return; } + $this->writeParagraph($element); + } + + private function writeParagraph(ListItemRunElement $element) + { + $xmlWriter = $this->getXmlWriter(); $xmlWriter->startElement('w:p'); - $xmlWriter->startElement('w:pPr'); - $paragraphStyle = $element->getParagraphStyle(); - $styleWriter = new ParagraphStyleWriter($xmlWriter, $paragraphStyle); - $styleWriter->setIsInline(true); - $styleWriter->write(); - - $xmlWriter->startElement('w:numPr'); - $xmlWriter->startElement('w:ilvl'); - $xmlWriter->writeAttribute('w:val', $element->getDepth()); - $xmlWriter->endElement(); // w:ilvl - $xmlWriter->startElement('w:numId'); - $xmlWriter->writeAttribute('w:val', $element->getStyle()->getNumId()); - $xmlWriter->endElement(); // w:numId - $xmlWriter->endElement(); // w:numPr - - $xmlWriter->endElement(); // w:pPr + $this->writeParagraphProperties($element); $containerWriter = new Container($xmlWriter, $element); $containerWriter->write(); $xmlWriter->endElement(); // w:p } + + private function writeParagraphProperties(ListItemRunElement $element) + { + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:pPr'); + + $styleWriter = new ParagraphStyleWriter($xmlWriter, $element->getParagraphStyle()); + $styleWriter->setIsInline(true); + $styleWriter->setWithoutPPR(true); + $styleWriter->write(); + + $this->writeParagraphPropertiesNumbering($element); + + $xmlWriter->endElement(); // w:pPr + } + + private function writeParagraphPropertiesNumbering(ListItemRunElement $element) + { + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:numPr'); + + $xmlWriter->writeElementBlock('w:ilvl', array( + 'w:val' => $element->getDepth(), + )); + + $xmlWriter->writeElementBlock('w:numId', array( + 'w:val' => $element->getStyle()->getNumId(), + )); + + $xmlWriter->endElement(); // w:numPr + } } diff --git a/tests/PhpWord/Element/CellTest.php b/tests/PhpWord/Element/CellTest.php index 7e63967a..f0d639bc 100644 --- a/tests/PhpWord/Element/CellTest.php +++ b/tests/PhpWord/Element/CellTest.php @@ -233,7 +233,7 @@ class CellTest extends AbstractWebServerEmbeddedTest public function testAddPreserveTextException() { $oCell = new Cell(); - $oCell->setDocPart('Section', 1); + $oCell->setDocPart('TextRun', 1); $oCell->addPreserveText('text'); } diff --git a/tests/PhpWord/Writer/Word2007/ElementTest.php b/tests/PhpWord/Writer/Word2007/ElementTest.php index 703f4590..6a295965 100644 --- a/tests/PhpWord/Writer/Word2007/ElementTest.php +++ b/tests/PhpWord/Writer/Word2007/ElementTest.php @@ -510,4 +510,25 @@ class ElementTest extends \PHPUnit\Framework\TestCase $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t')); $this->assertEquals('this text contains an & (ampersant)', $doc->getElement('/w:document/w:body/w:p/w:r/w:t')->nodeValue); } + + /** + * Test ListItemRun paragraph style writing + */ + public function testListItemRunStyleWriting() + { + $phpWord = new PhpWord(); + $phpWord->addParagraphStyle('MyParagraphStyle', array('spaceBefore' => 400)); + + $section = $phpWord->addSection(); + $listItemRun = $section->addListItemRun(0, null, 'MyParagraphStyle'); + $listItemRun->addText('List item'); + $listItemRun->addText(' in bold', array('bold' => true)); + + $doc = TestHelperDOCX::getDocument($phpWord); + $this->assertFalse($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:pPr')); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:pStyle')); + $this->assertEquals('List item', $doc->getElement('/w:document/w:body/w:p/w:r[1]/w:t')->nodeValue); + $this->assertEquals(' in bold', $doc->getElement('/w:document/w:body/w:p/w:r[2]/w:t')->nodeValue); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r[2]/w:rPr/w:b')); + } }