diff --git a/CHANGELOG.md b/CHANGELOG.md index bdc240d9..7a9f810e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Support cell comments in HTML writer and reader- [#308](https://github.com/PHPOffice/PhpSpreadsheet/issues/308) + ### Fixed - Better auto-detection of CSV separators - [#305](https://github.com/PHPOffice/PhpSpreadsheet/issues/305) diff --git a/docs/references/features-cross-reference.md b/docs/references/features-cross-reference.md index 4bac57a1..2d28cd4d 100644 --- a/docs/references/features-cross-reference.md +++ b/docs/references/features-cross-reference.md @@ -1231,14 +1231,14 @@ ● ● N/A - + ● 1 N/A Rich Text - ✖ 1 + ✖ 2 ✔ ✖ ✖ @@ -1256,7 +1256,7 @@ Alignment - ✖ 2 + ✖ 3 ✖ ✖ ✖ @@ -1568,5 +1568,6 @@ -1. Only BIFF8 files support Rich Text. Prior to that, comments could only be plain text -2. Only BIFF8 files support alignment and rotation. Prior to that, comments could only be unformatted text +1. Only text contents +2. Only BIFF8 files support Rich Text. Prior to that, comments could only be plain text +3. Only BIFF8 files support alignment and rotation. Prior to that, comments could only be unformatted text diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index a6720de6..43d3e66c 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -312,6 +312,14 @@ class Html extends BaseReader case 'em': case 'strong': case 'b': + if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') { + $sheet->getComment($column . $row) + ->getText() + ->createTextRun($child->textContent); + + break; + } + if ($cellContent > '') { $cellContent .= ' '; } @@ -354,6 +362,10 @@ class Html extends BaseReader } break; + case 'class': + if ($attributeValue === 'comment-indicator') { + break; // Ignore - it's just a red square. + } } } $cellContent .= ' '; diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index 7e1a0048..c0e4503e 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -830,6 +830,25 @@ class Html extends BaseWriter $css['html']['background-color'] = 'white'; } + // CSS for comments as found in LibreOffice + $css['a.comment-indicator:hover + div.comment'] = [ + 'background' => '#ffd', + 'position' => 'absolute', + 'display' => 'block', + 'border' => '1px solid black', + 'padding' => '0.5em', + ]; + + $css['a.comment-indicator'] = [ + 'background' => 'red', + 'display' => 'inline-block', + 'border' => '1px solid black', + 'width' => '0.5em', + 'height' => '0.5em', + ]; + + $css['div.comment']['display'] = 'none'; + // table { } $css['table']['border-collapse'] = 'collapse'; if (!$this->isPdf) { @@ -1385,6 +1404,8 @@ class Html extends BaseWriter } $html .= '>'; + $html .= $this->writeComment($pSheet, $coordinate); + // Image? $html .= $this->writeImageInCell($pSheet, $coordinate); @@ -1646,4 +1667,26 @@ class Html extends BaseWriter return "\n"; } + + /** + * Write a comment in the same format as LibreOffice. + * + * @see https://github.com/LibreOffice/core/blob/9fc9bf3240f8c62ad7859947ab8a033ac1fe93fa/sc/source/filter/html/htmlexp.cxx#L1073-L1092 + * + * @param Worksheet $pSheet + * @param string $coordinate + * + * @return string + */ + private function writeComment(Worksheet $pSheet, $coordinate) + { + $result = ''; + if (!$this->isPdf && isset($pSheet->getComments()[$coordinate])) { + $result .= ''; + $result .= '
' . nl2br($pSheet->getComment($coordinate)->getText()->getPlainText()) . '
'; + $result .= PHP_EOL; + } + + return $result; + } } diff --git a/tests/PhpSpreadsheetTests/Functional/HtmlCommentsTest.php b/tests/PhpSpreadsheetTests/Functional/HtmlCommentsTest.php new file mode 100644 index 00000000..1b497277 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Functional/HtmlCommentsTest.php @@ -0,0 +1,63 @@ +createText($valueSingle); + + $plainMulti = new RichText(); + $plainMulti->createText($valueMulti); + + $richSingle = new RichText(); + $richSingle->createTextRun($valueSingle)->getFont()->setBold(true); + + $richMultiSimple = new RichText(); + $richMultiSimple->createTextRun($valueMulti)->getFont()->setBold(true); + + $richMultiMixed = new RichText(); + $richMultiMixed->createText('I am' . PHP_EOL); + $richMultiMixed->createTextRun('multi-line')->getFont()->setBold(true); + $richMultiMixed->createText(PHP_EOL . 'comment!'); + + return [ + 'single line plain text' => [$plainSingle], + 'multi-line plain text' => [$plainMulti], + 'single line simple rich text' => [$richSingle], + 'multi-line simple rich text' => [$richMultiSimple], + 'multi-line mixed rich text' => [$richMultiMixed], + ]; + } + + /** + * @dataProvider providerCommentRichText + * + * @param mixed $richText + */ + public function testComments($richText) + { + $this->spreadsheet = new Spreadsheet(); + + $this->spreadsheet->getActiveSheet()->getCell('A1')->setValue('Comment'); + + $this->spreadsheet->getActiveSheet() + ->getComment('A1') + ->setText($richText); + + $reloadedSpreadsheet = $this->writeAndReload($this->spreadsheet, 'Html'); + + $actual = $reloadedSpreadsheet->getActiveSheet()->getComment('A1')->getText()->getPlainText(); + self::assertSame($richText->getPlainText(), $actual); + } +}