diff --git a/composer.json b/composer.json
index ccb628d..cef1d43 100644
--- a/composer.json
+++ b/composer.json
@@ -26,7 +26,7 @@
},
"require-dev": {
"phpunit/phpunit": "^9.0",
- "symfony/var-dumper": "^4.0|^5.0|^6.0"
+ "symfony/var-dumper": "^5.0|^6.0"
},
"scripts": {
"test": "php -d pcov.enabled=1 ./vendor/bin/phpunit --colors=always",
diff --git a/phpstan.neon b/phpstan.neon
index 54472b5..222bb84 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -7,4 +7,4 @@ parameters:
ignoreErrors:
# false positives
- '~Class DH\\Adf\\.* constructor invoked with \d parameters?, .* required\.~'
- - '~Parameter \#1 \$.* of class DH\\Adf\\Node\\.* constructor expects .*, DH\\Adf\\Node\\BlockNode\|null given\.~'
+ - '~Parameter \#1 \$.* of class DH\\Adf\\.* constructor expects .*, DH\\Adf\\Node\\(Block|Inline)Node(\|null)? given\.~'
diff --git a/src/Exporter/ExporterInterface.php b/src/Exporter/ExporterInterface.php
new file mode 100644
index 0000000..e7244d5
--- /dev/null
+++ b/src/Exporter/ExporterInterface.php
@@ -0,0 +1,10 @@
+tags = ['
', '
'];
+ }
+}
diff --git a/src/Exporter/Html/Block/BulletListExporter.php b/src/Exporter/Html/Block/BulletListExporter.php
new file mode 100644
index 0000000..ea06d04
--- /dev/null
+++ b/src/Exporter/Html/Block/BulletListExporter.php
@@ -0,0 +1,17 @@
+tags = [''];
+ }
+}
diff --git a/src/Exporter/Html/Block/CodeBlockExporter.php b/src/Exporter/Html/Block/CodeBlockExporter.php
new file mode 100644
index 0000000..3b36fff
--- /dev/null
+++ b/src/Exporter/Html/Block/CodeBlockExporter.php
@@ -0,0 +1,17 @@
+tags = ['', '
'];
+ }
+}
diff --git a/src/Exporter/Html/Block/DocumentExporter.php b/src/Exporter/Html/Block/DocumentExporter.php
new file mode 100644
index 0000000..199d077
--- /dev/null
+++ b/src/Exporter/Html/Block/DocumentExporter.php
@@ -0,0 +1,17 @@
+tags = ['', '
'];
+ }
+}
diff --git a/src/Exporter/Html/Block/ExpandExporter.php b/src/Exporter/Html/Block/ExpandExporter.php
new file mode 100644
index 0000000..aac00f9
--- /dev/null
+++ b/src/Exporter/Html/Block/ExpandExporter.php
@@ -0,0 +1,17 @@
+tags = [''.$node->getTitle().'
', '
'];
+ }
+}
diff --git a/src/Exporter/Html/Block/HeadingExporter.php b/src/Exporter/Html/Block/HeadingExporter.php
new file mode 100644
index 0000000..f3033b5
--- /dev/null
+++ b/src/Exporter/Html/Block/HeadingExporter.php
@@ -0,0 +1,20 @@
+tags = [
+ 'getLevel().'>',
+ 'getLevel().'>',
+ ];
+ }
+}
diff --git a/src/Exporter/Html/Block/MediaGroupExporter.php b/src/Exporter/Html/Block/MediaGroupExporter.php
new file mode 100644
index 0000000..019aea9
--- /dev/null
+++ b/src/Exporter/Html/Block/MediaGroupExporter.php
@@ -0,0 +1,17 @@
+tags = ['', '
'];
+ }
+}
diff --git a/src/Exporter/Html/Block/MediaSingleExporter.php b/src/Exporter/Html/Block/MediaSingleExporter.php
new file mode 100644
index 0000000..75b54a4
--- /dev/null
+++ b/src/Exporter/Html/Block/MediaSingleExporter.php
@@ -0,0 +1,17 @@
+tags = ['', '
'];
+ }
+}
diff --git a/src/Exporter/Html/Block/OrderedListExporter.php b/src/Exporter/Html/Block/OrderedListExporter.php
new file mode 100644
index 0000000..2133cd1
--- /dev/null
+++ b/src/Exporter/Html/Block/OrderedListExporter.php
@@ -0,0 +1,17 @@
+tags = ['', '
'];
+ }
+}
diff --git a/src/Exporter/Html/Block/PanelExporter.php b/src/Exporter/Html/Block/PanelExporter.php
new file mode 100644
index 0000000..76a54c9
--- /dev/null
+++ b/src/Exporter/Html/Block/PanelExporter.php
@@ -0,0 +1,54 @@
+node instanceof Panel);
+
+ switch ($this->node->getPanelType()) {
+ case Panel::ERROR:
+ $icon = '';
+
+ break;
+
+ case Panel::WARNING:
+ $icon = '';
+
+ break;
+
+ case Panel::SUCCESS:
+ $icon = '';
+
+ break;
+
+ case Panel::NOTE:
+ $icon = '';
+
+ break;
+
+ case Panel::INFO:
+ default:
+ $icon = '';
+
+ break;
+ }
+
+ $outputs = [];
+ foreach ($this->node->getContent() as $child) {
+ $class = self::NODE_MAPPING[$child::class];
+ $exporter = new $class($child);
+ $outputs[] = $exporter->export();
+ }
+ $output = implode('', $outputs);
+
+ return '';
+ }
+}
diff --git a/src/Exporter/Html/Block/ParagraphExporter.php b/src/Exporter/Html/Block/ParagraphExporter.php
new file mode 100644
index 0000000..c52c049
--- /dev/null
+++ b/src/Exporter/Html/Block/ParagraphExporter.php
@@ -0,0 +1,17 @@
+tags = ['', '
'];
+ }
+}
diff --git a/src/Exporter/Html/Block/RuleExporter.php b/src/Exporter/Html/Block/RuleExporter.php
new file mode 100644
index 0000000..97154f1
--- /dev/null
+++ b/src/Exporter/Html/Block/RuleExporter.php
@@ -0,0 +1,17 @@
+tags = ['
'];
+ }
+}
diff --git a/src/Exporter/Html/Block/TableExporter.php b/src/Exporter/Html/Block/TableExporter.php
new file mode 100644
index 0000000..6ad8d2f
--- /dev/null
+++ b/src/Exporter/Html/Block/TableExporter.php
@@ -0,0 +1,18 @@
+tags = [sprintf('', $node->getLayout()), '
'];
+ }
+}
diff --git a/src/Exporter/Html/Child/ListItemExporter.php b/src/Exporter/Html/Child/ListItemExporter.php
new file mode 100644
index 0000000..84d9e37
--- /dev/null
+++ b/src/Exporter/Html/Child/ListItemExporter.php
@@ -0,0 +1,17 @@
+tags = ['', ''];
+ }
+}
diff --git a/src/Exporter/Html/Child/MediaExporter.php b/src/Exporter/Html/Child/MediaExporter.php
new file mode 100644
index 0000000..fc67e02
--- /dev/null
+++ b/src/Exporter/Html/Child/MediaExporter.php
@@ -0,0 +1,19 @@
+tags = [''];
+// $this->tags = [''];
+ }
+}
diff --git a/src/Exporter/Html/Child/TableCellExporter.php b/src/Exporter/Html/Child/TableCellExporter.php
new file mode 100644
index 0000000..78496f3
--- /dev/null
+++ b/src/Exporter/Html/Child/TableCellExporter.php
@@ -0,0 +1,33 @@
+getBackground()) {
+ $attributes[] = sprintf('style="background-color: %s"', $node->getBackground());
+ }
+ if (null !== $node->getColspan()) {
+ $attributes[] = sprintf('colspan="%s"', $node->getColspan());
+ }
+ if (null !== $node->getRowspan()) {
+ $attributes[] = sprintf('rowspan="%s"', $node->getRowspan());
+ }
+ // TODO: support colwidth
+
+ $this->tags = [
+ sprintf('', implode(' ', $attributes)),
+ ' | ',
+ ];
+ }
+}
diff --git a/src/Exporter/Html/Child/TableHeaderExporter.php b/src/Exporter/Html/Child/TableHeaderExporter.php
new file mode 100644
index 0000000..e3a3de0
--- /dev/null
+++ b/src/Exporter/Html/Child/TableHeaderExporter.php
@@ -0,0 +1,17 @@
+tags = ['', ' | '];
+ }
+}
diff --git a/src/Exporter/Html/Child/TableRowExporter.php b/src/Exporter/Html/Child/TableRowExporter.php
new file mode 100644
index 0000000..c40c7f8
--- /dev/null
+++ b/src/Exporter/Html/Child/TableRowExporter.php
@@ -0,0 +1,17 @@
+tags = ['', '
'];
+ }
+}
diff --git a/src/Exporter/Html/HtmlExporter.php b/src/Exporter/Html/HtmlExporter.php
new file mode 100644
index 0000000..8eddd9f
--- /dev/null
+++ b/src/Exporter/Html/HtmlExporter.php
@@ -0,0 +1,164 @@
+ DocumentExporter::class,
+ Blockquote::class => BlockquoteExporter::class,
+ BulletList::class => BulletListExporter::class,
+ CodeBlock::class => CodeBlockExporter::class,
+ Heading::class => HeadingExporter::class,
+ MediaGroup::class => MediaGroupExporter::class,
+ MediaSingle::class => MediaSingleExporter::class,
+ OrderedList::class => OrderedListExporter::class,
+ Panel::class => PanelExporter::class,
+ Paragraph::class => ParagraphExporter::class,
+ Rule::class => RuleExporter::class,
+ Table::class => TableExporter::class,
+ Expand::class => ExpandExporter::class,
+
+ // child nodes
+ ListItem::class => ListItemExporter::class,
+ TableCell::class => TableCellExporter::class,
+ TableHeader::class => TableHeaderExporter::class,
+ TableRow::class => TableRowExporter::class,
+ Media::class => MediaExporter::class,
+ InlineCard::class => InlineCardExporter::class,
+
+ // inline nodes
+ Emoji::class => EmojiExporter::class,
+ Hardbreak::class => HardbreakExporter::class,
+ Mention::class => MentionExporter::class,
+ Text::class => TextExporter::class,
+ Status::class => StatusExporter::class,
+ Date::class => DateExporter::class,
+
+ // mark nodes
+ Em::class => EmExporter::class,
+ Strong::class => StrongExporter::class,
+ Code::class => CodeExporter::class,
+ Strike::class => StrikeExporter::class,
+ Subsup::class => SubsupExporter::class,
+ Underline::class => UnderlineExporter::class,
+ Link::class => LinkExporter::class,
+ TextColor::class => TextColorExporter::class,
+ ];
+
+ protected Node $node;
+ protected array $tags = [];
+
+ public function __construct(Node $node)
+ {
+ $this->node = $node;
+ }
+
+ public function export(): string
+ {
+ $outputs = [];
+
+ if ($this->node instanceof BlockNode) {
+ // $node has children (content) => iterate over them
+ foreach ($this->node->getContent() as $child) {
+ $class = self::NODE_MAPPING[$child::class];
+ $exporter = new $class($child);
+ $outputs[] = $exporter->export();
+ }
+ } elseif ($this->node instanceof InlineNode) {
+ // $node doesn't have children but can have marks
+ $class = self::NODE_MAPPING[$this->node::class];
+ $exporter = new $class($this->node);
+ $outputs[] = $exporter->export();
+ }
+
+ $output = implode('', $outputs);
+
+ if (0 === \count($this->tags)) {
+ // no wrapping tags
+ return $output;
+ }
+
+ if (1 === \count($this->tags)) {
+ // no closing tag
+ return $this->tags[0].$output;
+ }
+
+ // opening and closing tags
+ return $this->tags[0].$output.$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Inline/DateExporter.php b/src/Exporter/Html/Inline/DateExporter.php
new file mode 100644
index 0000000..a95bbf0
--- /dev/null
+++ b/src/Exporter/Html/Inline/DateExporter.php
@@ -0,0 +1,28 @@
+tags = ['', ''];
+ }
+
+ public function export(): string
+ {
+ \assert($this->node instanceof Date);
+
+ $timestamp = (int) $this->node->getTimestamp();
+ $date = (new DateTimeImmutable())->setTimestamp($timestamp / 1000);
+
+ return $this->tags[0].$date->format('Y-m-d').$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Inline/EmojiExporter.php b/src/Exporter/Html/Inline/EmojiExporter.php
new file mode 100644
index 0000000..111d3a0
--- /dev/null
+++ b/src/Exporter/Html/Inline/EmojiExporter.php
@@ -0,0 +1,22 @@
+node instanceof Emoji);
+
+ return sprintf(
+ '',
+ $this->node->getId(),
+ $this->node->getShortName()
+ );
+ }
+}
diff --git a/src/Exporter/Html/Inline/HardbreakExporter.php b/src/Exporter/Html/Inline/HardbreakExporter.php
new file mode 100644
index 0000000..0821f1a
--- /dev/null
+++ b/src/Exporter/Html/Inline/HardbreakExporter.php
@@ -0,0 +1,18 @@
+node instanceof Hardbreak);
+
+ return '
';
+ }
+}
diff --git a/src/Exporter/Html/Inline/InlineCardExporter.php b/src/Exporter/Html/Inline/InlineCardExporter.php
new file mode 100644
index 0000000..fa35e41
--- /dev/null
+++ b/src/Exporter/Html/Inline/InlineCardExporter.php
@@ -0,0 +1,25 @@
+tags = ['', '
'];
+ }
+
+ public function export(): string
+ {
+ \assert($this->node instanceof InlineCard);
+
+ return $this->tags[0].$this->node->getUrl().$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Inline/MentionExporter.php b/src/Exporter/Html/Inline/MentionExporter.php
new file mode 100644
index 0000000..ceeb34f
--- /dev/null
+++ b/src/Exporter/Html/Inline/MentionExporter.php
@@ -0,0 +1,24 @@
+tags = ['', ''];
+ }
+
+ public function export(): string
+ {
+ \assert($this->node instanceof Mention);
+
+ return $this->tags[0].$this->node->getText().$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Inline/StatusExporter.php b/src/Exporter/Html/Inline/StatusExporter.php
new file mode 100644
index 0000000..bf701d4
--- /dev/null
+++ b/src/Exporter/Html/Inline/StatusExporter.php
@@ -0,0 +1,24 @@
+tags = [sprintf('', $node->getColor()), ''];
+ }
+
+ public function export(): string
+ {
+ \assert($this->node instanceof Status);
+
+ return $this->tags[0].$this->node->getText().$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Inline/TextExporter.php b/src/Exporter/Html/Inline/TextExporter.php
new file mode 100644
index 0000000..70e8701
--- /dev/null
+++ b/src/Exporter/Html/Inline/TextExporter.php
@@ -0,0 +1,25 @@
+node instanceof Text);
+ $output = $this->node->getText();
+
+ foreach ($this->node->getMarks() as $mark) {
+ $class = self::NODE_MAPPING[$mark::class];
+ $exporter = new $class($mark, $output);
+ $output = $exporter->export();
+ }
+
+ return $output;
+ }
+}
diff --git a/src/Exporter/Html/Mark/CodeExporter.php b/src/Exporter/Html/Mark/CodeExporter.php
new file mode 100644
index 0000000..4f8f184
--- /dev/null
+++ b/src/Exporter/Html/Mark/CodeExporter.php
@@ -0,0 +1,25 @@
+text = $text;
+ $this->tags = ['', '
'];
+ }
+
+ public function export(): string
+ {
+ return $this->tags[0].$this->text.$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Mark/EmExporter.php b/src/Exporter/Html/Mark/EmExporter.php
new file mode 100644
index 0000000..fefca3c
--- /dev/null
+++ b/src/Exporter/Html/Mark/EmExporter.php
@@ -0,0 +1,25 @@
+text = $text;
+ $this->tags = ['', ''];
+ }
+
+ public function export(): string
+ {
+ return $this->tags[0].$this->text.$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Mark/LinkExporter.php b/src/Exporter/Html/Mark/LinkExporter.php
new file mode 100644
index 0000000..ab43930
--- /dev/null
+++ b/src/Exporter/Html/Mark/LinkExporter.php
@@ -0,0 +1,27 @@
+text = $text;
+ $this->tags = ['', ''];
+ }
+
+ public function export(): string
+ {
+ \assert($this->node instanceof Link);
+
+ return sprintf($this->tags[0], $this->node->getHref(), $this->node->getTitle()).$this->text.$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Mark/StrikeExporter.php b/src/Exporter/Html/Mark/StrikeExporter.php
new file mode 100644
index 0000000..1b6078a
--- /dev/null
+++ b/src/Exporter/Html/Mark/StrikeExporter.php
@@ -0,0 +1,25 @@
+text = $text;
+ $this->tags = ['', ''];
+ }
+
+ public function export(): string
+ {
+ return $this->tags[0].$this->text.$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Mark/StrongExporter.php b/src/Exporter/Html/Mark/StrongExporter.php
new file mode 100644
index 0000000..782ad19
--- /dev/null
+++ b/src/Exporter/Html/Mark/StrongExporter.php
@@ -0,0 +1,25 @@
+text = $text;
+ $this->tags = ['', ''];
+ }
+
+ public function export(): string
+ {
+ return $this->tags[0].$this->text.$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Mark/SubsupExporter.php b/src/Exporter/Html/Mark/SubsupExporter.php
new file mode 100644
index 0000000..c25699b
--- /dev/null
+++ b/src/Exporter/Html/Mark/SubsupExporter.php
@@ -0,0 +1,25 @@
+text = $text;
+ $this->tags = 'sub' === $node->getType() ? ['', ''] : ['', ''];
+ }
+
+ public function export(): string
+ {
+ return $this->tags[0].$this->text.$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Mark/TextColorExporter.php b/src/Exporter/Html/Mark/TextColorExporter.php
new file mode 100644
index 0000000..8d2e2f5
--- /dev/null
+++ b/src/Exporter/Html/Mark/TextColorExporter.php
@@ -0,0 +1,27 @@
+text = $text;
+ $this->tags = ['', ''];
+ }
+
+ public function export(): string
+ {
+ \assert($this->node instanceof TextColor);
+
+ return sprintf($this->tags[0], $this->node->getColor()).$this->text.$this->tags[1];
+ }
+}
diff --git a/src/Exporter/Html/Mark/UnderlineExporter.php b/src/Exporter/Html/Mark/UnderlineExporter.php
new file mode 100644
index 0000000..719db0d
--- /dev/null
+++ b/src/Exporter/Html/Mark/UnderlineExporter.php
@@ -0,0 +1,25 @@
+text = $text;
+ $this->tags = ['', ''];
+ }
+
+ public function export(): string
+ {
+ return $this->tags[0].$this->text.$this->tags[1];
+ }
+}
diff --git a/src/Node/Block/CodeBlock.php b/src/Node/Block/CodeBlock.php
index 85a7361..a4bc5fc 100644
--- a/src/Node/Block/CodeBlock.php
+++ b/src/Node/Block/CodeBlock.php
@@ -32,9 +32,8 @@ public function __construct(?string $language = null, ?BlockNode $parent = null)
public static function load(array $data, ?BlockNode $parent = null): self
{
self::checkNodeData(static::class, $data, ['attrs']);
- self::checkRequiredKeys(['language'], $data['attrs']);
- $node = new self($data['attrs']['language'], $parent);
+ $node = new self($data['attrs']['language'] ?? null, $parent);
// set content if defined
if (\array_key_exists('content', $data)) {
@@ -49,6 +48,11 @@ public static function load(array $data, ?BlockNode $parent = null): self
return $node;
}
+ public function getLanguage(): ?string
+ {
+ return $this->language;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Block/Expand.php b/src/Node/Block/Expand.php
index 2d8446f..532fd60 100644
--- a/src/Node/Block/Expand.php
+++ b/src/Node/Block/Expand.php
@@ -76,6 +76,11 @@ public static function load(array $data, ?BlockNode $parent = null): self
return $node;
}
+ public function getTitle(): ?string
+ {
+ return $this->title;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Block/Heading.php b/src/Node/Block/Heading.php
index bd9f785..aacb770 100644
--- a/src/Node/Block/Heading.php
+++ b/src/Node/Block/Heading.php
@@ -51,6 +51,11 @@ public static function load(array $data, ?BlockNode $parent = null): self
return $node;
}
+ public function getLevel(): int
+ {
+ return $this->level;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Block/MediaSingle.php b/src/Node/Block/MediaSingle.php
index 8bfcfb4..d14802e 100644
--- a/src/Node/Block/MediaSingle.php
+++ b/src/Node/Block/MediaSingle.php
@@ -72,6 +72,16 @@ public static function load(array $data, ?BlockNode $parent = null): self
return $node;
}
+ public function getLayout(): string
+ {
+ return $this->layout;
+ }
+
+ public function getWidth(): ?int
+ {
+ return $this->width;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Block/Panel.php b/src/Node/Block/Panel.php
index 0024397..ac6f6d2 100644
--- a/src/Node/Block/Panel.php
+++ b/src/Node/Block/Panel.php
@@ -67,6 +67,11 @@ public static function load(array $data, ?BlockNode $parent = null): self
return $node;
}
+ public function getPanelType(): string
+ {
+ return $this->panelType;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Block/Rule.php b/src/Node/Block/Rule.php
index e460d65..8ce29c7 100644
--- a/src/Node/Block/Rule.php
+++ b/src/Node/Block/Rule.php
@@ -4,13 +4,21 @@
namespace DH\Adf\Node\Block;
-use DH\Adf\Node\InlineNode;
+use DH\Adf\Node\BlockNode;
use JsonSerializable;
/**
* @see https://developer.atlassian.com/cloud/jira/platform/apis/document/nodes/rule
*/
-class Rule extends InlineNode implements JsonSerializable
+class Rule extends BlockNode implements JsonSerializable
{
protected string $type = 'rule';
+
+ public function jsonSerialize(): array
+ {
+ $result = parent::jsonSerialize();
+ unset($result['content']);
+
+ return $result;
+ }
}
diff --git a/src/Node/Block/Table.php b/src/Node/Block/Table.php
index acf0b94..21e3351 100644
--- a/src/Node/Block/Table.php
+++ b/src/Node/Block/Table.php
@@ -65,6 +65,21 @@ public static function load(array $data, ?BlockNode $parent = null): self
return $node;
}
+ public function getLayout(): string
+ {
+ return $this->layout;
+ }
+
+ public function isNumberColumnEnabled(): bool
+ {
+ return $this->isNumberColumnEnabled;
+ }
+
+ public function getLocalId(): ?string
+ {
+ return $this->localId;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/BlockNode.php b/src/Node/BlockNode.php
index e2136d9..9dac9bc 100644
--- a/src/Node/BlockNode.php
+++ b/src/Node/BlockNode.php
@@ -47,6 +47,11 @@ public static function load(array $data, ?self $parent = null): self
return $node;
}
+ public function getContent(): array
+ {
+ return $this->content;
+ }
+
protected function append(Node $node): void
{
foreach ($this->allowedContentTypes as $type) {
diff --git a/src/Node/Child/Media.php b/src/Node/Child/Media.php
index d8a4b4b..b3fbd3b 100644
--- a/src/Node/Child/Media.php
+++ b/src/Node/Child/Media.php
@@ -55,6 +55,36 @@ public static function load(array $data, ?BlockNode $parent = null): self
);
}
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ public function getMediaType(): string
+ {
+ return $this->mediaType;
+ }
+
+ public function getCollection(): string
+ {
+ return $this->collection;
+ }
+
+ public function getOccurrenceKey(): ?string
+ {
+ return $this->occurrenceKey;
+ }
+
+ public function getWidth(): ?int
+ {
+ return $this->width;
+ }
+
+ public function getHeight(): ?int
+ {
+ return $this->height;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Child/TableCell.php b/src/Node/Child/TableCell.php
index d59b91c..b46fc20 100644
--- a/src/Node/Child/TableCell.php
+++ b/src/Node/Child/TableCell.php
@@ -93,6 +93,26 @@ public static function load(array $data, ?BlockNode $parent = null): self
return $node;
}
+ public function getBackground(): ?string
+ {
+ return $this->background;
+ }
+
+ public function getColspan(): ?int
+ {
+ return $this->colspan;
+ }
+
+ public function getRowspan(): ?int
+ {
+ return $this->rowspan;
+ }
+
+ public function getColwidth(): ?array
+ {
+ return $this->colwidth;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Inline/Date.php b/src/Node/Inline/Date.php
index cb7f830..a6257b4 100644
--- a/src/Node/Inline/Date.php
+++ b/src/Node/Inline/Date.php
@@ -26,6 +26,11 @@ public static function load(array $data): self
return new self($data['attrs']['timestamp']);
}
+ public function getTimestamp(): string
+ {
+ return $this->timestamp;
+ }
+
protected function attrs(): array
{
return [
diff --git a/src/Node/Inline/Emoji.php b/src/Node/Inline/Emoji.php
index fff9d74..d68ed84 100644
--- a/src/Node/Inline/Emoji.php
+++ b/src/Node/Inline/Emoji.php
@@ -31,6 +31,21 @@ public static function load(array $data): self
return new self(trim($data['attrs']['shortName'], ' \t\n\r\0\x0B:'), $data['attrs']['id'] ?? null, $data['attrs']['text'] ?? null);
}
+ public function getShortName(): string
+ {
+ return $this->shortName;
+ }
+
+ public function getId(): ?string
+ {
+ return $this->id;
+ }
+
+ public function getText(): ?string
+ {
+ return $this->text;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Inline/InlineCard.php b/src/Node/Inline/InlineCard.php
index 3cba032..bf71a7a 100644
--- a/src/Node/Inline/InlineCard.php
+++ b/src/Node/Inline/InlineCard.php
@@ -35,6 +35,16 @@ public static function load(array $data, ?BlockNode $parent = null): self
return new self($data['attrs']['url'] ?? null, $data['attrs']['data'] ?? null);
}
+ public function getData(): ?string
+ {
+ return $this->data;
+ }
+
+ public function getUrl(): ?string
+ {
+ return $this->url;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Inline/Mention.php b/src/Node/Inline/Mention.php
index 8196744..e88a1f0 100644
--- a/src/Node/Inline/Mention.php
+++ b/src/Node/Inline/Mention.php
@@ -68,6 +68,31 @@ public static function load(array $data, ?BlockNode $parent = null): self
);
}
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ public function getText(): string
+ {
+ return $this->text;
+ }
+
+ public function getAccessLevel(): ?string
+ {
+ return $this->accessLevel;
+ }
+
+ public function getUserType(): ?string
+ {
+ return $this->userType;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Inline/Status.php b/src/Node/Inline/Status.php
index f44dcfd..7478599 100644
--- a/src/Node/Inline/Status.php
+++ b/src/Node/Inline/Status.php
@@ -57,6 +57,26 @@ public static function load(array $data): self
);
}
+ public function getText(): string
+ {
+ return $this->text;
+ }
+
+ public function getColor(): string
+ {
+ return $this->color;
+ }
+
+ public function getLocalId(): ?string
+ {
+ return $this->localId;
+ }
+
+ public function getStyle(): ?string
+ {
+ return $this->style;
+ }
+
protected function attrs(): array
{
$attrs = parent::attrs();
diff --git a/src/Node/Inline/Text.php b/src/Node/Inline/Text.php
index f0820b3..9b08575 100644
--- a/src/Node/Inline/Text.php
+++ b/src/Node/Inline/Text.php
@@ -15,6 +15,7 @@ class Text extends InlineNode
{
protected string $type = 'text';
private string $text;
+ private array $marks = [];
public function __construct(string $text, MarkNode ...$marks)
{
@@ -28,11 +29,19 @@ public function __construct(string $text, MarkNode ...$marks)
public function jsonSerialize(): array
{
$result = parent::jsonSerialize();
+ if ($this->marks) {
+ $result['marks'] = $this->marks;
+ }
$result['text'] = $this->text;
return $result;
}
+ public function getMarks(): array
+ {
+ return $this->marks;
+ }
+
public static function load(array $data): self
{
self::checkNodeData(static::class, $data, ['text']);
@@ -49,4 +58,14 @@ public static function load(array $data): self
return new self($data['text'], ...$args);
}
+
+ public function getText(): string
+ {
+ return $this->text;
+ }
+
+ protected function addMark(MarkNode $mark): void
+ {
+ $this->marks[] = $mark;
+ }
}
diff --git a/src/Node/InlineNode.php b/src/Node/InlineNode.php
index 464bfdb..1108c8b 100644
--- a/src/Node/InlineNode.php
+++ b/src/Node/InlineNode.php
@@ -6,20 +6,4 @@
abstract class InlineNode extends Node
{
- private array $marks = [];
-
- public function jsonSerialize(): array
- {
- $result = parent::jsonSerialize();
- if ($this->marks) {
- $result['marks'] = $this->marks;
- }
-
- return $result;
- }
-
- protected function addMark(MarkNode $mark): void
- {
- $this->marks[] = $mark;
- }
}
diff --git a/src/Node/Mark/Link.php b/src/Node/Mark/Link.php
index 099cf67..3de69a2 100644
--- a/src/Node/Mark/Link.php
+++ b/src/Node/Mark/Link.php
@@ -42,6 +42,36 @@ public static function load(array $data, ?BlockNode $parent = null): self
);
}
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ public function getHref(): string
+ {
+ return $this->href;
+ }
+
+ public function getTitle(): ?string
+ {
+ return $this->title;
+ }
+
+ public function getId(): ?string
+ {
+ return $this->id;
+ }
+
+ public function getCollection(): ?string
+ {
+ return $this->collection;
+ }
+
+ public function getOccurrenceKey(): ?string
+ {
+ return $this->occurrenceKey;
+ }
+
protected function attrs(): array
{
$attrs = [
diff --git a/src/Node/Mark/Subsup.php b/src/Node/Mark/Subsup.php
index 7d4512e..6767664 100644
--- a/src/Node/Mark/Subsup.php
+++ b/src/Node/Mark/Subsup.php
@@ -27,6 +27,11 @@ public static function load(array $data): self
return new self($data['attrs']['type'] ?? 'sup');
}
+ public function getType(): string
+ {
+ return $this->subType;
+ }
+
protected function attrs(): array
{
return [
diff --git a/src/Node/Mark/TextColor.php b/src/Node/Mark/TextColor.php
index 044ae9f..7f9c9c4 100644
--- a/src/Node/Mark/TextColor.php
+++ b/src/Node/Mark/TextColor.php
@@ -27,6 +27,11 @@ public static function load(array $data): self
return new self($data['attrs']['color'] ?? 'black');
}
+ public function getColor(): string
+ {
+ return $this->color;
+ }
+
protected function attrs(): array
{
return [
diff --git a/tests/Exporter/Html/Block/BlockquoteExporterTest.php b/tests/Exporter/Html/Block/BlockquoteExporterTest.php
new file mode 100644
index 0000000..281a2e3
--- /dev/null
+++ b/tests/Exporter/Html/Block/BlockquoteExporterTest.php
@@ -0,0 +1,49 @@
+', $exporter->export());
+ }
+
+ public function testBlockquoteWithText(): void
+ {
+ $node = (new Blockquote())
+ ->paragraph()
+ ->text('This is a text inside a paragraph wrapped into a blockquote.')
+ ->end()
+ ;
+ $exporter = new BlockquoteExporter($node);
+
+ self::assertSame('This is a text inside a paragraph wrapped into a blockquote.
', $exporter->export());
+ }
+
+ public function testBlockquoteWithEmoji(): void
+ {
+ $node = (new Blockquote())
+ ->paragraph()
+ ->emoji('poop', '1f4a9', '\\ud83d\\udca9')
+ ->end()
+ ;
+ $exporter = new BlockquoteExporter($node);
+
+ self::assertSame('
', $exporter->export());
+ }
+}
diff --git a/tests/Exporter/Html/Block/DocumentExporterTest.php b/tests/Exporter/Html/Block/DocumentExporterTest.php
new file mode 100644
index 0000000..2b69715
--- /dev/null
+++ b/tests/Exporter/Html/Block/DocumentExporterTest.php
@@ -0,0 +1,334 @@
+', $exporter->export());
+ }
+
+ public function testDocumentWithEmptyParagraph(): void
+ {
+ $document = (new Document())
+ ->paragraph()
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithTextInParagraph(): void
+ {
+ $document = (new Document())
+ ->paragraph()
+ ->color('Luke, ', 'red')
+ ->text('may ')
+ ->em('the ')
+ ->strong('force ')
+ ->text('be ')
+ ->underline('with ')
+ ->strike('you! ')
+ ->sub('Obi-Wan')
+ ->sup('Kenobi')
+ ->Link('Star Wars @ Wikipedia', 'https://wikipedia.org/wiki/Star_Wars')
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithMentionInParagraph(): void
+ {
+ $document = (new Document())
+ ->paragraph()
+ ->mention('ABCDE-ABCDE-ABCDE-ABCDE', '@DarkVador', Mention::ACCESS_LEVEL_APPLICATION)
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithMultipleTextNodes(): void
+ {
+ $document = (new Document())
+ ->paragraph()
+ ->text('Hello world')
+ ->text('How are you')
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithBlockquote(): void
+ {
+ $document = (new Document())
+ ->blockquote()
+ ->paragraph()
+ ->text('quoted text')
+ ->end()
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithHeading(): void
+ {
+ $document = (new Document())
+ ->heading(3)
+ ->text('heading text')
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('heading text
', $exporter->export());
+ }
+
+ public function testDocumentWithCodeblock(): void
+ {
+ $document = (new Document())
+ ->codeblock('php')
+ ->text('var_dump($foo);')
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithBulletList(): void
+ {
+ $document = (new Document())
+ ->bulletlist()
+ ->item()
+ ->paragraph()
+ ->text('item 1')
+ ->end()
+ ->end()
+ ->item()
+ ->paragraph()
+ ->text('item 2')
+ ->end()
+ ->end()
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithOrderedList(): void
+ {
+ $document = (new Document())
+ ->orderedlist()
+ ->item()
+ ->paragraph()
+ ->text('item 1')
+ ->end()
+ ->end()
+ ->item()
+ ->paragraph()
+ ->text('item 2')
+ ->end()
+ ->end()
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithPanel(): void
+ {
+ $document = (new Document())
+ ->panel(Panel::INFO)
+ ->paragraph()
+ ->text('panel text')
+ ->end()
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithRule(): void
+ {
+ $document = (new Document())
+ ->rule()
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('
', $exporter->export());
+ }
+
+ public function testDocumentWithMediaSingle(): void
+ {
+ $document = (new Document())
+ ->mediaSingle(MediaSingle::LAYOUT_WIDE)
+ ->media('6e7c7f2c-dd7a-499c-bceb-6f32bfbf30b5', Media::TYPE_FILE, 'my project files', 100, 200)
+ ->end()
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithMediaGroup(): void
+ {
+ $document = (new Document())
+ ->mediaGroup()
+ ->media('6e7c7f2c-dd7a-499c-bceb-6f32bfbf30b5', Media::TYPE_FILE, 'my project files', 100, 200)
+ ->end()
+ ->media('7a7c7f2c-dd7a-499c-bceb-6f32bfbf30c7', Media::TYPE_FILE, 'my project files', 100, 200)
+ ->end()
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('', $exporter->export());
+ }
+
+ public function testDocumentWithTable(): void
+ {
+ $document = (new Document())
+ ->table(Table::LAYOUT_DEFAULT)
+ ->row()
+ ->header()
+ ->paragraph()
+ ->text('header 1')
+ ->end()
+ ->end()
+ ->header()
+ ->paragraph()
+ ->text('header 2')
+ ->end()
+ ->end()
+ ->end()
+ ->row()
+ ->cell()
+ ->paragraph()
+ ->text('cell 1')
+ ->end()
+ ->end()
+ ->cell()
+ ->paragraph()
+ ->text('cell 2')
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ;
+ $exporter = new DocumentExporter($document);
+
+ self::assertSame('header 1 | header 2 |
---|
cell 1 | cell 2 |
', $exporter->export());
+ }
+
+ public function testLoad(): void
+ {
+ $json = <<<'TXT'
+{"version":1,"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"En-t\u00eate #1"}]},{"type":"paragraph","content":[{"type":"text","text":"lorem","marks":[{"type":"strong"},{"type":"textColor","attrs":{"color":"#008da6"}}]},{"type":"text","text":" "},{"type":"text","text":"ipsum","marks":[{"type":"textColor","attrs":{"color":"#6554c0"}}]},{"type":"text","text":" "},{"type":"text","text":"dolor","marks":[{"type":"strong"},{"type":"textColor","attrs":{"color":"#4c9aff"}}]},{"type":"text","text":" "},{"type":"text","text":"sit","marks":[{"type":"textColor","attrs":{"color":"#00b8d9"}}]},{"type":"text","text":" "},{"type":"text","text":"amet","marks":[{"type":"strong"},{"type":"textColor","attrs":{"color":"#ffc400"}}]}]},{"type":"paragraph","content":[{"type":"text","text":"lorem","marks":[{"type":"strong"}]},{"type":"text","text":" "},{"type":"text","text":"ipsum","marks":[{"type":"em"}]},{"type":"text","text":" "},{"type":"text","text":"dolor","marks":[{"type":"underline"}]},{"type":"text","text":" "},{"type":"text","text":"sit","marks":[{"type":"strike"}]},{"type":"text","text":" "},{"type":"text","text":"amet","marks":[{"type":"code"}]}]},{"type":"paragraph","content":[{"type":"text","text":"lorem","marks":[{"type":"strong"},{"type":"subsup","attrs":{"type":"sub"}}]},{"type":"text","text":" "},{"type":"text","text":"ipsum","marks":[{"type":"em"},{"type":"subsup","attrs":{"type":"sup"}}]}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 2"}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 2.1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 2.2"}]}]}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 3"}]}]}]},{"type":"paragraph","content":[{"type":"text","text":"This is a link!","marks":[{"type":"link","attrs":{"href":"https://www.google.fr","title":"Google"}}]}]},{"type":"heading","attrs":{"level":2},"content":[{"type":"text","text":"En-t\u00eate #2"}]},{"type":"paragraph","content":[{"type":"text","text":"lorem ipsum"}]},{"type":"orderedList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 2"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 3"}]},{"type":"orderedList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 3.1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 3.2"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"type":"text","text":"En-t\u00eate #3"}]},{"type":"paragraph","content":[{"type":"text","text":"lorem ipsum"}]},{"type":"paragraph","content":[{"type":"inlineCard","attrs":{"url":"https:\/\/github.com"}},{"type":"text","text":" "}]},{"type":"mediaSingle","attrs":{"layout":"align-start"},"content":[{"type":"media","attrs":{"id":"d7174b86-2fb7-4126-991d-0e2fc9f0dac7","type":"file","collection":"","width":483,"height":130}}]},{"type":"paragraph","content":[{"type":"text","text":"Yo "},{"type":"mention","attrs":{"id":"60196ed3cd564b0068440c33","text":"@Damien Harper","accessLevel":""}},{"type":"text","text":", tu peux check ce ticket please "},{"type":"emoji","attrs":{"shortName":":poop:","id":"1f4a9","text":"\ud83d\udca9"}},{"type":"text","text":" "}]},{"type":"table","attrs":{"isNumberColumnEnabled":false,"layout":"default","localId":"e77ccf12-2262-42ea-a9f2-979c14ff1efc"},"content":[{"type":"tableRow","content":[{"type":"tableHeader","content":[{"type":"paragraph","content":[{"type":"text","text":"Col 1","marks":[{"type":"strong"}]}]}]},{"type":"tableHeader","content":[{"type":"paragraph","content":[{"type":"text","text":"Col 2","marks":[{"type":"strong"}]}]}]},{"type":"tableHeader","content":[{"type":"paragraph","content":[{"type":"text","text":"Col 3","marks":[{"type":"strong"}]}]}]}]},{"type":"tableRow","content":[{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"lorem","marks":[{"type":"strong"}]}]}]},{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"ipsum","marks":[{"type":"em"}]}]}]},{"type":"tableCell","content":[{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 2"}]}]}]}]}]},{"type":"tableRow","content":[{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"dolor"}]}]},{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"sit"}]}]},{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"amet","marks":[{"type":"strong"},{"type":"textColor","attrs":{"color":"#00b8d9"}},{"type":"underline"}]}]}]}]}]},{"type":"rule"},{"type":"codeBlock","attrs":{"language":"php"},"content":[{"type":"text","text":"version;\n\n return $result;\n }\n}\n"}]},{"type":"panel","attrs":{"panelType":"info"},"content":[{"type":"paragraph","content":[{"type":"text","text":"Info news"}]},{"type":"orderedList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item "},{"type":"text","text":"1","marks":[{"type":"strong"}]}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item "},{"type":"text","text":"2","marks":[{"type":"strong"}]}]}]}]}]},{"type":"panel","attrs":{"panelType":"warning"},"content":[{"type":"paragraph","content":[{"type":"text","text":"warning!"}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"type":"text","text":"lorem ipsum ","marks":[{"type":"em"}]}]},{"type":"paragraph","content":[{"type":"text","text":"dolor sit amet","marks":[{"type":"em"}]}]}]},{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"En-t\u00eate #1 bis"}]},{"type":"paragraph","content":[{"type":"status","attrs":{"text":"In progress","color":"blue","localId":"05c0d929-8e76-4469-94f8-0762cc0216bf","style":""}},{"type":"text","text":" "},{"type":"status","attrs":{"text":"TODO","color":"neutral","localId":"9163f1e7-d30a-451b-90d3-eb43b505b38c","style":""}},{"type":"text","text":" "},{"type":"status","attrs":{"text":"Done","color":"green","localId":"bf2a0f38-c488-49ab-8faa-e749c6e37841","style":""}},{"type":"text","text":" "}]},{"type":"paragraph","content":[{"type":"date","attrs":{"timestamp":"1655424000000"}},{"type":"text","text":" "}]},{"type":"paragraph","content":[]},{"type":"expand","attrs":{"title":"Section masquable"},"content":[{"type":"paragraph","content":[{"type":"text","text":"Texte qui peut se cacher "},{"type":"emoji","attrs":{"shortName":":slight_smile:","id":"1f642","text":"\ud83d\ude42"}},{"type":"text","text":" "}]}]}]}
+TXT;
+ $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
+ $document = Document::load($data);
+
+ \assert($document instanceof Document);
+ $exporter = new DocumentExporter($document);
+
+ $expected = <<<'TXT'
+En-tête #1
lorem ipsum dolor sit amet
lorem ipsum dolor sit
amet
lorem ipsum
This is a link!
En-tête #2
lorem ipsum
item 1
item 2
item 3
item 3.1
item 3.2
En-tête #3
lorem ipsum
https://github.com
Yo @Damien Harper, tu peux check ce ticket please
Col 1 | Col 2 | Col 3 |
---|
lorem | ipsum | |
dolor | sit | amet |
version;
+
+ return $result;
+ }
+}
+
lorem ipsum
dolor sit amet
En-tête #1 bis
In progress TODO Done
2022-06-17
Section masquable
Texte qui peut se cacher
+TXT;
+
+ self::assertSame($expected, $exporter->export());
+ }
+}