summaryrefslogtreecommitdiff
path: root/platform/www/inc/Parsing/Handler/Table.php
diff options
context:
space:
mode:
Diffstat (limited to 'platform/www/inc/Parsing/Handler/Table.php')
-rw-r--r--platform/www/inc/Parsing/Handler/Table.php320
1 files changed, 320 insertions, 0 deletions
diff --git a/platform/www/inc/Parsing/Handler/Table.php b/platform/www/inc/Parsing/Handler/Table.php
new file mode 100644
index 0000000..35ff5a8
--- /dev/null
+++ b/platform/www/inc/Parsing/Handler/Table.php
@@ -0,0 +1,320 @@
+<?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);
+ }
+}