1<?php
2
3namespace dokuwiki\plugin\prosemirror\parser;
4
5class Mark
6{
7
8    public static $markOrder = [
9        'strong' => 1,
10        'underline' => 2,
11        'em' => 3,
12        'code' => 4,
13        'subscript' => 5,
14        'superscript' => 6,
15        'deleted' => 7,
16        'unformatted' => 99,
17    ];
18
19    protected $type;
20    protected $attrs;
21
22    protected $tailLength = 0;
23
24    /** @var  Mark */
25    protected $previousMark = null;
26
27    /** @var  Mark */
28    protected $nextMark = null;
29
30    /** @var  TextNode */
31    protected $parent;
32
33    public function __construct($data, &$parent)
34    {
35        $this->type = $data['type'];
36        if (isset($data['attrs'])) {
37            $this->attrs = $data['attrs'];
38        }
39        $this->parent = &$parent;
40    }
41
42    public function setPrevious($previousMark)
43    {
44        $this->previousMark = &$previousMark;
45    }
46
47    public function setNext($nextMark)
48    {
49        $this->nextMark = &$nextMark;
50    }
51
52    public function isOpeningMark()
53    {
54        return $this->parent->getStartingNodeMarkScore($this->type) === $this->getTailLength();
55    }
56
57    public function isClosingMark()
58    {
59        return $this->tailLength === 0;
60    }
61
62    public function incrementTail()
63    {
64        $this->tailLength += 1;
65    }
66
67    public function getTailLength()
68    {
69        return $this->tailLength;
70    }
71
72    public function getType()
73    {
74        return $this->type;
75    }
76
77    /**
78     * @param Mark      $newPrevious
79     * @param null|Mark $newNext
80     *
81     * @return Mark
82     */
83    public function switchPlaces(Mark $newPrevious, $newNext)
84    {
85        $oldPrevious = $this->previousMark;
86        $this->previousMark = &$newPrevious;
87        $this->nextMark = &$newNext;
88        if (null !== $newNext) {
89            $newNext->setPrevious($this);
90        }
91        return $oldPrevious;
92    }
93
94    public function sort()
95    {
96        if ($this->previousMark === null) {
97            return true;
98        }
99        if ($this->previousMark->getTailLength() > $this->tailLength) {
100            // the marks that ends later must be printed in front of those that end earlier
101            return true;
102        }
103        if ($this->previousMark->getTailLength() === $this->tailLength) {
104            if (self::$markOrder[$this->previousMark->getType()] < self::$markOrder[$this->type]) {
105                return true;
106            }
107        }
108
109        $newPrevious = $this->previousMark->switchPlaces($this, $this->nextMark);
110        $this->nextMark = &$this->previousMark;
111        $this->previousMark = &$newPrevious;
112        if (null !== $newPrevious) {
113            $newPrevious->setNext($this);
114        }
115
116        return false;
117    }
118
119    public function getFirst()
120    {
121        if (!$this->previousMark) {
122            return $this;
123        }
124        return $this->previousMark->getFirst();
125    }
126
127    public function getLast()
128    {
129        if (!$this->nextMark) {
130            return $this;
131        }
132        return $this->nextMark->getLast();
133    }
134
135    public function getPrevious()
136    {
137        return $this->previousMark;
138    }
139
140    public function getNext()
141    {
142        return $this->nextMark;
143    }
144
145    protected static $openingMarks = [
146        'strong' => '**',
147        'em' => '//',
148        'underline' => '__',
149        'code' => '\'\'',
150        'subscript' => '<sub>',
151        'superscript' => '<sup>',
152        'deleted' => '<del>',
153    ];
154
155    protected static $closingMarks = [
156        'strong' => '**',
157        'em' => '//',
158        'underline' => '__',
159        'code' => '\'\'',
160        'subscript' => '</sub>',
161        'superscript' => '</sup>',
162        'deleted' => '</del>',
163    ];
164
165    public function getOpeningSyntax()
166    {
167        if ($this->type !== 'unformatted') {
168            return self::$openingMarks[$this->type];
169        }
170        return $this->getUnformattedSyntax('opening');
171    }
172
173    public function getClosingSyntax()
174    {
175        if ($this->type !== 'unformatted') {
176            return self::$closingMarks[$this->type];
177        }
178
179        return $this->getUnformattedSyntax('closing');
180    }
181
182    /**
183     * Handle the edge case that %% is wrapped in nowiki syntax
184     *
185     * @param string $type 'opening' or 'closing'
186     *
187     * @return string
188     */
189    protected function getUnformattedSyntax($type)
190    {
191        if (strpos($this->parent->getInnerSyntax(), '%%') === false) {
192            return '%%';
193        }
194        if ($type === 'opening') {
195            return '<nowiki>';
196        }
197        return '</nowiki>';
198    }
199}
200