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