[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 namespace dokuwiki\Parsing\Handler; 4 5 class Table extends AbstractRewriter 6 { 7 protected $tableCalls = []; 8 protected $maxCols = 0; 9 protected $maxRows = 1; 10 protected $currentCols = 0; 11 protected $firstCell = false; 12 protected $lastCellType = 'tablecell'; 13 protected $inTableHead = true; 14 protected $currentRow = ['tableheader' => 0, 'tablecell' => 0]; 15 protected $countTableHeadRows = 0; 16 17 /** @inheritdoc */ 18 public function finalise() 19 { 20 $last_call = end($this->calls); 21 $this->writeCall(['table_end', [], $last_call[2]]); 22 23 $this->process(); 24 $this->callWriter->finalise(); 25 unset($this->callWriter); 26 } 27 28 /** @inheritdoc */ 29 public function process() 30 { 31 foreach ($this->calls as $call) { 32 switch ($call[0]) { 33 case 'table_start': 34 $this->tableStart($call); 35 break; 36 case 'table_row': 37 $this->tableRowClose($call); 38 $this->tableRowOpen(['tablerow_open', $call[1], $call[2]]); 39 break; 40 case 'tableheader': 41 case 'tablecell': 42 $this->tableCell($call); 43 break; 44 case 'table_end': 45 $this->tableRowClose($call); 46 $this->tableEnd($call); 47 break; 48 default: 49 $this->tableDefault($call); 50 break; 51 } 52 } 53 $this->callWriter->writeCalls($this->tableCalls); 54 55 return $this->callWriter; 56 } 57 58 protected function tableStart($call) 59 { 60 $this->tableCalls[] = ['table_open', $call[1], $call[2]]; 61 $this->tableCalls[] = ['tablerow_open', [], $call[2]]; 62 $this->firstCell = true; 63 } 64 65 protected function tableEnd($call) 66 { 67 $this->tableCalls[] = ['table_close', $call[1], $call[2]]; 68 $this->finalizeTable(); 69 } 70 71 protected function tableRowOpen($call) 72 { 73 $this->tableCalls[] = $call; 74 $this->currentCols = 0; 75 $this->firstCell = true; 76 $this->lastCellType = 'tablecell'; 77 $this->maxRows++; 78 if ($this->inTableHead) { 79 $this->currentRow = ['tablecell' => 0, 'tableheader' => 0]; 80 } 81 } 82 83 protected function tableRowClose($call) 84 { 85 if ($this->inTableHead && ($this->inTableHead = $this->isTableHeadRow())) { 86 $this->countTableHeadRows++; 87 } 88 // Strip off final cell opening and anything after it 89 while ($discard = array_pop($this->tableCalls)) { 90 if ($discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') { 91 break; 92 } 93 if (!empty($this->currentRow[$discard[0]])) { 94 $this->currentRow[$discard[0]]--; 95 } 96 } 97 $this->tableCalls[] = ['tablerow_close', [], $call[2]]; 98 99 if ($this->currentCols > $this->maxCols) { 100 $this->maxCols = $this->currentCols; 101 } 102 } 103 104 protected function isTableHeadRow() 105 { 106 $td = $this->currentRow['tablecell']; 107 $th = $this->currentRow['tableheader']; 108 109 if (!$th || $td > 2) return false; 110 if (2 * $td > $th) return false; 111 112 return true; 113 } 114 115 protected function tableCell($call) 116 { 117 if ($this->inTableHead) { 118 $this->currentRow[$call[0]]++; 119 } 120 if (!$this->firstCell) { 121 // Increase the span 122 $lastCall = end($this->tableCalls); 123 124 // A cell call which follows an open cell means an empty cell so span 125 if ($lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open') { 126 $this->tableCalls[] = ['colspan', [], $call[2]]; 127 } 128 129 $this->tableCalls[] = [$this->lastCellType . '_close', [], $call[2]]; 130 $this->tableCalls[] = [$call[0] . '_open', [1, null, 1], $call[2]]; 131 $this->lastCellType = $call[0]; 132 } else { 133 $this->tableCalls[] = [$call[0] . '_open', [1, null, 1], $call[2]]; 134 $this->lastCellType = $call[0]; 135 $this->firstCell = false; 136 } 137 138 $this->currentCols++; 139 } 140 141 protected function tableDefault($call) 142 { 143 $this->tableCalls[] = $call; 144 } 145 146 protected function finalizeTable() 147 { 148 149 // Add the max cols and rows to the table opening 150 if ($this->tableCalls[0][0] == 'table_open') { 151 // Adjust to num cols not num col delimeters 152 $this->tableCalls[0][1][] = $this->maxCols - 1; 153 $this->tableCalls[0][1][] = $this->maxRows; 154 $this->tableCalls[0][1][] = array_shift($this->tableCalls[0][1]); 155 } else { 156 trigger_error('First element in table call list is not table_open'); 157 } 158 159 $lastRow = 0; 160 $lastCell = 0; 161 $cellKey = []; 162 $toDelete = []; 163 164 // if still in tableheader, then there can be no table header 165 // as all rows can't be within <THEAD> 166 if ($this->inTableHead) { 167 $this->inTableHead = false; 168 $this->countTableHeadRows = 0; 169 } 170 // Look for the colspan elements and increment the colspan on the 171 // previous non-empty opening cell. Once done, delete all the cells 172 // that contain colspans 173 $counter = count($this->tableCalls); 174 175 // Look for the colspan elements and increment the colspan on the 176 // previous non-empty opening cell. Once done, delete all the cells 177 // that contain colspans 178 for ($key = 0; $key < $counter; ++$key) { 179 $call = $this->tableCalls[$key]; 180 181 switch ($call[0]) { 182 case 'table_open': 183 if ($this->countTableHeadRows) { 184 array_splice($this->tableCalls, $key + 1, 0, [['tablethead_open', [], $call[2]]]); 185 } 186 break; 187 188 case 'tablerow_open': 189 $lastRow++; 190 $lastCell = 0; 191 break; 192 193 case 'tablecell_open': 194 case 'tableheader_open': 195 $lastCell++; 196 $cellKey[$lastRow][$lastCell] = $key; 197 break; 198 199 case 'table_align': 200 $prev = in_array($this->tableCalls[$key - 1][0], ['tablecell_open', 'tableheader_open']); 201 $next = in_array($this->tableCalls[$key + 1][0], ['tablecell_close', 'tableheader_close']); 202 // If the cell is empty, align left 203 if ($prev && $next) { 204 $this->tableCalls[$key - 1][1][1] = 'left'; 205 206 // If the previous element was a cell open, align right 207 } elseif ($prev) { 208 $this->tableCalls[$key - 1][1][1] = 'right'; 209 210 // If the next element is the close of an element, align either center or left 211 } elseif ($next) { 212 if ($this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] == 'right') { 213 $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'center'; 214 } else { 215 $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'left'; 216 } 217 } 218 219 // Now convert the whitespace back to cdata 220 $this->tableCalls[$key][0] = 'cdata'; 221 break; 222 223 case 'colspan': 224 $this->tableCalls[$key - 1][1][0] = false; 225 226 for ($i = $key - 2; $i >= $cellKey[$lastRow][1]; $i--) { 227 if ( 228 $this->tableCalls[$i][0] == 'tablecell_open' || 229 $this->tableCalls[$i][0] == 'tableheader_open' 230 ) { 231 if (false !== $this->tableCalls[$i][1][0]) { 232 $this->tableCalls[$i][1][0]++; 233 break; 234 } 235 } 236 } 237 238 $toDelete[] = $key - 1; 239 $toDelete[] = $key; 240 $toDelete[] = $key + 1; 241 break; 242 243 case 'rowspan': 244 if ($this->tableCalls[$key - 1][0] == 'cdata') { 245 // ignore rowspan if previous call was cdata (text mixed with :::) 246 // we don't have to check next call as that wont match regex 247 $this->tableCalls[$key][0] = 'cdata'; 248 } else { 249 $spanning_cell = null; 250 251 // can't cross thead/tbody boundary 252 if (!$this->countTableHeadRows || ($lastRow - 1 != $this->countTableHeadRows)) { 253 for ($i = $lastRow - 1; $i > 0; $i--) { 254 if ( 255 $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' || 256 $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open' 257 ) { 258 if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) { 259 $spanning_cell = $i; 260 break; 261 } 262 } 263 } 264 } 265 if (is_null($spanning_cell)) { 266 // No spanning cell found, so convert this cell to 267 // an empty one to avoid broken tables 268 $this->tableCalls[$key][0] = 'cdata'; 269 $this->tableCalls[$key][1][0] = ''; 270 break; 271 } 272 $this->tableCalls[$cellKey[$spanning_cell][$lastCell]][1][2]++; 273 274 $this->tableCalls[$key - 1][1][2] = false; 275 276 $toDelete[] = $key - 1; 277 $toDelete[] = $key; 278 $toDelete[] = $key + 1; 279 } 280 break; 281 282 case 'tablerow_close': 283 // Fix broken tables by adding missing cells 284 $moreCalls = []; 285 while (++$lastCell < $this->maxCols) { 286 $moreCalls[] = ['tablecell_open', [1, null, 1], $call[2]]; 287 $moreCalls[] = ['cdata', [''], $call[2]]; 288 $moreCalls[] = ['tablecell_close', [], $call[2]]; 289 } 290 $moreCallsLength = count($moreCalls); 291 if ($moreCallsLength) { 292 array_splice($this->tableCalls, $key, 0, $moreCalls); 293 $key += $moreCallsLength; 294 } 295 296 if ($this->countTableHeadRows == $lastRow) { 297 array_splice($this->tableCalls, $key + 1, 0, [['tablethead_close', [], $call[2]]]); 298 } 299 break; 300 } 301 } 302 303 // condense cdata 304 $cnt = count($this->tableCalls); 305 for ($key = 0; $key < $cnt; $key++) { 306 if ($this->tableCalls[$key][0] == 'cdata') { 307 $ckey = $key; 308 $key++; 309 while ($this->tableCalls[$key][0] == 'cdata') { 310 $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0]; 311 $toDelete[] = $key; 312 $key++; 313 } 314 continue; 315 } 316 } 317 318 foreach ($toDelete as $delete) { 319 unset($this->tableCalls[$delete]); 320 } 321 $this->tableCalls = array_values($this->tableCalls); 322 } 323 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body