diff --git a/CHANGELOG.md b/CHANGELOG.md index 72b93e7b72..f96b4a63d2 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ This is the changelog between releases of PHPWord. Releases are listed in revers ## 0.9.2 - Not yet released -This release marked heavy refactorings on internal code structure with the creation of some abstract classes to reduce code duplication. `Element` subnamespace is introduced in this release to replace `Section`. Word2007 reader capability is greatly enhanced. Endnote is introduced. +This release marked heavy refactorings on internal code structure with the creation of some abstract classes to reduce code duplication. `Element` subnamespace is introduced in this release to replace `Section`. Word2007 reader capability is greatly enhanced. Endnote is introduced. List numbering is now customizable. ### Features @@ -27,8 +27,9 @@ This release marked heavy refactorings on internal code structure with the creat - Object: Ability to add object in header, footer, textrun, and footnote - @ivanlanin GH-187 - Media: Add `Media::resetElements()` to reset all media data - @juzi GH-19 - General: Add `Style::resetStyles()`, `Footnote::resetElements()`, and `TOC::resetTitles()` - @ivanlanin GH-187 -- Reader: Ability to read header, footer, footnotes, link, preservetext, textbreak, pagebreak, table - @ivanlanin +- Reader: Ability to read header, footer, footnotes, link, preservetext, textbreak, pagebreak, table, and list - @ivanlanin - Endnote: Ability to add endnotes - @ivanlanin +- ListItem: Ability to create custom list and reset list number - @ivanlanin GH-10 ### Bugfixes diff --git a/docs/elements.rst b/docs/elements.rst index 62ee007fe9..b6842b6518 100644 --- a/docs/elements.rst +++ b/docs/elements.rst @@ -207,10 +207,14 @@ Lists To add a list item use the function ``addListItem``. +Basic usage: + .. code-block:: php $section->addListItem($text, [$depth], [$fontStyle], [$listStyle], [$paragraphStyle]); +Parameters: + - ``$text`` Text that appears in the document. - ``$depth`` Depth of list item. - ``$fontStyle`` See "Font style" section. @@ -219,6 +223,40 @@ To add a list item use the function ``addListItem``. PHPWord\_Style\_ListItem. - ``$paragraphStyle`` See "Paragraph style" section. +Advanced usage: + +You can also create your own numbering style by changing the ``$listStyle`` parameter +with the name of your numbering style. + +.. code-block:: php + + $phpWord->addNumberingStyle( + 'multilevel', + array('type' => 'multilevel', 'levels' => array( + array('format' => 'decimal', 'text' => '%1.', 'left' => 360, 'hanging' => 360, 'tabPos' => 360), + array('format' => 'upperLetter', 'text' => '%2.', 'left' => 720, 'hanging' => 360, 'tabPos' => 720), + ) + ) + ); + $section->addListItem('List Item I', 0, null, 'multilevel'); + $section->addListItem('List Item I.a', 1, null, 'multilevel'); + $section->addListItem('List Item I.b', 1, null, 'multilevel'); + $section->addListItem('List Item II', 0, null, 'multilevel'); + +Level styles: + +- ``start`` Starting value +- ``format`` Numbering format bullet|decimal|upperRoman|lowerRoman|upperLetter|lowerLetter +- ``restart`` Restart numbering level symbol +- ``suffix`` Content between numbering symbol and paragraph text tab|space|nothing +- ``text`` Numbering level text e.g. %1 for nonbullet or bullet character +- ``align`` Numbering symbol align left|center|right|both +- ``left`` See paragraph style +- ``hanging`` See paragraph style +- ``tabPos`` See paragraph style +- ``font`` Font name +- ``hint`` See font style + Tables ------ diff --git a/docs/general.rst b/docs/general.rst index c267d87d4f..40c8da7e21 100644 --- a/docs/general.rst +++ b/docs/general.rst @@ -63,9 +63,9 @@ XML Writer compatibility ~~~~~~~~~~~~~~~~~~~~~~~~ This option sets -```XMLWriter::setIndent`` `__ +`XMLWriter::setIndent `__ and -```XMLWriter::setIndentString`` `__. +`XMLWriter::setIndentString `__. The default value of this option is ``true`` (compatible), which is `required for OpenOffice `__ to render OOXML document correctly. You can set this option to ``false`` diff --git a/samples/Sample_14_ListItem.php b/samples/Sample_14_ListItem.php index 4cd9edea7b..45d9c1a70d 100644 --- a/samples/Sample_14_ListItem.php +++ b/samples/Sample_14_ListItem.php @@ -8,41 +8,52 @@ // Begin code $section = $phpWord->addSection(); -// Add listitem elements -$section->addListItem('List Item 1', 0); -$section->addListItem('List Item 2', 0); -$section->addListItem('List Item 3', 0); +// Style definition + +$phpWord->addFontStyle('myOwnStyle', array('color'=>'FF0000')); +$phpWord->addParagraphStyle('P-Style', array('spaceAfter'=>95)); +$phpWord->addNumberingStyle( + 'multilevel', + array('type' => 'multilevel', 'levels' => array( + array('format' => 'decimal', 'text' => '%1.', 'left' => 360, 'hanging' => 360, 'tabPos' => 360), + array('format' => 'upperLetter', 'text' => '%2.', 'left' => 720, 'hanging' => 360, 'tabPos' => 720), + ) + ) +); +$predefinedMultilevel = array('listType' => \PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER_NESTED); + +// Lists + +$section->addText('Multilevel list.'); +$section->addListItem('List Item I', 0, null, 'multilevel'); +$section->addListItem('List Item I.a', 1, null, 'multilevel'); +$section->addListItem('List Item I.b', 1, null, 'multilevel'); +$section->addListItem('List Item II', 0, null, 'multilevel'); +$section->addListItem('List Item II.a', 1, null, 'multilevel'); +$section->addListItem('List Item III', 0, null, 'multilevel'); $section->addTextBreak(2); -// Add listitem elements -$section->addListItem('List Item 1', 0); -$section->addListItem('List Item 1.1', 1); -$section->addListItem('List Item 1.2', 1); -$section->addListItem('List Item 1.3 (styled)', 1, array('bold'=>true)); -$section->addListItem('List Item 1.3.1', 2); -$section->addListItem('List Item 1.3.2', 2); +$section->addText('Basic simple bulleted list.'); +$section->addListItem('List Item 1'); +$section->addListItem('List Item 2'); +$section->addListItem('List Item 3'); $section->addTextBreak(2); -// Add listitem elements -$listStyle = array('listType' => \PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER); -$section->addListItem('List Item 1', 0, null, $listStyle); -$section->addListItem('List Item 2', 0, null, $listStyle); -$section->addListItem('List Item 3', 0, null, $listStyle); +$section->addText('Continue from multilevel list above.'); +$section->addListItem('List Item IV', 0, null, 'multilevel'); +$section->addListItem('List Item IV.a', 1, null, 'multilevel'); +$section->addTextBreak(2); + +$section->addText('Multilevel predefined list.'); +$section->addListItem('List Item 1', 0, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); +$section->addListItem('List Item 2', 0, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); +$section->addListItem('List Item 3', 1, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); +$section->addListItem('List Item 4', 1, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); +$section->addListItem('List Item 5', 2, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); +$section->addListItem('List Item 6', 1, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); +$section->addListItem('List Item 7', 0, 'myOwnStyle', $predefinedMultilevel, 'P-Style'); $section->addTextBreak(2); -// Add listitem elements -$phpWord->addFontStyle('myOwnStyle', array('color'=>'FF0000')); -$phpWord->addParagraphStyle('P-Style', array('spaceAfter'=>95)); -$listStyle = array('listType' => \PhpOffice\PhpWord\Style\ListItem::TYPE_NUMBER_NESTED); -$section->addListItem('List Item 1', 0, 'myOwnStyle', $listStyle, 'P-Style'); -$section->addListItem('List Item 2', 0, 'myOwnStyle', $listStyle, 'P-Style'); -$section->addListItem('List Item 3', 1, 'myOwnStyle', $listStyle, 'P-Style'); -$section->addListItem('List Item 4', 1, 'myOwnStyle', $listStyle, 'P-Style'); -$section->addListItem('List Item 5', 2, 'myOwnStyle', $listStyle, 'P-Style'); -$section->addListItem('List Item 6', 1, 'myOwnStyle', $listStyle, 'P-Style'); -$section->addListItem('List Item 7', 0, 'myOwnStyle', $listStyle, 'P-Style'); - -// End code // Save file $name = basename(__FILE__, '.php'); diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 56505cbce3..6562cb5ba8 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -469,9 +469,6 @@ protected function setStyle($styleObject, $styleValue = null, $returnObject = fa { if (!is_null($styleValue) && is_array($styleValue)) { foreach ($styleValue as $key => $value) { - if (substr($key, 0, 1) == '_') { - $key = substr($key, 1); - } $styleObject->setStyleValue($key, $value); } $style = $styleObject; diff --git a/src/PhpWord/Element/ListItem.php b/src/PhpWord/Element/ListItem.php index 5d19097eb8..39bd6b1b3f 100644 --- a/src/PhpWord/Element/ListItem.php +++ b/src/PhpWord/Element/ListItem.php @@ -44,15 +44,21 @@ class ListItem extends AbstractElement * * @param string $text * @param int $depth - * @param mixed $styleFont - * @param mixed $styleList - * @param mixed $styleParagraph + * @param mixed $fontStyle + * @param array|string|null $listStyle + * @param mixed $paragraphStyle */ - public function __construct($text, $depth = 0, $styleFont = null, $styleList = null, $styleParagraph = null) + public function __construct($text, $depth = 0, $fontStyle = null, $listStyle = null, $paragraphStyle = null) { - $this->textObject = new Text($text, $styleFont, $styleParagraph); + $this->textObject = new Text($text, $fontStyle, $paragraphStyle); $this->depth = $depth; - $this->style = $this->setStyle(new ListItemStyle(), $styleList, true); + + // Version >= 0.9.2 will pass numbering style name. Older version will use old method + if (!is_null($listStyle) && is_string($listStyle)) { + $this->style = new ListItemStyle($listStyle); + } else { + $this->style = $this->setStyle(new ListItemStyle(), $listStyle, true); + } } /** diff --git a/src/PhpWord/Endnotes.php b/src/PhpWord/Endnotes.php index 86c923d366..4a065b9687 100644 --- a/src/PhpWord/Endnotes.php +++ b/src/PhpWord/Endnotes.php @@ -9,7 +9,6 @@ namespace PhpOffice\PhpWord; -use PhpOffice\PhpWord\Media; use PhpOffice\PhpWord\Element\Endnote; /** diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index c9bf4b63fa..2764a3a1ec 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -214,6 +214,17 @@ public function addLinkStyle($styleName, $styles) Style::addLinkStyle($styleName, $styles); } + /** + * Adds a numbering style + * + * @param string $styleName + * @param mixed $styles + */ + public function addNumberingStyle($styleName, $styles) + { + Style::addNumberingStyle($styleName, $styles); + } + /** * Get all sections * diff --git a/src/PhpWord/Reader/Word2007.php b/src/PhpWord/Reader/Word2007.php index 36c81c3b06..8df6c4ec1b 100644 --- a/src/PhpWord/Reader/Word2007.php +++ b/src/PhpWord/Reader/Word2007.php @@ -11,8 +11,6 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; -use PhpOffice\PhpWord\Footnote; -use PhpOffice\PhpWord\Endnotes; use PhpOffice\PhpWord\DocumentProperties; use PhpOffice\PhpWord\Shared\XMLReader; use PhpOffice\PhpWord\Element\Section; @@ -52,6 +50,19 @@ public function load($filename) $this->readRelationships($filename); + + // Read styles and numbering first + foreach ($this->rels['document'] as $rId => $rel) { + switch ($rel['type']) { + case 'styles': + $this->readStyles($filename, $rel['target']); + break; + case 'numbering': + $this->readNumbering($filename, $rel['target']); + break; + } + } + // Read main relationship foreach ($this->rels['main'] as $rId => $rel) { switch ($rel['type']) { @@ -87,14 +98,9 @@ public function load($filename) } } - // Read document relationships + // Read footnotes and endnotes foreach ($this->rels['document'] as $rId => $rel) { switch ($rel['type']) { - - case 'styles': - $this->readStyles($filename, $rel['target']); - break; - case 'footnotes': case 'endnotes': $this->readNotes($filename, $rel['target'], $rel['type']); @@ -178,7 +184,6 @@ private function readDocPropsCustom($filename, $xmlFile) $nodes = $xmlReader->getElements('*'); if ($nodes->length > 0) { foreach ($nodes as $node) { - $nodeName = $node->nodeName; $propertyName = $xmlReader->getAttribute('name', $node); $attributeNode = $xmlReader->getElement('*', $node); $attributeType = $attributeNode->nodeName; @@ -206,6 +211,7 @@ private function readDocument($filename, $xmlFile) $section = $this->phpWord->addSection(); foreach ($nodes as $node) { switch ($node->nodeName) { + case 'w:p': // Paragraph if ($xmlReader->getAttribute('w:type', $node, 'w:r/w:br') == 'page') { $section->addPageBreak(); // PageBreak @@ -215,19 +221,27 @@ private function readDocument($filename, $xmlFile) // Section properties if ($xmlReader->elementExists('w:pPr/w:sectPr', $node)) { $settingsNode = $xmlReader->getElement('w:pPr/w:sectPr', $node); - $settings = $this->readSectionStyle($xmlReader, $settingsNode); - $section->setSettings($settings); - $this->readHeaderFooter($filename, $settings, $section); + if (!is_null($settingsNode)) { + $settings = $this->readSectionStyle($xmlReader, $settingsNode); + $section->setSettings($settings); + if (!is_null($settings)) { + $this->readHeaderFooter($filename, $settings, $section); + } + } $section = $this->phpWord->addSection(); } break; + case 'w:tbl': // Table $this->readTable($xmlReader, $node, $section, 'document'); break; + case 'w:sectPr': // Last section $settings = $this->readSectionStyle($xmlReader, $node); $section->setSettings($settings); - $this->readHeaderFooter($filename, $settings, $section); + if (!is_null($settings)) { + $this->readHeaderFooter($filename, $settings, $section); + } break; } } @@ -255,21 +269,26 @@ private function readStyles($filename, $xmlFile) } // $default = ($xmlReader->getAttribute('w:default', $node) == 1); switch ($type) { + case 'paragraph': $pStyle = $this->readParagraphStyle($xmlReader, $node); $fStyle = $this->readFontStyle($xmlReader, $node); if (empty($fStyle)) { - $this->phpWord->addParagraphStyle($name, $pStyle); + if (is_array($pStyle)) { + $this->phpWord->addParagraphStyle($name, $pStyle); + } } else { $this->phpWord->addFontStyle($name, $fStyle, $pStyle); } break; + case 'character': $fStyle = $this->readFontStyle($xmlReader, $node); if (!empty($fStyle)) { $this->phpWord->addFontStyle($name, $fStyle); } break; + case 'table': $tStyle = $this->readTableStyle($xmlReader, $node); if (!empty($tStyle)) { @@ -281,6 +300,98 @@ private function readStyles($filename, $xmlFile) } } + /** + * Read numbering.xml + * + * @param string $filename + * @param string $xmlFile + */ + private function readNumbering($filename, $xmlFile) + { + $abstracts = array(); + $numberings = array(); + $xmlReader = new XMLReader(); + $xmlReader->getDomFromZip($filename, $xmlFile); + + // Abstract numbering definition + $nodes = $xmlReader->getElements('w:abstractNum'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $abstractId = $xmlReader->getAttribute('w:abstractNumId', $node); + $abstracts[$abstractId] = array('levels' => array()); + $abstract = &$abstracts[$abstractId]; + $subnodes = $xmlReader->getElements('*', $node); + foreach ($subnodes as $subnode) { + switch ($subnode->nodeName) { + case 'w:multiLevelType': + $abstract['type'] = $xmlReader->getAttribute('w:val', $subnode); + break; + case 'w:lvl': + $levelId = $xmlReader->getAttribute('w:ilvl', $subnode); + $abstract['levels'][$levelId] = $this->readNumberingLevel($xmlReader, $subnode, $levelId); + break; + } + } + } + } + + // Numbering instance definition + $nodes = $xmlReader->getElements('w:num'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $numId = $xmlReader->getAttribute('w:numId', $node); + $abstractId = $xmlReader->getAttribute('w:val', $node, 'w:abstractNumId'); + $numberings[$numId] = $abstracts[$abstractId]; + $numberings[$numId]['numId'] = $numId; + $subnodes = $xmlReader->getElements('w:lvlOverride/w:lvl', $node); + foreach ($subnodes as $subnode) { + $levelId = $xmlReader->getAttribute('w:ilvl', $subnode); + $overrides = $this->readNumberingLevel($xmlReader, $subnode, $levelId); + foreach ($overrides as $key => $value) { + $numberings[$numId]['levels'][$levelId][$key] = $value; + } + } + } + } + + // Push to Style collection + foreach ($numberings as $numId => $numbering) { + $this->phpWord->addNumberingStyle("PHPWordList{$numId}", $numbering); + } + } + + /** + * Read numbering level definition from w:abstractNum and w:num + * + * @param integer $levelId + * @return array + */ + private function readNumberingLevel(XMLReader $xmlReader, \DOMElement $subnode, $levelId) + { + $level = array(); + + $level['level'] = $levelId; + $level['start'] = $xmlReader->getAttribute('w:val', $subnode, 'w:start'); + $level['format'] = $xmlReader->getAttribute('w:val', $subnode, 'w:numFmt'); + $level['restart'] = $xmlReader->getAttribute('w:val', $subnode, 'w:lvlRestart'); + $level['suffix'] = $xmlReader->getAttribute('w:val', $subnode, 'w:suff'); + $level['text'] = $xmlReader->getAttribute('w:val', $subnode, 'w:lvlText'); + $level['align'] = $xmlReader->getAttribute('w:val', $subnode, 'w:lvlJc'); + $level['tab'] = $xmlReader->getAttribute('w:pos', $subnode, 'w:pPr/w:tabs/w:tab'); + $level['left'] = $xmlReader->getAttribute('w:left', $subnode, 'w:pPr/w:ind'); + $level['hanging'] = $xmlReader->getAttribute('w:hanging', $subnode, 'w:pPr/w:ind'); + $level['font'] = $xmlReader->getAttribute('w:ascii', $subnode, 'w:rPr/w:rFonts'); + $level['hint'] = $xmlReader->getAttribute('w:hint', $subnode, 'w:rPr/w:rFonts'); + + foreach ($level as $key => $value) { + if (is_null($value)) { + unset($level[$key]); + } + } + + return $level; + } + /** * Read header footer * @@ -325,6 +436,7 @@ private function readHeaderFooter($filename, $settings, &$section) * * @param string $filename * @param string $xmlFile + * @param string $notesType */ private function readNotes($filename, $xmlFile, $notesType = 'footnotes') { @@ -362,7 +474,7 @@ private function readNotes($filename, $xmlFile, $notesType = 'footnotes') * * @todo Get font style for preserve text */ - private function readParagraph(XMLReader $xmlReader, \DOMNode $domNode, &$parent, $docPart) + private function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, &$parent, $docPart) { // Paragraph style $pStyle = null; @@ -396,6 +508,17 @@ private function readParagraph(XMLReader $xmlReader, \DOMNode $domNode, &$parent } $parent->addPreserveText($textContent, $fStyle, $pStyle); + // List item + } elseif ($xmlReader->elementExists('w:pPr/w:numPr', $domNode)) { + $textContent = ''; + $numId = $xmlReader->getAttribute('w:val', $domNode, 'w:pPr/w:numPr/w:numId'); + $levelId = $xmlReader->getAttribute('w:val', $domNode, 'w:pPr/w:numPr/w:ilvl'); + $nodes = $xmlReader->getElements('w:r', $domNode); + foreach ($nodes as $node) { + $textContent .= $xmlReader->getValue('w:t', $node); + } + $parent->addListItem($textContent, $levelId, null, "PHPWordList{$numId}", $pStyle); + // Text and TextRun } else { $runCount = $xmlReader->countElements('w:r', $domNode); @@ -421,13 +544,15 @@ private function readParagraph(XMLReader $xmlReader, \DOMNode $domNode, &$parent /** * Read w:r * + * @param \PhpOffice\PhpWord\Shared\XMLReader $xmlReader + * @param \DOMElement $domNode * @param mixed $parent * @param string $docPart * @param mixed $pStyle * * @todo Footnote paragraph style */ - private function readRun(XMLReader $xmlReader, \DOMNode $domNode, &$parent, $docPart, $pStyle = null) + private function readRun(XMLReader $xmlReader, \DOMElement $domNode, &$parent, $docPart, $pStyle = null) { if (!in_array($domNode->nodeName, array('w:r', 'w:hyperlink'))) { return; @@ -484,7 +609,7 @@ private function readRun(XMLReader $xmlReader, \DOMNode $domNode, &$parent, $doc * @param mixed $parent * @param string $docPart */ - private function readTable(XMLReader $xmlReader, \DOMNode $domNode, &$parent, $docPart) + private function readTable(XMLReader $xmlReader, \DOMElement $domNode, &$parent, $docPart) { // Table style $tblStyle = null; @@ -517,8 +642,9 @@ private function readTable(XMLReader $xmlReader, \DOMNode $domNode, &$parent, $d } elseif ($rowNode->nodeName == 'w:tc') { // Cell $cellWidth = $xmlReader->getAttribute('w:w', $rowNode, 'w:tcPr/w:tcW'); $cellStyle = null; - if ($xmlReader->elementExists('w:tcPr', $rowNode)) { - $cellStyle = $this->readCellStyle($xmlReader, $xmlReader->getElement('w:tcPr', $rowNode)); + $cellStyleNode = $xmlReader->getElement('w:tcPr', $rowNode); + if (!is_null($cellStyleNode)) { + $cellStyle = $this->readCellStyle($xmlReader, $cellStyleNode); } $cell = $row->addCell($cellWidth, $cellStyle); @@ -539,7 +665,7 @@ private function readTable(XMLReader $xmlReader, \DOMNode $domNode, &$parent, $d * * @return array|null */ - private function readSectionStyle(XMLReader $xmlReader, \DOMNode $domNode) + private function readSectionStyle(XMLReader $xmlReader, \DOMElement $domNode) { $ret = null; $mapping = array( @@ -554,6 +680,7 @@ private function readSectionStyle(XMLReader $xmlReader, \DOMNode $domNode) } $property = $mapping[$node->nodeName]; switch ($node->nodeName) { + case 'w:type': $ret['breakType'] = $xmlReader->getAttribute('w:val', $node); break; @@ -598,7 +725,7 @@ private function readSectionStyle(XMLReader $xmlReader, \DOMNode $domNode) * * @return string|array|null */ - private function readParagraphStyle(XMLReader $xmlReader, \DOMNode $domNode) + private function readParagraphStyle(XMLReader $xmlReader, \DOMElement $domNode) { $style = null; if ($xmlReader->elementExists('w:pPr', $domNode)) { @@ -620,6 +747,7 @@ private function readParagraphStyle(XMLReader $xmlReader, \DOMNode $domNode) } $property = $mapping[$node->nodeName]; switch ($node->nodeName) { + case 'w:ind': $style['indent'] = $xmlReader->getAttribute('w:left', $node); $style['hanging'] = $xmlReader->getAttribute('w:hanging', $node); @@ -660,13 +788,16 @@ private function readParagraphStyle(XMLReader $xmlReader, \DOMNode $domNode) * * @return string|array|null */ - private function readFontStyle(XMLReader $xmlReader, \DOMNode $domNode) + private function readFontStyle(XMLReader $xmlReader, \DOMElement $domNode) { $style = null; // Hyperlink has an extra w:r child if ($domNode->nodeName == 'w:hyperlink') { $domNode = $xmlReader->getElement('w:r', $domNode); } + if (is_null($domNode)) { + return $style; + } if ($xmlReader->elementExists('w:rPr', $domNode)) { if ($xmlReader->elementExists('w:rPr/w:rStyle', $domNode)) { $style = $xmlReader->getAttribute('w:val', $domNode, 'w:rPr/w:rStyle'); @@ -686,6 +817,7 @@ private function readFontStyle(XMLReader $xmlReader, \DOMNode $domNode) } $property = $mapping[$node->nodeName]; switch ($node->nodeName) { + case 'w:rFonts': $style['name'] = $xmlReader->getAttribute('w:ascii', $node); $style['hint'] = $xmlReader->getAttribute('w:hint', $node); @@ -729,7 +861,7 @@ private function readFontStyle(XMLReader $xmlReader, \DOMNode $domNode) * @return string|array|null * @todo Capture w:tblStylePr w:type="firstRow" */ - private function readTableStyle(XMLReader $xmlReader, \DOMNode $domNode) + private function readTableStyle(XMLReader $xmlReader, \DOMElement $domNode) { $style = null; $margins = array('top', 'left', 'bottom', 'right'); @@ -752,6 +884,7 @@ private function readTableStyle(XMLReader $xmlReader, \DOMNode $domNode) } // $property = $mapping[$node->nodeName]; switch ($node->nodeName) { + case 'w:tblCellMar': foreach ($margins as $side) { $ucfSide = ucfirst($side); @@ -779,7 +912,7 @@ private function readTableStyle(XMLReader $xmlReader, \DOMNode $domNode) * * @return array|null */ - private function readCellStyle(XMLReader $xmlReader, \DOMNode $domNode) + private function readCellStyle(XMLReader $xmlReader, \DOMElement $domNode) { $style = null; $mapping = array( diff --git a/src/PhpWord/Shared/XMLReader.php b/src/PhpWord/Shared/XMLReader.php index e5b5facc4b..17e8454db9 100644 --- a/src/PhpWord/Shared/XMLReader.php +++ b/src/PhpWord/Shared/XMLReader.php @@ -70,7 +70,7 @@ public function getDomFromZip($zipFile, $xmlFile) * @param string $path * @return \DOMNodeList */ - public function getElements($path, \DOMNode $contextNode = null) + public function getElements($path, \DOMElement $contextNode = null) { if ($this->dom === null) { return array(); @@ -86,9 +86,9 @@ public function getElements($path, \DOMNode $contextNode = null) * Get element * * @param string $path - * @return \DOMNode|null + * @return \DOMElement|null */ - public function getElement($path, \DOMNode $contextNode) + public function getElement($path, \DOMElement $contextNode) { $elements = $this->getElements($path, $contextNode); if ($elements->length > 0) { @@ -105,7 +105,7 @@ public function getElement($path, \DOMNode $contextNode) * @param string $path * @return string|null */ - public function getAttribute($attribute, \DOMNode $contextNode, $path = null) + public function getAttribute($attribute, \DOMElement $contextNode, $path = null) { if (is_null($path)) { $return = $contextNode->getAttribute($attribute); @@ -127,7 +127,7 @@ public function getAttribute($attribute, \DOMNode $contextNode, $path = null) * @param string $path * @return string|null */ - public function getValue($path, \DOMNode $contextNode) + public function getValue($path, \DOMElement $contextNode) { $elements = $this->getElements($path, $contextNode); if ($elements->length > 0) { @@ -143,7 +143,7 @@ public function getValue($path, \DOMNode $contextNode) * @param string $path * @return integer */ - public function countElements($path, \DOMNode $contextNode) + public function countElements($path, \DOMElement $contextNode) { $elements = $this->getElements($path, $contextNode); @@ -156,7 +156,7 @@ public function countElements($path, \DOMNode $contextNode) * @param string $path * @return boolean */ - public function elementExists($path, \DOMNode $contextNode) + public function elementExists($path, \DOMElement $contextNode) { return $this->getElements($path, $contextNode)->length > 0; } diff --git a/src/PhpWord/Style.php b/src/PhpWord/Style.php index 5afbb91b9c..084d7131b2 100755 --- a/src/PhpWord/Style.php +++ b/src/PhpWord/Style.php @@ -12,6 +12,7 @@ use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWord\Style\Table; +use PhpOffice\PhpWord\Style\Numbering; /** * Style collection @@ -33,7 +34,7 @@ class Style */ public static function addParagraphStyle($styleName, $styles) { - self::setStyleValues($styleName, $styles, new Paragraph()); + self::setStyleValues($styleName, new Paragraph(), $styles); } /** @@ -45,7 +46,7 @@ public static function addParagraphStyle($styleName, $styles) */ public static function addFontStyle($styleName, $styleFont, $styleParagraph = null) { - self::setStyleValues($styleName, $styleFont, new Font('text', $styleParagraph)); + self::setStyleValues($styleName, new Font('text', $styleParagraph), $styleFont); } /** @@ -56,7 +57,7 @@ public static function addFontStyle($styleName, $styleFont, $styleParagraph = nu */ public static function addLinkStyle($styleName, $styles) { - self::setStyleValues($styleName, $styles, new Font('link')); + self::setStyleValues($styleName, new Font('link'), $styles); } /** @@ -64,15 +65,11 @@ public static function addLinkStyle($styleName, $styles) * * @param string $styleName * @param array $styleTable - * @param array $styleFirstRow + * @param array|null $styleFirstRow */ public static function addTableStyle($styleName, $styleTable, $styleFirstRow = null) { - if (!array_key_exists($styleName, self::$styles)) { - $style = new Table($styleTable, $styleFirstRow); - - self::$styles[$styleName] = $style; - } + self::setStyleValues($styleName, new Table($styleTable, $styleFirstRow), null); } /** @@ -84,12 +81,36 @@ public static function addTableStyle($styleName, $styleTable, $styleFirstRow = n */ public static function addTitleStyle($titleCount, $styleFont, $styleParagraph = null) { - $styleName = 'Heading_' . $titleCount; - self::setStyleValues("Heading_{$titleCount}", $styleFont, new Font('title', $styleParagraph)); + self::setStyleValues("Heading_{$titleCount}", new Font('title', $styleParagraph), $styleFont); + } + + /** + * Add numbering style + * + * @param string $styleName + * @param array $styleValues + * @return Numbering + * @since 0.9.2 + */ + public static function addNumberingStyle($styleName, $styleValues) + { + self::setStyleValues($styleName, new Numbering(), $styleValues); + } + + /** + * Count styles + * + * @return integer + * @since 0.9.2 + */ + public static function countStyles() + { + return count(self::$styles); } /** * Reset styles + * @since 0.9.2 */ public static function resetStyles() { @@ -120,6 +141,7 @@ public static function getStyles() * Get style by name * * @param string $styleName + * @return Paragraph|Font|Table|Numbering|null */ public static function getStyle($styleName) { @@ -131,24 +153,21 @@ public static function getStyle($styleName) } /** - * Set style values + * Set style values and put it to static style collection * * @param string $styleName - * @param array $styleValues - * @param mixed $styleObject + * @param Paragraph|Font|Table|Numbering $styleObject + * @param array|null $styleValues */ - private static function setStyleValues($styleName, $styleValues, $styleObject) + private static function setStyleValues($styleName, $styleObject, $styleValues = null) { if (!array_key_exists($styleName, self::$styles)) { - if (is_array($styleValues)) { + if (!is_null($styleValues) && is_array($styleValues)) { foreach ($styleValues as $key => $value) { - if (substr($key, 0, 1) == '_') { - $key = substr($key, 1); - } $styleObject->setStyleValue($key, $value); } } - + $styleObject->setIndex(self::countStyles() + 1); // One based index self::$styles[$styleName] = $styleObject; } } diff --git a/src/PhpWord/Style/AbstractStyle.php b/src/PhpWord/Style/AbstractStyle.php index c17ea6e23d..11bfd8a45b 100644 --- a/src/PhpWord/Style/AbstractStyle.php +++ b/src/PhpWord/Style/AbstractStyle.php @@ -16,6 +16,37 @@ */ abstract class AbstractStyle { + /** + * Index number in Style collection for named style + * + * This number starts from one and defined in Style::setStyleValues() + * + * @var integer|null + */ + protected $index; + + /** + * Get index number + * + * @return integer|null + */ + public function getIndex() + { + return $this->index; + } + + /** + * Set index number + * + * @param integer|null $value + */ + public function setIndex($value = null) + { + $this->index = $this->setIntVal($value, $this->index); + + return $this; + } + /** * Set style value template method * @@ -23,8 +54,7 @@ abstract class AbstractStyle * * @param string $key * @param string $value - * - * @todo Implement type check mechanism, e.g. boolean, integer, enum, defaults + * @return self */ public function setStyleValue($key, $value) { @@ -39,5 +69,88 @@ public function setStyleValue($key, $value) if (method_exists($this, $method)) { $this->$method($value); } + + return $this; + } + + /** + * Set style by using associative array + * + * @param array $styles + * @return self + */ + public function setStyleByArray($styles = array()) + { + foreach ($styles as $key => $value) { + $this->setStyleValue($key, $value); + } + + return $this; + } + + /** + * Set boolean value + * + * @param mixed $value + * @param boolean|null $default + * @return boolean|null + */ + protected function setBoolVal($value, $default = null) + { + if (!is_bool($value)) { + $value = $default; + } + + return $value; + } + + /** + * Set integer value + * + * @param integer|null $value + * @param integer|null $default + * @return integer|null + */ + protected function setIntVal($value, $default = null) + { + $value = intval($value); + if (!is_int($value)) { + $value = $default; + } + + return $value; + } + + /** + * Set float value + * + * @param mixed $value + * @param float|null $default + * @return float|null + */ + protected function setFloatVal($value, $default = null) + { + $value = floatval($value); + if (!is_float($value)) { + $value = $default; + } + + return $value; + } + + /** + * Set enum value + * + * @param mixed $value + * @param array $enum + * @param mixed $default + */ + protected function setEnumVal($value, $enum, $default = null) + { + if (!in_array($value, $enum)) { + $value = $default; + } + + return $value; } } diff --git a/src/PhpWord/Style/ListItem.php b/src/PhpWord/Style/ListItem.php index ecc4274c1d..bb04d0e674 100644 --- a/src/PhpWord/Style/ListItem.php +++ b/src/PhpWord/Style/ListItem.php @@ -9,46 +9,237 @@ namespace PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Style; + /** * List item style + * + * Before version 0.9.2, numbering style is defined statically with $listType. + * After version 0.9.2, numbering style is defined by using Numbering and + * recorded by $numStyle. $listStyle is maintained for backward compatility */ class ListItem extends AbstractStyle { + const TYPE_SQUARE_FILLED = 1; + const TYPE_BULLET_FILLED = 3; // default + const TYPE_BULLET_EMPTY = 5; const TYPE_NUMBER = 7; const TYPE_NUMBER_NESTED = 8; const TYPE_ALPHANUM = 9; - const TYPE_BULLET_FILLED = 3; - const TYPE_BULLET_EMPTY = 5; - const TYPE_SQUARE_FILLED = 1; /** - * List Type + * Legacy list type + * + * @var integer */ private $listType; /** - * Create a new ListItem Style + * Numbering style name + * + * @var string + * @since 0.9.2 */ - public function __construct() - { - $this->listType = self::TYPE_BULLET_FILLED; - } + private $numStyle; + + /** + * Numbering definition instance ID + * + * @var integer + * @since 0.9.2 + */ + private $numId; /** - * Set List Type + * Create new instance * - * @param int $pValue + * @param string $numStyle */ - public function setListType($pValue = self::TYPE_BULLET_FILLED) + public function __construct($numStyle = null) { - $this->listType = $pValue; + if (!is_null($numStyle)) { + $this->setNumStyle($numStyle); + } else { + $this->setListType(); + } } /** * Get List Type + * + * @return integer */ public function getListType() { return $this->listType; } + + /** + * Set legacy list type for version < 0.9.2 + * + * @param integer $value + */ + public function setListType($value = self::TYPE_BULLET_FILLED) + { + $enum = array(self::TYPE_SQUARE_FILLED, self::TYPE_BULLET_FILLED, + self::TYPE_BULLET_EMPTY, self::TYPE_NUMBER, + self::TYPE_NUMBER_NESTED, self::TYPE_ALPHANUM); + $this->listType = $this->setEnumVal($value, $enum, $this->listType); + $this->getListTypeStyle(); + } + + /** + * Get numbering style name + * + * @return string + */ + public function getNumStyle() + { + return $this->numStyle; + } + + /** + * Set numbering style name + */ + public function setNumStyle($value) + { + $this->numStyle = $value; + $numStyleObject = Style::getStyle($this->numStyle); + if ($numStyleObject instanceof Numbering) { + $this->numId = $numStyleObject->getIndex(); + $numStyleObject->setNumId($this->numId); + } + } + + /** + * Get numbering Id + * + * @return integer + */ + public function getNumId() + { + return $this->numId; + } + + /** + * Get legacy numbering definition + * + * @return array + * @since 0.9.2 + */ + private function getListTypeStyle() + { + // Check if legacy style already registered in global Style collection + $numStyle = "PHPWordList{$this->listType}"; + if (!is_null(Style::getStyle($numStyle))) { + $this->setNumStyle($numStyle); + return; + } + + // Property mapping for numbering level information + $properties = array('start', 'format', 'text', 'align', 'tabPos', 'left', 'hanging', 'font', 'hint'); + + // Legacy level information + $listTypeStyles = array( + self::TYPE_SQUARE_FILLED => array( + 'type' => 'hybridMultilevel', + 'levels' => array( + 0 => '1, bullet, , left, 720, 720, 360, Wingdings, default', + 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', + 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', + 3 => '1, bullet, , left, 2880, 2880, 360, Symbol, default', + 4 => '1, bullet, o, left, 3600, 3600, 360, Courier New, default', + 5 => '1, bullet, , left, 4320, 4320, 360, Wingdings, default', + 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', + 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', + 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', + ), + ), + self::TYPE_BULLET_FILLED => array( + 'type' => 'hybridMultilevel', + 'levels' => array( + 0 => '1, bullet, , left, 720, 720, 360, Symbol, default', + 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', + 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', + 3 => '1, bullet, , left, 2880, 2880, 360, Symbol, default', + 4 => '1, bullet, o, left, 3600, 3600, 360, Courier New, default', + 5 => '1, bullet, , left, 4320, 4320, 360, Wingdings, default', + 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', + 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', + 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', + ), + ), + self::TYPE_BULLET_EMPTY => array( + 'type' => 'hybridMultilevel', + 'levels' => array( + 0 => '1, bullet, o, left, 720, 720, 360, Courier New, default', + 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', + 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', + 3 => '1, bullet, , left, 2880, 2880, 360, Symbol, default', + 4 => '1, bullet, o, left, 3600, 3600, 360, Courier New, default', + 5 => '1, bullet, , left, 4320, 4320, 360, Wingdings, default', + 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', + 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', + 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', + ), + ), + self::TYPE_NUMBER => array( + 'type' => 'hybridMultilevel', + 'levels' => array( + 0 => '1, decimal, %1., left, 720, 720, 360, , default', + 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', + 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', + 3 => '1, bullet, , left, 2880, 2880, 360, Symbol, default', + 4 => '1, bullet, o, left, 3600, 3600, 360, Courier New, default', + 5 => '1, bullet, , left, 4320, 4320, 360, Wingdings, default', + 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', + 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', + 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', + ), + ), + self::TYPE_NUMBER_NESTED => array( + 'type' => 'multilevel', + 'levels' => array( + 0 => '1, decimal, %1., left, 360, 360, 360, , ', + 1 => '1, decimal, %1.%2., left, 792, 792, 432, , ', + 2 => '1, decimal, %1.%2.%3., left, 1224, 1224, 504, , ', + 3 => '1, decimal, %1.%2.%3.%4., left, 1800, 1728, 648, , ', + 4 => '1, decimal, %1.%2.%3.%4.%5., left, 2520, 2232, 792, , ', + 5 => '1, decimal, %1.%2.%3.%4.%5.%6., left, 2880, 2736, 936, , ', + 6 => '1, decimal, %1.%2.%3.%4.%5.%6.%7., left, 3600, 3240, 1080, , ', + 7 => '1, decimal, %1.%2.%3.%4.%5.%6.%7.%8., left, 3960, 3744, 1224, , ', + 8 => '1, decimal, %1.%2.%3.%4.%5.%6.%7.%8.%9., left, 4680, 4320, 1440, , ', + ), + ), + self::TYPE_ALPHANUM => array( + 'type' => 'multilevel', + 'levels' => array( + 0 => '1, decimal, %1., left, 720, 720, 360, , ', + 1 => '1, lowerLetter, %2., left, 1440, 1440, 360, , ', + 2 => '1, lowerRoman, %3., right, 2160, 2160, 180, , ', + 3 => '1, decimal, %4., left, 2880, 2880, 360, , ', + 4 => '1, lowerLetter, %5., left, 3600, 3600, 360, , ', + 5 => '1, lowerRoman, %6., right, 4320, 4320, 180, , ', + 6 => '1, decimal, %7., left, 5040, 5040, 360, , ', + 7 => '1, lowerLetter, %8., left, 5760, 5760, 360, , ', + 8 => '1, lowerRoman, %9., right, 6480, 6480, 180, , ', + ), + ), + ); + + // Populate style and register to global Style register + $style = $listTypeStyles[$this->listType]; + foreach ($style['levels'] as $key => $value) { + $level = array(); + $levelProperties = explode(', ', $value); + $level['level'] = $key; + for ($i = 0; $i < count($properties); $i++) { + $property = $properties[$i]; + $level[$property] = $levelProperties[$i]; + } + $style['levels'][$key] = $level; + } + Style::addNumberingStyle($numStyle, $style); + $this->setNumStyle($numStyle); + } } diff --git a/src/PhpWord/Style/Numbering.php b/src/PhpWord/Style/Numbering.php new file mode 100644 index 0000000000..ce935b9892 --- /dev/null +++ b/src/PhpWord/Style/Numbering.php @@ -0,0 +1,123 @@ +numId; + } + + /** + * Set Id + * + * @param integer $value + * @return self + */ + public function setNumId($value) + { + $this->numId = $this->setIntVal($value, $this->numId); + return $this; + } + + /** + * Get multilevel type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set multilevel type + * + * @param string $value + * @return self + */ + public function setType($value) + { + $enum = array('singleLevel', 'multilevel', 'hybridMultilevel'); + $this->type = $this->setEnumVal($value, $enum, $this->type); + return $this; + } + + /** + * Get levels + * + * @return NumberingLevel[] + */ + public function getLevels() + { + return $this->levels; + } + + /** + * Set multilevel type + * + * @param array $values + * @return self + */ + public function setLevels($values) + { + if (is_array($values)) { + foreach ($values as $key => $value) { + $numberingLevel = new NumberingLevel(); + if (is_array($value)) { + $numberingLevel->setStyleByArray($value); + $numberingLevel->setLevel($key); + } + $this->levels[$key] = $numberingLevel; + } + } + + return $this; + } +} diff --git a/src/PhpWord/Style/NumberingLevel.php b/src/PhpWord/Style/NumberingLevel.php new file mode 100644 index 0000000000..2979f0c2a7 --- /dev/null +++ b/src/PhpWord/Style/NumberingLevel.php @@ -0,0 +1,378 @@ +level; + } + + /** + * Set level + * + * @param integer $value + * @return self + */ + public function setLevel($value) + { + $this->level = $this->setIntVal($value, $this->level); + return $this; + } + + /** + * Get start + * + * @return integer + */ + public function getStart() + { + return $this->start; + } + + /** + * Set start + * + * @param integer $value + * @return self + */ + public function setStart($value) + { + $this->start = $this->setIntVal($value, $this->start); + return $this; + } + + /** + * Get format + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Set format + * + * @param string $value + * @return self + */ + public function setFormat($value) + { + $enum = array('bullet', 'decimal', 'upperRoman', 'lowerRoman', 'upperLetter', 'lowerLetter'); + $this->format = $this->setEnumVal($value, $enum, $this->format); + return $this; + } + + /** + * Get start + * + * @return integer + */ + public function getRestart() + { + return $this->restart; + } + + /** + * Set start + * + * @param integer $value + * @return self + */ + public function setRestart($value) + { + $this->restart = $this->setIntVal($value, $this->restart); + return $this; + } + + /** + * Get suffix + * + * @return string + */ + public function getSuffix() + { + return $this->suffix; + } + + /** + * Set suffix + * + * @param string $value + * @return self + */ + public function setSuffix($value) + { + $enum = array('tab', 'space', 'nothing'); + $this->suffix = $this->setEnumVal($value, $enum, $this->suffix); + return $this; + } + + /** + * Get text + * + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * Set text + * + * @param string $value + * @return self + */ + public function setText($value) + { + $this->text = $value; + return $this; + } + + /** + * Get align + * + * @return string + */ + public function getAlign() + { + return $this->align; + } + + /** + * Set align + * + * @param string $value + * @return self + */ + public function setAlign($value) + { + $enum = array('left', 'center', 'right', 'both'); + $this->align = $this->setEnumVal($value, $enum, $this->align); + return $this; + } + + /** + * Get left + * + * @return integer + */ + public function getLeft() + { + return $this->left; + } + + /** + * Set left + * + * @param integer $value + * @return self + */ + public function setLeft($value) + { + $this->left = $this->setIntVal($value, $this->left); + return $this; + } + + /** + * Get hanging + * + * @return integer + */ + public function getHanging() + { + return $this->hanging; + } + + /** + * Set hanging + * + * @param integer $value + * @return self + */ + public function setHanging($value) + { + $this->hanging = $this->setIntVal($value, $this->hanging); + return $this; + } + + /** + * Get tab + * + * @return integer + */ + public function getTabPos() + { + return $this->tabPos; + } + + /** + * Set tab + * + * @param integer $value + * @return self + */ + public function setTabPos($value) + { + $this->tabPos = $this->setIntVal($value, $this->tabPos); + return $this; + } + + /** + * Get font + * + * @return string + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font + * + * @param string $value + * @return self + */ + public function setFont($value) + { + $this->font = $value; + return $this; + } + + /** + * Get hint + * + * @return string + */ + public function getHint() + { + return $this->hint; + } + + /** + * Set hint + * + * @param string $value + * @return self + */ + public function setHint($value) + { + $enum = array('default', 'eastAsia', 'cs'); + $this->hint = $this->setEnumVal($value, $enum, $this->hint); + return $this; + } +} diff --git a/src/PhpWord/Template.php b/src/PhpWord/Template.php index b0be592d68..65ffd75fe5 100644 --- a/src/PhpWord/Template.php +++ b/src/PhpWord/Template.php @@ -421,17 +421,4 @@ private function getSlice($startPosition, $endPosition = 0) } return substr($this->documentXML, $startPosition, ($endPosition - $startPosition)); } - - /** - * Delete a block of text - * - * @param string $blockname - * @param string $replacement - * @deprecated - * @codeCoverageIgnore - */ - public function deleteTemplateBlock($blockname, $replacement = '') - { - $this->deleteBlock($blockname, $replacement); - } } diff --git a/src/PhpWord/Writer/Word2007.php b/src/PhpWord/Writer/Word2007.php index b44708a34d..7b55290551 100755 --- a/src/PhpWord/Writer/Word2007.php +++ b/src/PhpWord/Writer/Word2007.php @@ -9,17 +9,18 @@ namespace PhpOffice\PhpWord\Writer; +use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Exception\Exception; -use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Media; -use PhpOffice\PhpWord\Element\Section; +use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Writer\Word2007\ContentTypes; -use PhpOffice\PhpWord\Writer\Word2007\Rels; use PhpOffice\PhpWord\Writer\Word2007\DocProps; use PhpOffice\PhpWord\Writer\Word2007\Document; use PhpOffice\PhpWord\Writer\Word2007\Footer; -use PhpOffice\PhpWord\Writer\Word2007\Notes; use PhpOffice\PhpWord\Writer\Word2007\Header; +use PhpOffice\PhpWord\Writer\Word2007\Notes; +use PhpOffice\PhpWord\Writer\Word2007\Numbering; +use PhpOffice\PhpWord\Writer\Word2007\Rels; use PhpOffice\PhpWord\Writer\Word2007\Styles; /** @@ -57,6 +58,7 @@ public function __construct(PhpWord $phpWord = null) $this->writerParts['docprops'] = new DocProps(); $this->writerParts['document'] = new Document(); $this->writerParts['styles'] = new Styles(); + $this->writerParts['numbering'] = new Numbering(); $this->writerParts['header'] = new Header(); $this->writerParts['footer'] = new Footer(); $this->writerParts['footnotes'] = new Notes(); @@ -97,7 +99,6 @@ public function save($filename = null) $this->addHeaderFooterMedia($objZip, 'footer'); // Add header/footer contents - $overrides = array(); $rId = Media::countElements('section') + 6; // @see Rels::writeDocRels for 6 first elements $sections = $this->phpWord->getSections(); foreach ($sections as $section) { @@ -116,9 +117,9 @@ public function save($filename = null) $objZip->addFromString('word/_rels/document.xml.rels', $this->getWriterPart('rels')->writeDocRels($this->docRels)); $objZip->addFromString('word/document.xml', $this->getWriterPart('document')->writeDocument($this->phpWord)); $objZip->addFromString('word/styles.xml', $this->getWriterPart('styles')->writeStyles($this->phpWord)); + $objZip->addFromString('word/numbering.xml', $this->getWriterPart('numbering')->writeNumbering()); // Write static files - $objZip->addFile(__DIR__ . '/../_staticDocParts/numbering.xml', 'word/numbering.xml'); $objZip->addFile(__DIR__ . '/../_staticDocParts/settings.xml', 'word/settings.xml'); $objZip->addFile(__DIR__ . '/../_staticDocParts/theme1.xml', 'word/theme/theme1.xml'); $objZip->addFile(__DIR__ . '/../_staticDocParts/webSettings.xml', 'word/webSettings.xml'); @@ -224,8 +225,8 @@ private function addHeaderFooterContent(Section &$section, $objZip, $elmType, &$ * Add footnotes/endnotes * * @param mixed $objZip - * @param string $elmType * @param integer $rId + * @param string $notesType */ private function addNotes($objZip, &$rId, $notesType = 'footnote') { diff --git a/src/PhpWord/Writer/Word2007/Base.php b/src/PhpWord/Writer/Word2007/Base.php index 4fd38b5714..b74211a5bc 100644 --- a/src/PhpWord/Writer/Word2007/Base.php +++ b/src/PhpWord/Writer/Word2007/Base.php @@ -279,7 +279,7 @@ protected function writeListItem(XMLWriter $xmlWriter, ListItem $listItem) { $textObject = $listItem->getTextObject(); $depth = $listItem->getDepth(); - $listType = $listItem->getStyle()->getListType(); + $numId = $listItem->getStyle()->getNumId(); $styleParagraph = $textObject->getParagraphStyle(); $xmlWriter->startElement('w:p'); @@ -290,7 +290,7 @@ protected function writeListItem(XMLWriter $xmlWriter, ListItem $listItem) $xmlWriter->writeAttribute('w:val', $depth); $xmlWriter->endElement(); // w:ilvl $xmlWriter->startElement('w:numId'); - $xmlWriter->writeAttribute('w:val', $listType); + $xmlWriter->writeAttribute('w:val', $numId); $xmlWriter->endElement(); // w:numId $xmlWriter->endElement(); // w:numPr $xmlWriter->endElement(); // w:pPr diff --git a/src/PhpWord/Writer/Word2007/Notes.php b/src/PhpWord/Writer/Word2007/Notes.php index 96393b4c9a..3b70ee742f 100644 --- a/src/PhpWord/Writer/Word2007/Notes.php +++ b/src/PhpWord/Writer/Word2007/Notes.php @@ -69,7 +69,7 @@ public function writeNotes($elements, $notesTypes = 'footnotes') // Content foreach ($elements as $element) { if ($element instanceof Footnote || $element instanceof Endnote) { - $this->writeNote($xmlWriter, $element, null, $notesTypes); + $this->writeNote($xmlWriter, $element, $notesTypes); } } @@ -83,10 +83,9 @@ public function writeNotes($elements, $notesTypes = 'footnotes') * * @param XMLWriter $xmlWriter * @param Footnote|Endnote $element - * @param boolean $withoutP * @param string $notesTypes */ - protected function writeNote(XMLWriter $xmlWriter, $element, $withoutP = false, $notesTypes = 'footnotes') + protected function writeNote(XMLWriter $xmlWriter, $element, $notesTypes = 'footnotes') { $isFootnote = ($notesTypes == 'footnotes'); $elementNode = $isFootnote ? 'w:footnote' : 'w:endnote'; diff --git a/src/PhpWord/Writer/Word2007/Numbering.php b/src/PhpWord/Writer/Word2007/Numbering.php new file mode 100644 index 0000000000..da127891b4 --- /dev/null +++ b/src/PhpWord/Writer/Word2007/Numbering.php @@ -0,0 +1,177 @@ +getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('w:numbering'); + $xmlWriter->writeAttribute('xmlns:ve', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xmlWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:m', 'http://schemas.openxmlformats.org/officeDocument/2006/math'); + $xmlWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); + $xmlWriter->writeAttribute('xmlns:wp', 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'); + $xmlWriter->writeAttribute('xmlns:w10', 'urn:schemas-microsoft-com:office:word'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:wne', 'http://schemas.microsoft.com/office/word/2006/wordml'); + + // Abstract numbering definitions + foreach ($styles as $style) { + if ($style instanceof NumberingStyle) { + $levels = $style->getLevels(); + + $xmlWriter->startElement('w:abstractNum'); + $xmlWriter->writeAttribute('w:abstractNumId', $style->getNumId()); + + $xmlWriter->startElement('w:nsid'); + $xmlWriter->writeAttribute('w:val', $this->getRandomHexNumber()); + $xmlWriter->endElement(); // w:nsid + + $xmlWriter->startElement('w:multiLevelType'); + $xmlWriter->writeAttribute('w:val', $style->getType()); + $xmlWriter->endElement(); // w:multiLevelType + + if (is_array($levels)) { + foreach ($levels as $levelNum => $levelObject) { + if ($levelObject instanceof NumberingLevel) { + $start = $levelObject->getStart(); + $format = $levelObject->getFormat(); + $restart = $levelObject->getRestart(); + $suffix = $levelObject->getSuffix(); + $text = $levelObject->getText(); + $align = $levelObject->getAlign(); + $tabPos = $levelObject->getTabPos(); + $left = $levelObject->getLeft(); + $hanging = $levelObject->getHanging(); + $font = $levelObject->getFont(); + $hint = $levelObject->getHint(); + + $xmlWriter->startElement('w:lvl'); + $xmlWriter->writeAttribute('w:ilvl', $levelNum); + + if (!is_null($start)) { + $xmlWriter->startElement('w:start'); + $xmlWriter->writeAttribute('w:val', $start); + $xmlWriter->endElement(); // w:start + } + if (!is_null($format)) { + $xmlWriter->startElement('w:numFmt'); + $xmlWriter->writeAttribute('w:val', $format); + $xmlWriter->endElement(); // w:numFmt + } + if (!is_null($restart)) { + $xmlWriter->startElement('w:lvlRestart'); + $xmlWriter->writeAttribute('w:val', $restart); + $xmlWriter->endElement(); // w:lvlRestart + } + if (!is_null($suffix)) { + $xmlWriter->startElement('w:suff'); + $xmlWriter->writeAttribute('w:val', $suffix); + $xmlWriter->endElement(); // w:suff + } + if (!is_null($text)) { + $xmlWriter->startElement('w:lvlText'); + $xmlWriter->writeAttribute('w:val', $text); + $xmlWriter->endElement(); // w:start + } + if (!is_null($align)) { + $xmlWriter->startElement('w:lvlJc'); + $xmlWriter->writeAttribute('w:val', $align); + $xmlWriter->endElement(); // w:lvlJc + } + if (!is_null($tabPos) || !is_null($left) || !is_null($hanging)) { + $xmlWriter->startElement('w:pPr'); + if (!is_null($tabPos)) { + $xmlWriter->startElement('w:tabs'); + $xmlWriter->startElement('w:tab'); + $xmlWriter->writeAttribute('w:val', 'num'); + $xmlWriter->writeAttribute('w:pos', $tabPos); + $xmlWriter->endElement(); // w:tab + $xmlWriter->endElement(); // w:tabs + } + if (!is_null($left) || !is_null($hanging)) { + $xmlWriter->startElement('w:ind'); + if (!is_null($left)) { + $xmlWriter->writeAttribute('w:left', $left); + } + if (!is_null($hanging)) { + $xmlWriter->writeAttribute('w:hanging', $hanging); + } + $xmlWriter->endElement(); // w:ind + } + $xmlWriter->endElement(); // w:pPr + } + if (!is_null($font) || !is_null($hint)) { + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rFonts'); + if (!is_null($font)) { + $xmlWriter->writeAttribute('w:ascii', $font); + $xmlWriter->writeAttribute('w:hAnsi', $font); + $xmlWriter->writeAttribute('w:cs', $font); + } + if (!is_null($hint)) { + $xmlWriter->writeAttribute('w:hint', $hint); + } + $xmlWriter->endElement(); // w:rFonts + $xmlWriter->endElement(); // w:rPr + } + $xmlWriter->endElement(); // w:lvl + } + } + } + $xmlWriter->endElement(); // w:abstractNum + } + } + + // Numbering definition instances + foreach ($styles as $style) { + if ($style instanceof NumberingStyle) { + $xmlWriter->startElement('w:num'); + $xmlWriter->writeAttribute('w:numId', $style->getNumId()); + $xmlWriter->startElement('w:abstractNumId'); + $xmlWriter->writeAttribute('w:val', $style->getNumId()); + $xmlWriter->endElement(); // w:abstractNumId + $xmlWriter->endElement(); // w:num + } + } + + $xmlWriter->endElement(); + + return $xmlWriter->getData(); + } + + /** + * Get random hexadecimal number value + * + * @param int $length + * @return string + */ + private function getRandomHexNumber($length = 8) + { + return strtoupper(substr(md5(rand()), 0, $length)); + } +} diff --git a/src/PhpWord/Writer/Word2007/Styles.php b/src/PhpWord/Writer/Word2007/Styles.php index f0305f7f6e..ae78498278 100644 --- a/src/PhpWord/Writer/Word2007/Styles.php +++ b/src/PhpWord/Writer/Word2007/Styles.php @@ -18,6 +18,8 @@ /** * Word2007 styles part writer + * + * @todo Do something with the numbering style introduced in 0.9.2 */ class Styles extends Base { @@ -38,37 +40,31 @@ public function writeStyles(PhpWord $phpWord = null) // XML header $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); $xmlWriter->startElement('w:styles'); - $xmlWriter->writeAttribute( - 'xmlns:r', - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' - ); - $xmlWriter->writeAttribute( - 'xmlns:w', - 'http://schemas.openxmlformats.org/wordprocessingml/2006/main' - ); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + // Write default styles $styles = Style::getStyles(); $this->writeDefaultStyles($xmlWriter, $phpWord, $styles); - // Write other styles + + // Write styles if (count($styles) > 0) { foreach ($styles as $styleName => $style) { if ($styleName == 'Normal') { continue; } - if ($style instanceof Font) { + // Font style + if ($style instanceof Font) { $paragraphStyle = $style->getParagraphStyle(); $styleType = $style->getStyleType(); - $type = ($styleType == 'title') ? 'paragraph' : 'character'; - if (!is_null($paragraphStyle)) { $type = 'paragraph'; } $xmlWriter->startElement('w:style'); $xmlWriter->writeAttribute('w:type', $type); - if ($styleType == 'title') { $arrStyle = explode('_', $styleName); $styleId = 'Heading' . $arrStyle[1]; @@ -80,11 +76,9 @@ public function writeStyles(PhpWord $phpWord = null) $xmlWriter->writeAttribute('w:val', $styleLink); $xmlWriter->endElement(); } - $xmlWriter->startElement('w:name'); $xmlWriter->writeAttribute('w:val', $styleName); $xmlWriter->endElement(); - if (!is_null($paragraphStyle)) { // Point parent style to Normal $xmlWriter->startElement('w:basedOn'); @@ -94,19 +88,17 @@ public function writeStyles(PhpWord $phpWord = null) } $this->writeFontStyle($xmlWriter, $style); - $xmlWriter->endElement(); + // Paragraph style } elseif ($style instanceof Paragraph) { $xmlWriter->startElement('w:style'); $xmlWriter->writeAttribute('w:type', 'paragraph'); $xmlWriter->writeAttribute('w:customStyle', '1'); $xmlWriter->writeAttribute('w:styleId', $styleName); - $xmlWriter->startElement('w:name'); $xmlWriter->writeAttribute('w:val', $styleName); $xmlWriter->endElement(); - // Parent style $basedOn = $style->getBasedOn(); if (!is_null($basedOn)) { @@ -114,7 +106,6 @@ public function writeStyles(PhpWord $phpWord = null) $xmlWriter->writeAttribute('w:val', $basedOn); $xmlWriter->endElement(); } - // Next paragraph style $next = $style->getNext(); if (!is_null($next)) { @@ -126,22 +117,20 @@ public function writeStyles(PhpWord $phpWord = null) $this->writeParagraphStyle($xmlWriter, $style); $xmlWriter->endElement(); + // Table style } elseif ($style instanceof Table) { $xmlWriter->startElement('w:style'); $xmlWriter->writeAttribute('w:type', 'table'); $xmlWriter->writeAttribute('w:customStyle', '1'); $xmlWriter->writeAttribute('w:styleId', $styleName); - $xmlWriter->startElement('w:name'); $xmlWriter->writeAttribute('w:val', $styleName); $xmlWriter->endElement(); - $xmlWriter->startElement('w:uiPriority'); $xmlWriter->writeAttribute('w:val', '99'); $xmlWriter->endElement(); $this->writeTableStyle($xmlWriter, $style); - $xmlWriter->endElement(); // w:style } } @@ -149,7 +138,6 @@ public function writeStyles(PhpWord $phpWord = null) $xmlWriter->endElement(); // w:styles - // Return return $xmlWriter->getData(); } diff --git a/src/PhpWord/_staticDocParts/numbering.xml b/src/PhpWord/_staticDocParts/numbering.xml deleted file mode 100644 index ac229f400e..0000000000 --- a/src/PhpWord/_staticDocParts/numbering.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/tests/PhpWord/Tests/Writer/Word2007/DocumentTest.php b/tests/PhpWord/Tests/Writer/Word2007/DocumentTest.php index 8cceecb30a..fa11638e0a 100644 --- a/tests/PhpWord/Tests/Writer/Word2007/DocumentTest.php +++ b/tests/PhpWord/Tests/Writer/Word2007/DocumentTest.php @@ -99,12 +99,12 @@ public function testElementStyles() $objectSrc = __DIR__ . "/../../_files/documents/sheet.xls"; $phpWord = new PhpWord(); - $phpWord->addParagraphStyle('pStyle', array('align' => 'center')); - $phpWord->addFontStyle('fStyle', array('size' => '20')); - $phpWord->addTitleStyle(1, array('color' => '333333', 'bold' => true)); + $phpWord->addParagraphStyle('pStyle', array('align' => 'center')); // Style #1 + $phpWord->addFontStyle('fStyle', array('size' => '20')); // Style #2 + $phpWord->addTitleStyle(1, array('color' => '333333', 'bold' => true)); // Style #3 $fontStyle = new Font('text', array('align' => 'center')); $section = $phpWord->addSection(); - $section->addListItem('List Item', 0, null, null, 'pStyle'); + $section->addListItem('List Item', 0, null, null, 'pStyle'); // Style #4 $section->addObject($objectSrc, array('align' => 'center')); $section->addTOC($fontStyle); $section->addTitle('Title 1', 1); @@ -113,7 +113,7 @@ public function testElementStyles() // List item $element = $doc->getElement('/w:document/w:body/w:p[1]/w:pPr/w:numPr/w:numId'); - $this->assertEquals(3, $element->getAttribute('w:val')); + $this->assertEquals(4, $element->getAttribute('w:val')); // Object $element = $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:object/o:OLEObject');