Skip to content

Commit

Permalink
Word2007 Reader : Added support for Comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Progi1984 committed Sep 14, 2023
1 parent 34db7cf commit 0ea59e7
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 279 deletions.
1 change: 1 addition & 0 deletions docs/changes/1.x/1.2.0.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
53 changes: 4 additions & 49 deletions src/PhpWord/PhpWord.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
namespace PhpOffice\PhpWord;

use BadMethodCallException;
use InvalidArgumentException;
use PhpOffice\PhpWord\Element\AbstractElement;
use PhpOffice\PhpWord\Element\Section;
use PhpOffice\PhpWord\Exception\Exception;

Expand Down Expand Up @@ -70,14 +68,7 @@ class PhpWord
private $metadata = [];

/**
* Comment reference cache
*
* @var array
*/
private $commentReferenceCache = [];

/**
* Create new instance
* Create new instance.
*
* Collections are created dynamically
*/
Expand Down Expand Up @@ -336,7 +327,7 @@ public function save($filename, $format = 'Word2007', $download = false)
}

/**
* Create new section
* Create new section.
*
* @deprecated 0.10.0
*
Expand All @@ -352,7 +343,7 @@ public function createSection($settings = null)
}

/**
* Get document properties object
* Get document properties object.
*
* @deprecated 0.12.0
*
Expand All @@ -366,7 +357,7 @@ public function getDocumentProperties()
}

/**
* Set document properties object
* Set document properties object.
*
* @deprecated 0.12.0
*
Expand All @@ -382,40 +373,4 @@ public function setDocumentProperties($documentProperties)

return $this;
}

/**
* Cache commentReference (as well as commentRangeStart and commentRangeEnd) for later use
*
* @param 'start'|'end' $type
* @param string $id,
* @param $element
*
* @return self
*/
public function cacheCommentReference(string $type, string $id, AbstractElement $element)
{
//dump('cacheCommentReference', func_get_args(), array_key_exists($id, $this->commentReferenceCache));
if (!in_array($type, [ 'start', 'end' ])) {
throw new InvalidArgumentException('Type must be "start" or "end"');
}

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

return $this;
}

public function getCommentReference(string $id)
{
if (!array_key_exists($id, $this->commentReferenceCache)) {
//dd($this->commentReferenceCache);
throw new InvalidArgumentException('Comment with id '.$id.' isn\'t referenced in document');
}
return $this->commentReferenceCache[$id];
}
}
72 changes: 44 additions & 28 deletions src/PhpWord/Reader/Word2007.php
Original file line number Diff line number Diff line change
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,24 +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',
'comments' => 'Comments'
]],
[
'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 @@ -73,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 @@ -84,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
96 changes: 86 additions & 10 deletions src/PhpWord/Reader/Word2007/AbstractPart.php
Original file line number Diff line number Diff line change
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,10 +191,18 @@ 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)) {
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));
$id = $node->attributes->getNamedItem('id')->value;
if ($node) {
$attributeIdentifier = $node->attributes->getNamedItem('id');
if ($attributeIdentifier) {
$id = $attributeIdentifier->nodeValue;
}
}
}
if ($xmlReader->elementExists('w:pPr', $domNode)) {
$paragraphStyle = $this->readParagraphStyle($xmlReader, $domNode);
Expand Down Expand Up @@ -235,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, array('w:ins', 'w:del', 'w:smartTag', 'w:hyperlink', 'w:commentReference'))) {
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 @@ -248,13 +321,15 @@ protected function readRun(XMLReader $xmlReader, DOMElement $domNode, $parent, $
}
}

if($xmlReader->elementExists('.//*["commentReference"=local-name()]', $domNode)) {
$curEl = iterator_to_array($xmlReader->getElements('.//*["commentReference"=local-name()]', $domNode))[0];
$id = $curEl->attributes->getNamedItem('id')->value;
//$path = './/*[("commentRangeStart"=local-name() or "commentRangeEnd"=local-name()) and @*[local-name()="id" and .="'.$id.'"]]';
//$range = $xmlReader->getElements($path);
$this->phpWord->cacheCommentReference('start', $id, $parent->getElement($parent->countElements() - 1));
$this->phpWord->cacheCommentReference('end', $id, $parent->getElement($parent->countElements() - 1));
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 @@ -353,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
Loading

0 comments on commit 0ea59e7

Please sign in to comment.