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