Skip to content

Commit 8557ccb

Browse files
committed
Ods Comments With Newlines
Fix #4081. Ods Reader was not reading entire contents of comment. On further inspection, Ods Writer also was not handling comments completely correctly. Ods comments are recorded as `text:p` children of `office:annotation` elements. A newline is inserted between successive `text:p` elements. The `text:p` element itself can have as descendants (at least): - raw text - `text:span` elements - `text:line-break` elements, which also causes the insertion of a newline Ods Writer is changed to use a single `text:p` with multiple span/linebreak elements. Ods Reader is changed to process in their entirety either that form, or multiple `text:p` elements. Styling of the individual elements of the comment is permitted in Ods. That has not been supported till now by PhpSpreadsheet, and this PR will not address that situation - Ods Reader hast little style support, and this would hardly be the most urgent case where it is missing.
1 parent 318a82e commit 8557ccb

File tree

4 files changed

+77
-8
lines changed

4 files changed

+77
-8
lines changed

src/PhpSpreadsheet/Reader/Ods.php

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -436,14 +436,25 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
436436

437437
if ($annotation->length > 0 && $annotation->item(0) !== null) {
438438
$textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p');
439+
$textNodeLength = $textNode->length;
440+
$newLineOwed = false;
441+
for ($textNodeIndex = 0; $textNodeIndex < $textNodeLength; ++$textNodeIndex) {
442+
$textNodeItem = $textNode->item($textNodeIndex);
443+
if ($textNodeItem !== null) {
444+
$text = $this->scanElementForText($textNodeItem);
445+
if ($newLineOwed) {
446+
$spreadsheet->getActiveSheet()
447+
->getComment($columnID . $rowID)
448+
->getText()
449+
->createText("\n");
450+
}
451+
$newLineOwed = true;
439452

440-
if ($textNode->length > 0 && $textNode->item(0) !== null) {
441-
$text = $this->scanElementForText($textNode->item(0));
442-
443-
$spreadsheet->getActiveSheet()
444-
->getComment($columnID . $rowID)
445-
->setText($this->parseRichText($text));
446-
// ->setAuthor( $author )
453+
$spreadsheet->getActiveSheet()
454+
->getComment($columnID . $rowID)
455+
->getText()
456+
->createText($this->parseRichText($text));
457+
}
447458
}
448459
}
449460

@@ -731,6 +742,8 @@ protected function scanElementForText(DOMNode $element): string
731742
/** @var DOMNode $child */
732743
if ($child->nodeType == XML_TEXT_NODE) {
733744
$str .= $child->nodeValue;
745+
} elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:line-break') {
746+
$str .= "\n";
734747
} elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') {
735748
// It's a space
736749

src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,22 @@ public static function write(XMLWriter $objWriter, Cell $cell): void
2424
$objWriter->writeAttribute('svg:x', $comment->getMarginLeft());
2525
$objWriter->writeAttribute('svg:y', $comment->getMarginTop());
2626
$objWriter->writeElement('dc:creator', $comment->getAuthor());
27-
$objWriter->writeElement('text:p', $comment->getText()->getPlainText());
27+
28+
$objWriter->startElement('text:p');
29+
$text = $comment->getText()->getPlainText();
30+
$textElements = explode("\n", $text);
31+
$newLineOwed = false;
32+
foreach ($textElements as $textSegment) {
33+
if ($newLineOwed) {
34+
$objWriter->writeElement('text:line-break');
35+
}
36+
$newLineOwed = true;
37+
if ($textSegment !== '') {
38+
$objWriter->writeElement('text:span', $textSegment);
39+
}
40+
}
41+
$objWriter->endElement(); // text:p
42+
2843
$objWriter->endElement();
2944
}
3045
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Reader\Ods;
6+
7+
use PhpOffice\PhpSpreadsheet\Reader\Ods;
8+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
10+
11+
class MultiLineCommentTest extends AbstractFunctional
12+
{
13+
public function testMultipleParagraphs(): void
14+
{
15+
$filename = 'tests/data/Reader/Ods/issue.4081.ods';
16+
$reader = new Ods();
17+
$spreadsheet = $reader->load($filename);
18+
$sheet = $spreadsheet->getActiveSheet();
19+
self::assertSame("First line.\n\nSecond line.", $sheet->getComment('A1')->getText()->getPlainText());
20+
$spreadsheet->disconnectWorksheets();
21+
}
22+
23+
public function testOneParagraphMultipleSpans(): void
24+
{
25+
$spreadsheetOld = new Spreadsheet();
26+
$sheetOld = $spreadsheetOld->getActiveSheet();
27+
$sheetOld->getCell('A1')->setValue('Hello');
28+
$text = $sheetOld->getComment('A1')->getText();
29+
$text->createText('First');
30+
$text->createText(' line.');
31+
$text->createText("\n");
32+
$text->createText("\n");
33+
$text->createText("Second line.\nThird line.");
34+
$spreadsheet = $this->writeAndReload($spreadsheetOld, 'Ods');
35+
$spreadsheetOld->disconnectWorksheets();
36+
37+
$sheet = $spreadsheet->getActiveSheet();
38+
self::assertSame("First line.\n\nSecond line.\nThird line.", $sheet->getComment('A1')->getText()->getPlainText());
39+
$spreadsheet->disconnectWorksheets();
40+
}
41+
}

tests/data/Reader/Ods/issue.4081.ods

3.07 KB
Binary file not shown.

0 commit comments

Comments
 (0)