1<?php 2 3namespace dokuwiki\Parsing\Handler; 4 5class Lists extends AbstractRewriter 6{ 7 protected $listCalls = []; 8 protected $listStack = []; 9 10 protected $initialDepth = 0; 11 12 public const NODE = 1; 13 14 /** @inheritdoc */ 15 public function finalise() 16 { 17 $last_call = end($this->calls); 18 $this->writeCall(['list_close', [], $last_call[2]]); 19 20 $this->process(); 21 $this->callWriter->finalise(); 22 unset($this->callWriter); 23 } 24 25 /** @inheritdoc */ 26 public function process() 27 { 28 29 foreach ($this->calls as $call) { 30 switch ($call[0]) { 31 case 'list_item': 32 $this->listOpen($call); 33 break; 34 case 'list_open': 35 $this->listStart($call); 36 break; 37 case 'list_close': 38 $this->listEnd($call); 39 break; 40 default: 41 $this->listContent($call); 42 break; 43 } 44 } 45 46 $this->callWriter->writeCalls($this->listCalls); 47 return $this->callWriter; 48 } 49 50 protected function listStart($call) 51 { 52 $depth = $this->interpretSyntax($call[1][0], $listType); 53 54 $this->initialDepth = $depth; 55 // array(list type, current depth, index of current listitem_open) 56 $this->listStack[] = [$listType, $depth, 1]; 57 58 $this->listCalls[] = ['list' . $listType . '_open', [], $call[2]]; 59 $this->listCalls[] = ['listitem_open', [1], $call[2]]; 60 $this->listCalls[] = ['listcontent_open', [], $call[2]]; 61 } 62 63 64 protected function listEnd($call) 65 { 66 $closeContent = true; 67 68 while ($list = array_pop($this->listStack)) { 69 if ($closeContent) { 70 $this->listCalls[] = ['listcontent_close', [], $call[2]]; 71 $closeContent = false; 72 } 73 $this->listCalls[] = ['listitem_close', [], $call[2]]; 74 $this->listCalls[] = ['list' . $list[0] . '_close', [], $call[2]]; 75 } 76 } 77 78 protected function listOpen($call) 79 { 80 $depth = $this->interpretSyntax($call[1][0], $listType); 81 $end = end($this->listStack); 82 $key = key($this->listStack); 83 84 // Not allowed to be shallower than initialDepth 85 if ($depth < $this->initialDepth) { 86 $depth = $this->initialDepth; 87 } 88 89 if ($depth == $end[1]) { 90 // Just another item in the list... 91 if ($listType == $end[0]) { 92 $this->listCalls[] = ['listcontent_close', [], $call[2]]; 93 $this->listCalls[] = ['listitem_close', [], $call[2]]; 94 $this->listCalls[] = ['listitem_open', [$depth - 1], $call[2]]; 95 $this->listCalls[] = ['listcontent_open', [], $call[2]]; 96 97 // new list item, update list stack's index into current listitem_open 98 $this->listStack[$key][2] = count($this->listCalls) - 2; 99 100 // Switched list type... 101 } else { 102 $this->listCalls[] = ['listcontent_close', [], $call[2]]; 103 $this->listCalls[] = ['listitem_close', [], $call[2]]; 104 $this->listCalls[] = ['list' . $end[0] . '_close', [], $call[2]]; 105 $this->listCalls[] = ['list' . $listType . '_open', [], $call[2]]; 106 $this->listCalls[] = ['listitem_open', [$depth - 1], $call[2]]; 107 $this->listCalls[] = ['listcontent_open', [], $call[2]]; 108 109 array_pop($this->listStack); 110 $this->listStack[] = [$listType, $depth, count($this->listCalls) - 2]; 111 } 112 } elseif ($depth > $end[1]) { // Getting deeper... 113 $this->listCalls[] = ['listcontent_close', [], $call[2]]; 114 $this->listCalls[] = ['list' . $listType . '_open', [], $call[2]]; 115 $this->listCalls[] = ['listitem_open', [$depth - 1], $call[2]]; 116 $this->listCalls[] = ['listcontent_open', [], $call[2]]; 117 118 // set the node/leaf state of this item's parent listitem_open to NODE 119 $this->listCalls[$this->listStack[$key][2]][1][1] = self::NODE; 120 121 $this->listStack[] = [$listType, $depth, count($this->listCalls) - 2]; 122 } else { // Getting shallower ( $depth < $end[1] ) 123 $this->listCalls[] = ['listcontent_close', [], $call[2]]; 124 $this->listCalls[] = ['listitem_close', [], $call[2]]; 125 $this->listCalls[] = ['list' . $end[0] . '_close', [], $call[2]]; 126 127 // Throw away the end - done 128 array_pop($this->listStack); 129 130 while (1) { 131 $end = end($this->listStack); 132 $key = key($this->listStack); 133 134 if ($end[1] <= $depth) { 135 // Normalize depths 136 $depth = $end[1]; 137 138 $this->listCalls[] = ['listitem_close', [], $call[2]]; 139 140 if ($end[0] == $listType) { 141 $this->listCalls[] = ['listitem_open', [$depth - 1], $call[2]]; 142 $this->listCalls[] = ['listcontent_open', [], $call[2]]; 143 144 // new list item, update list stack's index into current listitem_open 145 $this->listStack[$key][2] = count($this->listCalls) - 2; 146 } else { 147 // Switching list type... 148 $this->listCalls[] = ['list' . $end[0] . '_close', [], $call[2]]; 149 $this->listCalls[] = ['list' . $listType . '_open', [], $call[2]]; 150 $this->listCalls[] = ['listitem_open', [$depth - 1], $call[2]]; 151 $this->listCalls[] = ['listcontent_open', [], $call[2]]; 152 153 array_pop($this->listStack); 154 $this->listStack[] = [$listType, $depth, count($this->listCalls) - 2]; 155 } 156 157 break; 158 159 // Haven't dropped down far enough yet.... ( $end[1] > $depth ) 160 } else { 161 $this->listCalls[] = ['listitem_close', [], $call[2]]; 162 $this->listCalls[] = ['list' . $end[0] . '_close', [], $call[2]]; 163 164 array_pop($this->listStack); 165 } 166 } 167 } 168 } 169 170 protected function listContent($call) 171 { 172 $this->listCalls[] = $call; 173 } 174 175 protected function interpretSyntax($match, &$type) 176 { 177 if (str_ends_with($match, '*')) { 178 $type = 'u'; 179 } else { 180 $type = 'o'; 181 } 182 // Is the +1 needed? It used to be count(explode(...)) 183 // but I don't think the number is seen outside this handler 184 return substr_count(str_replace("\t", ' ', $match), ' ') + 1; 185 } 186} 187