DokuWiki

It's better when it's simple

User Tools

Site Tools


tips:tableswithrowspans2

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tips:tableswithrowspans2 [2008-08-27 18:35] 147.47.238.83tips:tableswithrowspans2 [2015-08-29 21:38] (current) Aleksandr
Line 1: Line 1:
 +=====Tables with rowspans and vertical alignment=====
  
 +>I hacked this together in a couple hours last night and it seems to be working correctly. Unfortunately, I don't have time to learn darc and how to make a patch so I'm just posting this hack here and hopefully someone else can put together a code patch for the devs. -- jmucchiello [at] yahoo [dot] com
 +
 +By applying the code fixes listed here, you can create tables with rowspans each with their own vertical alignment. This patch does not alter the existing table syntax. To create a rowspan, the "lost" columns must equal (without any spaces between |'s or ^'s) one of the following character sequences ''/\'', ''/-\'', or ''/_\'' to indicate ''top'', ''center'', or ''bottom'' alignment respectively. Multi-dimensional spans work just like existing colspans: the ghost column's must be followed by the appropriate number of |'s or ^'s.
 +
 +====Features at a glance====
 +  * Compatible with 99.9% of existing DokuWiki tables. (How many tables have content that looks like ''/\'', ''/-\'' and/or ''/_\''?)
 +  * Supports bidirectional spans.
 +  * Supports ''valign="top"'', ''valign="center"'' and ''valign="bottom"'' in each cell.
 +  * Supports both header and non-header spans.
 +
 +
 +
 +====Usage====
 +The following code should generate the HTML listed below:
 +<code>
 +^ ^col 1 ^  col 2  ^  col 3&4^^
 +^row a  |a1-b1  |  a2-c3  || a4 |
 +^row b& |/-\|/_\|| b4|
 +^/\|c1  |/_\|^ c4|
 +^row d  |d1  |  d2  |  d3| d4 |
 +</code>
 +
 +This 4x4 table (plus headers) has individual cells at a4, b4, c1, c4, d1, d2, d4, and d4.\\
 +The top row and leftmost columns are header cells. c4 is also a header cell.\\
 +There are 2 2-cell blocks, the one at row2&3 is bottom aligned and the one at a1-b1 is top aligned.\\
 +There is a 6-cell block spanning a2, a3, b2, b3, c2 and c3 which is center aligned.
 +
 +<code html>
 +<html>
 +<table class="inline">
 +    <tbody><tr>
 +        <th valign="top"> </th>
 +        <th valign="top">col 1 </th>
 +        <th class="centeralign" valign="top">  col 2  </th>
 +        <th class="rightalign" colspan="2" valign="top">  col 3&amp;4</th>
 +    </tr><tr>
 +        <th valign="top">row a  </th>
 +        <td class="leftalign" rowspan="2" valign="center">a1-b1  </td>
 +        <td class="centeralign" rowspan="3" colspan="2" valign="bottom">  a2-c3  </td>
 +        <td valign="top"> a4 </td>
 +    </tr><tr>
 +        <th rowspan="2" valign="top">row b&amp; </th>
 +        <td valign="top"> b4</td>
 +    </tr><tr>
 +        <th valign="top">c1  </th>
 +        <th valign="top"> c4</th>                         <!-- Notice the caret in the source? -->
 +    </tr><tr>
 +        <th valign="top">row d  </th>
 +        <td class="leftalign" valign="top">d1  </td>
 +        <td class="centeralign" valign="top">  d2  </td>
 +        <td class="rightalign" valign="top">  d3</td>
 +        <td valign="top"> d4 </td>
 +    </tr>
 +</tbody></table>
 +</html>
 +</code>
 +
 +
 +====Known Issues====
 +  * While you can have leading spaces in ghost columns, trailing spaces are forbidden.
 +  * The colspan code makes sure all rows have the same number of columns but this code doesn't ensure all columns have the same number of rows.
 +  * The ''valign=""'' stuff should be CSS. Someone else can make the 9 different alignment styles. This method is more backward compatible.
 +
 +====Patch Code====
 +
 +The patch involves 5 files in the ''inc/parser'' directory: ''parser.php'', ''handler.php'', ''metadata.php'', ''renderer.php'', and ''xhtml.php'':
 +
 +''inc/parser/parser.php''
 +<code php>
 +// Add this extra line to the indicated class/method
 +//-------------------------------------------------------------------
 +class Doku_Parser_Mode_table extends Doku_Parser_Mode {
 +
 +    function postConnect() {
 +        $this->Lexer->addPattern('\n\^','table');
 +        $this->Lexer->addPattern('\n\|','table');
 +        #$this->Lexer->addPattern(' {2,}','table');
 +        $this->Lexer->addPattern('[\t ]+','table');
 +// rowspan:patch++
 +        $this->Lexer->addPattern('/[-_]?\\\\[|^]+','table');
 +// rowspan:patch--
 +        $this->Lexer->addPattern('\^','table');
 +        $this->Lexer->addPattern('\|','table');
 +        $this->Lexer->addExitPattern('\n','table');
 +    }
 +}
 +</code>
 +''inc/parser/handler.php''
 +<code php>
 +// In this file, we must modify the Doku_Handler and Doku_Handler_Table classes.
 +// These listings are 
 +class Doku_Handler {
 +    function table($match, $state, $pos) {
 +        switch ( $state ) {
 +            case DOKU_LEXER_MATCHED:
 +                if ( $match == ' ' ){
 +                    $this->_addCall('cdata', array($match), $pos);
 +                } else if ( preg_match('/\t+/',$match) ) {
 +                    $this->_addCall('table_align', array($match), $pos);
 +                } else if ( preg_match('/ {2,}/',$match) ) {
 +                    $this->_addCall('table_align', array($match), $pos);
 +                } else if ( $match == "\n|" ) {
 +                    $this->_addCall('table_row', array(), $pos);
 +                    $this->_addCall('tablecell', array(), $pos);
 +                } else if ( $match == "\n^" ) {
 +                    $this->_addCall('table_row', array(), $pos);
 +                    $this->_addCall('tableheader', array(), $pos);
 +                } else if ( $match == '|' ) {
 +                    $this->_addCall('tablecell', array(), $pos);
 +                } else if ( $match == '^' ) {
 +                    $this->_addCall('tableheader', array(), $pos);
 +// rowspan:patch++
 +                } else if ( substr($match,0,2) == '/\\') {
 +                    $this->_addCall(($match[2] == '^') ? 'tableheader' : 'tablecell', array('top',strlen($match)-2), $pos);
 +                } else if ( substr($match,0,3) == '/-\\') {
 +                    $this->_addCall(($match[2] == '^') ? 'tableheader' : 'tablecell', array('center',strlen($match)-3), $pos);
 +                } else if ( substr($match,0,3) == '/_\\') {
 +                    $this->_addCall(($match[2] == '^') ? 'tableheader' : 'tablecell', array('bottom',strlen($match)-3), $pos);
 +// rowspan:patch--
 +                }
 +            break;
 +
 +
 +
 +//------------------------------------------------------------------------
 +class Doku_Handler_Table {
 +
 +    //------------------------------------------------------------------------
 +    function process() {
 +        foreach ( $this->calls as $call ) {
 +            switch ( $call[0] ) {
 +                case 'tableheader':
 +                case 'tablecell':
 +// rowspan:patch++
 +                case 'tablevspan':
 +// rowspan:patch--
 +                    $this->tableCell($call);
 +                break;
 +
 +
 +
 +    function tableCell($call) {
 +        if ( !$this->firstCell ) {
 +            $lastCall = end($this->tableCalls);
 +            
 +// rowspan:patch++
 +            if (count($call[1]) > 0) {
 +                 $this->tableCalls[] = array('rowspan',$call[1],$call[2]);
 +            }
 +            // A cell call which follows an open cell means an empty cell so span
 +            else if ( $lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open' ) {
 +// rowspan:patch--
 +                 $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,'top'),$call[2]);
 +            $this->lastCellType = $call[0];
 +
 +        } else {
 +
 +            $this->tableCalls[] = array($call[0].'_open',array(1,NULL,1,'top'),$call[2]);
 +            $this->lastCellType = $call[0];
 +            $this->firstCell = false;
 +
 +        }
 +
 +        $this->currentCols++;
 +    }
 +
 +
 +
 +// rowspan:patch++
 +    // new function: finds a call in the tableCalls array
 +    function _findCall($needle, $key, $dir = -1) {
 +        while (1) {
 +            if ($key < 0 || $key >= count($this->tableCalls)) {
 +                return false;
 +            }
 +            
 +            if ($this->tableCalls[$key][0] == $needle) {
 +                return $key;
 +            }
 +            
 +            $key += $dir;
 +        }
 +        return false;
 +    }
 +// rowspan:patch--
 +
 +    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;
 +        } else {
 +            trigger_error('First element in table call list is not table_open');
 +        }
 +
 +        $lastRow = 0;
 +        $lastCell = 0;
 +        $toDelete = array();
 +
 +        // 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
 +        foreach ( $this->tableCalls as $key => $call ) {
 +
 +            if ( $call[0] == 'tablerow_open' ) {
 +
 +                $lastRow = $key;
 +
 +            } else if ( $call[0] == 'tablecell_open' || $call[0] == 'tableheader_open' ) {
 +
 +                $lastCell = $key;
 +
 +            } else if ( $call[0] == 'table_align' ) {
 +
 +                // If the previous element was a cell open, align right
 +                if ( $this->tableCalls[$key-1][0] == 'tablecell_open' || $this->tableCalls[$key-1][0] == 'tableheader_open' ) {
 +                    $this->tableCalls[$key-1][1][1] = 'right';
 +
 +                // If the next element if the close of an element, align either center or left
 +                } else if ( $this->tableCalls[$key+1][0] == 'tablecell_close' || $this->tableCalls[$key+1][0] == 'tableheader_close' ) {
 +                    if ( $this->tableCalls[$lastCell][1][1] == 'right' ) {
 +                        $this->tableCalls[$lastCell][1][1] = 'center';
 +                    } else {
 +                        $this->tableCalls[$lastCell][1][1] = 'left';
 +                    }
 +
 +                }
 +
 +                // Now convert the whitespace back to cdata
 +                $this->tableCalls[$key][0] = 'cdata';
 +
 +            } else if ( $call[0] == 'colspan' ) {
 +
 +                $this->tableCalls[$key-1][1][0] = false;
 +
 +                for($i = $key-2; $i > $lastRow; $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;
 +                
 +// rowspan:patch++
 +            } else if ( $call[0] == 'rowspan' ) {
 +
 +                $this->tableCalls[$key-1][1][2] = 0;
 +
 +                $colofs = $call[1][1];
 +                $rowstart = $this->_findCall('tablerow_open', $key-2);
 +                for($i = $key-2; $i > $rowstart; $i--) {
 +                    if ( $this->tableCalls[$i][0] == 'tablecell_open' || $this->tableCalls[$i][0] == 'tableheader_open' ) {
 +                        $colofs += $this->tableCalls[$i][1][0];
 +                    }
 +                }
 +                
 +                $rowend = $rowstart-1;
 +                while (($prevrow = $this->_findCall('tablerow_open', $rowend)) !== false) {
 +                    $colsleft = $colofs;
 +                    for ($i = $prevrow; $i < $rowend; ++$i) {
 +                        if ($this->tableCalls[$i][0] == 'tablecell_open' || $this->tableCalls[$i][0] == 'tableheader_open') {
 +                            if ($this->tableCalls[$i][1][2] == 0) {
 +                                $colsleft = -1;
 +                            } else {
 +                                $colsleft -= $this->tableCalls[$i][1][0];
 +                            }
 +                        }
 +                        if ($colsleft == 0) {
 +                            $this->tableCalls[$i][1][2]++;
 +                            $this->tableCalls[$i][1][3] = $call[1][0];
 +                            $this->tableCalls[$key-1][1][0] = $this->tableCalls[$i][1][0]; // set the colspan to the parent colspan
 +                            break 2;
 +                        } else if ($colsleft < 0) {
 +                            break;
 +                        }                        
 +                    }   
 +                    $rowend = $prevrow - 1;                
 +                }
 +
 +                $toDelete[] = $key-1;
 +                $toDelete[] = $key;
 +                $toDelete[] = $key+1;
 +            }
 +// rowspan:patch--
 +        }
 +
 +</code>
 +
 +
 +''inc/parser/renderer.php'' and ''inc/parser/metadata.php''
 +<code php>
 +
 +class Doku_Renderer extends DokuWiki_Plugin {
 +    // we just need to add some parameters to these two functions
 +    function tableheader_open($colspan = 1, $align = NULL, $rowspan = 1, $valign = 'bottom'){}
 +
 +    function tablecell_open($colspan = 1, $align = NULL, $rowspan = 1, $valign = 'top'){}
 +
 +</code>
 +
 +
 +
 +''inc/parser/xhtml.php''
 +<code php>
 +
 +    function tableheader_open($colspan = 1, $align = NULL, $rowspan = 1, $valign = 'bottom'){
 +        $this->doc .= '<th';
 +        if ( !is_null($align) ) {
 +            $this->doc .= ' class="'.$align.'align"';
 +        }
 +        if ( !is_null($valign) ) {
 +            $this->doc .= ' valign="'.$valign.'"';
 +        }
 +        if ( $rowspan > 1 ) {
 +            $this->doc .= ' rowspan="'.$rowspan.'"';
 +        }
 +        if ( $colspan > 1 ) {
 +            $this->doc .= ' colspan="'.$colspan.'"';
 +        }
 +        $this->doc .= '>';
 +    }
 +
 +    function tablecell_open($colspan = 1, $align = NULL, $rowspan = 1, $valign = 'top'){
 +        $this->doc .= '<td';
 +        if ( !is_null($align) ) {
 +            $this->doc .= ' class="'.$align.'align"';
 +        }
 +        if ( !is_null($valign) ) {
 +            $this->doc .= ' valign="'.$valign.'"';
 +        }
 +        if ( $rowspan > 1 ) {
 +            $this->doc .= ' rowspan="'.$rowspan.'"';
 +        }
 +        if ( $colspan > 1 ) {
 +            $this->doc .= ' colspan="'.$colspan.'"';
 +        }
 +        $this->doc .= '>';
 +    }
 +
 +</code>
 +
 +
 +----
 +This version has some errors.
 +Like lower table :
 +<code>
 +^ col1  ^ col2  ^ col3  ^
 +|  2,1  | 2,2  | 2,3  |
 +|/\| 3,2  | 3,3  |
 +|/\|/-\| 4,3  |
 +|/\|/-\| 5,3  |
 +|/-\| 6,2  | 6,3  |
 +| 7,1  | 7,2  | 7,3  |
 +</code>
 +
 +So I changed(inc/parser/handler.php):
 +<code php>
 +                while (($prevrow = $this->_findCall('tablerow_open', $rowend)) !== false) {
 +                    $colsleft = $colofs;
 +                    for ($i = $prevrow; $i < $rowend; ++$i) {
 +                        // patch++
 +                        if ($this->tableCalls[$i][0] == 'tablecell_open' || $this->tableCalls[$i][0] == 'tableheader_open') {
 +                            $colsleft -= $this->tableCalls[$i][1][0];
 +                        }
 +                        if ($colsleft == 0 && $this->tableCalls[$i][1][2] != 0) {
 +                        // patch--
 +                            $this->tableCalls[$i][1][2]++;
 +                            $this->tableCalls[$i][1][3] = $call[1][0];
 +                            $this->tableCalls[$key-1][1][0] = $this->tableCalls[$i][1][0]; // set the colspan to the parent colspan
 +                            break 2;
 +                        } else if ($colsleft < 0) {
 +                            break;
 +                        }                        
 +                    }
 +                    
 +                    $rowend = $prevrow - 1;                
 +                }
 +</code>
 +
 +
 +====== Discussion ======
 +
 +It's a bit complex to modify the code and is error prone, I wrote an article in http://forum.dokuwiki.org/thread/3662 which is easy to understand. 
tips/tableswithrowspans2.txt · Last modified: 2015-08-29 21:38 by Aleksandr

Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 4.0 International
CC Attribution-Share Alike 4.0 International Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki