diff --git a/CHANGELOG.md b/CHANGELOG.md index d5e043b5..72b93e7b 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`. +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. ### Features @@ -28,6 +28,7 @@ This release marked heavy refactorings on internal code structure with the creat - 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 +- Endnote: Ability to add endnotes - @ivanlanin ### Bugfixes @@ -59,6 +60,7 @@ This release marked heavy refactorings on internal code structure with the creat - General: Give `Abstract` prefix and `Interface` suffix for all abstract classes and interfaces as per [PHP-FIG recommendation](https://github.com/php-fig/fig-standards/blob/master/bylaws/002-psr-naming-conventions.md) - @ivanlanin GH-187 - Style: New `Style\AbstractStyle` abstract class - @ivanlanin GH-187 - Writer: New 'ODText\Base` class - @ivanlanin GH-187 +- General: Rename `Footnote` to `Footnotes` to reflect the nature of collection - @ivanlanin ## 0.9.1 - 27 Mar 2014 diff --git a/README.md b/README.md index 8470aa90..3a9a4615 100755 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ With PHPWord, you can create DOCX, ODT, or RTF documents dynamically using your * Insert and format table with customized properties for each rows (e.g. repeat as header row) and cells (e.g. background color, rowspan, colspan) * Insert list items as bulleted, numbered, or multilevel * Insert hyperlinks +* Insert footnotes and endnotes * Create document from templates * Use XSL 1.0 style sheets to transform main document part of OOXML template * ... and many more features on progress diff --git a/docs/elements.rst b/docs/elements.rst index 428db6e2..62ee007f 100644 --- a/docs/elements.rst +++ b/docs/elements.rst @@ -37,7 +37,9 @@ the containers while the rows lists the elements. +-----+---------------+---------+--------+--------+------+----------+----------+ | 14 | Footnote | v | \- | \- | v\*\*| v\*\* | \- | +-----+---------------+---------+--------+--------+------+----------+----------+ -| 15 | CheckBox | v | v | v | v | ? | ? | +| 15 | Endnote | v | \- | \- | v\*\*| v\*\* | \- | ++-----+---------------+---------+--------+--------+------+----------+----------+ +| 16 | CheckBox | v | v | v | v | ? | ? | +-----+---------------+---------+--------+--------+------+----------+----------+ Legend: @@ -376,12 +378,13 @@ Options for ``$tocStyle``: twips. - ``indent`` The indent factor of the titles in twips. -Footnotes ---------- +Footnotes & endnotes +-------------------- -You can create footnotes in texts or textruns, but it's recommended to -use textrun to have better layout. You can use ``addText``, ``addLink``, -and ``addTextBreak`` on a footnote. +You can create footnotes with ``addFootnote`` and endnotes with ``addEndnote`` +in texts or textruns, but it's recommended to use textrun to have better layout. +You can use ``addText``, ``addLink``, ``addTextBreak``, ``addImage``, +``addObject`` on footnotes and endnotes. On textrun: @@ -396,6 +399,8 @@ On textrun: $footnote->addTextBreak(); $footnote->addText('And text break.'); $textrun->addText('Trailing text.'); + $endnote = $textrun->addEndnote(); + $endnote->addText('Endnote put at the end'); On text: diff --git a/docs/intro.rst b/docs/intro.rst index a30dc553..0f1f3d74 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -48,6 +48,7 @@ Features rowspan, colspan) - Insert list items as bulleted, numbered, or multilevel - Insert hyperlinks +- Insert footnotes and endnotes - Create document from templates - Use XSL 1.0 style sheets to transform main document part of OOXML template @@ -90,8 +91,6 @@ Writers + +-----------------------+--------+-------+-------+ | | Image | ✓ | | | + +-----------------------+--------+-------+-------+ -| | MemoryImage | ✓ | | | -+ +-----------------------+--------+-------+-------+ | | Object | ✓ | | | + +-----------------------+--------+-------+-------+ | | Watermark | ✓ | | | @@ -103,6 +102,8 @@ Writers | | Footer | ✓ | | | + +-----------------------+--------+-------+-------+ | | Footnote | ✓ | | | ++ +-----------------------+--------+-------+-------+ +| | Endnote | ✓ | | | +-------------------------+-----------------------+--------+-------+-------+ | **Graphs** | 2D basic graphs | | | | + +-----------------------+--------+-------+-------+ @@ -126,11 +127,11 @@ Readers +-------------------------------------------------+--------+-------+-------+ | Features | DOCX | ODT | RTF | +=========================+=======================+========+=======+=======+ -| **Document Properties** | Standard | | | | +| **Document Properties** | Standard | ✓ | | | + +-----------------------+--------+-------+-------+ -| | Extended | | | | +| | Extended | ✓ | | | + +-----------------------+--------+-------+-------+ -| | UserDefined | | | | +| | UserDefined | ✓ | | | +-------------------------+-----------------------+--------+-------+-------+ | **Element Type** | Text | ✓ | | | + +-----------------------+--------+-------+-------+ @@ -138,33 +139,33 @@ Readers + +-----------------------+--------+-------+-------+ | | Title | | | | + +-----------------------+--------+-------+-------+ -| | Link | | | | +| | Link | ✓ | | | + +-----------------------+--------+-------+-------+ -| | Preserve Text | | | | +| | Preserve Text | ✓ | | | + +-----------------------+--------+-------+-------+ | | Text Break | ✓ | | | + +-----------------------+--------+-------+-------+ -| | Page Break | | | | +| | Page Break | ✓ | | | + +-----------------------+--------+-------+-------+ | | List | | | | + +-----------------------+--------+-------+-------+ -| | Table | | | | +| | Table | ✓ | | | + +-----------------------+--------+-------+-------+ | | Image | | | | + +-----------------------+--------+-------+-------+ -| | MemoryImage | | | | -+ +-----------------------+--------+-------+-------+ | | Object | | | | + +-----------------------+--------+-------+-------+ | | Watermark | | | | + +-----------------------+--------+-------+-------+ | | Table of Contents | | | | + +-----------------------+--------+-------+-------+ -| | Header | | | | +| | Header | ✓ | | | + +-----------------------+--------+-------+-------+ -| | Footer | | | | +| | Footer | ✓ | | | + +-----------------------+--------+-------+-------+ -| | Footnote | | | | +| | Footnote | ✓ | | | ++ +-----------------------+--------+-------+-------+ +| | Endnote | ✓ | | | +-------------------------+-----------------------+--------+-------+-------+ | **Graphs** | 2D basic graphs | | | | + +-----------------------+--------+-------+-------+ diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 8ee75b90..56505cbc 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -13,7 +13,8 @@ use PhpOffice\PhpWord\Exception\InvalidObjectException; use PhpOffice\PhpWord\Media; use PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\TOC; -use PhpOffice\PhpWord\Footnote as FootnoteCollection; +use PhpOffice\PhpWord\Footnotes; +use PhpOffice\PhpWord\Endnotes; use PhpOffice\PhpWord\Shared\String; use PhpOffice\PhpWord\Element\Element; use PhpOffice\PhpWord\Element\Text; @@ -27,6 +28,7 @@ use PhpOffice\PhpWord\Element\Table; use PhpOffice\PhpWord\Element\Image; use PhpOffice\PhpWord\Element\Object; use PhpOffice\PhpWord\Element\Footnote as FootnoteElement; +use PhpOffice\PhpWord\Element\Endnote; use PhpOffice\PhpWord\Element\CheckBox; /** @@ -37,7 +39,7 @@ use PhpOffice\PhpWord\Element\CheckBox; abstract class AbstractElement { /** - * Container type section|header|footer|cell|textrun|footnote + * Container type section|header|footer|cell|textrun|footnote|endnote * * @var string */ @@ -99,7 +101,7 @@ abstract class AbstractElement $this->checkValidity('text'); // Reset paragraph style for footnote and textrun. They have their own - if (in_array($this->container, array('footnote', 'textrun'))) { + if (in_array($this->container, array('textrun', 'footnote', 'endnote'))) { $paragraphStyle = null; } @@ -323,7 +325,7 @@ abstract class AbstractElement $this->checkValidity('footnote'); $footnote = new FootnoteElement($paragraphStyle); - $rId = FootnoteCollection::addFootnoteElement($footnote); + $rId = Footnotes::addElement($footnote); $footnote->setDocPart('footnote', $this->getDocPartId()); $footnote->setRelationId($rId); @@ -332,6 +334,26 @@ abstract class AbstractElement return $footnote; } + /** + * Add endnote element + * + * @param mixed $paragraphStyle + * @return Endnote + */ + public function addEndnote($paragraphStyle = null) + { + $this->checkValidity('endnote'); + + $endnote = new Endnote($paragraphStyle); + $rId = Endnotes::addElement($endnote); + + $endnote->setDocPart('endnote', $this->getDocPartId()); + $endnote->setRelationId($rId); + $this->elements[] = $endnote; + + return $endnote; + } + /** * Add a CheckBox Element * @@ -469,7 +491,7 @@ abstract class AbstractElement private function checkValidity($method) { // Valid containers for each element - $allContainers = array('section', 'header', 'footer', 'cell', 'textrun', 'footnote'); + $allContainers = array('section', 'header', 'footer', 'cell', 'textrun', 'footnote', 'endnote'); $validContainers = array( 'text' => $allContainers, 'link' => $allContainers, @@ -481,6 +503,7 @@ abstract class AbstractElement 'checkbox' => array('section', 'header', 'footer', 'cell'), 'table' => array('section', 'header', 'footer'), 'footnote' => array('section', 'textrun', 'cell'), + 'endnote' => array('section', 'textrun', 'cell'), 'preservetext' => array('header', 'footer', 'cell'), 'title' => array('section'), ); @@ -489,6 +512,7 @@ abstract class AbstractElement $validContainerInContainers = array( 'preservetext' => array(array('cell'), array('header', 'footer')), 'footnote' => array(array('cell', 'textrun'), array('section')), + 'endnote' => array(array('cell', 'textrun'), array('section')), ); // Check if a method is valid for current container diff --git a/src/PhpWord/Element/Endnote.php b/src/PhpWord/Element/Endnote.php new file mode 100644 index 00000000..084119c2 --- /dev/null +++ b/src/PhpWord/Element/Endnote.php @@ -0,0 +1,31 @@ +container = 'endnote'; + $this->paragraphStyle = $this->setStyle(new Paragraph(), $paragraphStyle); + } +} diff --git a/src/PhpWord/Element/Footnote.php b/src/PhpWord/Element/Footnote.php index d35dd548..4dcc1fd4 100644 --- a/src/PhpWord/Element/Footnote.php +++ b/src/PhpWord/Element/Footnote.php @@ -21,7 +21,7 @@ class Footnote extends AbstractElement * * @var string|Paragraph */ - private $paragraphStyle; + protected $paragraphStyle; /** * Create new instance diff --git a/src/PhpWord/Endnotes.php b/src/PhpWord/Endnotes.php new file mode 100644 index 00000000..86c923d3 --- /dev/null +++ b/src/PhpWord/Endnotes.php @@ -0,0 +1,98 @@ +getIsMemImage(); - $extension = $image->getImageExtension(); - $mediaData['imageExtension'] = $extension; - $mediaData['imageType'] = $image->getImageType(); - if ($isMemImage) { - $mediaData['isMemImage'] = true; - $mediaData['createFunction'] = $image->getImageCreateFunction(); - $mediaData['imageFunction'] = $image->getImageFunction(); - } - $target = "media/{$container}_image{$mediaTypeCount}.{$extension}"; - // Objects - } elseif ($mediaType == 'object') { - $file = "oleObject{$mediaTypeCount}.bin"; - $target = "embeddings/{$container}_oleObject{$mediaTypeCount}.bin"; - // Links - } elseif ($mediaType == 'link') { - $target = $source; + switch ($mediaType) { + // Images + case 'image': + if (is_null($image)) { + throw new Exception('Image object not assigned.'); + } + $isMemImage = $image->getIsMemImage(); + $extension = $image->getImageExtension(); + $mediaData['imageExtension'] = $extension; + $mediaData['imageType'] = $image->getImageType(); + if ($isMemImage) { + $mediaData['isMemImage'] = true; + $mediaData['createFunction'] = $image->getImageCreateFunction(); + $mediaData['imageFunction'] = $image->getImageFunction(); + } + $target = "media/{$container}_image{$mediaTypeCount}.{$extension}"; + break; + + // Objects + case 'object': + $target = "embeddings/{$container}_oleObject{$mediaTypeCount}.bin"; + break; + + // Links + case 'link': + $target = $source; + break; } $mediaData['source'] = $source; @@ -89,7 +94,7 @@ class Media /** * Get media elements count * - * @param string $container section|headerx|footerx|footnote + * @param string $container section|headerx|footerx|footnote|endnote * @param string $mediaType image|object|link * @return integer * @since 0.9.2 @@ -116,7 +121,7 @@ class Media /** * Get media elements * - * @param string $container section|headerx|footerx|footnote + * @param string $container section|headerx|footerx|footnote|endnote * @param string $mediaType image|object|link * @return array * @since 0.9.2 diff --git a/src/PhpWord/Reader/Word2007.php b/src/PhpWord/Reader/Word2007.php index f2687924..36c81c3b 100644 --- a/src/PhpWord/Reader/Word2007.php +++ b/src/PhpWord/Reader/Word2007.php @@ -12,8 +12,8 @@ namespace PhpOffice\PhpWord\Reader; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Footnote; +use PhpOffice\PhpWord\Endnotes; use PhpOffice\PhpWord\DocumentProperties; -use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\Shared\XMLReader; use PhpOffice\PhpWord\Element\Section; @@ -44,7 +44,7 @@ class Word2007 extends AbstractReader implements ReaderInterface * Loads PhpWord from file * * @param string $filename - * @return PhpWord|null + * @return PhpWord */ public function load($filename) { @@ -96,7 +96,8 @@ class Word2007 extends AbstractReader implements ReaderInterface break; case 'footnotes': - $this->readFootnotes($filename, $rel['target']); + case 'endnotes': + $this->readNotes($filename, $rel['target'], $rel['type']); break; } } @@ -147,14 +148,13 @@ class Word2007 extends AbstractReader implements ReaderInterface $nodes = $xmlReader->getElements('*'); if ($nodes->length > 0) { foreach ($nodes as $node) { - $nodeName = $node->nodeName; - if (!array_key_exists($nodeName, $mapping)) { + if (!array_key_exists($node->nodeName, $mapping)) { continue; } - $method = $mapping[$nodeName]; + $method = $mapping[$node->nodeName]; $value = $node->nodeValue == '' ? null : $node->nodeValue; - if (array_key_exists($nodeName, $callbacks)) { - $value = $callbacks[$nodeName]($value); + if (array_key_exists($node->nodeName, $callbacks)) { + $value = $callbacks[$node->nodeName]($value); } if (method_exists($docProps, $method)) { $docProps->$method($value); @@ -253,7 +253,7 @@ class Word2007 extends AbstractReader implements ReaderInterface if (is_null($name)) { $name = $xmlReader->getAttribute('w:val', $node, 'w:name'); } - $default = ($xmlReader->getAttribute('w:default', $node) == 1); + // $default = ($xmlReader->getAttribute('w:default', $node) == 1); switch ($type) { case 'paragraph': $pStyle = $this->readParagraphStyle($xmlReader, $node); @@ -321,14 +321,16 @@ class Word2007 extends AbstractReader implements ReaderInterface } /** - * Read footnotes.xml + * Read (footnotes|endnotes).xml * * @param string $filename * @param string $xmlFile */ - private function readFootnotes($filename, $xmlFile) + private function readNotes($filename, $xmlFile, $notesType = 'footnotes') { - $footnotes = Footnote::getElements(); + $notesType = ($notesType == 'endnotes') ? 'endnotes' : 'footnotes'; + $collectionClass = 'PhpOffice\\PhpWord\\' . ucfirst($notesType); + $collection = $collectionClass::getElements(); $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($filename, $xmlFile); @@ -339,14 +341,14 @@ class Word2007 extends AbstractReader implements ReaderInterface $type = $xmlReader->getAttribute('w:type', $node); // Avoid w:type "separator" and "continuationSeparator" - // Only look for without w:type attribute - if (is_null($type) && array_key_exists($id, $footnotes)) { - $footnote = $footnotes[$id]; + // Only look for or without w:type attribute + if (is_null($type) && array_key_exists($id, $collection)) { + $element = $collection[$id]; $pNodes = $xmlReader->getElements('w:p/*', $node); foreach ($pNodes as $pNode) { - $this->readRun($xmlReader, $pNode, $footnote, 'footnotes'); + $this->readRun($xmlReader, $pNode, $element, $notesType); } - Footnote::setElement($id, $footnote); + $collectionClass::setElement($id, $element); } } } @@ -445,6 +447,10 @@ class Word2007 extends AbstractReader implements ReaderInterface if ($xmlReader->elementExists('w:footnoteReference', $domNode)) { $parent->addFootnote(); + // Endnote + } elseif ($xmlReader->elementExists('w:endnoteReference', $domNode)) { + $parent->addEndnote(); + // Image } elseif ($xmlReader->elementExists('w:pict', $domNode)) { $rId = $xmlReader->getAttribute('r:id', $domNode, 'w:pict/v:shape/v:imagedata'); @@ -457,7 +463,7 @@ class Word2007 extends AbstractReader implements ReaderInterface // Object } elseif ($xmlReader->elementExists('w:object', $domNode)) { $rId = $xmlReader->getAttribute('r:id', $domNode, 'w:object/o:OLEObject'); - $rIdIcon = $xmlReader->getAttribute('r:id', $domNode, 'w:object/v:shape/v:imagedata'); + // $rIdIcon = $xmlReader->getAttribute('r:id', $domNode, 'w:object/v:shape/v:imagedata'); $target = $this->getMediaTarget($docPart, $rId); if (!is_null($target)) { $textContent = ""; @@ -489,7 +495,6 @@ class Word2007 extends AbstractReader implements ReaderInterface $table = $parent->addTable($tblStyle); $tblNodes = $xmlReader->getElements('*', $domNode); foreach ($tblNodes as $tblNode) { - $tblNodeName = $tblNode->nodeName; if ($tblNode->nodeName == 'w:tblGrid') { // Column // @todo Do something with table columns @@ -745,7 +750,7 @@ class Word2007 extends AbstractReader implements ReaderInterface if (!array_key_exists($node->nodeName, $mapping)) { continue; } - $property = $mapping[$node->nodeName]; + // $property = $mapping[$node->nodeName]; switch ($node->nodeName) { case 'w:tblCellMar': foreach ($margins as $side) { diff --git a/src/PhpWord/Shared/XMLReader.php b/src/PhpWord/Shared/XMLReader.php index 0f6ce3f0..e5b5facc 100644 --- a/src/PhpWord/Shared/XMLReader.php +++ b/src/PhpWord/Shared/XMLReader.php @@ -105,7 +105,7 @@ class XMLReader * @param string $path * @return string|null */ - public function getAttribute($attribute, \DOMElement $contextNode, $path = null) + public function getAttribute($attribute, \DOMNode $contextNode, $path = null) { if (is_null($path)) { $return = $contextNode->getAttribute($attribute); @@ -154,7 +154,7 @@ class XMLReader * Element exists * * @param string $path - * @return \DOMNodeList + * @return boolean */ public function elementExists($path, \DOMNode $contextNode) { diff --git a/src/PhpWord/Style.php b/src/PhpWord/Style.php index 3c2eef09..5afbb91b 100755 --- a/src/PhpWord/Style.php +++ b/src/PhpWord/Style.php @@ -109,7 +109,7 @@ class Style /** * Get all styles * - * @return Font[] + * @return array */ public static function getStyles() { diff --git a/src/PhpWord/Style/Image.php b/src/PhpWord/Style/Image.php index 49060607..f21e6674 100644 --- a/src/PhpWord/Style/Image.php +++ b/src/PhpWord/Style/Image.php @@ -192,7 +192,6 @@ class Image extends AbstractStyle break; default: throw new \InvalidArgumentException('Wrapping style does not exists'); - break; } return $this; } diff --git a/src/PhpWord/Writer/ODText.php b/src/PhpWord/Writer/ODText.php index 4a4b1cbb..50064708 100755 --- a/src/PhpWord/Writer/ODText.php +++ b/src/PhpWord/Writer/ODText.php @@ -48,6 +48,7 @@ class ODText extends AbstractWriter implements WriterInterface * * @param string $filename * @throws Exception + * @todo Not in \ZipArchive::CM_STORE mode */ public function save($filename = null) { @@ -56,20 +57,11 @@ class ODText extends AbstractWriter implements WriterInterface $objZip = $this->getZipArchive($filename); // Add mimetype to ZIP file - //@todo Not in \ZipArchive::CM_STORE mode - $objZip->addFromString('mimetype', $this->getWriterPart('mimetype')->writeMimetype($this->phpWord)); - - // Add content.xml to ZIP file + $objZip->addFromString('mimetype', $this->getWriterPart('mimetype')->writeMimetype()); $objZip->addFromString('content.xml', $this->getWriterPart('content')->writeContent($this->phpWord)); - - // Add meta.xml to ZIP file $objZip->addFromString('meta.xml', $this->getWriterPart('meta')->writeMeta($this->phpWord)); - - // Add styles.xml to ZIP file $objZip->addFromString('styles.xml', $this->getWriterPart('styles')->writeStyles($this->phpWord)); - - // Add META-INF/manifest.xml - $objZip->addFromString('META-INF/manifest.xml', $this->getWriterPart('manifest')->writeManifest($this->phpWord)); + $objZip->addFromString('META-INF/manifest.xml', $this->getWriterPart('manifest')->writeManifest()); // Close file if ($objZip->close() === false) { diff --git a/src/PhpWord/Writer/ODText/Manifest.php b/src/PhpWord/Writer/ODText/Manifest.php index b82a6041..220834c4 100755 --- a/src/PhpWord/Writer/ODText/Manifest.php +++ b/src/PhpWord/Writer/ODText/Manifest.php @@ -9,8 +9,6 @@ namespace PhpOffice\PhpWord\Writer\ODText; -use PhpOffice\PhpWord\PhpWord; - /** * ODText manifest part writer */ @@ -19,10 +17,9 @@ class Manifest extends AbstractWriterPart /** * Write Manifest file to XML format * - * @param PhpWord $phpWord * @return string XML Output */ - public function writeManifest(PhpWord $phpWord = null) + public function writeManifest() { // Create XML writer $xmlWriter = $this->getXmlWriter(); diff --git a/src/PhpWord/Writer/ODText/Mimetype.php b/src/PhpWord/Writer/ODText/Mimetype.php index b8bc6539..269c377f 100644 --- a/src/PhpWord/Writer/ODText/Mimetype.php +++ b/src/PhpWord/Writer/ODText/Mimetype.php @@ -9,8 +9,6 @@ namespace PhpOffice\PhpWord\Writer\ODText; -use PhpOffice\PhpWord\PhpWord; - /** * ODText mimetype part writer */ @@ -19,10 +17,9 @@ class Mimetype extends AbstractWriterPart /** * Write Mimetype to Text format * - * @param PhpWord $phpWord * @return string Text Output */ - public function writeMimetype(PhpWord $phpWord = null) + public function writeMimetype() { return 'application/vnd.oasis.opendocument.text'; } diff --git a/src/PhpWord/Writer/Word2007.php b/src/PhpWord/Writer/Word2007.php index c89f09ba..b44708a3 100755 --- a/src/PhpWord/Writer/Word2007.php +++ b/src/PhpWord/Writer/Word2007.php @@ -11,7 +11,6 @@ namespace PhpOffice\PhpWord\Writer; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\PhpWord; -use PhpOffice\PhpWord\Footnote; use PhpOffice\PhpWord\Media; use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Writer\Word2007\ContentTypes; @@ -19,7 +18,7 @@ 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\Footnotes; +use PhpOffice\PhpWord\Writer\Word2007\Notes; use PhpOffice\PhpWord\Writer\Word2007\Header; use PhpOffice\PhpWord\Writer\Word2007\Styles; @@ -60,7 +59,8 @@ class Word2007 extends AbstractWriter implements WriterInterface $this->writerParts['styles'] = new Styles(); $this->writerParts['header'] = new Header(); $this->writerParts['footer'] = new Footer(); - $this->writerParts['footnotes'] = new Footnotes(); + $this->writerParts['footnotes'] = new Notes(); + $this->writerParts['endnotes'] = new Notes(); foreach ($this->writerParts as $writer) { $writer->setParentWriter($this); } @@ -105,17 +105,8 @@ class Word2007 extends AbstractWriter implements WriterInterface $this->addHeaderFooterContent($section, $objZip, 'footer', $rId); } - // Add footnotes media files, relations, and contents - if (Footnote::countFootnoteElements() > 0) { - $footnoteMedia = Media::getElements('footnote'); - $this->addFilesToPackage($objZip, $footnoteMedia); - if (!empty($footnoteMedia)) { - $objZip->addFromString('word/_rels/footnotes.xml.rels', $this->getWriterPart('rels')->writeMediaRels($footnoteMedia)); - } - $objZip->addFromString('word/footnotes.xml', $this->getWriterPart('footnotes')->writeFootnotes(Footnote::getFootnoteElements())); - $this->cTypes['override']["/word/footnotes.xml"] = 'footnotes'; - $this->docRels[] = array('target' => 'footnotes.xml', 'type' => 'footnotes', 'rID' => ++$rId); - } + $this->addNotes($objZip, $rId, 'footnote'); + $this->addNotes($objZip, $rId, 'endnote'); // Write dynamic files $objZip->addFromString('[Content_Types].xml', $this->getWriterPart('contenttypes')->writeContentTypes($this->cTypes)); @@ -199,7 +190,8 @@ class Word2007 extends AbstractWriter implements WriterInterface if (!empty($media)) { $this->addFilesToPackage($objZip, $media); } - $objZip->addFromString("word/_rels/{$file}.xml.rels", $this->getWriterPart('rels')->writeMediaRels($media)); + $relsFile = "word/_rels/{$file}.xml.rels"; + $objZip->addFromString($relsFile, $this->getWriterPart('rels')->writeMediaRels($media)); } } } @@ -227,4 +219,34 @@ class Word2007 extends AbstractWriter implements WriterInterface $this->docRels[] = array('target' => $elmFile, 'type' => $elmType, 'rID' => $rId); } } + + /** + * Add footnotes/endnotes + * + * @param mixed $objZip + * @param string $elmType + * @param integer $rId + */ + private function addNotes($objZip, &$rId, $notesType = 'footnote') + { + $notesType = ($notesType == 'endnote') ? 'endnote' : 'footnote'; + $notesTypes = "{$notesType}s"; + $collection = 'PhpOffice\\PhpWord\\' . ucfirst($notesTypes); + $xmlFile = "{$notesTypes}.xml"; + $relsFile = "word/_rels/{$xmlFile}.rels"; + $xmlPath = "word/{$xmlFile}"; + + // Add footnotes media files, relations, and contents + if ($collection::countElements() > 0) { + $media = Media::getElements($notesType); + $elements = $collection::getElements(); + $this->addFilesToPackage($objZip, $media); + if (!empty($media)) { + $objZip->addFromString($relsFile, $this->getWriterPart('rels')->writeMediaRels($media)); + } + $objZip->addFromString($xmlPath, $this->getWriterPart($notesTypes)->writeNotes($elements, $notesTypes)); + $this->cTypes['override']["/{$xmlPath}"] = $notesTypes; + $this->docRels[] = array('target' => $xmlFile, 'type' => $notesTypes, 'rID' => ++$rId); + } + } } diff --git a/src/PhpWord/Writer/Word2007/Base.php b/src/PhpWord/Writer/Word2007/Base.php index 12291694..4fd38b57 100644 --- a/src/PhpWord/Writer/Word2007/Base.php +++ b/src/PhpWord/Writer/Word2007/Base.php @@ -23,6 +23,7 @@ use PhpOffice\PhpWord\Element\Table; use PhpOffice\PhpWord\Element\Image; use PhpOffice\PhpWord\Element\Object; use PhpOffice\PhpWord\Element\Footnote; +use PhpOffice\PhpWord\Element\Endnote; use PhpOffice\PhpWord\Element\CheckBox; use PhpOffice\PhpWord\Shared\String; use PhpOffice\PhpWord\Shared\XMLWriter; @@ -623,6 +624,33 @@ class Base extends AbstractWriterPart } } + /** + * Write endnote element which links to the actual content in endnotes.xml + * + * @param XMLWriter $xmlWriter + * @param Endnote $endnote + * @param boolean $withoutP + */ + protected function writeEndnote(XMLWriter $xmlWriter, Endnote $endnote, $withoutP = false) + { + if (!$withoutP) { + $xmlWriter->startElement('w:p'); + } + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rStyle'); + $xmlWriter->writeAttribute('w:val', 'EndnoteReference'); + $xmlWriter->endElement(); // w:rStyle + $xmlWriter->endElement(); // w:rPr + $xmlWriter->startElement('w:endnoteReference'); + $xmlWriter->writeAttribute('w:id', $endnote->getRelationId()); + $xmlWriter->endElement(); // w:endnoteReference + $xmlWriter->endElement(); // w:r + if (!$withoutP) { + $xmlWriter->endElement(); // w:p + } + } + /** * Write CheckBox * @@ -1132,9 +1160,10 @@ class Base extends AbstractWriterPart 'Section' => array_merge($elmMainCell, array('Table', 'Footnote', 'Title', 'PageBreak', 'TOC')), 'Header' => array_merge($elmMainCell, array('Table', 'PreserveText')), 'Footer' => array_merge($elmMainCell, array('Table', 'PreserveText')), - 'Cell' => array_merge($elmMainCell, array('PreserveText', 'Footnote')), - 'TextRun' => array_merge($elmCommon, array('Footnote')), + 'Cell' => array_merge($elmMainCell, array('PreserveText', 'Footnote', 'Endnote')), + 'TextRun' => array_merge($elmCommon, array('Footnote', 'Endnote')), 'Footnote' => $elmCommon, + 'Endnote' => $elmCommon, ); $containerName = get_class($container); $containerName = substr($containerName, strrpos($containerName, '\\') + 1); @@ -1158,10 +1187,14 @@ class Base extends AbstractWriterPart $method = "writeWatermark"; } } - if (in_array($containerName, array('TextRun', 'Footnote'))) { - $this->$method($xmlWriter, $element, true); - } else { - $this->$method($xmlWriter, $element); + switch ($containerName) { + case 'TextRun': + case 'Footnote': + case 'Endnote': + $this->$method($xmlWriter, $element, true); + break; + default: + $this->$method($xmlWriter, $element); } } } diff --git a/src/PhpWord/Writer/Word2007/Footnotes.php b/src/PhpWord/Writer/Word2007/Notes.php similarity index 65% rename from src/PhpWord/Writer/Word2007/Footnotes.php rename to src/PhpWord/Writer/Word2007/Notes.php index ac85929d..96393b4c 100644 --- a/src/PhpWord/Writer/Word2007/Footnotes.php +++ b/src/PhpWord/Writer/Word2007/Notes.php @@ -10,27 +10,30 @@ namespace PhpOffice\PhpWord\Writer\Word2007; use PhpOffice\PhpWord\Element\Footnote; +use PhpOffice\PhpWord\Element\Endnote; use PhpOffice\PhpWord\Shared\XMLWriter; /** * Word2007 footnotes part writer */ -class Footnotes extends Base +class Notes extends Base { /** - * Write word/footnotes.xml + * Write word/(footnotes|endnotes).xml * - * @param array $allFootnotesCollection + * @param array $elements + * @param string $notesTypes */ - public function writeFootnotes($allFootnotesCollection) + public function writeNotes($elements, $notesTypes = 'footnotes') { - // Create XML writer + $isFootnote = $notesTypes == 'footnotes'; + $rootNode = $isFootnote ? 'w:footnotes' : 'w:endnotes'; + $elementNode = $isFootnote ? 'w:footnote' : 'w:endnote'; $xmlWriter = $this->getXmlWriter(); // XML header $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); - $xmlWriter->startElement('w:footnotes'); - + $xmlWriter->startElement($rootNode); $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'); @@ -42,7 +45,7 @@ class Footnotes extends Base $xmlWriter->writeAttribute('xmlns:wne', 'http://schemas.microsoft.com/office/word/2006/wordml'); // Separator and continuation separator - $xmlWriter->startElement('w:footnote'); + $xmlWriter->startElement($elementNode); $xmlWriter->writeAttribute('w:id', -1); $xmlWriter->writeAttribute('w:type', 'separator'); $xmlWriter->startElement('w:p'); @@ -51,8 +54,8 @@ class Footnotes extends Base $xmlWriter->endElement(); // w:separator $xmlWriter->endElement(); // w:r $xmlWriter->endElement(); // w:p - $xmlWriter->endElement(); // w:footnote - $xmlWriter->startElement('w:footnote'); + $xmlWriter->endElement(); // $elementNode + $xmlWriter->startElement($elementNode); $xmlWriter->writeAttribute('w:id', 0); $xmlWriter->writeAttribute('w:type', 'continuationSeparator'); $xmlWriter->startElement('w:p'); @@ -61,42 +64,53 @@ class Footnotes extends Base $xmlWriter->endElement(); // w:continuationSeparator $xmlWriter->endElement(); // w:r $xmlWriter->endElement(); // w:p - $xmlWriter->endElement(); // w:footnote + $xmlWriter->endElement(); // $elementNode + // Content - foreach ($allFootnotesCollection as $footnote) { - if ($footnote instanceof Footnote) { - $this->writeFootnote($xmlWriter, $footnote); + foreach ($elements as $element) { + if ($element instanceof Footnote || $element instanceof Endnote) { + $this->writeNote($xmlWriter, $element, null, $notesTypes); } } + $xmlWriter->endElement(); return $xmlWriter->getData(); } /** - * Write footnote content, overrides method in parent class + * Write note item * * @param XMLWriter $xmlWriter - * @param Footnote $footnote + * @param Footnote|Endnote $element * @param boolean $withoutP + * @param string $notesTypes */ - protected function writeFootnote(XMLWriter $xmlWriter, Footnote $footnote, $withoutP = false) + protected function writeNote(XMLWriter $xmlWriter, $element, $withoutP = false, $notesTypes = 'footnotes') { - $xmlWriter->startElement('w:footnote'); - $xmlWriter->writeAttribute('w:id', $footnote->getRelationId()); + $isFootnote = ($notesTypes == 'footnotes'); + $elementNode = $isFootnote ? 'w:footnote' : 'w:endnote'; + $refNode = $isFootnote ? 'w:footnoteRef' : 'w:endnoteRef'; + $styleName = $isFootnote ? 'FootnoteReference' : 'EndnoteReference'; + + $xmlWriter->startElement($elementNode); + $xmlWriter->writeAttribute('w:id', $element->getRelationId()); $xmlWriter->startElement('w:p'); + // Paragraph style - $styleParagraph = $footnote->getParagraphStyle(); + $styleParagraph = $element->getParagraphStyle(); $this->writeInlineParagraphStyle($xmlWriter, $styleParagraph); + // Reference symbol $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:rPr'); $xmlWriter->startElement('w:rStyle'); - $xmlWriter->writeAttribute('w:val', 'FootnoteReference'); + $xmlWriter->writeAttribute('w:val', $styleName); $xmlWriter->endElement(); // w:rStyle $xmlWriter->endElement(); // w:rPr - $xmlWriter->writeElement('w:footnoteRef'); + $xmlWriter->writeElement($refNode); $xmlWriter->endElement(); // w:r + // Empty space after refence symbol $xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:t'); @@ -105,9 +119,9 @@ class Footnotes extends Base $xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:r - $this->writeContainerElements($xmlWriter, $footnote); + $this->writeContainerElements($xmlWriter, $element); $xmlWriter->endElement(); // w:p - $xmlWriter->endElement(); // w:footnote + $xmlWriter->endElement(); // $elementNode } } diff --git a/tests/PhpWord/Tests/EndnotesTest.php b/tests/PhpWord/Tests/EndnotesTest.php new file mode 100644 index 00000000..a72e85f9 --- /dev/null +++ b/tests/PhpWord/Tests/EndnotesTest.php @@ -0,0 +1,39 @@ +assertEquals(1, $rId); + $this->assertEquals(1, count(Endnotes::getElements())); + $this->assertEquals($endnote2, Endnotes::getElement(1)); + $this->assertNull(Endnotes::getElement(2)); + + Endnotes::resetElements(); + $this->assertEquals(0, Endnotes::countElements()); + } +} diff --git a/tests/PhpWord/Tests/FootnoteTest.php b/tests/PhpWord/Tests/FootnoteTest.php deleted file mode 100644 index 6dcda73f..00000000 --- a/tests/PhpWord/Tests/FootnoteTest.php +++ /dev/null @@ -1,39 +0,0 @@ -assertEquals(1, $rId); - $this->assertEquals(1, count(Footnote::getElements())); - $this->assertEquals($footnote2, Footnote::getElement(1)); - $this->assertNull(Footnote::getElement(2)); - - Footnote::resetElements(); - $this->assertEquals(0, Footnote::countElements()); - } -} diff --git a/tests/PhpWord/Tests/FootnotesTest.php b/tests/PhpWord/Tests/FootnotesTest.php new file mode 100644 index 00000000..869c69a7 --- /dev/null +++ b/tests/PhpWord/Tests/FootnotesTest.php @@ -0,0 +1,39 @@ +assertEquals(1, $rId); + $this->assertEquals(1, count(Footnotes::getElements())); + $this->assertEquals($footnote2, Footnotes::getElement(1)); + $this->assertNull(Footnotes::getElement(2)); + + Footnotes::resetElements(); + $this->assertEquals(0, Footnotes::countElements()); + } +} diff --git a/tests/PhpWord/Tests/Writer/Word2007/FootnotesTest.php b/tests/PhpWord/Tests/Writer/Word2007/NotesTest.php similarity index 86% rename from tests/PhpWord/Tests/Writer/Word2007/FootnotesTest.php rename to tests/PhpWord/Tests/Writer/Word2007/NotesTest.php index 11817962..f883314a 100644 --- a/tests/PhpWord/Tests/Writer/Word2007/FootnotesTest.php +++ b/tests/PhpWord/Tests/Writer/Word2007/NotesTest.php @@ -12,12 +12,11 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Tests\TestHelperDOCX; /** - * Test class for PhpOffice\PhpWord\Writer\Word2007\Footnotes + * Test class for PhpOffice\PhpWord\Writer\Word2007\Notes * - * @coversDefaultClass \PhpOffice\PhpWord\Writer\Word2007\Footnotes * @runTestsInSeparateProcesses */ -class FootnotesTest extends \PHPUnit_Framework_TestCase +class NotesTest extends \PHPUnit_Framework_TestCase { /** * Executed before each method of the class diff --git a/tests/PhpWord/Tests/Writer/Word2007Test.php b/tests/PhpWord/Tests/Writer/Word2007Test.php index 38c2e6e7..5e990615 100644 --- a/tests/PhpWord/Tests/Writer/Word2007Test.php +++ b/tests/PhpWord/Tests/Writer/Word2007Test.php @@ -35,18 +35,19 @@ class Word2007Test extends \PHPUnit_Framework_TestCase $object = new Word2007(new PhpWord()); $writerParts = array( - 'ContentTypes', - 'Rels', - 'DocProps', - 'Document', - 'Styles', - 'Header', - 'Footer', - 'Footnotes', + 'ContentTypes' => 'ContentTypes', + 'Rels' => 'Rels', + 'DocProps' => 'DocProps', + 'Document' => 'Document', + 'Styles' => 'Styles', + 'Header' => 'Header', + 'Footer' => 'Footer', + 'Footnotes' => 'Notes', + 'Endnotes' => 'Notes', ); - foreach ($writerParts as $part) { + foreach ($writerParts as $part => $type) { $this->assertInstanceOf( - "PhpOffice\\PhpWord\\Writer\\Word2007\\{$part}", + "PhpOffice\\PhpWord\\Writer\\Word2007\\{$type}", $object->getWriterPart($part) ); $this->assertInstanceOf(