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