diff --git a/CHANGELOG.md b/CHANGELOG.md index 548ef026..4b1ea897 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This is the changelog between releases of PHPWord. Releases are listed in revers - Element: New `CheckBox` element for sections and table cells - @ozilion GH-156 - Settings: Ability to use PCLZip as alternative to ZipArchive - @bskrtich @ivanlanin GH-106 GH-140 GH-185 - Template: Ability to find & replace variables in headers & footers - @dgudgeon GH-190 +- Template: Ability to clone & delete block of text using `cloneBlock` and `deleteBlock` - @diego-vieira GH-191 +- TOC: Ability to have two or more TOC in one document and to set min and max depth for TOC - @Pyreweb GH-189 - Table: Ability to add footnote in table cell - @ivanlanin GH-187 - Footnote: Ability to add image in footnote - @ivanlanin GH-187 - ListItem: Ability to add list item in header/footer - @ivanlanin GH-187 diff --git a/docs/elements.rst b/docs/elements.rst index abe3f7ba..428db6e2 100644 --- a/docs/elements.rst +++ b/docs/elements.rst @@ -361,7 +361,14 @@ Your TOC can only be generated if you have add at least one title (See .. code-block:: php - $section->addTOC([$fontStyle], [$tocStyle]); + $section->addTOC([$fontStyle], [$tocStyle], [$minDepth], [$maxDepth]); + +- ``$fontStyle``: See font style section +- ``$tocStyle``: See available options below +- ``$minDepth``: Minimum depth of header to be shown. Default 1 +- ``$maxDepth``: Maximum depth of header to be shown. Default 9 + +Options for ``$tocStyle``: - ``tabLeader`` Fill type between the title text and the page number. Use the defined constants in PHPWord\_Style\_TOC. diff --git a/docs/templates.rst b/docs/templates.rst index 6b627b06..ec9c7126 100644 --- a/docs/templates.rst +++ b/docs/templates.rst @@ -19,6 +19,8 @@ Example: $template->setValue('Name', 'Somebody someone'); $template->setValue('Street', 'Coming-Undone-Street 32'); -See ``Sample_07_TemplateCloneRow.php`` for more code sample, including -how to create multirow from a single row in a template by using -``cloneRow``. +See ``Sample_07_TemplateCloneRow.php`` for example on how to create multirow +from a single row in a template by using ``cloneRow``. + +See ``Sample_23_TemplateBlock.php`` for example on how to clone a block of +text using ``cloneBlock`` and delete a block of text using ``deleteBlock``. diff --git a/samples/Sample_17_TitleTOC.php b/samples/Sample_17_TitleTOC.php index 951632af..87c379b2 100644 --- a/samples/Sample_17_TitleTOC.php +++ b/samples/Sample_17_TitleTOC.php @@ -9,18 +9,34 @@ $phpWord = new \PhpOffice\PhpWord\PhpWord(); $section = $phpWord->addSection(); // Define the TOC font style -$fontStyle = array('spaceAfter'=>60, 'size'=>12); +$fontStyle = array('spaceAfter' => 60, 'size' => 12); +$fontStyle2 = array('size' => 10); // Add title styles -$phpWord->addTitleStyle(1, array('size'=>20, 'color'=>'333333', 'bold'=>true)); -$phpWord->addTitleStyle(2, array('size'=>16, 'color'=>'666666')); +$phpWord->addTitleStyle(1, array('size' => 20, 'color' => '333333', 'bold' => true)); +$phpWord->addTitleStyle(2, array('size' => 16, 'color' => '666666')); +$phpWord->addTitleStyle(3, array('size' => 14, 'italic' => true)); +$phpWord->addTitleStyle(4, array('size' => 12)); // Add text elements -$section->addText('Table of contents:'); +$section->addText('Table of contents 1'); $section->addTextBreak(2); -// Add TOC -$section->addTOC($fontStyle); +// Add TOC #1 +$toc = $section->addTOC($fontStyle); +$section->addTextBreak(2); + +// Filler +$section->addText('Text between TOC'); +$section->addTextBreak(2); + +// Add TOC #1 +$section->addText('Table of contents 2'); +$section->addTextBreak(2); +$toc2 = $section->addTOC($fontStyle2); +$toc2->setMinDepth(2); +$toc2->setMaxDepth(3); + // Add Titles $section->addPageBreak(); @@ -41,6 +57,14 @@ $section->addText('And more text...'); $section->addTextBreak(2); $section->addTitle('I am a Subtitle of Title 3', 2); $section->addText('Again and again, more text...'); +$section->addTitle('Subtitle 3.1.1', 3); +$section->addText('Text'); +$section->addTitle('Subtitle 3.1.1.1', 4); +$section->addText('Text'); +$section->addTitle('Subtitle 3.1.1.2', 4); +$section->addText('Text'); +$section->addTitle('Subtitle 3.1.2', 3); +$section->addText('Text'); echo date('H:i:s'), " Note: Please refresh TOC manually.", \EOL; // End code diff --git a/samples/Sample_23_TemplateBlock.php b/samples/Sample_23_TemplateBlock.php new file mode 100644 index 00000000..168070e0 --- /dev/null +++ b/samples/Sample_23_TemplateBlock.php @@ -0,0 +1,21 @@ +loadTemplate('resources/Sample_23_TemplateBlock.docx'); + +// Will clone everything between ${tag} and ${/tag}, the number of times. By default, 1. +$document->cloneBlock('CLONEME', 3); + +// Everything between ${tag} and ${/tag}, will be deleted/erased. +$document->deleteBlock('DELETEME'); + +$name = 'Sample_23_TemplateBlock.docx'; +echo date('H:i:s'), " Write to Word2007 format", EOL; +$document->saveAs($name); +rename($name, "results/{$name}"); + +include_once 'Sample_Footer.php'; diff --git a/samples/resources/Sample_23_TemplateBlock.docx b/samples/resources/Sample_23_TemplateBlock.docx new file mode 100644 index 00000000..049d5ca4 Binary files /dev/null and b/samples/resources/Sample_23_TemplateBlock.docx differ diff --git a/src/PhpWord/Container/Section.php b/src/PhpWord/Container/Section.php index 46f2d44e..01e7fafe 100644 --- a/src/PhpWord/Container/Section.php +++ b/src/PhpWord/Container/Section.php @@ -99,9 +99,9 @@ class Section extends Container * @param mixed $styleTOC * @return TOC */ - public function addTOC($styleFont = null, $styleTOC = null) + public function addTOC($styleFont = null, $styleTOC = null, $minDepth = 1, $maxDepth = 9) { - $toc = new TOC($styleFont, $styleTOC); + $toc = new TOC($styleFont, $styleTOC, $minDepth, $maxDepth); $this->elements[] = $toc; return $toc; } diff --git a/src/PhpWord/Element/Image.php b/src/PhpWord/Element/Image.php index 1824cec1..ec82665e 100755 --- a/src/PhpWord/Element/Image.php +++ b/src/PhpWord/Element/Image.php @@ -113,7 +113,7 @@ class Image extends Element \IMAGETYPE_PNG, \IMAGETYPE_BMP, \IMAGETYPE_TIFF_II, \IMAGETYPE_TIFF_MM ); - if (!\file_exists($source)) { + if (!file_exists($source)) { throw new InvalidImageException; } $imgData = getimagesize($source); diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index 420b9d7e..447706af 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -233,7 +233,7 @@ class PhpWord */ public function loadTemplate($filename) { - if (\file_exists($filename)) { + if (file_exists($filename)) { return new Template($filename); } else { throw new Exception("Template file {$filename} not found."); diff --git a/src/PhpWord/Reader/Reader.php b/src/PhpWord/Reader/Reader.php index e50ca7b5..e93b4636 100644 --- a/src/PhpWord/Reader/Reader.php +++ b/src/PhpWord/Reader/Reader.php @@ -65,7 +65,7 @@ abstract class Reader implements IReader protected function openFile($pFilename) { // Check if file exists - if (!\file_exists($pFilename) || !is_readable($pFilename)) { + if (!file_exists($pFilename) || !is_readable($pFilename)) { throw new Exception("Could not open " . $pFilename . " for reading! File does not exist."); } diff --git a/src/PhpWord/Reader/Word2007.php b/src/PhpWord/Reader/Word2007.php index f8add95a..2cdb7f9a 100644 --- a/src/PhpWord/Reader/Word2007.php +++ b/src/PhpWord/Reader/Word2007.php @@ -29,7 +29,7 @@ class Word2007 extends Reader implements IReader public function canRead($pFilename) { // Check if file exists - if (!\file_exists($pFilename)) { + if (!file_exists($pFilename)) { throw new Exception("Could not open {$pFilename} for reading! File does not exist."); } diff --git a/src/PhpWord/TOC.php b/src/PhpWord/TOC.php index 0dc7c874..a992cd55 100644 --- a/src/PhpWord/TOC.php +++ b/src/PhpWord/TOC.php @@ -53,13 +53,28 @@ class TOC private static $bookmarkId = 0; + /** + * Min title depth to show + * + * @var int + */ + private $minDepth = 1; + + /** + * Max title depth to show + * + * @var int + */ + private $maxDepth = 9; + + /** * Create a new Table-of-Contents Element * * @param mixed $styleFont * @param array $styleTOC */ - public function __construct($styleFont = null, $styleTOC = null) + public function __construct($styleFont = null, $styleTOC = null, $minDepth = 1, $maxDepth = 9) { self::$TOCStyle = new TOCStyle(); @@ -85,6 +100,9 @@ class TOC self::$fontStyle = $styleFont; } } + + $this->minDepth = $minDepth; + $this->maxDepth = $maxDepth; } /** @@ -115,9 +133,20 @@ class TOC * * @return array */ - public static function getTitles() + public function getTitles() { - return self::$titles; + $titles = self::$titles; + foreach ($titles as $i => $title) { + if ($this->minDepth > $title['depth']) { + unset($titles[$i]); + } + if (($this->maxDepth != 0) && ($this->maxDepth < $title['depth'])) { + unset($titles[$i]); + } + } + $titles = array_merge(array(), $titles); + + return $titles; } /** @@ -147,4 +176,44 @@ class TOC { return self::$fontStyle; } + + /** + * Set max depth + * + * @param int $value + */ + public function setMaxDepth($value) + { + $this->maxDepth = $value; + } + + /** + * Get Max Depth + * + * @return int Max depth of titles + */ + public function getMaxDepth() + { + return $this->maxDepth; + } + + /** + * Set min depth + * + * @param int $value + */ + public function setMinDepth($value) + { + $this->minDepth = $value; + } + + /** + * Get Min Depth + * + * @return int Min depth of titles + */ + public function getMinDepth() + { + return $this->minDepth; + } } diff --git a/src/PhpWord/Template.php b/src/PhpWord/Template.php index 142228c3..30c2ee08 100644 --- a/src/PhpWord/Template.php +++ b/src/PhpWord/Template.php @@ -165,7 +165,7 @@ class Template * Clone a table row in a template document * * @param string $search - * @param int $numberOfClones + * @param integer $numberOfClones * @throws Exception */ public function cloneRow($search, $numberOfClones) @@ -216,6 +216,60 @@ class Template $this->documentXML = $result; } + /** + * Clone a block + * + * @param string $blockname + * @param integer $clones + * @param boolean $replace + * @return null + */ + public function cloneBlock($blockname, $clones = 1, $replace = true) + { + $xmlBlock = null; + preg_match('/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', $this->documentXML, $matches); + + if (isset($matches[3])) { + $xmlBlock = $matches[3]; + $cloned = array(); + for ($i = 1; $i <= $clones; $i++) { + $cloned[] = $xmlBlock; + } + + if ($replace) { + $this->documentXML = str_replace($matches[2] . $matches[3] . $matches[4], implode('', $cloned), $this->documentXML); + } + } + + return $xmlBlock; + } + + /** + * Replace a block + * + * @param string $blockname + * @param string $replacement + */ + public function replaceBlock($blockname, $replacement) + { + preg_match('/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', $this->documentXML, $matches); + + if (isset($matches[3])) { + $this->documentXML = str_replace($matches[2] . $matches[3] . $matches[4], $replacement, $this->documentXML); + } + } + + /** + * Delete a block of text + * + * @param string $blockname + * @param string $replacement + */ + public function deleteBlock($blockname) + { + $this->replaceBlock($blockname, ''); + } + /** * Save XML to temporary file * @@ -251,7 +305,7 @@ class Template { $tempFilename = $this->save(); - if (\file_exists($strFilename)) { + if (file_exists($strFilename)) { unlink($strFilename); } @@ -332,8 +386,8 @@ class Template /** * Find the start position of the nearest table row before $offset * - * @param int $offset - * @return int + * @param integer $offset + * @return integer * @throws Exception */ private function findRowStart($offset) @@ -351,8 +405,8 @@ class Template /** * Find the end position of the nearest table row after $offset * - * @param int $offset - * @return int + * @param integer $offset + * @return integer */ private function findRowEnd($offset) { @@ -363,8 +417,8 @@ class Template /** * Get a slice of a string * - * @param int $startPosition - * @param int $endPosition + * @param integer $startPosition + * @param integer $endPosition * @return string */ private function getSlice($startPosition, $endPosition = 0) @@ -374,4 +428,17 @@ class Template } 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 ba887f01..f7b49cfe 100755 --- a/src/PhpWord/Writer/Word2007.php +++ b/src/PhpWord/Writer/Word2007.php @@ -229,7 +229,7 @@ class Word2007 extends Writer implements IWriter /** * Add header/footer content * - * @param Section $section + * @param PhpOffice\PhpWord\Container\Section $section * @param string $elmType * @param integer $rID */ diff --git a/src/PhpWord/Writer/Word2007/Document.php b/src/PhpWord/Writer/Word2007/Document.php index 98f158fc..2b5e6e95 100644 --- a/src/PhpWord/Writer/Word2007/Document.php +++ b/src/PhpWord/Writer/Word2007/Document.php @@ -228,14 +228,17 @@ class Document extends Base */ protected function writeTOC(XMLWriter $xmlWriter, TOC $toc) { - $titles = TOC::getTitles(); - $styleFont = TOC::getStyleFont(); + $titles = $toc->getTitles(); + $styleFont = $toc->getStyleFont(); - $styleTOC = TOC::getStyleTOC(); + $styleTOC = $toc->getStyleTOC(); $fIndent = $styleTOC->getIndent(); $tabLeader = $styleTOC->getTabLeader(); $tabPos = $styleTOC->getTabPos(); + $maxDepth = $toc->getMaxDepth(); + $minDepth = $toc->getMinDepth(); + $isObject = ($styleFont instanceof Font) ? true : false; for ($i = 0; $i < count($titles); $i++) { @@ -287,7 +290,7 @@ class Document extends Base $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:instrText'); $xmlWriter->writeAttribute('xml:space', 'preserve'); - $xmlWriter->writeRaw('TOC \o "1-9" \h \z \u'); + $xmlWriter->writeRaw('TOC \o "' . $minDepth . '-' . $maxDepth . '" \h \z \u'); $xmlWriter->endElement(); $xmlWriter->endElement(); diff --git a/tests/PhpWord/Tests/TOCTest.php b/tests/PhpWord/Tests/TOCTest.php index 0a060eb8..a5f509c9 100644 --- a/tests/PhpWord/Tests/TOCTest.php +++ b/tests/PhpWord/Tests/TOCTest.php @@ -64,15 +64,16 @@ class TOCTest extends \PHPUnit_Framework_TestCase 'Heading 2' => 2, 'Heading 3' => 3, ); + $toc = new TOC(); foreach ($titles as $text => $depth) { - $response = TOC::addTitle($text, $depth); + $response = $toc->addTitle($text, $depth); } $this->assertEquals($anchor, $response[0]); $this->assertEquals($bookmark, $response[1]); $i = 0; - $savedTitles = TOC::getTitles(); + $savedTitles = $toc->getTitles(); foreach ($titles as $text => $depth) { $this->assertEquals($text, $savedTitles[$i]['text']); $this->assertEquals($depth, $savedTitles[$i]['depth']); @@ -83,4 +84,31 @@ class TOCTest extends \PHPUnit_Framework_TestCase $this->assertEquals(0, count(TOC::getTitles())); } + + /** + * Set/get minDepth and maxDepth + */ + public function testSetGetMinMaxDepth() + { + $toc = new TOC(); + $titles = array( + 'Heading 1' => 1, + 'Heading 2' => 2, + 'Heading 3' => 3, + 'Heading 4' => 4, + ); + foreach ($titles as $text => $depth) { + $toc->addTitle($text, $depth); + } + + $this->assertEquals(1, $toc->getMinDepth()); + $this->assertEquals(9, $toc->getMaxDepth()); + + $toc->setMinDepth(2); + $toc->setMaxDepth(3); + $toc->getTitles(); + + $this->assertEquals(2, $toc->getMinDepth()); + $this->assertEquals(3, $toc->getMaxDepth()); + } } diff --git a/tests/PhpWord/Tests/TemplateTest.php b/tests/PhpWord/Tests/TemplateTest.php index 6929c4fd..f99ce294 100644 --- a/tests/PhpWord/Tests/TemplateTest.php +++ b/tests/PhpWord/Tests/TemplateTest.php @@ -179,4 +179,27 @@ final class TemplateTest extends \PHPUnit_Framework_TestCase $this->assertTrue($docFound); } + + /** + * Clone and delete block + */ + public function testCloneDeleteBlock() + { + $template = __DIR__ . "/_files/templates/clone-delete-block.docx"; + $expectedVar = array('DELETEME', '/DELETEME', 'CLONEME', '/CLONEME'); + $docName = 'clone-delete-block-result.docx'; + + $document = new Template($template); + $actualVar = $document->getVariables(); + + $document->cloneBlock('CLONEME', 3); + $document->deleteBlock('DELETEME'); + + $document->saveAs($docName); + $docFound = file_exists($docName); + unlink($docName); + + $this->assertEquals($expectedVar, $actualVar); + $this->assertTrue($docFound); + } } diff --git a/tests/PhpWord/Tests/Writer/ODTextTest.php b/tests/PhpWord/Tests/Writer/ODTextTest.php index 743907be..e113c373 100644 --- a/tests/PhpWord/Tests/Writer/ODTextTest.php +++ b/tests/PhpWord/Tests/Writer/ODTextTest.php @@ -82,7 +82,7 @@ class ODTextTest extends \PHPUnit_Framework_TestCase $writer = new ODText($phpWord); $writer->save($file); - $this->assertTrue(\file_exists($file)); + $this->assertTrue(file_exists($file)); unlink($file); } diff --git a/tests/PhpWord/Tests/Writer/RTFTest.php b/tests/PhpWord/Tests/Writer/RTFTest.php index 1abeeefa..9cc7ed71 100644 --- a/tests/PhpWord/Tests/Writer/RTFTest.php +++ b/tests/PhpWord/Tests/Writer/RTFTest.php @@ -71,7 +71,7 @@ class RTFTest extends \PHPUnit_Framework_TestCase $writer = new RTF($phpWord); $writer->save($file); - $this->assertTrue(\file_exists($file)); + $this->assertTrue(file_exists($file)); unlink($file); } diff --git a/tests/PhpWord/Tests/_files/templates/clone-delete-block.docx b/tests/PhpWord/Tests/_files/templates/clone-delete-block.docx new file mode 100644 index 00000000..049d5ca4 Binary files /dev/null and b/tests/PhpWord/Tests/_files/templates/clone-delete-block.docx differ diff --git a/tests/PhpWord/Tests/_includes/TestHelperDOCX.php b/tests/PhpWord/Tests/_includes/TestHelperDOCX.php index 3e24c4cc..bea0d037 100644 --- a/tests/PhpWord/Tests/_includes/TestHelperDOCX.php +++ b/tests/PhpWord/Tests/_includes/TestHelperDOCX.php @@ -56,7 +56,7 @@ class TestHelperDOCX */ public static function clear() { - if (\file_exists(self::$file)) { + if (file_exists(self::$file)) { unlink(self::$file); } if (is_dir(sys_get_temp_dir() . '/PhpWord_Unit_Test/')) {