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 .= '';
+ $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);
+ }
+}