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