Skip to content

Commit

Permalink
Merge pull request #2469 from PHPOffice/pr2161
Browse files Browse the repository at this point in the history
Word2007 Reader : Added support for Comments
  • Loading branch information
Progi1984 committed Sep 14, 2023
2 parents d94b00c + 0ea59e7 commit 8c23738
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 43 deletions.
1 change: 1 addition & 0 deletions docs/changes/1.x/1.2.0.md
Expand Up @@ -11,6 +11,7 @@
- HTML Writer : Added border-spacing to default styles for table by [@kernusr](https://github.com/kernusr) in GH-2451
- Word2007 Reader : Support for table cell borders and margins by [@kernusr](https://github.com/kernusr) in GH-2454
- PDF Writer : Add config for defining the default font by [@MikeMaldini](https://github.com/MikeMaldini) in [#2262](https://github.com/PHPOffice/PHPWord/pull/2262) & [#2468](https://github.com/PHPOffice/PHPWord/pull/2468)
- Word2007 Reader : Added support for Comments by [@shaedrich](https://github.com/shaedrich) in [#2161](https://github.com/PHPOffice/PHPWord/pull/2161) & [#2469](https://github.com/PHPOffice/PHPWord/pull/2469)

### Bug fixes

Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Expand Up @@ -95,7 +95,7 @@ Below are the supported features for each file formats.
| | Footer | :material-check: | | | | |
| | Footnote | :material-check: | | | | |
| | Endnote | :material-check: | | | | |
| | Comments | | | | | |
| | Comments | :material-check: | | | | |
| **Graphs** | 2D basic graphs | | | | | |
| | 2D advanced graphs | | | | | |
| | 3D graphs | | | | | |
Expand Down
10 changes: 0 additions & 10 deletions phpstan-baseline.neon
Expand Up @@ -230,11 +230,6 @@ parameters:
count: 2
path: src/PhpWord/Reader/Word2007/AbstractPart.php

-
message: "#^Call to method setChangeInfo\\(\\) on an unknown class PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractElement\\.$#"
count: 1
path: src/PhpWord/Reader/Word2007/AbstractPart.php

-
message: "#^Method PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractPart\\:\\:getHeadingDepth\\(\\) never returns float so it can be removed from the return type\\.$#"
count: 1
Expand All @@ -250,11 +245,6 @@ parameters:
count: 1
path: src/PhpWord/Reader/Word2007/AbstractPart.php

-
message: "#^PHPDoc tag @var for variable \\$element contains unknown class PhpOffice\\\\PhpWord\\\\Reader\\\\Word2007\\\\AbstractElement\\.$#"
count: 1
path: src/PhpWord/Reader/Word2007/AbstractPart.php

-
message: "#^Parameter \\#1 \\$count of method PhpOffice\\\\PhpWord\\\\Element\\\\AbstractContainer\\:\\:addTextBreak\\(\\) expects int, null given\\.$#"
count: 1
Expand Down
2 changes: 1 addition & 1 deletion src/PhpWord/Collection/AbstractCollection.php
Expand Up @@ -79,7 +79,7 @@ public function setItem($index, $item): void
*/
public function addItem($item)
{
$index = $this->countItems() + 1;
$index = $this->countItems();
$this->items[$index] = $item;

return $index;
Expand Down
48 changes: 48 additions & 0 deletions src/PhpWord/PhpWord.php
Expand Up @@ -325,4 +325,52 @@ public function save($filename, $format = 'Word2007', $download = false)

return true;
}

/**
* Create new section.
*
* @deprecated 0.10.0
*
* @param array $settings
*
* @return \PhpOffice\PhpWord\Element\Section
*
* @codeCoverageIgnore
*/
public function createSection($settings = null)
{
return $this->addSection($settings);
}

/**
* Get document properties object.
*
* @deprecated 0.12.0
*
* @return \PhpOffice\PhpWord\Metadata\DocInfo
*
* @codeCoverageIgnore
*/
public function getDocumentProperties()
{
return $this->getDocInfo();
}

/**
* Set document properties object.
*
* @deprecated 0.12.0
*
* @param \PhpOffice\PhpWord\Metadata\DocInfo $documentProperties
*
* @return self
*
* @codeCoverageIgnore
*/
public function setDocumentProperties($documentProperties)
{
$this->metadata['Document'] = $documentProperties;

return $this;
}
}
71 changes: 44 additions & 27 deletions src/PhpWord/Reader/Word2007.php
Expand Up @@ -17,7 +17,10 @@

namespace PhpOffice\PhpWord\Reader;

use Exception;
use PhpOffice\PhpWord\Element\AbstractElement;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Reader\Word2007\AbstractPart;
use PhpOffice\PhpWord\Shared\XMLReader;
use PhpOffice\PhpWord\Shared\ZipArchive;

Expand All @@ -42,23 +45,34 @@ public function load($docFile)
{
$phpWord = new PhpWord();
$relationships = $this->readRelationships($docFile);
$commentRefs = [];

$steps = [
['stepPart' => 'document', 'stepItems' => [
'styles' => 'Styles',
'numbering' => 'Numbering',
]],
['stepPart' => 'main', 'stepItems' => [
'officeDocument' => 'Document',
'core-properties' => 'DocPropsCore',
'extended-properties' => 'DocPropsApp',
'custom-properties' => 'DocPropsCustom',
]],
['stepPart' => 'document', 'stepItems' => [
'endnotes' => 'Endnotes',
'footnotes' => 'Footnotes',
'settings' => 'Settings',
]],
[
'stepPart' => 'document',
'stepItems' => [
'styles' => 'Styles',
'numbering' => 'Numbering',
],
],
[
'stepPart' => 'main',
'stepItems' => [
'officeDocument' => 'Document',
'core-properties' => 'DocPropsCore',
'extended-properties' => 'DocPropsApp',
'custom-properties' => 'DocPropsCustom',
],
],
[
'stepPart' => 'document',
'stepItems' => [
'endnotes' => 'Endnotes',
'footnotes' => 'Footnotes',
'settings' => 'Settings',
'comments' => 'Comments',
],
],
];

foreach ($steps as $step) {
Expand All @@ -72,7 +86,8 @@ public function load($docFile)
if (isset($stepItems[$relType])) {
$partName = $stepItems[$relType];
$xmlFile = $relItem['target'];
$this->readPart($phpWord, $relationships, $partName, $docFile, $xmlFile);
$part = $this->readPart($phpWord, $relationships, $commentRefs, $partName, $docFile, $xmlFile);
$commentRefs = $part->getCommentReferences();
}
}
}
Expand All @@ -83,21 +98,23 @@ public function load($docFile)
/**
* Read document part.
*
* @param array $relationships
* @param string $partName
* @param string $docFile
* @param string $xmlFile
* @param array<string, array<string, null|AbstractElement>> $commentRefs
*/
private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile): void
private function readPart(PhpWord $phpWord, array $relationships, array $commentRefs, string $partName, string $docFile, string $xmlFile): AbstractPart
{
$partClass = "PhpOffice\\PhpWord\\Reader\\Word2007\\{$partName}";
if (class_exists($partClass)) {
/** @var \PhpOffice\PhpWord\Reader\Word2007\AbstractPart $part Type hint */
$part = new $partClass($docFile, $xmlFile);
$part->setImageLoading($this->hasImageLoading());
$part->setRels($relationships);
$part->read($phpWord);
if (!class_exists($partClass)) {
throw new Exception(sprintf('The part "%s" doesn\'t exist', $partClass));
}

/** @var AbstractPart $part Type hint */
$part = new $partClass($docFile, $xmlFile);
$part->setImageLoading($this->hasImageLoading());
$part->setRels($relationships);
$part->setCommentReferences($commentRefs);
$part->read($phpWord);

return $part;
}

/**
Expand Down
94 changes: 92 additions & 2 deletions src/PhpWord/Reader/Word2007/AbstractPart.php
Expand Up @@ -19,8 +19,10 @@

use DateTime;
use DOMElement;
use InvalidArgumentException;
use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType;
use PhpOffice\PhpWord\Element\AbstractContainer;
use PhpOffice\PhpWord\Element\AbstractElement;
use PhpOffice\PhpWord\Element\TextRun;
use PhpOffice\PhpWord\Element\TrackChange;
use PhpOffice\PhpWord\PhpWord;
Expand Down Expand Up @@ -67,6 +69,13 @@ abstract class AbstractPart
*/
protected $rels = [];

/**
* Comment references.
*
* @var array<string, array<string, AbstractElement>>
*/
protected $commentRefs = [];

/**
* Image Loading.
*
Expand Down Expand Up @@ -113,6 +122,62 @@ public function hasImageLoading(): bool
return $this->imageLoading;
}

/**
* Get comment references.
*
* @return array<string, array<string, null|AbstractElement>>
*/
public function getCommentReferences(): array
{
return $this->commentRefs;
}

/**
* Set comment references.
*
* @param array<string, array<string, null|AbstractElement>> $commentRefs
*/
public function setCommentReferences(array $commentRefs): self
{
$this->commentRefs = $commentRefs;

return $this;
}

/**
* Set comment reference.
*/
private function setCommentReference(string $type, string $id, AbstractElement $element): self
{
if (!in_array($type, ['start', 'end'])) {
throw new InvalidArgumentException('Type must be "start" or "end"');
}

if (!array_key_exists($id, $this->commentRefs)) {
$this->commentRefs[$id] = [
'start' => null,
'end' => null,
];
}
$this->commentRefs[$id][$type] = $element;

return $this;
}

/**
* Get comment reference.
*
* @return array<string, null|AbstractElement>
*/
protected function getCommentReference(string $id): array
{
if (!array_key_exists($id, $this->commentRefs)) {
throw new InvalidArgumentException(sprintf('Comment with id %s isn\'t referenced in document', $id));
}

return $this->commentRefs[$id];
}

/**
* Read w:p.
*
Expand All @@ -126,6 +191,19 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par
// Paragraph style
$paragraphStyle = null;
$headingDepth = null;
if ($xmlReader->elementExists('w:commentReference', $domNode)
|| $xmlReader->elementExists('w:commentRangeStart', $domNode)
|| $xmlReader->elementExists('w:commentRangeEnd', $domNode)
) {
$nodes = $xmlReader->getElements('w:commentReference|w:commentRangeStart|w:commentRangeEnd', $domNode);
$node = current(iterator_to_array($nodes));
if ($node) {
$attributeIdentifier = $node->attributes->getNamedItem('id');
if ($attributeIdentifier) {
$id = $attributeIdentifier->nodeValue;
}
}
}
if ($xmlReader->elementExists('w:pPr', $domNode)) {
$paragraphStyle = $this->readParagraphStyle($xmlReader, $domNode);
$headingDepth = $this->getHeadingDepth($paragraphStyle);
Expand Down Expand Up @@ -182,7 +260,7 @@ protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $par
$parent->addTitle($textContent, $headingDepth);
} else {
// Text and TextRun
$textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag', $domNode);
$textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag|w:commentReference|w:commentRangeStart|w:commentRangeEnd', $domNode);
if (0 === $textRunContainers) {
$parent->addTextBreak(null, $paragraphStyle);
} else {
Expand Down Expand Up @@ -230,7 +308,7 @@ private function getHeadingDepth(?array $paragraphStyle = null)
*/
protected function readRun(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart, $paragraphStyle = null): void
{
if (in_array($domNode->nodeName, ['w:ins', 'w:del', 'w:smartTag', 'w:hyperlink'])) {
if (in_array($domNode->nodeName, ['w:ins', 'w:del', 'w:smartTag', 'w:hyperlink', 'w:commentReference'])) {
$nodes = $xmlReader->getElements('*', $domNode);
foreach ($nodes as $node) {
$this->readRun($xmlReader, $node, $parent, $docPart, $paragraphStyle);
Expand All @@ -242,6 +320,17 @@ protected function readRun(XMLReader $xmlReader, DOMElement $domNode, $parent, $
$this->readRunChild($xmlReader, $node, $parent, $docPart, $paragraphStyle, $fontStyle);
}
}

if ($xmlReader->elementExists('.//*["commentReference"=local-name()]', $domNode)) {
$node = iterator_to_array($xmlReader->getElements('.//*["commentReference"=local-name()]', $domNode))[0];
$attributeIdentifier = $node->attributes->getNamedItem('id');
if ($attributeIdentifier) {
$id = $attributeIdentifier->nodeValue;

$this->setCommentReference('start', $id, $parent->getElement($parent->countElements() - 1));
$this->setCommentReference('end', $id, $parent->getElement($parent->countElements() - 1));
}
}
}

/**
Expand Down Expand Up @@ -339,6 +428,7 @@ protected function readRunChild(XMLReader $xmlReader, DOMElement $node, Abstract
$type = ($runParent->nodeName == 'w:del') ? TrackChange::DELETED : TrackChange::INSERTED;
$author = $runParent->getAttribute('w:author');
$date = DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $runParent->getAttribute('w:date'));
$date = $date instanceof DateTime ? $date : null;
$element->setChangeInfo($type, $author, $date);
}
}
Expand Down

0 comments on commit 8c23738

Please sign in to comment.