From 84374a5ca694f8bbee4a6877dff548a94427c954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= Date: Mon, 1 Nov 2021 22:50:08 +0100 Subject: [PATCH 1/5] Add support for comments to reader --- src/PhpWord/PhpWord.php | 95 +++++++++++++++- src/PhpWord/Reader/Word2007/AbstractPart.php | 18 ++- src/PhpWord/Reader/Word2007/Comments.php | 109 +++++++++++++++++++ src/PhpWord/Reader/Word2007/Document.php | 7 +- 4 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 src/PhpWord/Reader/Word2007/Comments.php diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index 69efa76723..ce27ebc04a 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -18,6 +18,8 @@ namespace PhpOffice\PhpWord; use BadMethodCallException; +use InvalidArgumentException; +use PhpOffice\PhpWord\Element\AbstractElement; use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Exception\Exception; @@ -68,7 +70,14 @@ class PhpWord private $metadata = []; /** - * Create new instance. + * Comment reference cache + * + * @var array + */ + private $commentReferenceCache = []; + + /** + * Create new instance * * Collections are created dynamically */ @@ -325,4 +334,88 @@ 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; + } + + /** + * 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]; + } } diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 3ab8995f9e..7809628e14 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -126,6 +126,11 @@ 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)); + $id = $node->attributes->getNamedItem('id')->value; + } if ($xmlReader->elementExists('w:pPr', $domNode)) { $paragraphStyle = $this->readParagraphStyle($xmlReader, $domNode); $headingDepth = $this->getHeadingDepth($paragraphStyle); @@ -182,7 +187,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 { @@ -230,7 +235,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, array('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); @@ -242,6 +247,15 @@ protected function readRun(XMLReader $xmlReader, DOMElement $domNode, $parent, $ $this->readRunChild($xmlReader, $node, $parent, $docPart, $paragraphStyle, $fontStyle); } } + + 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)); + } } /** diff --git a/src/PhpWord/Reader/Word2007/Comments.php b/src/PhpWord/Reader/Word2007/Comments.php new file mode 100644 index 0000000000..18c2016bf2 --- /dev/null +++ b/src/PhpWord/Reader/Word2007/Comments.php @@ -0,0 +1,109 @@ +getDomFromZip($this->docFile, $this->xmlFile); + + //$xmlReader2 = new XMLReader(); + //$xmlReader2->getDomFromZip($this->docFile, 'word/document.xml'); + //dd($xmlReader2); + + $comments = $phpWord->getComments(); + + $nodes = $xmlReader->getElements('*'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $name = str_replace('w:', '', $node->nodeName); + $value = $xmlReader->getAttribute('w:author', $node); + $author = $xmlReader->getAttribute('w:author', $node); + $date = $xmlReader->getAttribute('w:date', $node); + $initials = $xmlReader->getAttribute('w:initials', $node); + $id = $xmlReader->getAttribute('w:id', $node); + $element = new Comment($author, new DateTime($date), $initials);//$this->getElement($phpWord, $id); + //$element->set + // $range = $xmlReader2->getElements('.//*[("commentRangeStart"=local-name() or "commentRangeEnd"=local-name()) and @*[local-name()="id" and .="'.$id.'"]]'); + try { + unset($range); + $range = $phpWord->getCommentReference($id); + $range->start->setCommentRangeStart($element); + $range->end->setCommentRangeEnd($element); + } catch(\Exception $e) { + //dd('range', [$element, $id, $node, $node->C14N(), $range ?? null, $e]); + } + //dd($startElement, $endElement, current(current($phpWord->getSections())->getElements())); + //dump($element, $range); + //dd($element, $node, $id, $node->C14N()); + $method = 'set' . $name; + //dump([$element, $id, $name, $value, $author, $date, $initials, $method, $xmlReader->getElements('w:p/w:r/w:t', $node)]); + //dd('dsf'); + $pNodes = $xmlReader->getElements('w:p/w:r', $node); + foreach ($pNodes as $pNode) { + //dump(['>', $xmlReader, $pNode, $node, $this->collection, '<']); + $this->readRun($xmlReader, $pNode, $element, $this->collection); + } + + /*if (in_array($name, $this::$booleanProperties)) { + if ($value == 'false') { + $comments->$method(false); + } else { + $comments->$method(true); + } + } else*/if (method_exists($this, $method)) { + $this->$method($xmlReader, $phpWord, $node); + } elseif (method_exists($comments, $method)) { + $comments->$method($value); + } elseif (method_exists($phpWord, $method)) { + $phpWord->$method($value); + } elseif (method_exists($comments, 'addItem')) { + $comments->addItem($element); + } + } + } + } + + /** + * 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}"; + //$getMethod = "getTrackChange"; + $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; + } +} diff --git a/src/PhpWord/Reader/Word2007/Document.php b/src/PhpWord/Reader/Word2007/Document.php index da42bddc9e..55d9d3a4eb 100644 --- a/src/PhpWord/Reader/Word2007/Document.php +++ b/src/PhpWord/Reader/Word2007/Document.php @@ -36,7 +36,7 @@ class Document extends AbstractPart * * @var \PhpOffice\PhpWord\PhpWord */ - private $phpWord; + protected $phpWord; /** * Read document.xml. @@ -170,4 +170,9 @@ private function readWSectPrNode(XMLReader $xmlReader, DOMElement $node, Section $section->setStyle($style); $this->readHeaderFooter($style, $section); } + + protected function cacheCommentReference(string $type, string $id, $element) + { + $this->phpWord->cacheCommentReference($type, $id, $element); + } } From 7222e6ce2f36347c1a101c3438e3b71b529a9384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= Date: Tue, 2 Nov 2021 19:13:43 +0100 Subject: [PATCH 2/5] Add test for comments in reader --- tests/PhpWord/Reader/Word2007Test.php | 97 ++++++++++++++++++ .../documents/reader-ooxml-comments.docx | Bin 0 -> 12895 bytes 2 files changed, 97 insertions(+) create mode 100644 tests/PhpWord/Reader/Word2007Test.php create mode 100644 tests/PhpWord/_files/documents/reader-ooxml-comments.docx diff --git a/tests/PhpWord/Reader/Word2007Test.php b/tests/PhpWord/Reader/Word2007Test.php new file mode 100644 index 0000000000..543bd80de9 --- /dev/null +++ b/tests/PhpWord/Reader/Word2007Test.php @@ -0,0 +1,97 @@ +assertTrue($object->canRead($filename)); + } + + /** + * Can read exception + */ + public function testCanReadFailed() + { + $object = new Word2007(); + $filename = __DIR__ . '/../_files/documents/foo.docx'; + $this->assertFalse($object->canRead($filename)); + } + + /** + * Load + */ + public function testLoad() + { + $filename = __DIR__ . '/../_files/documents/reader.docx'; + $phpWord = IOFactory::load($filename); + + $this->assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + $this->assertTrue($phpWord->getSettings()->hasDoNotTrackMoves()); + $this->assertFalse($phpWord->getSettings()->hasDoNotTrackFormatting()); + $this->assertEquals(100, $phpWord->getSettings()->getZoom()); + + $doc = TestHelperDOCX::getDocument($phpWord); + $this->assertEquals('0', $doc->getElementAttribute('/w:document/w:body/w:p/w:r[w:t/node()="italics"]/w:rPr/w:b', 'w:val')); + } + + /** + * Load a Word 2011 file + */ + public function testLoadWord2011() + { + $filename = __DIR__ . '/../_files/documents/reader-2011.docx'; + $phpWord = IOFactory::load($filename); + + $this->assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + + $doc = TestHelperDOCX::getDocument($phpWord); + $this->assertTrue($doc->elementExists('/w:document/w:body/w:p[3]/w:r/w:pict/v:shape/v:imagedata')); + } + + public function testLoadComments() + { + $filename = __DIR__ . '/../_files/documents/reader-ooxml-comments.docx'; + $phpWord = IOFactory::load($filename); + + $this->assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); + + //$doc = TestHelperDOCX::getDocument($phpWord); + $comment = new Comment('shaedrich', new DateTime('2021-10-28T13:56:00Z'), 'SH'); + $comment2 = $phpWord->getComments()[0]; + $this->assertEquals($comment->getAuthor(), $comment2->getAuthor()); + $this->assertEquals($comment->getInitials(), $comment2->getInitials()); + } +} diff --git a/tests/PhpWord/_files/documents/reader-ooxml-comments.docx b/tests/PhpWord/_files/documents/reader-ooxml-comments.docx new file mode 100644 index 0000000000000000000000000000000000000000..4748da68386adcf536f0657f5462ef6e40f20795 GIT binary patch literal 12895 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 literal 0 HcmV?d00001 From 400ea8cc2a67d6b4b9b6ed1dfc12feb0074a0a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= Date: Thu, 4 Nov 2021 15:24:14 +0100 Subject: [PATCH 3/5] Add comments step part to reader --- src/PhpWord/Reader/Word2007.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PhpWord/Reader/Word2007.php b/src/PhpWord/Reader/Word2007.php index 1febe0ff0a..f7c11c2868 100644 --- a/src/PhpWord/Reader/Word2007.php +++ b/src/PhpWord/Reader/Word2007.php @@ -57,7 +57,8 @@ public function load($docFile) ['stepPart' => 'document', 'stepItems' => [ 'endnotes' => 'Endnotes', 'footnotes' => 'Footnotes', - 'settings' => 'Settings', + 'settings' => 'Settings', + 'comments' => 'Comments' ]], ]; From 34db7cf3842c064963c522d6b22bbe969e159cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= Date: Sun, 7 Nov 2021 02:45:11 +0100 Subject: [PATCH 4/5] Fix wrong testing namespace --- src/PhpWord/Reader/Word2007/Comments.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpWord/Reader/Word2007/Comments.php b/src/PhpWord/Reader/Word2007/Comments.php index 18c2016bf2..4851e57938 100644 --- a/src/PhpWord/Reader/Word2007/Comments.php +++ b/src/PhpWord/Reader/Word2007/Comments.php @@ -1,6 +1,6 @@ Date: Wed, 13 Sep 2023 17:38:56 +0200 Subject: [PATCH 5/5] Word2007 Reader : Added support for Comments --- docs/changes/1.x/1.2.0.md | 1 + docs/index.md | 2 +- phpstan-baseline.neon | 10 -- src/PhpWord/Collection/AbstractCollection.php | 2 +- src/PhpWord/PhpWord.php | 53 +--------- src/PhpWord/Reader/Word2007.php | 72 ++++++++----- src/PhpWord/Reader/Word2007/AbstractPart.php | 96 +++++++++++++++-- src/PhpWord/Reader/Word2007/Comments.php | 97 ++++-------------- src/PhpWord/Reader/Word2007/Document.php | 7 +- tests/PhpWord/Reader/Word2007Test.php | 97 ------------------ .../Collection/CollectionTest.php | 17 ++- tests/PhpWordTests/Reader/Word2007Test.php | 41 ++++++++ .../_files/documents/reader-comments.docx} | Bin 13 files changed, 216 insertions(+), 279 deletions(-) delete mode 100644 tests/PhpWord/Reader/Word2007Test.php rename tests/{PhpWord/_files/documents/reader-ooxml-comments.docx => PhpWordTests/_files/documents/reader-comments.docx} (100%) diff --git a/docs/changes/1.x/1.2.0.md b/docs/changes/1.x/1.2.0.md index ba8e6bfb38..5b055ca7c0 100644 --- a/docs/changes/1.x/1.2.0.md +++ b/docs/changes/1.x/1.2.0.md @@ -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 diff --git a/docs/index.md b/docs/index.md index 1398fd90c4..bd38dd3238 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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 | | | | | | diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e4b89e68dd..9d6e6366d4 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -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 @@ -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 diff --git a/src/PhpWord/Collection/AbstractCollection.php b/src/PhpWord/Collection/AbstractCollection.php index 70c92689b8..78b5b891b8 100644 --- a/src/PhpWord/Collection/AbstractCollection.php +++ b/src/PhpWord/Collection/AbstractCollection.php @@ -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; diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index ce27ebc04a..da57f38d29 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -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; @@ -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 */ @@ -336,7 +327,7 @@ public function save($filename, $format = 'Word2007', $download = false) } /** - * Create new section + * Create new section. * * @deprecated 0.10.0 * @@ -352,7 +343,7 @@ public function createSection($settings = null) } /** - * Get document properties object + * Get document properties object. * * @deprecated 0.12.0 * @@ -366,7 +357,7 @@ public function getDocumentProperties() } /** - * Set document properties object + * Set document properties object. * * @deprecated 0.12.0 * @@ -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]; - } } diff --git a/src/PhpWord/Reader/Word2007.php b/src/PhpWord/Reader/Word2007.php index f7c11c2868..bb20a8fed2 100644 --- a/src/PhpWord/Reader/Word2007.php +++ b/src/PhpWord/Reader/Word2007.php @@ -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; @@ -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) { @@ -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(); } } } @@ -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> $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; } /** diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 7809628e14..dc61b09356 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -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; @@ -67,6 +69,13 @@ abstract class AbstractPart */ protected $rels = []; + /** + * Comment references. + * + * @var array> + */ + protected $commentRefs = []; + /** * Image Loading. * @@ -113,6 +122,62 @@ public function hasImageLoading(): bool return $this->imageLoading; } + /** + * Get comment references. + * + * @return array> + */ + public function getCommentReferences(): array + { + return $this->commentRefs; + } + + /** + * Set comment references. + * + * @param array> $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 + */ + 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. * @@ -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); @@ -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); @@ -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)); + } } } @@ -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); } } diff --git a/src/PhpWord/Reader/Word2007/Comments.php b/src/PhpWord/Reader/Word2007/Comments.php index 4851e57938..61b31713b5 100644 --- a/src/PhpWord/Reader/Word2007/Comments.php +++ b/src/PhpWord/Reader/Word2007/Comments.php @@ -5,13 +5,12 @@ use DateTime; use PhpOffice\PhpWord\Element\Comment; use PhpOffice\PhpWord\PhpWord; -use PhpOffice\PhpWord\Reader\Word2007\AbstractPart; use PhpOffice\PhpWord\Shared\XMLReader; class Comments extends AbstractPart { /** - * Collection name comments + * Collection name comments. * * @var string */ @@ -19,91 +18,39 @@ class Comments extends AbstractPart /** * Read settings.xml. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); - //$xmlReader2 = new XMLReader(); - //$xmlReader2->getDomFromZip($this->docFile, 'word/document.xml'); - //dd($xmlReader2); - $comments = $phpWord->getComments(); $nodes = $xmlReader->getElements('*'); - if ($nodes->length > 0) { - foreach ($nodes as $node) { - $name = str_replace('w:', '', $node->nodeName); - $value = $xmlReader->getAttribute('w:author', $node); - $author = $xmlReader->getAttribute('w:author', $node); - $date = $xmlReader->getAttribute('w:date', $node); - $initials = $xmlReader->getAttribute('w:initials', $node); - $id = $xmlReader->getAttribute('w:id', $node); - $element = new Comment($author, new DateTime($date), $initials);//$this->getElement($phpWord, $id); - //$element->set - // $range = $xmlReader2->getElements('.//*[("commentRangeStart"=local-name() or "commentRangeEnd"=local-name()) and @*[local-name()="id" and .="'.$id.'"]]'); - try { - unset($range); - $range = $phpWord->getCommentReference($id); - $range->start->setCommentRangeStart($element); - $range->end->setCommentRangeEnd($element); - } catch(\Exception $e) { - //dd('range', [$element, $id, $node, $node->C14N(), $range ?? null, $e]); - } - //dd($startElement, $endElement, current(current($phpWord->getSections())->getElements())); - //dump($element, $range); - //dd($element, $node, $id, $node->C14N()); - $method = 'set' . $name; - //dump([$element, $id, $name, $value, $author, $date, $initials, $method, $xmlReader->getElements('w:p/w:r/w:t', $node)]); - //dd('dsf'); - $pNodes = $xmlReader->getElements('w:p/w:r', $node); - foreach ($pNodes as $pNode) { - //dump(['>', $xmlReader, $pNode, $node, $this->collection, '<']); - $this->readRun($xmlReader, $pNode, $element, $this->collection); - } - /*if (in_array($name, $this::$booleanProperties)) { - if ($value == 'false') { - $comments->$method(false); - } else { - $comments->$method(true); - } - } else*/if (method_exists($this, $method)) { - $this->$method($xmlReader, $phpWord, $node); - } elseif (method_exists($comments, $method)) { - $comments->$method($value); - } elseif (method_exists($phpWord, $method)) { - $phpWord->$method($value); - } elseif (method_exists($comments, 'addItem')) { - $comments->addItem($element); - } - } - } - } + foreach ($nodes as $node) { + $name = str_replace('w:', '', $node->nodeName); - /** - * 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}"; - //$getMethod = "getTrackChange"; - $collection = $phpWord->$getMethod();//->getItems(); + $author = $xmlReader->getAttribute('w:author', $node); + $date = $xmlReader->getAttribute('w:date', $node); + $initials = $xmlReader->getAttribute('w:initials', $node); + + $element = new Comment($author, new DateTime($date), $initials); - //not found by key, looping to search by relationId - foreach ($collection as $collectionElement) { - if ($collectionElement->getRelationId() == $relationId) { - return $collectionElement; + $range = $this->getCommentReference($xmlReader->getAttribute('w:id', $node)); + if ($range['start']) { + $range['start']->setCommentRangeStart($element); + } + if ($range['end']) { + $range['end']->setCommentRangeEnd($element); } - } - return null; + $pNodes = $xmlReader->getElements('w:p/w:r', $node); + foreach ($pNodes as $pNode) { + $this->readRun($xmlReader, $pNode, $element, $this->collection); + } + + $phpWord->getComments()->addItem($element); + } } } diff --git a/src/PhpWord/Reader/Word2007/Document.php b/src/PhpWord/Reader/Word2007/Document.php index 55d9d3a4eb..da42bddc9e 100644 --- a/src/PhpWord/Reader/Word2007/Document.php +++ b/src/PhpWord/Reader/Word2007/Document.php @@ -36,7 +36,7 @@ class Document extends AbstractPart * * @var \PhpOffice\PhpWord\PhpWord */ - protected $phpWord; + private $phpWord; /** * Read document.xml. @@ -170,9 +170,4 @@ private function readWSectPrNode(XMLReader $xmlReader, DOMElement $node, Section $section->setStyle($style); $this->readHeaderFooter($style, $section); } - - protected function cacheCommentReference(string $type, string $id, $element) - { - $this->phpWord->cacheCommentReference($type, $id, $element); - } } diff --git a/tests/PhpWord/Reader/Word2007Test.php b/tests/PhpWord/Reader/Word2007Test.php deleted file mode 100644 index 543bd80de9..0000000000 --- a/tests/PhpWord/Reader/Word2007Test.php +++ /dev/null @@ -1,97 +0,0 @@ -assertTrue($object->canRead($filename)); - } - - /** - * Can read exception - */ - public function testCanReadFailed() - { - $object = new Word2007(); - $filename = __DIR__ . '/../_files/documents/foo.docx'; - $this->assertFalse($object->canRead($filename)); - } - - /** - * Load - */ - public function testLoad() - { - $filename = __DIR__ . '/../_files/documents/reader.docx'; - $phpWord = IOFactory::load($filename); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); - $this->assertTrue($phpWord->getSettings()->hasDoNotTrackMoves()); - $this->assertFalse($phpWord->getSettings()->hasDoNotTrackFormatting()); - $this->assertEquals(100, $phpWord->getSettings()->getZoom()); - - $doc = TestHelperDOCX::getDocument($phpWord); - $this->assertEquals('0', $doc->getElementAttribute('/w:document/w:body/w:p/w:r[w:t/node()="italics"]/w:rPr/w:b', 'w:val')); - } - - /** - * Load a Word 2011 file - */ - public function testLoadWord2011() - { - $filename = __DIR__ . '/../_files/documents/reader-2011.docx'; - $phpWord = IOFactory::load($filename); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); - - $doc = TestHelperDOCX::getDocument($phpWord); - $this->assertTrue($doc->elementExists('/w:document/w:body/w:p[3]/w:r/w:pict/v:shape/v:imagedata')); - } - - public function testLoadComments() - { - $filename = __DIR__ . '/../_files/documents/reader-ooxml-comments.docx'; - $phpWord = IOFactory::load($filename); - - $this->assertInstanceOf('PhpOffice\\PhpWord\\PhpWord', $phpWord); - - //$doc = TestHelperDOCX::getDocument($phpWord); - $comment = new Comment('shaedrich', new DateTime('2021-10-28T13:56:00Z'), 'SH'); - $comment2 = $phpWord->getComments()[0]; - $this->assertEquals($comment->getAuthor(), $comment2->getAuthor()); - $this->assertEquals($comment->getInitials(), $comment2->getInitials()); - } -} diff --git a/tests/PhpWordTests/Collection/CollectionTest.php b/tests/PhpWordTests/Collection/CollectionTest.php index 8f7e3c2ebf..9a18a2a75f 100644 --- a/tests/PhpWordTests/Collection/CollectionTest.php +++ b/tests/PhpWordTests/Collection/CollectionTest.php @@ -35,13 +35,26 @@ public function testCollection(): void $object = new Footnotes(); $object->addItem(new Footnote()); // addItem #1 - self::assertEquals(2, $object->addItem(new Footnote())); // addItem #2. Should returns new item index + self::assertEquals(1, $object->addItem(new Footnote())); // addItem #2. Should returns new item index self::assertCount(2, $object->getItems()); // getItems returns array - self::assertInstanceOf('PhpOffice\\PhpWord\\Element\\Footnote', $object->getItem(1)); // getItem returns object + self::assertInstanceOf(Footnote::class, $object->getItem(1)); // getItem returns object self::assertNull($object->getItem(3)); // getItem returns null when invalid index is referenced $object->setItem(2, null); // Set item #2 to null self::assertNull($object->getItem(2)); // Check if it's null } + + /** + * @covers ::setItem + */ + public function testCollectionSetItem(): void + { + $object = new Footnotes(); + $object->addItem(new Footnote()); + self::assertCount(1, $object->getItems()); + + $object->setItem(0, new Footnote()); + self::assertCount(1, $object->getItems()); + } } diff --git a/tests/PhpWordTests/Reader/Word2007Test.php b/tests/PhpWordTests/Reader/Word2007Test.php index 883dc84d53..e42f0110d5 100644 --- a/tests/PhpWordTests/Reader/Word2007Test.php +++ b/tests/PhpWordTests/Reader/Word2007Test.php @@ -17,11 +17,15 @@ namespace PhpOffice\PhpWordTests\Reader; +use DateTime; +use PhpOffice\PhpWord\Element\Comment; use PhpOffice\PhpWord\Element\Image; +use PhpOffice\PhpWord\Element\Text; use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\IOFactory; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Reader\Word2007; +use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWordTests\TestHelperDOCX; /** @@ -118,4 +122,41 @@ public function providerSettingsImageLoading(): iterable [false], ]; } + + public function testLoadComments(): void + { + $phpWord = IOFactory::load(dirname(__DIR__, 1) . '/_files/documents/reader-comments.docx'); + + self::assertInstanceOf(PhpWord::class, $phpWord); + + self::assertEquals(2, $phpWord->getComments()->countItems()); + + /** @var Comment $comment */ + $comment = $phpWord->getComments()->getItem(0); + self::assertInstanceOf(Comment::class, $comment); + self::assertEquals('shaedrich', $comment->getAuthor()); + self::assertEquals(new DateTime('2021-10-28T13:56:55Z'), $comment->getDate()); + self::assertEquals('SH', $comment->getInitials()); + self::assertCount(1, $comment->getElements()); + self::assertInstanceOf(Text::class, $comment->getElement(0)); + self::assertEquals('This this be lowercase', $comment->getElement(0)->getText()); + /** @var Font $fontStyle */ + $fontStyle = $comment->getElement(0)->getFontStyle(); + self::assertInstanceOf(Font::class, $fontStyle); + self::assertEquals('de-DE', $fontStyle->getLang()->getLatin()); + + /** @var Comment $comment */ + $comment = $phpWord->getComments()->getItem(1); + self::assertInstanceOf(Comment::class, $comment); + self::assertEquals('shaedrich', $comment->getAuthor()); + self::assertEquals(new DateTime('2021-11-02T19:10:00Z'), $comment->getDate()); + self::assertEquals('SH', $comment->getInitials()); + self::assertCount(1, $comment->getElements()); + self::assertInstanceOf(Text::class, $comment->getElement(0)); + self::assertEquals('But this should be uppercase', $comment->getElement(0)->getText()); + /** @var Font $fontStyle */ + $fontStyle = $comment->getElement(0)->getFontStyle(); + self::assertInstanceOf(Font::class, $fontStyle); + self::assertEquals('de-DE', $fontStyle->getLang()->getLatin()); + } } diff --git a/tests/PhpWord/_files/documents/reader-ooxml-comments.docx b/tests/PhpWordTests/_files/documents/reader-comments.docx similarity index 100% rename from tests/PhpWord/_files/documents/reader-ooxml-comments.docx rename to tests/PhpWordTests/_files/documents/reader-comments.docx