1<?php
2
3namespace dokuwiki\plugin\prosemirror\schema;
4
5/**
6 * Class Node
7 *
8 * @package dokuwiki\plugin\prosemirror\schema
9 * @link    http://prosemirror.net/ref.html#model.Node
10 */
11class Node implements \JsonSerializable
12{
13    /** @var  string The type of node that this is */
14    protected $type;
15
16    /** @var  Node[] holding the node's children */
17    protected $content = [];
18
19    /** @var  string For text nodes, this contains the node's text content. */
20    protected $text;
21
22    /** @var Mark[] The marks (things like whether it is emphasized or part of a link) associated with this node */
23    protected $marks = [];
24
25    /** @var array list of attributes */
26    protected $attrs = [];
27
28    /**
29     * Node constructor.
30     *
31     * @param string $type
32     */
33    public function __construct($type)
34    {
35        $this->type = $type;
36        if ($type == 'text') {
37            $this->setText('');
38        }
39    }
40
41    /**
42     * @param Node $child
43     */
44    public function addChild(Node $child)
45    {
46        if ($this->type == 'text') {
47            throw new \RuntimeException('TextNodes may not have children');
48        }
49        $this->content[] = $child;
50    }
51
52    /**
53     * @param Mark $mark
54     */
55    public function addMark(Mark $mark)
56    {
57        $this->marks[] = $mark;
58    }
59
60    /**
61     * @return string
62     */
63    public function getType()
64    {
65        return $this->type;
66    }
67
68    /**
69     * @return string
70     */
71    public function getText()
72    {
73        return $this->text;
74    }
75
76    /**
77     * @param string $text
78     */
79    public function setText($text)
80    {
81        if ($this->type != 'text') {
82            throw new \RuntimeException('Non-TextNodes may not have text');
83        }
84        $this->text = $text;
85    }
86
87    /**
88     * @param string $key   Attribute key to get or set
89     * @param null   $value Attribute value to set, null to get
90     *
91     * @return $this|mixed Either the wanted value or the Node itself
92     */
93    public function attr($key, $value = null)
94    {
95        if (is_null($value)) {
96            if (isset($this->attrs[$key])) {
97                return $this->attrs[$key];
98            } else {
99                return null;
100            }
101        }
102
103        $this->attrs[$key] = $value;
104        return $this;
105    }
106
107    /**
108     * Specify data which should be serialized to JSON
109     *
110     * @link  http://php.net/manual/en/jsonserializable.jsonserialize.php
111     * @return mixed data which can be serialized by <b>json_encode</b>,
112     * which is a value of any type other than a resource.
113     * @since 5.4.0
114     */
115    public function jsonSerialize()
116    {
117        $json = [
118            'type' => $this->type,
119        ];
120        if ($this->type == 'text') {
121            $json['text'] = $this->text;
122        } elseif ($this->content) {
123            $json['content'] = $this->content;
124        }
125
126        if ($this->marks) {
127            $json['marks'] = $this->marks;
128        }
129        if ($this->attrs) {
130            $json['attrs'] = $this->attrs;
131        }
132
133        return $json;
134    }
135
136    /**
137     * Check if any child nodes have been added to this node
138     *
139     * @return bool
140     */
141    public function hasContent()
142    {
143        return $this->content !== [];
144    }
145
146    /**
147     * Trim all whitespace from the beginning of this node's content
148     *
149     * If this is a text-node then this node's text is left-trimmed
150     *
151     * If the first node in the content is afterwards only an empty string, then it is removed
152     *
153     * @return void
154     */
155    public function trimContentLeft()
156    {
157        if ($this->hasContent()) {
158            $this->content[0]->trimContentLeft();
159            if ($this->content[0]->getText() === '') {
160                array_shift($this->content);
161            }
162            return;
163        }
164        if ($this->text !== null) {
165            $this->text = ltrim($this->text);
166        }
167    }
168
169    /**
170     * Trim all whitespace from the end of this node's content
171     *
172     * If this is a text-node then this node's text is right-trimmed
173     *
174     * If the last node in the content is afterwards only an empty string, then it is removed
175     *
176     * @return void
177     */
178    public function trimContentRight()
179    {
180        if ($this->hasContent()) {
181            $contentLength = count($this->content) - 1;
182            $this->content[$contentLength]->trimContentRight();
183            if ($this->content[$contentLength]->getText() === '') {
184                array_pop($this->content);
185            }
186            return;
187        }
188        if ($this->text !== null) {
189            $this->text = rtrim($this->text);
190        }
191    }
192}
193