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