1 <?php
2 
3 namespace dokuwiki\plugin\prosemirror\parser;
4 
5 class 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