Skip to content

Word2007 Reader : Added support for Comments #2469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
48 changes: 48 additions & 0 deletions src/PhpWord/PhpWord.php
Original file line number Diff line number Diff line change
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
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,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
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,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
Loading