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