diff --git a/CHANGELOG.md b/CHANGELOG.md index 30be78e6..25689dbc 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,12 @@ This is the changelog between releases of PHPWord. Releases are listed in revers - Image: Get image dimensions without EXIF extension - @andrew-kzoo GH-184 - Table: Add tblGrid element for Libre/Open Office table sizing - @gianis6 GH-183 +- Footnote: Ability to insert textbreak in footnote `$footnote->addTextBreak()` - @ivanlanin +- Footnote: Ability to style footnote reference mark by using `FootnoteReference` style - @ivanlanin ### Bugfixes -- +- Footnote: Footnote content doesn't show footnote reference number - @ivanlanin GH-170 ### Miscellaneous diff --git a/docs/elements.rst b/docs/elements.rst index d0f78d2e..f7ce0319 100644 --- a/docs/elements.rst +++ b/docs/elements.rst @@ -326,7 +326,8 @@ Footnotes --------- You can create footnotes in texts or textruns, but it's recommended to -use textrun to have better layout. +use textrun to have better layout. You can use ``addText``, ``addLink``, +and ``addTextBreak`` on a footnote. On textrun: @@ -335,7 +336,11 @@ On textrun: $textrun = $section->createTextRun(); $textrun->addText('Lead text.'); $footnote = $textrun->createFootnote(); - $footnote->addText('Footnote text.'); + $footnote->addText('Footnote text can have '); + $footnote->addLink('http://test.com', 'links'); + $footnote->addText('.'); + $footnote->addTextBreak(); + $footnote->addText('And text break.'); $textrun->addText('Trailing text.'); On text: @@ -345,3 +350,8 @@ On text: $section->addText('Lead text.'); $footnote = $section->createFootnote(); $footnote->addText('Footnote text.'); + +The footnote reference number will be displayed with decimal number starting +from 1. This number use ``FooterReference`` style which you can redefine by +``addFontStyle`` method. Default value for this style is +``array('superScript' => true)``; diff --git a/samples/Sample_06_Footnote.php b/samples/Sample_06_Footnote.php index c430e548..081d5918 100755 --- a/samples/Sample_06_Footnote.php +++ b/samples/Sample_06_Footnote.php @@ -24,7 +24,8 @@ $footnote->addText(' No break is placed after adding an element.', 'BoldText'); $footnote->addText(' All elements are placed inside a paragraph.', 'ColoredText'); $footnote->addText(' The best search engine: '); $footnote->addLink('http://www.google.com', null, 'NLink'); -$footnote->addText('. Also not bad: '); +$footnote->addText('. Also not bad:'); +$footnote->addTextBreak(); $footnote->addLink('http://www.bing.com', null, 'NLink'); $textrun->addText('The trailing text in the paragraph.'); diff --git a/src/PhpWord/Section/Footnote.php b/src/PhpWord/Section/Footnote.php index 857c4e20..c86d8e44 100644 --- a/src/PhpWord/Section/Footnote.php +++ b/src/PhpWord/Section/Footnote.php @@ -77,6 +77,20 @@ class Footnote return $text; } + /** + * Add TextBreak + * + * @param int $count + * @param mixed $fontStyle + * @param mixed $paragraphStyle + */ + public function addTextBreak($count = 1, $fontStyle = null, $paragraphStyle = null) + { + for ($i = 1; $i <= $count; $i++) { + $this->_elementCollection[] = new TextBreak($fontStyle, $paragraphStyle); + } + } + /** * Add a Link Element * diff --git a/src/PhpWord/Writer/Word2007/Base.php b/src/PhpWord/Writer/Word2007/Base.php index 733b376a..7234aed9 100644 --- a/src/PhpWord/Writer/Word2007/Base.php +++ b/src/PhpWord/Writer/Word2007/Base.php @@ -125,7 +125,7 @@ class Base extends WriterPart } elseif ($element instanceof Image) { $this->_writeImage($xmlWriter, $element, true); } elseif ($element instanceof Footnote) { - $this->_writeFootnoteReference($xmlWriter, $element, true); + $this->_writeFootnote($xmlWriter, $element, true); } elseif ($element instanceof TextBreak) { $xmlWriter->writeElement('w:br'); } @@ -1172,61 +1172,24 @@ class Base extends WriterPart } /** - * Write footnote element - * - * @param PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter - * @param PhpOffice\PhpWord\Section\Footnote $footnote - */ - protected function _writeFootnote(XMLWriter $xmlWriter, Footnote $footnote) - { - $xmlWriter->startElement('w:footnote'); - $xmlWriter->writeAttribute('w:id', $footnote->getReferenceId()); - - $styleParagraph = $footnote->getParagraphStyle(); - $SpIsObject = ($styleParagraph instanceof Paragraph) ? true : false; - - $xmlWriter->startElement('w:p'); - - if ($SpIsObject) { - $this->_writeParagraphStyle($xmlWriter, $styleParagraph); - } elseif (!$SpIsObject && !is_null($styleParagraph)) { - $xmlWriter->startElement('w:pPr'); - $xmlWriter->startElement('w:pStyle'); - $xmlWriter->writeAttribute('w:val', $styleParagraph); - $xmlWriter->endElement(); - $xmlWriter->endElement(); - } - - $elements = $footnote->getElements(); - if (count($elements) > 0) { - foreach ($elements as $element) { - if ($element instanceof Text) { - $this->_writeText($xmlWriter, $element, true); - } elseif ($element instanceof Link) { - $this->_writeLink($xmlWriter, $element, true); - } - } - } - - $xmlWriter->endElement(); // w:p - $xmlWriter->endElement(); // w:footnote - } - - /** - * Write footnote reference element + * Write footnote element which links to the actual content in footnotes.xml * * @param PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter * @param PhpOffice\PhpWord\Section\Footnote $footnote * @param boolean $withoutP */ - protected function _writeFootnoteReference(XMLWriter $xmlWriter, Footnote $footnote, $withoutP = false) + protected function _writeFootnote(XMLWriter $xmlWriter, Footnote $footnote, $withoutP = false) { if (!$withoutP) { $xmlWriter->startElement('w:p'); } $xmlWriter->startElement('w:r'); - + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rStyle'); + $xmlWriter->writeAttribute('w:val', 'FootnoteReference'); + $xmlWriter->endElement(); // w:rStyle + $xmlWriter->endElement(); // w:rPr $xmlWriter->startElement('w:footnoteReference'); $xmlWriter->writeAttribute('w:id', $footnote->getReferenceId()); $xmlWriter->endElement(); // w:footnoteReference diff --git a/src/PhpWord/Writer/Word2007/Document.php b/src/PhpWord/Writer/Word2007/Document.php index ffce32d2..368d1b31 100644 --- a/src/PhpWord/Writer/Word2007/Document.php +++ b/src/PhpWord/Writer/Word2007/Document.php @@ -98,7 +98,7 @@ class Document extends Base } elseif ($element instanceof TOC) { $this->_writeTOC($xmlWriter); } elseif ($element instanceof Footnote) { - $this->_writeFootnoteReference($xmlWriter, $element); + $this->_writeFootnote($xmlWriter, $element); } } diff --git a/src/PhpWord/Writer/Word2007/Footnotes.php b/src/PhpWord/Writer/Word2007/Footnotes.php index 59d8cc1f..eb410393 100644 --- a/src/PhpWord/Writer/Word2007/Footnotes.php +++ b/src/PhpWord/Writer/Word2007/Footnotes.php @@ -10,6 +10,10 @@ namespace PhpOffice\PhpWord\Writer\Word2007; use PhpOffice\PhpWord\Section\Footnote; +use PhpOffice\PhpWord\Section\Text; +use PhpOffice\PhpWord\Section\Link; +use PhpOffice\PhpWord\Section\TextBreak; +use PhpOffice\PhpWord\Style\Paragraph; use PhpOffice\PhpWord\Shared\XMLWriter; /** @@ -27,19 +31,25 @@ class Footnotes extends Base // Create XML writer $xmlWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { - $xmlWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + $xmlWriter = new XMLWriter( + XMLWriter::STORAGE_DISK, + $this->getParentWriter()->getDiskCachingDirectory() + ); } else { $xmlWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); } - // XML header $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); - $xmlWriter->startElement('w:footnotes'); - $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); - $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); - - // write separator and continuation separator + $xmlWriter->writeAttribute( + 'xmlns:r', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' + ); + $xmlWriter->writeAttribute( + 'xmlns:w', + 'http://schemas.openxmlformats.org/wordprocessingml/2006/main' + ); + // Separator and continuation separator $xmlWriter->startElement('w:footnote'); $xmlWriter->writeAttribute('w:id', 0); $xmlWriter->writeAttribute('w:type', 'separator'); @@ -50,7 +60,7 @@ class Footnotes extends Base $xmlWriter->endElement(); // w:r $xmlWriter->endElement(); // w:p $xmlWriter->endElement(); // w:footnote - + // Content $xmlWriter->startElement('w:footnote'); $xmlWriter->writeAttribute('w:id', 1); $xmlWriter->writeAttribute('w:type', 'continuationSeparator'); @@ -61,16 +71,69 @@ class Footnotes extends Base $xmlWriter->endElement(); // w:r $xmlWriter->endElement(); // w:p $xmlWriter->endElement(); // w:footnote - foreach ($allFootnotesCollection as $footnote) { if ($footnote instanceof Footnote) { - $this->_writeFootnote($xmlWriter, $footnote); + $this->writeFootnote($xmlWriter, $footnote); } } - $xmlWriter->endElement(); - // Return return $xmlWriter->getData(); } + + /** + * Write footnote content, overrides method in parent class + * + * @param PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter + * @param PhpOffice\PhpWord\Section\Footnote $footnote + */ + private function writeFootnote(XMLWriter $xmlWriter, Footnote $footnote) + { + $xmlWriter->startElement('w:footnote'); + $xmlWriter->writeAttribute('w:id', $footnote->getReferenceId()); + $xmlWriter->startElement('w:p'); + // Paragraph style + $paragraphStyle = $footnote->getParagraphStyle(); + $spIsObject = ($paragraphStyle instanceof Paragraph) ? true : false; + if ($spIsObject) { + $this->_writeParagraphStyle($xmlWriter, $paragraphStyle); + } elseif (!$spIsObject && !is_null($paragraphStyle)) { + $xmlWriter->startElement('w:pPr'); + $xmlWriter->startElement('w:pStyle'); + $xmlWriter->writeAttribute('w:val', $paragraphStyle); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + } + // Reference symbol + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rStyle'); + $xmlWriter->writeAttribute('w:val', 'FootnoteReference'); + $xmlWriter->endElement(); // w:rStyle + $xmlWriter->endElement(); // w:rPr + $xmlWriter->writeElement('w:footnoteRef'); + $xmlWriter->endElement(); // w:r + // Empty space after refence symbol + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:t'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->writeRaw(' '); + $xmlWriter->endElement(); // w:t + $xmlWriter->endElement(); // w:r + // Actual footnote contents + $elements = $footnote->getElements(); + if (count($elements) > 0) { + foreach ($elements as $element) { + if ($element instanceof Text) { + $this->_writeText($xmlWriter, $element, true); + } elseif ($element instanceof Link) { + $this->_writeLink($xmlWriter, $element, true); + } elseif ($element instanceof TextBreak) { + $xmlWriter->writeElement('w:br'); + } + } + } + $xmlWriter->endElement(); // w:p + $xmlWriter->endElement(); // w:footnote + } } diff --git a/src/PhpWord/Writer/Word2007/Styles.php b/src/PhpWord/Writer/Word2007/Styles.php index dc665300..a24ac516 100644 --- a/src/PhpWord/Writer/Word2007/Styles.php +++ b/src/PhpWord/Writer/Word2007/Styles.php @@ -21,11 +21,11 @@ use PhpOffice\PhpWord\Style\Paragraph; class Styles extends Base { /** - * PHPWord object + * PhpWord object * * @var PhpWord */ - private $_document; + private $phpWord; /** * Write word/styles.xml @@ -37,44 +37,28 @@ class Styles extends Base // Create XML writer $xmlWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { - $xmlWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + $xmlWriter = new XMLWriter( + XMLWriter::STORAGE_DISK, + $this->getParentWriter()->getDiskCachingDirectory() + ); } else { $xmlWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); } - - $this->_document = $phpWord; - + $this->phpWord = $phpWord; // 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'); - - // Write DocDefaults - $this->_writeDocDefaults($xmlWriter); - - // Write Style Definitions + $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(); - - // Write normal paragraph style - $normalStyle = null; - if (array_key_exists('Normal', $styles)) { - $normalStyle = $styles['Normal']; - } - $xmlWriter->startElement('w:style'); - $xmlWriter->writeAttribute('w:type', 'paragraph'); - $xmlWriter->writeAttribute('w:default', '1'); - $xmlWriter->writeAttribute('w:styleId', 'Normal'); - $xmlWriter->startElement('w:name'); - $xmlWriter->writeAttribute('w:val', 'Normal'); - $xmlWriter->endElement(); - if (!is_null($normalStyle)) { - $this->_writeParagraphStyle($xmlWriter, $normalStyle); - } - $xmlWriter->endElement(); - + $this->writeDefaultStyles($xmlWriter, $styles); // Write other styles if (count($styles) > 0) { foreach ($styles as $styleName => $style) { @@ -180,36 +164,65 @@ class Styles extends Base } /** - * Write document defaults + * Write default font and other default styles * * @param PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter + * @param array $styles */ - private function _writeDocDefaults(XMLWriter $xmlWriter) + private function writeDefaultStyles(XMLWriter $xmlWriter, $styles) { - $fontName = $this->_document->getDefaultFontName(); - $fontSize = $this->_document->getDefaultFontSize(); + $fontName = $this->phpWord->getDefaultFontName(); + $fontSize = $this->phpWord->getDefaultFontSize(); + // Default font $xmlWriter->startElement('w:docDefaults'); $xmlWriter->startElement('w:rPrDefault'); $xmlWriter->startElement('w:rPr'); - $xmlWriter->startElement('w:rFonts'); $xmlWriter->writeAttribute('w:ascii', $fontName); $xmlWriter->writeAttribute('w:hAnsi', $fontName); $xmlWriter->writeAttribute('w:eastAsia', $fontName); $xmlWriter->writeAttribute('w:cs', $fontName); - $xmlWriter->endElement(); - + $xmlWriter->endElement(); // w:rFonts $xmlWriter->startElement('w:sz'); $xmlWriter->writeAttribute('w:val', $fontSize * 2); - $xmlWriter->endElement(); - + $xmlWriter->endElement(); // w:sz $xmlWriter->startElement('w:szCs'); $xmlWriter->writeAttribute('w:val', $fontSize * 2); - $xmlWriter->endElement(); + $xmlWriter->endElement(); // w:szCs + $xmlWriter->endElement(); // w:rPr + $xmlWriter->endElement(); // w:rPrDefault + $xmlWriter->endElement(); // w:docDefaults - $xmlWriter->endElement(); - $xmlWriter->endElement(); - $xmlWriter->endElement(); + // Normal style + $xmlWriter->startElement('w:style'); + $xmlWriter->writeAttribute('w:type', 'paragraph'); + $xmlWriter->writeAttribute('w:default', '1'); + $xmlWriter->writeAttribute('w:styleId', 'Normal'); + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', 'Normal'); + $xmlWriter->endElement(); // w:name + if (array_key_exists('Normal', $styles)) { + $this->_writeParagraphStyle($xmlWriter, $styles['Normal']); + } + $xmlWriter->endElement(); // w:style + + // FootnoteReference style + if (!array_key_exists('FootnoteReference', $styles)) { + $xmlWriter->startElement('w:style'); + $xmlWriter->writeAttribute('w:type', 'character'); + $xmlWriter->writeAttribute('w:styleId', 'FootnoteReference'); + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', 'Footnote Reference'); + $xmlWriter->endElement(); // w:name + $xmlWriter->writeElement('w:semiHidden'); + $xmlWriter->writeElement('w:unhideWhenUsed'); + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:vertAlign'); + $xmlWriter->writeAttribute('w:val', 'superscript'); + $xmlWriter->endElement(); // w:vertAlign + $xmlWriter->endElement(); // w:rPr + $xmlWriter->endElement(); // w:style + } } } diff --git a/tests/PhpWord/Tests/Section/FootnoteTest.php b/tests/PhpWord/Tests/Section/FootnoteTest.php index 19cf58c7..ed2e36de 100644 --- a/tests/PhpWord/Tests/Section/FootnoteTest.php +++ b/tests/PhpWord/Tests/Section/FootnoteTest.php @@ -19,6 +19,11 @@ use PhpOffice\PhpWord\Section\Footnote; */ class FootnoteTest extends \PHPUnit_Framework_TestCase { + /** + * New instance without parameter + * + * @covers ::__construct + */ public function testConstruct() { $oFootnote = new Footnote(); @@ -28,6 +33,11 @@ class FootnoteTest extends \PHPUnit_Framework_TestCase $this->assertEquals($oFootnote->getParagraphStyle(), null); } + /** + * New instance with string parameter + * + * @covers ::__construct + */ public function testConstructString() { $oFootnote = new Footnote('pStyle'); @@ -35,6 +45,11 @@ class FootnoteTest extends \PHPUnit_Framework_TestCase $this->assertEquals($oFootnote->getParagraphStyle(), 'pStyle'); } + /** + * New instance with array parameter + * + * @covers ::__construct + */ public function testConstructArray() { $oFootnote = new Footnote(array('spacing' => 100)); @@ -45,6 +60,11 @@ class FootnoteTest extends \PHPUnit_Framework_TestCase ); } + /** + * Add text element + * + * @covers ::addText + */ public function testAddText() { $oFootnote = new Footnote(); @@ -54,6 +74,24 @@ class FootnoteTest extends \PHPUnit_Framework_TestCase $this->assertInstanceOf('PhpOffice\\PhpWord\\Section\\Text', $element); } + /** + * Add text break element + * + * @covers ::addTextBreak + */ + public function testAddTextBreak() + { + $oFootnote = new Footnote(); + $oFootnote->addTextBreak(2); + + $this->assertCount(2, $oFootnote->getElements()); + } + + /** + * Add link element + * + * @covers ::addLink + */ public function testAddLink() { $oFootnote = new Footnote(); @@ -63,6 +101,12 @@ class FootnoteTest extends \PHPUnit_Framework_TestCase $this->assertInstanceOf('PhpOffice\\PhpWord\\Section\\Link', $element); } + /** + * Set/get reference Id + * + * @covers ::setReferenceId + * @covers ::getReferenceId + */ public function testReferenceId() { $oFootnote = new Footnote(); @@ -72,6 +116,11 @@ class FootnoteTest extends \PHPUnit_Framework_TestCase $this->assertEquals($oFootnote->getReferenceId(), $iVal); } + /** + * Get elements + * + * @covers ::getElements + */ public function testGetElements() { $oFootnote = new Footnote(); diff --git a/tests/PhpWord/Tests/Writer/Word2007/BaseTest.php b/tests/PhpWord/Tests/Writer/Word2007/BaseTest.php index ac14fd17..e4abb7a1 100644 --- a/tests/PhpWord/Tests/Writer/Word2007/BaseTest.php +++ b/tests/PhpWord/Tests/Writer/Word2007/BaseTest.php @@ -79,9 +79,13 @@ class BaseTest extends \PHPUnit_Framework_TestCase { $phpWord = new PhpWord(); $section = $phpWord->createSection(); + $fontStyleArray = array('bold' => true); + $fontStyleName = 'Test'; $expected = 'PhpWord'; $section->addLink('http://github.com/phpoffice/phpword', $expected); + $section->addLink('http://github.com/phpoffice/phpword', 'Test', $fontStyleArray); + $section->addLink('http://github.com/phpoffice/phpword', 'Test', $fontStyleName); $doc = TestHelperDOCX::getDocument($phpWord); $element = $doc->getElement('/w:document/w:body/w:p/w:hyperlink/w:r/w:t'); @@ -97,8 +101,14 @@ class BaseTest extends \PHPUnit_Framework_TestCase $phpWord = new PhpWord(); $section = $phpWord->createSection(); $footer = $section->createFooter(); + $fontStyleArray = array('bold' => true); + $fontStyleName = 'Font'; + $paragraphStyleArray = array('align' => 'right'); + $paragraphStyleName = 'Paragraph'; - $footer->addPreserveText('{PAGE}'); + $footer->addPreserveText('Page {PAGE}'); + $footer->addPreserveText('{PAGE}', $fontStyleArray, $paragraphStyleArray); + $footer->addPreserveText('{PAGE}', $fontStyleName, $paragraphStyleName); $doc = TestHelperDOCX::getDocument($phpWord); $preserve = $doc->getElement("w:p/w:r[2]/w:instrText", 'word/footer1.xml'); @@ -193,6 +203,7 @@ class BaseTest extends \PHPUnit_Framework_TestCase $styles['superScript'] = true; $styles['color'] = 'FF0000'; $styles['fgColor'] = 'yellow'; + $styles['hint'] = 'eastAsia'; $section = $phpWord->createSection(); $section->addText('Test', $styles);