123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- <?php
- namespace dokuwiki\Parsing\Handler;
- class Table extends AbstractRewriter
- {
- protected $tableCalls = array();
- protected $maxCols = 0;
- protected $maxRows = 1;
- protected $currentCols = 0;
- protected $firstCell = false;
- protected $lastCellType = 'tablecell';
- protected $inTableHead = true;
- protected $currentRow = array('tableheader' => 0, 'tablecell' => 0);
- protected $countTableHeadRows = 0;
- /** @inheritdoc */
- public function finalise()
- {
- $last_call = end($this->calls);
- $this->writeCall(array('table_end',array(), $last_call[2]));
- $this->process();
- $this->callWriter->finalise();
- unset($this->callWriter);
- }
- /** @inheritdoc */
- public function process()
- {
- foreach ($this->calls as $call) {
- switch ($call[0]) {
- case 'table_start':
- $this->tableStart($call);
- break;
- case 'table_row':
- $this->tableRowClose($call);
- $this->tableRowOpen(array('tablerow_open',$call[1],$call[2]));
- break;
- case 'tableheader':
- case 'tablecell':
- $this->tableCell($call);
- break;
- case 'table_end':
- $this->tableRowClose($call);
- $this->tableEnd($call);
- break;
- default:
- $this->tableDefault($call);
- break;
- }
- }
- $this->callWriter->writeCalls($this->tableCalls);
- return $this->callWriter;
- }
- protected function tableStart($call)
- {
- $this->tableCalls[] = array('table_open',$call[1],$call[2]);
- $this->tableCalls[] = array('tablerow_open',array(),$call[2]);
- $this->firstCell = true;
- }
- protected function tableEnd($call)
- {
- $this->tableCalls[] = array('table_close',$call[1],$call[2]);
- $this->finalizeTable();
- }
- protected function tableRowOpen($call)
- {
- $this->tableCalls[] = $call;
- $this->currentCols = 0;
- $this->firstCell = true;
- $this->lastCellType = 'tablecell';
- $this->maxRows++;
- if ($this->inTableHead) {
- $this->currentRow = array('tablecell' => 0, 'tableheader' => 0);
- }
- }
- protected function tableRowClose($call)
- {
- if ($this->inTableHead && ($this->inTableHead = $this->isTableHeadRow())) {
- $this->countTableHeadRows++;
- }
- // Strip off final cell opening and anything after it
- while ($discard = array_pop($this->tableCalls)) {
- if ($discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') {
- break;
- }
- if (!empty($this->currentRow[$discard[0]])) {
- $this->currentRow[$discard[0]]--;
- }
- }
- $this->tableCalls[] = array('tablerow_close', array(), $call[2]);
- if ($this->currentCols > $this->maxCols) {
- $this->maxCols = $this->currentCols;
- }
- }
- protected function isTableHeadRow()
- {
- $td = $this->currentRow['tablecell'];
- $th = $this->currentRow['tableheader'];
- if (!$th || $td > 2) return false;
- if (2*$td > $th) return false;
- return true;
- }
- protected function tableCell($call)
- {
- if ($this->inTableHead) {
- $this->currentRow[$call[0]]++;
- }
- if (!$this->firstCell) {
- // Increase the span
- $lastCall = end($this->tableCalls);
- // A cell call which follows an open cell means an empty cell so span
- if ($lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open') {
- $this->tableCalls[] = array('colspan',array(),$call[2]);
- }
- $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]);
- $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]);
- $this->lastCellType = $call[0];
- } else {
- $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]);
- $this->lastCellType = $call[0];
- $this->firstCell = false;
- }
- $this->currentCols++;
- }
- protected function tableDefault($call)
- {
- $this->tableCalls[] = $call;
- }
- protected function finalizeTable()
- {
- // Add the max cols and rows to the table opening
- if ($this->tableCalls[0][0] == 'table_open') {
- // Adjust to num cols not num col delimeters
- $this->tableCalls[0][1][] = $this->maxCols - 1;
- $this->tableCalls[0][1][] = $this->maxRows;
- $this->tableCalls[0][1][] = array_shift($this->tableCalls[0][1]);
- } else {
- trigger_error('First element in table call list is not table_open');
- }
- $lastRow = 0;
- $lastCell = 0;
- $cellKey = array();
- $toDelete = array();
- // if still in tableheader, then there can be no table header
- // as all rows can't be within <THEAD>
- if ($this->inTableHead) {
- $this->inTableHead = false;
- $this->countTableHeadRows = 0;
- }
- // Look for the colspan elements and increment the colspan on the
- // previous non-empty opening cell. Once done, delete all the cells
- // that contain colspans
- for ($key = 0; $key < count($this->tableCalls); ++$key) {
- $call = $this->tableCalls[$key];
- switch ($call[0]) {
- case 'table_open':
- if ($this->countTableHeadRows) {
- array_splice($this->tableCalls, $key+1, 0, array(
- array('tablethead_open', array(), $call[2])));
- }
- break;
- case 'tablerow_open':
- $lastRow++;
- $lastCell = 0;
- break;
- case 'tablecell_open':
- case 'tableheader_open':
- $lastCell++;
- $cellKey[$lastRow][$lastCell] = $key;
- break;
- case 'table_align':
- $prev = in_array($this->tableCalls[$key-1][0], array('tablecell_open', 'tableheader_open'));
- $next = in_array($this->tableCalls[$key+1][0], array('tablecell_close', 'tableheader_close'));
- // If the cell is empty, align left
- if ($prev && $next) {
- $this->tableCalls[$key-1][1][1] = 'left';
- // If the previous element was a cell open, align right
- } elseif ($prev) {
- $this->tableCalls[$key-1][1][1] = 'right';
- // If the next element is the close of an element, align either center or left
- } elseif ($next) {
- if ($this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] == 'right') {
- $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'center';
- } else {
- $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'left';
- }
- }
- // Now convert the whitespace back to cdata
- $this->tableCalls[$key][0] = 'cdata';
- break;
- case 'colspan':
- $this->tableCalls[$key-1][1][0] = false;
- for ($i = $key-2; $i >= $cellKey[$lastRow][1]; $i--) {
- if ($this->tableCalls[$i][0] == 'tablecell_open' ||
- $this->tableCalls[$i][0] == 'tableheader_open'
- ) {
- if (false !== $this->tableCalls[$i][1][0]) {
- $this->tableCalls[$i][1][0]++;
- break;
- }
- }
- }
- $toDelete[] = $key-1;
- $toDelete[] = $key;
- $toDelete[] = $key+1;
- break;
- case 'rowspan':
- if ($this->tableCalls[$key-1][0] == 'cdata') {
- // ignore rowspan if previous call was cdata (text mixed with :::)
- // we don't have to check next call as that wont match regex
- $this->tableCalls[$key][0] = 'cdata';
- } else {
- $spanning_cell = null;
- // can't cross thead/tbody boundary
- if (!$this->countTableHeadRows || ($lastRow-1 != $this->countTableHeadRows)) {
- for ($i = $lastRow-1; $i > 0; $i--) {
- if ($this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' ||
- $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open'
- ) {
- if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) {
- $spanning_cell = $i;
- break;
- }
- }
- }
- }
- if (is_null($spanning_cell)) {
- // No spanning cell found, so convert this cell to
- // an empty one to avoid broken tables
- $this->tableCalls[$key][0] = 'cdata';
- $this->tableCalls[$key][1][0] = '';
- break;
- }
- $this->tableCalls[$cellKey[$spanning_cell][$lastCell]][1][2]++;
- $this->tableCalls[$key-1][1][2] = false;
- $toDelete[] = $key-1;
- $toDelete[] = $key;
- $toDelete[] = $key+1;
- }
- break;
- case 'tablerow_close':
- // Fix broken tables by adding missing cells
- $moreCalls = array();
- while (++$lastCell < $this->maxCols) {
- $moreCalls[] = array('tablecell_open', array(1, null, 1), $call[2]);
- $moreCalls[] = array('cdata', array(''), $call[2]);
- $moreCalls[] = array('tablecell_close', array(), $call[2]);
- }
- $moreCallsLength = count($moreCalls);
- if ($moreCallsLength) {
- array_splice($this->tableCalls, $key, 0, $moreCalls);
- $key += $moreCallsLength;
- }
- if ($this->countTableHeadRows == $lastRow) {
- array_splice($this->tableCalls, $key+1, 0, array(
- array('tablethead_close', array(), $call[2])));
- }
- break;
- }
- }
- // condense cdata
- $cnt = count($this->tableCalls);
- for ($key = 0; $key < $cnt; $key++) {
- if ($this->tableCalls[$key][0] == 'cdata') {
- $ckey = $key;
- $key++;
- while ($this->tableCalls[$key][0] == 'cdata') {
- $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0];
- $toDelete[] = $key;
- $key++;
- }
- continue;
- }
- }
- foreach ($toDelete as $delete) {
- unset($this->tableCalls[$delete]);
- }
- $this->tableCalls = array_values($this->tableCalls);
- }
- }
|