Word2007 parsing title formatting (#1297)

* Improve Title parsing
- Title should be able to contain TextRun
- Style 'Title' should be treated the same with as Heading
- Add tests for Heading/Title reader

* update the documentation and the changelog
* PHP 7.2 build should not fail anymore
* reduce dependencies versions
* fix parsing of footnotes and endnotes
* add method to remove an element from a section
This commit is contained in:
troosan 2018-03-06 06:34:55 +01:00 committed by GitHub
parent 740e66acf5
commit 30b224b3d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 539 additions and 99 deletions

View File

@ -1,3 +1,8 @@
build:
nodes:
analysis:
tests:
override: [php-scrutinizer-run]
filter: filter:
excluded_paths: [ 'vendor/*', 'tests/*', 'samples/*', 'src/PhpWord/Shared/PCLZip/*' ] excluded_paths: [ 'vendor/*', 'tests/*', 'samples/*', 'src/PhpWord/Shared/PCLZip/*' ]

View File

@ -15,12 +15,9 @@ matrix:
include: include:
- php: 5.6 - php: 5.6
env: COVERAGE=1 env: COVERAGE=1
allow_failures:
- php: 7.2
cache: cache:
directories: directories:
- vendor
- $HOME/.composer/cache - $HOME/.composer/cache
- .php-cs.cache - .php-cs.cache
@ -38,7 +35,7 @@ before_script:
- if [ -z "$COVERAGE" ]; then phpenv config-rm xdebug.ini ; fi - if [ -z "$COVERAGE" ]; then phpenv config-rm xdebug.ini ; fi
## Composer ## Composer
- composer self-update - composer self-update
- composer install --prefer-source - travis_wait composer install --prefer-source
## PHPDocumentor ## PHPDocumentor
- mkdir -p build/docs - mkdir -p build/docs
- mkdir -p build/coverage - mkdir -p build/coverage

View File

@ -23,6 +23,7 @@ v0.15.0 (?? ??? 2018)
- Fix parsing of `<w:br/>` tag. @troosan #1274 - Fix parsing of `<w:br/>` tag. @troosan #1274
- Bookmark are not writton as internal link in html writer @troosan #1263 - Bookmark are not writton as internal link in html writer @troosan #1263
- It should be possible to add a Footnote in a ListItemRun @troosan #1287 #1287 - It should be possible to add a Footnote in a ListItemRun @troosan #1287 #1287
- Fix parsing of Heading and Title formating @troosan @gthomas2 #465
### Changed ### Changed
- Remove zend-stdlib dependency @Trainmaster #1284 - Remove zend-stdlib dependency @Trainmaster #1284

View File

@ -89,6 +89,7 @@ Titles
If you want to structure your document or build table of contents, you need titles or headings. If you want to structure your document or build table of contents, you need titles or headings.
To add a title to the document, use the ``addTitleStyle`` and ``addTitle`` method. To add a title to the document, use the ``addTitleStyle`` and ``addTitle`` method.
If `depth` is 0, a Title will be inserted, otherwise a Heading1, Heading2, ...
.. code-block:: php .. code-block:: php
@ -98,7 +99,7 @@ To add a title to the document, use the ``addTitleStyle`` and ``addTitle`` metho
- ``depth``. - ``depth``.
- ``$fontStyle``. See :ref:`font-style`. - ``$fontStyle``. See :ref:`font-style`.
- ``$paragraphStyle``. See :ref:`paragraph-style`. - ``$paragraphStyle``. See :ref:`paragraph-style`.
- ``$text``. Text to be displayed in the document. - ``$text``. Text to be displayed in the document. This can be `string` or a `\PhpOffice\PhpWord\Element\TextRun`
It's necessary to add a title style to your document because otherwise the title won't be detected as a real title. It's necessary to add a title style to your document because otherwise the title won't be detected as a real title.

View File

@ -12,13 +12,14 @@ $section = $phpWord->addSection();
// Define styles // Define styles
$fontStyle12 = array('spaceAfter' => 60, 'size' => 12); $fontStyle12 = array('spaceAfter' => 60, 'size' => 12);
$fontStyle10 = array('size' => 10); $fontStyle10 = array('size' => 10);
$phpWord->addTitleStyle(null, array('size' => 22, 'bold' => true));
$phpWord->addTitleStyle(1, array('size' => 20, 'color' => '333333', 'bold' => true)); $phpWord->addTitleStyle(1, array('size' => 20, 'color' => '333333', 'bold' => true));
$phpWord->addTitleStyle(2, array('size' => 16, 'color' => '666666')); $phpWord->addTitleStyle(2, array('size' => 16, 'color' => '666666'));
$phpWord->addTitleStyle(3, array('size' => 14, 'italic' => true)); $phpWord->addTitleStyle(3, array('size' => 14, 'italic' => true));
$phpWord->addTitleStyle(4, array('size' => 12)); $phpWord->addTitleStyle(4, array('size' => 12));
// Add text elements // Add text elements
$section->addText('Table of contents 1'); $section->addTitle('Table of contents 1', 0);
$section->addTextBreak(2); $section->addTextBreak(2);
// Add TOC #1 // Add TOC #1

View File

@ -27,14 +27,14 @@ abstract class AbstractCollection
/** /**
* Items * Items
* *
* @var array * @var \PhpOffice\PhpWord\Element\AbstractContainer[]
*/ */
private $items = array(); private $items = array();
/** /**
* Get items * Get items
* *
* @return array * @return \PhpOffice\PhpWord\Element\AbstractContainer[]
*/ */
public function getItems() public function getItems()
{ {
@ -45,7 +45,7 @@ abstract class AbstractCollection
* Get item by index * Get item by index
* *
* @param int $index * @param int $index
* @return mixed * @return \PhpOffice\PhpWord\Element\AbstractContainer
*/ */
public function getItem($index) public function getItem($index)
{ {
@ -60,7 +60,7 @@ abstract class AbstractCollection
* Set item. * Set item.
* *
* @param int $index * @param int $index
* @param mixed $item * @param \PhpOffice\PhpWord\Element\AbstractContainer $item
*/ */
public function setItem($index, $item) public function setItem($index, $item)
{ {
@ -72,7 +72,7 @@ abstract class AbstractCollection
/** /**
* Add new item * Add new item
* *
* @param mixed $item * @param \PhpOffice\PhpWord\Element\AbstractContainer $item
* @return int * @return int
*/ */
public function addItem($item) public function addItem($item)

View File

@ -54,7 +54,7 @@ abstract class AbstractContainer extends AbstractElement
/** /**
* Elements collection * Elements collection
* *
* @var array * @var \PhpOffice\PhpWord\Element\AbstractElement[]
*/ */
protected $elements = array(); protected $elements = array();
@ -164,6 +164,41 @@ abstract class AbstractContainer extends AbstractElement
return $this->elements; return $this->elements;
} }
/**
* Returns the element at the requested position
*
* @param int $index
* @return \PhpOffice\PhpWord\Element\AbstractElement|null
*/
public function getElement($index)
{
if (array_key_exists($index, $this->elements)) {
return $this->elements[$index];
}
return null;
}
/**
* Removes the element at requested index
*
* @param int|\PhpOffice\PhpWord\Element\AbstractElement $toRemove
*/
public function removeElement($toRemove)
{
if (is_int($toRemove) && array_key_exists($toRemove, $this->elements)) {
unset($this->elements[$toRemove]);
} elseif ($toRemove instanceof \PhpOffice\PhpWord\Element\AbstractElement) {
foreach ($this->elements as $key => $element) {
if ($element->getElementId() === $toRemove->getElementId()) {
unset($this->elements[$key]);
return;
}
}
}
}
/** /**
* Count elements * Count elements
* *

View File

@ -43,7 +43,7 @@ class Bookmark extends AbstractElement
* *
* @param string $name * @param string $name
*/ */
public function __construct($name) public function __construct($name = '')
{ {
$this->name = CommonText::toUTF8($name); $this->name = CommonText::toUTF8($name);
} }

View File

@ -62,7 +62,7 @@ class ListItem extends AbstractElement
// Version >= 0.10.0 will pass numbering style name. Older version will use old method // Version >= 0.10.0 will pass numbering style name. Older version will use old method
if (!is_null($listStyle) && is_string($listStyle)) { if (!is_null($listStyle) && is_string($listStyle)) {
$this->style = new ListItemStyle($listStyle); $this->style = new ListItemStyle($listStyle); // @codeCoverageIgnore
} else { } else {
$this->style = $this->setNewStyle(new ListItemStyle(), $listStyle, true); $this->style = $this->setNewStyle(new ListItemStyle(), $listStyle, true);
} }

View File

@ -28,7 +28,7 @@ class Title extends AbstractElement
/** /**
* Title Text content * Title Text content
* *
* @var string * @var string|TextRun
*/ */
private $text; private $text;
@ -56,15 +56,25 @@ class Title extends AbstractElement
/** /**
* Create a new Title Element * Create a new Title Element
* *
* @param string $text * @param string|TextRun $text
* @param int $depth * @param int $depth
*/ */
public function __construct($text, $depth = 1) public function __construct($text, $depth = 1)
{ {
if (isset($text)) {
if (is_string($text)) {
$this->text = CommonText::toUTF8($text); $this->text = CommonText::toUTF8($text);
} elseif ($text instanceof TextRun) {
$this->text = $text;
} else {
throw new \InvalidArgumentException('Invalid text, should be a string or a TextRun');
}
}
$this->depth = $depth; $this->depth = $depth;
if (array_key_exists("Heading_{$this->depth}", Style::getStyles())) { $styleName = $depth === 0 ? 'Title' : "Heading_{$this->depth}";
$this->style = "Heading{$this->depth}"; if (array_key_exists($styleName, Style::getStyles())) {
$this->style = str_replace('_', '', $styleName);
} }
return $this; return $this;

View File

@ -212,6 +212,21 @@ class PhpWord
return $this->sections; return $this->sections;
} }
/**
* Returns the section at the requested position
*
* @param int $index
* @return \PhpOffice\PhpWord\Element\Section|null
*/
public function getSection($index)
{
if (array_key_exists($index, $this->sections)) {
return $this->sections[$index];
}
return null;
}
/** /**
* Create new section * Create new section
* *

View File

@ -18,6 +18,7 @@
namespace PhpOffice\PhpWord\Reader\Word2007; namespace PhpOffice\PhpWord\Reader\Word2007;
use PhpOffice\Common\XMLReader; use PhpOffice\Common\XMLReader;
use PhpOffice\PhpWord\Element\TextRun;
use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\Element\TrackChange;
use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\PhpWord;
@ -103,12 +104,10 @@ abstract class AbstractPart
{ {
// Paragraph style // Paragraph style
$paragraphStyle = null; $paragraphStyle = null;
$headingMatches = array(); $headingDepth = null;
if ($xmlReader->elementExists('w:pPr', $domNode)) { if ($xmlReader->elementExists('w:pPr', $domNode)) {
$paragraphStyle = $this->readParagraphStyle($xmlReader, $domNode); $paragraphStyle = $this->readParagraphStyle($xmlReader, $domNode);
if (is_array($paragraphStyle) && isset($paragraphStyle['styleName'])) { $headingDepth = $this->getHeadingDepth($paragraphStyle);
preg_match('/Heading(\d)/', $paragraphStyle['styleName'], $headingMatches);
}
} }
// PreserveText // PreserveText
@ -147,14 +146,19 @@ abstract class AbstractPart
foreach ($nodes as $node) { foreach ($nodes as $node) {
$this->readRun($xmlReader, $node, $listItemRun, $docPart, $paragraphStyle); $this->readRun($xmlReader, $node, $listItemRun, $docPart, $paragraphStyle);
} }
} elseif (!empty($headingMatches)) { } elseif ($headingDepth !== null) {
// Heading // Heading or Title
$textContent = ''; $textContent = null;
$nodes = $xmlReader->getElements('w:r', $domNode); $nodes = $xmlReader->getElements('w:r', $domNode);
if ($nodes->length === 1) {
$textContent = $xmlReader->getValue('w:t', $nodes->item(0));
} else {
$textContent = new TextRun($paragraphStyle);
foreach ($nodes as $node) { foreach ($nodes as $node) {
$textContent .= $xmlReader->getValue('w:t', $node); $this->readRun($xmlReader, $node, $textContent, $docPart, $paragraphStyle);
} }
$parent->addTitle($textContent, $headingMatches[1]); }
$parent->addTitle($textContent, $headingDepth);
} else { } else {
// Text and TextRun // Text and TextRun
$runCount = $xmlReader->countElements('w:r', $domNode); $runCount = $xmlReader->countElements('w:r', $domNode);
@ -176,6 +180,29 @@ abstract class AbstractPart
} }
} }
/**
* Returns the depth of the Heading, returns 0 for a Title
*
* @param array $paragraphStyle
* @return number|null
*/
private function getHeadingDepth(array $paragraphStyle = null)
{
if (is_array($paragraphStyle) && isset($paragraphStyle['styleName'])) {
if ('Title' === $paragraphStyle['styleName']) {
return 0;
}
$headingMatches = array();
preg_match('/Heading(\d)/', $paragraphStyle['styleName'], $headingMatches);
if (!empty($headingMatches)) {
return $headingMatches[1];
}
}
return null;
}
/** /**
* Read w:r. * Read w:r.
* *
@ -212,10 +239,14 @@ abstract class AbstractPart
} else { } else {
if ($xmlReader->elementExists('w:footnoteReference', $domNode)) { if ($xmlReader->elementExists('w:footnoteReference', $domNode)) {
// Footnote // Footnote
$parent->addFootnote(); $wId = $xmlReader->getAttribute('w:id', $domNode, 'w:footnoteReference');
$footnote = $parent->addFootnote();
$footnote->setRelationId($wId);
} elseif ($xmlReader->elementExists('w:endnoteReference', $domNode)) { } elseif ($xmlReader->elementExists('w:endnoteReference', $domNode)) {
// Endnote // Endnote
$parent->addEndnote(); $wId = $xmlReader->getAttribute('w:id', $domNode, 'w:endnoteReference');
$endnote = $parent->addEndnote();
$endnote->setRelationId($wId);
} elseif ($xmlReader->elementExists('w:pict', $domNode)) { } elseif ($xmlReader->elementExists('w:pict', $domNode)) {
// Image // Image
$rId = $xmlReader->getAttribute('r:id', $domNode, 'w:pict/v:shape/v:imagedata'); $rId = $xmlReader->getAttribute('r:id', $domNode, 'w:pict/v:shape/v:imagedata');
@ -496,11 +527,9 @@ abstract class AbstractPart
return $possibleAttribute; return $possibleAttribute;
} }
} }
} else {
return $attributes;
} }
return null; return $attributes;
} }
/** /**
@ -578,7 +607,7 @@ abstract class AbstractPart
*/ */
private function isOn($value = null) private function isOn($value = null)
{ {
return $value == null || $value == '1' || $value == 'true' || $value == 'on'; return $value === null || $value === '1' || $value === 'true' || $value === 'on';
} }
/** /**

View File

@ -48,9 +48,6 @@ class Footnotes extends AbstractPart
*/ */
public function read(PhpWord $phpWord) public function read(PhpWord $phpWord)
{ {
$getMethod = "get{$this->collection}";
$collection = $phpWord->$getMethod()->getItems();
$xmlReader = new XMLReader(); $xmlReader = new XMLReader();
$xmlReader->getDomFromZip($this->docFile, $this->xmlFile); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile);
$nodes = $xmlReader->getElements('*'); $nodes = $xmlReader->getElements('*');
@ -60,9 +57,10 @@ class Footnotes extends AbstractPart
$type = $xmlReader->getAttribute('w:type', $node); $type = $xmlReader->getAttribute('w:type', $node);
// Avoid w:type "separator" and "continuationSeparator" // Avoid w:type "separator" and "continuationSeparator"
// Only look for <footnote> or <endnote> without w:type attribute // Only look for <footnote> or <endnote> without w:type attribute, or with w:type = normal
if (is_null($type) && isset($collection[$id])) { if ((is_null($type) || $type === 'normal')) {
$element = $collection[$id]; $element = $this->getElement($phpWord, $id);
if ($element !== null) {
$pNodes = $xmlReader->getElements('w:p/*', $node); $pNodes = $xmlReader->getElements('w:p/*', $node);
foreach ($pNodes as $pNode) { foreach ($pNodes as $pNode) {
$this->readRun($xmlReader, $pNode, $element, $this->collection); $this->readRun($xmlReader, $pNode, $element, $this->collection);
@ -74,3 +72,26 @@ class Footnotes extends AbstractPart
} }
} }
} }
/**
* Searches for the element with the given relationId
*
* @param PhpWord $phpWord
* @param int $relationId
* @return \PhpOffice\PhpWord\Element\AbstractContainer|null
*/
private function getElement(PhpWord $phpWord, $relationId)
{
$getMethod = "get{$this->collection}";
$collection = $phpWord->$getMethod()->getItems();
//not found by key, looping to search by relationId
foreach ($collection as $collectionElement) {
if ($collectionElement->getRelationId() == $relationId) {
return $collectionElement;
}
}
return null;
}
}

View File

@ -95,7 +95,13 @@ class Style
*/ */
public static function addTitleStyle($depth, $fontStyle, $paragraphStyle = null) public static function addTitleStyle($depth, $fontStyle, $paragraphStyle = null)
{ {
return self::setStyleValues("Heading_{$depth}", new Font('title', $paragraphStyle), $fontStyle); if ($depth == null) {
$styleName = 'Title';
} else {
$styleName = "Heading_{$depth}";
}
return self::setStyleValues($styleName, new Font('title', $paragraphStyle), $fontStyle);
} }
/** /**

View File

@ -47,6 +47,7 @@ class Title extends AbstractElement
$xmlWriter->endElement(); $xmlWriter->endElement();
} }
if ($element->getDepth() !== 0) {
$rId = $element->getRelationId(); $rId = $element->getRelationId();
$bookmarkRId = $element->getPhpWord()->addBookmark(); $bookmarkRId = $element->getPhpWord()->addBookmark();
@ -54,20 +55,28 @@ class Title extends AbstractElement
$xmlWriter->startElement('w:bookmarkStart'); $xmlWriter->startElement('w:bookmarkStart');
$xmlWriter->writeAttribute('w:id', $bookmarkRId); $xmlWriter->writeAttribute('w:id', $bookmarkRId);
$xmlWriter->writeAttribute('w:name', "_Toc{$rId}"); $xmlWriter->writeAttribute('w:name', "_Toc{$rId}");
$xmlWriter->endElement(); $xmlWriter->endElement(); //w:bookmarkStart
}
// Actual text // Actual text
$text = $element->getText();
if (is_string($text)) {
$xmlWriter->startElement('w:r'); $xmlWriter->startElement('w:r');
$xmlWriter->startElement('w:t'); $xmlWriter->startElement('w:t');
$this->writeText($this->getText($element->getText())); $this->writeText($text);
$xmlWriter->endElement(); // w:t $xmlWriter->endElement(); // w:t
$xmlWriter->endElement(); // w:r $xmlWriter->endElement(); // w:r
} elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) {
$containerWriter = new Container($xmlWriter, $text);
$containerWriter->write();
}
if ($element->getDepth() !== 0) {
// Bookmark end // Bookmark end
$xmlWriter->startElement('w:bookmarkEnd'); $xmlWriter->startElement('w:bookmarkEnd');
$xmlWriter->writeAttribute('w:id', $bookmarkRId); $xmlWriter->writeAttribute('w:id', $bookmarkRId);
$xmlWriter->endElement(); $xmlWriter->endElement(); //w:bookmarkEnd
}
$xmlWriter->endElement(); $xmlWriter->endElement(); //w:p
} }
} }

View File

@ -180,9 +180,15 @@ class Styles extends AbstractPart
// Heading style // Heading style
if ($styleType == 'title') { if ($styleType == 'title') {
$arrStyle = explode('_', $styleName); $arrStyle = explode('_', $styleName);
if (count($arrStyle) > 1) {
$styleId = 'Heading' . $arrStyle[1]; $styleId = 'Heading' . $arrStyle[1];
$styleName = 'heading ' . $arrStyle[1]; $styleName = 'heading ' . $arrStyle[1];
$styleLink = 'Heading' . $arrStyle[1] . 'Char'; $styleLink = 'Heading' . $arrStyle[1] . 'Char';
} else {
$styleId = $styleName;
$styleName = strtolower($styleName);
$styleLink = $styleName . 'Char';
}
$xmlWriter->writeAttribute('w:styleId', $styleId); $xmlWriter->writeAttribute('w:styleId', $styleId);
$xmlWriter->startElement('w:link'); $xmlWriter->startElement('w:link');

View File

@ -162,4 +162,35 @@ class SectionTest extends \PHPUnit\Framework\TestCase
$object = new Section(1); $object = new Section(1);
$object->addHeader('ODD'); $object->addHeader('ODD');
} }
/**
* @covers \PhpOffice\PhpWord\Element\AbstractContainer::removeElement
*/
public function testRemoveElementByIndex()
{
$section = new Section(1);
$section->addText('firstText');
$section->addText('secondText');
$this->assertEquals(2, $section->countElements());
$section->removeElement(1);
$this->assertEquals(1, $section->countElements());
}
/**
* @covers \PhpOffice\PhpWord\Element\AbstractContainer::removeElement
*/
public function testRemoveElementByElement()
{
$section = new Section(1);
$fistText = $section->addText('firstText');
$secondText = $section->addText('secondText');
$this->assertEquals(2, $section->countElements());
$section->removeElement($fistText);
$this->assertEquals(1, $section->countElements());
$this->assertEquals($secondText->getElementId(), $section->getElement(1)->getElementId());
}
} }

View File

@ -45,4 +45,25 @@ class TitleTest extends \PHPUnit\Framework\TestCase
$this->assertNull($oTitle->getStyle()); $this->assertNull($oTitle->getStyle());
} }
/**
* Create new instance with TextRun
*/
public function testConstructWithTextRun()
{
$oTextRun = new TextRun();
$oTextRun->addText('text');
$oTitle = new Title($oTextRun);
$this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\TextRun', $oTitle->getText());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testConstructWithInvalidArgument()
{
$oPageBreak = new PageBreak();
new Title($oPageBreak);
}
} }

View File

@ -36,9 +36,9 @@ class ElementTest extends AbstractTestReader
</w:r> </w:r>
</w:p>'; </w:p>';
$phpWord = $this->getDocumentFromString($documentXml); $phpWord = $this->getDocumentFromString(array('document' => $documentXml));
$elements = $this->get($phpWord->getSections(), 0)->getElements(); $elements = $phpWord->getSection(0)->getElements();
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextBreak', $elements[0]); $this->assertInstanceOf('PhpOffice\PhpWord\Element\TextBreak', $elements[0]);
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $elements[1]); $this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $elements[1]);
$this->assertEquals('test string', $elements[1]->getText()); $this->assertEquals('test string', $elements[1]->getText());
@ -70,17 +70,73 @@ class ElementTest extends AbstractTestReader
</w:r> </w:r>
</w:p>'; </w:p>';
$phpWord = $this->getDocumentFromString($documentXml); $phpWord = $this->getDocumentFromString(array('document' => $documentXml));
$elements = $this->get($phpWord->getSections(), 0)->getElements(); $sections = $phpWord->getSection(0);
$this->assertInstanceOf('PhpOffice\PhpWord\Element\ListItemRun', $elements[0]); $this->assertNull($sections->getElement(999));
$this->assertEquals(0, $elements[0]->getDepth()); $this->assertInstanceOf('PhpOffice\PhpWord\Element\ListItemRun', $sections->getElement(0));
$this->assertEquals(0, $sections->getElement(0)->getDepth());
$listElements = $this->get($elements, 0)->getElements(); $listElements = $sections->getElement(0)->getElements();
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $listElements[0]); $this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $listElements[0]);
$this->assertEquals('Two', $listElements[0]->getText()); $this->assertEquals('Two', $listElements[0]->getText());
$this->assertEquals(' with ', $listElements[1]->getText()); $this->assertEquals(' with ', $listElements[1]->getText());
$this->assertEquals('bold', $listElements[2]->getText()); $this->assertEquals('bold', $listElements[2]->getText());
$this->assertTrue($listElements[2]->getFontStyle()->getBold()); $this->assertTrue($listElements[2]->getFontStyle()->getBold());
} }
/**
* Test reading Title style
*/
public function testReadTitleStyle()
{
$documentXml = '<w:p>
<w:pPr>
<w:pStyle w:val="Title"/>
</w:pPr>
<w:r>
<w:t>This is a non formatted title</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="Title"/>
</w:pPr>
<w:r>
<w:t>This is a </w:t>
</w:r>
<w:r>
<w:rPr>
<w:b/>
</w:rPr>
<w:t>bold</w:t>
</w:r>
<w:r>
<w:t> title</w:t>
</w:r>
</w:p>';
$stylesXml = '<w:style w:type="paragraph" w:styleId="Title">
<w:name w:val="Title"/>
<w:link w:val="TitleChar"/>
<w:rPr>
<w:i/>
</w:rPr>
</w:style>';
$phpWord = $this->getDocumentFromString(array('document' => $documentXml, 'styles' => $stylesXml));
$elements = $phpWord->getSection(0)->getElements();
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Title', $elements[0]);
/** @var \PhpOffice\PhpWord\Element\Title $title */
$title = $elements[0];
$this->assertEquals('Title', $title->getStyle());
$this->assertEquals('This is a non formatted title', $title->getText());
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Title', $elements[1]);
/** @var \PhpOffice\PhpWord\Element\Title $formattedTitle */
$formattedTitle = $elements[1];
$this->assertEquals('Title', $formattedTitle->getStyle());
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $formattedTitle->getText());
}
} }

View File

@ -0,0 +1,163 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2017 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Reader\Word2007;
use PhpOffice\PhpWord\AbstractTestReader;
/**
* Test class for PhpOffice\PhpWord\Reader\Word2007 subnamespace
*/
class PartTest extends AbstractTestReader
{
/**
* Test reading Footnotes
*/
public function testReadFootnote()
{
$documentXml = '<w:p>
<w:r>
<w:t>This is a test</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="FootnoteReference"/>
</w:rPr>
<w:footnoteReference w:id="1"/>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>And another one</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="EndnoteReference"/>
</w:rPr>
<w:endnoteReference w:id="2"/>
</w:r>
</w:p>';
$footnotesXml = '<w:footnote w:type="separator" w:id="-1">
<w:p>
<w:r>
<w:separator/>
</w:r>
</w:p>
</w:footnote>
<w:footnote w:id="1">
<w:p>
<w:pPr>
<w:pStyle w:val="FootnoteText"/>
</w:pPr>
<w:r>
<w:rPr>
<w:rStyle w:val="FootnoteReference"/>
</w:rPr>
<w:footnoteRef/>
</w:r>
<w:r>
<w:rPr>
<w:lang w:val="nl-NL"/>
</w:rPr>
<w:t>footnote text</w:t>
</w:r>
</w:p>
</w:footnote>';
$endnotesXml = '<w:endnote w:type="separator" w:id="-1">
<w:p>
<w:r>
<w:separator/>
</w:r>
</w:p>
</w:endnote>
<w:endnote w:type="continuationNotice" w:id="1">
<w:p>
<w:r>
<w:separator/>
</w:r>
</w:p>
</w:endnote>
<w:endnote w:id="2">
<w:p>
<w:pPr>
<w:pStyle w:val="EndnoteText"/>
</w:pPr>
<w:r>
<w:rPr>
<w:rStyle w:val="EndnoteReference"/>
</w:rPr>
<w:endnoteRef/>
</w:r>
<w:r>
<w:rPr>
<w:lang w:val="nl-NL"/>
</w:rPr>
<w:t>This is an endnote</w:t>
</w:r>
</w:p>
</w:endnote>';
$phpWord = $this->getDocumentFromString(array('document' => $documentXml, 'footnotes' => $footnotesXml, 'endnotes' => $endnotesXml));
$elements = $phpWord->getSection(0)->getElements();
$this->assertInstanceOf('PhpOffice\PhpWord\Element\TextRun', $elements[0]);
/** @var \PhpOffice\PhpWord\Element\TextRun $textRun */
$textRun = $elements[0];
//test the text in the first paragraph
/** @var \PhpOffice\PhpWord\Element\Text $text */
$text = $elements[0]->getElement(0);
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $text);
$this->assertEquals('This is a test', $text->getText());
//test the presence of the footnote in the document.xml
/** @var \PhpOffice\PhpWord\Element\Footnote $footnote */
$documentFootnote = $textRun->getElement(1);
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Footnote', $documentFootnote);
$this->assertEquals(1, $documentFootnote->getRelationId());
//test the presence of the footnote in the footnote.xml
/** @var \PhpOffice\PhpWord\Element\Footnote $footnote */
$footnote = $phpWord->getFootnotes()->getItem(1);
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Footnote', $footnote);
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $footnote->getElement(0));
$this->assertEquals('footnote text', $footnote->getElement(0)->getText());
$this->assertEquals(1, $footnote->getRelationId());
//test the text in the second paragraph
/** @var \PhpOffice\PhpWord\Element\Text $text */
$text = $elements[1]->getElement(0);
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $text);
$this->assertEquals('And another one', $text->getText());
//test the presence of the endnote in the document.xml
/** @var \PhpOffice\PhpWord\Element\Endnote $endnote */
$documentEndnote = $elements[1]->getElement(1);
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Endnote', $documentEndnote);
$this->assertEquals(2, $documentEndnote->getRelationId());
//test the presence of the endnote in the endnote.xml
/** @var \PhpOffice\PhpWord\Element\Endnote $endnote */
$endnote = $phpWord->getEndnotes()->getItem(1);
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Endnote', $endnote);
$this->assertEquals(2, $endnote->getRelationId());
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Text', $endnote->getElement(0));
$this->assertEquals('This is an endnote', $endnote->getElement(0)->getText());
}
}

View File

@ -37,9 +37,9 @@ class StyleTest extends AbstractTestReader
</w:tblPr> </w:tblPr>
</w:tbl>'; </w:tbl>';
$phpWord = $this->getDocumentFromString($documentXml); $phpWord = $this->getDocumentFromString(array('document' => $documentXml));
$elements = $this->get($phpWord->getSections(), 0)->getElements(); $elements = $phpWord->getSection(0)->getElements();
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]); $this->assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]);
$this->assertInstanceOf('PhpOffice\PhpWord\Style\Table', $elements[0]->getStyle()); $this->assertInstanceOf('PhpOffice\PhpWord\Style\Table', $elements[0]->getStyle());
$this->assertEquals(Table::LAYOUT_FIXED, $elements[0]->getStyle()->getLayout()); $this->assertEquals(Table::LAYOUT_FIXED, $elements[0]->getStyle()->getLayout());
@ -56,9 +56,9 @@ class StyleTest extends AbstractTestReader
</w:tblPr> </w:tblPr>
</w:tbl>'; </w:tbl>';
$phpWord = $this->getDocumentFromString($documentXml); $phpWord = $this->getDocumentFromString(array('document' => $documentXml));
$elements = $this->get($phpWord->getSections(), 0)->getElements(); $elements = $phpWord->getSection(0)->getElements();
$this->assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]); $this->assertInstanceOf('PhpOffice\PhpWord\Element\Table', $elements[0]);
$this->assertInstanceOf('PhpOffice\PhpWord\Style\Table', $elements[0]->getStyle()); $this->assertInstanceOf('PhpOffice\PhpWord\Style\Table', $elements[0]->getStyle());
$this->assertEquals(TblWidth::AUTO, $elements[0]->getStyle()->getUnit()); $this->assertEquals(TblWidth::AUTO, $elements[0]->getStyle()->getUnit());

View File

@ -447,6 +447,9 @@ class ElementTest extends \PHPUnit\Framework\TestCase
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:commentReference')); $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:commentReference'));
} }
/**
* Test Track changes
*/
public function testTrackChange() public function testTrackChange()
{ {
$phpWord = new PhpWord(); $phpWord = new PhpWord();
@ -462,4 +465,30 @@ class ElementTest extends \PHPUnit\Framework\TestCase
$this->assertEquals('author name', $doc->getElementAttribute('/w:document/w:body/w:p/w:ins', 'w:author')); $this->assertEquals('author name', $doc->getElementAttribute('/w:document/w:body/w:p/w:ins', 'w:author'));
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:del/w:r/w:delText')); $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:del/w:r/w:delText'));
} }
/**
* Test Title and Headings
*/
public function testTitleAndHeading()
{
$phpWord = new PhpWord();
$phpWord->addTitleStyle(0, array('size' => 14, 'italic' => true));
$phpWord->addTitleStyle(1, array('size' => 20, 'color' => '333333', 'bold' => true));
$section = $phpWord->addSection();
$section->addTitle('This is a title', 0);
$section->addTitle('Heading 1', 1);
$doc = TestHelperDOCX::getDocument($phpWord);
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:r/w:t'));
$this->assertEquals('This is a title', $doc->getElement('/w:document/w:body/w:p[1]/w:r/w:t')->textContent);
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p[1]/w:pPr/w:pStyle'));
$this->assertEquals('Title', $doc->getElementAttribute('/w:document/w:body/w:p[1]/w:pPr/w:pStyle', 'w:val'));
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:r/w:t'));
$this->assertEquals('Heading 1', $doc->getElement('/w:document/w:body/w:p[2]/w:r/w:t')->textContent);
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p[2]/w:pPr/w:pStyle'));
$this->assertEquals('Heading1', $doc->getElementAttribute('/w:document/w:body/w:p[2]/w:pPr/w:pStyle', 'w:val'));
}
} }

View File

@ -24,7 +24,6 @@ use PhpOffice\PhpWord\SimpleType\Jc;
use PhpOffice\PhpWord\SimpleType\NumberFormat; use PhpOffice\PhpWord\SimpleType\NumberFormat;
use PhpOffice\PhpWord\Style\Cell; use PhpOffice\PhpWord\Style\Cell;
use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Font;
use PhpOffice\PhpWord\Style\Paragraph;
use PhpOffice\PhpWord\TestHelperDOCX; use PhpOffice\PhpWord\TestHelperDOCX;
/** /**

View File

@ -17,43 +17,48 @@
namespace PhpOffice\PhpWord; namespace PhpOffice\PhpWord;
use PhpOffice\PhpWord\Reader\Word2007\Document;
/** /**
* Base class for Word2007 reader tests * Base class for Word2007 reader tests
*/ */
abstract class AbstractTestReader extends \PHPUnit\Framework\TestCase abstract class AbstractTestReader extends \PHPUnit\Framework\TestCase
{ {
private $parts = array(
'styles' => array('class' => 'PhpOffice\PhpWord\Reader\Word2007\Styles', 'xml' => '<w:styles xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:docDefaults><w:rPrDefault><w:rPr><w:sz w:val="24"/></w:rPr></w:rPrDefault></w:docDefaults>{toReplace}</w:styles>'),
'document' => array('class' => 'PhpOffice\PhpWord\Reader\Word2007\Document', 'xml' => '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>{toReplace}</w:body></w:document>'),
'footnotes' => array('class' => 'PhpOffice\PhpWord\Reader\Word2007\Footnotes', 'xml' => '<w:footnotes xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">{toReplace}</w:footnotes>'),
'endnotes' => array('class' => 'PhpOffice\PhpWord\Reader\Word2007\Endnotes', 'xml' => '<w:endnotes xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">{toReplace}</w:endnotes>'),
'settings' => array('class' => 'PhpOffice\PhpWord\Reader\Word2007\Settings', 'xml' => '<w:comments xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">{toReplace}</w:comments>'),
);
/** /**
* Builds a PhpWord instance based on the xml passed * Builds a PhpWord instance based on the xml passed
* *
* @param string $documentXml * @param string $documentXml
* @param null|string $stylesXml
* @return \PhpOffice\PhpWord\PhpWord * @return \PhpOffice\PhpWord\PhpWord
*/ */
protected function getDocumentFromString($documentXml) protected function getDocumentFromString(array $partXmls = array())
{ {
$phpWord = new PhpWord();
$file = __DIR__ . '/../_files/temp.docx'; $file = __DIR__ . '/../_files/temp.docx';
$zip = new \ZipArchive(); $zip = new \ZipArchive();
$zip->open($file, \ZipArchive::CREATE); $zip->open($file, \ZipArchive::CREATE);
$zip->addFromString('document.xml', '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>' . $documentXml . '</w:body></w:document>'); foreach ($this->parts as $partName => $part) {
if (array_key_exists($partName, $partXmls)) {
$zip->addFromString("{$partName}.xml", str_replace('{toReplace}', $partXmls[$partName], $this->parts[$partName]['xml']));
}
}
$zip->close(); $zip->close();
$documentReader = new Document($file, 'document.xml');
$documentReader->read($phpWord); $phpWord = new PhpWord();
foreach ($this->parts as $partName => $part) {
if (array_key_exists($partName, $partXmls)) {
$className = $this->parts[$partName]['class'];
$reader = new $className($file, "{$partName}.xml");
$reader->read($phpWord);
}
}
unlink($file); unlink($file);
return $phpWord; return $phpWord;
} }
/**
* Returns the element at position $index in the array
*
* @param array $array
* @param number $index
* @return mixed
*/
protected function get(array $array, $index = 0)
{
return $array[$index];
}
} }