1<?php
2
3/*
4 * This file is part of the league/commonmark package.
5 *
6 * (c) Colin O'Dell <colinodell@gmail.com>
7 *
8 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
9 *  - (c) John MacFarlane
10 *
11 * For the full copyright and license information, please view the LICENSE
12 * file that was distributed with this source code.
13 */
14
15namespace League\CommonMark\Block\Element;
16
17use League\CommonMark\ContextInterface;
18use League\CommonMark\Cursor;
19use League\CommonMark\Node\Node;
20
21/**
22 * Block-level element
23 *
24 * @method parent() ?AbstractBlock
25 */
26abstract class AbstractBlock extends Node
27{
28    /**
29     * Used for storage of arbitrary data.
30     *
31     * @var array<string, mixed>
32     */
33    public $data = [];
34
35    /**
36     * @var bool
37     */
38    protected $open = true;
39
40    /**
41     * @var bool
42     */
43    protected $lastLineBlank = false;
44
45    /**
46     * @var int
47     */
48    protected $startLine = 0;
49
50    /**
51     * @var int
52     */
53    protected $endLine = 0;
54
55    protected function setParent(Node $node = null)
56    {
57        if ($node && !$node instanceof self) {
58            throw new \InvalidArgumentException('Parent of block must also be block (can not be inline)');
59        }
60
61        parent::setParent($node);
62    }
63
64    public function isContainer(): bool
65    {
66        return true;
67    }
68
69    /**
70     * @return bool
71     */
72    public function hasChildren(): bool
73    {
74        return $this->firstChild !== null;
75    }
76
77    /**
78     * Returns true if this block can contain the given block as a child node
79     *
80     * @param AbstractBlock $block
81     *
82     * @return bool
83     */
84    abstract public function canContain(AbstractBlock $block): bool;
85
86    /**
87     * Whether this is a code block
88     *
89     * Code blocks are extra-greedy - they'll try to consume all subsequent
90     * lines of content without calling matchesNextLine() each time.
91     *
92     * @return bool
93     */
94    abstract public function isCode(): bool;
95
96    /**
97     * @param Cursor $cursor
98     *
99     * @return bool
100     */
101    abstract public function matchesNextLine(Cursor $cursor): bool;
102
103    /**
104     * @param int $startLine
105     *
106     * @return $this
107     */
108    public function setStartLine(int $startLine)
109    {
110        $this->startLine = $startLine;
111        if (empty($this->endLine)) {
112            $this->endLine = $startLine;
113        }
114
115        return $this;
116    }
117
118    /**
119     * @return int
120     */
121    public function getStartLine(): int
122    {
123        return $this->startLine;
124    }
125
126    /**
127     * @param int $endLine
128     *
129     * @return $this
130     */
131    public function setEndLine(int $endLine)
132    {
133        $this->endLine = $endLine;
134
135        return $this;
136    }
137
138    /**
139     * @return int
140     */
141    public function getEndLine(): int
142    {
143        return $this->endLine;
144    }
145
146    /**
147     * Whether the block ends with a blank line
148     *
149     * @return bool
150     */
151    public function endsWithBlankLine(): bool
152    {
153        return $this->lastLineBlank;
154    }
155
156    /**
157     * @param bool $blank
158     *
159     * @return void
160     */
161    public function setLastLineBlank(bool $blank)
162    {
163        $this->lastLineBlank = $blank;
164    }
165
166    /**
167     * Determines whether the last line should be marked as blank
168     *
169     * @param Cursor $cursor
170     * @param int    $currentLineNumber
171     *
172     * @return bool
173     */
174    public function shouldLastLineBeBlank(Cursor $cursor, int $currentLineNumber): bool
175    {
176        return $cursor->isBlank();
177    }
178
179    /**
180     * Whether the block is open for modifications
181     *
182     * @return bool
183     */
184    public function isOpen(): bool
185    {
186        return $this->open;
187    }
188
189    /**
190     * Finalize the block; mark it closed for modification
191     *
192     * @param ContextInterface $context
193     * @param int              $endLineNumber
194     *
195     * @return void
196     */
197    public function finalize(ContextInterface $context, int $endLineNumber)
198    {
199        if (!$this->open) {
200            return;
201        }
202
203        $this->open = false;
204        $this->endLine = $endLineNumber;
205
206        // This should almost always be true
207        if ($context->getTip() !== null) {
208            $context->setTip($context->getTip()->parent());
209        }
210    }
211
212    /**
213     * @param string $key
214     * @param mixed  $default
215     *
216     * @return mixed
217     */
218    public function getData(string $key, $default = null)
219    {
220        return \array_key_exists($key, $this->data) ? $this->data[$key] : $default;
221    }
222}
223