Extended Table Syntax 2 Plugin
http://www.dokuwiki.org/plugin:exttab2
—- plugin
description: Another MediaWiki style tables inside DokuWiki author : disorder Chang email : disorder.chang@gmail.com type : Syntax lastupdate : 2007-11-06 compatible : 2007-06-26b depends : conflicts : similar : exttab1 tags : Mediawiki, tables securityissue: XSS vulnerability allows arbitrary JavaScript insertion. Author informed on 2008-02-07.
About
I like the exttab1 plugin, which can handle Wikimedia way of table syntax. But exttab1 does not parse some DokuWiki syntax correctly, e.g. footnote, image link, etc. So I decided to write a new one.
This plugin is still in its early stages, please feel free to modify the code.
Installation
- create the folder lib/plugins/exttab2
- and copy the source code to lib/plugins/exttab2/syntax.php
Usage
Unlike exttab1 plugin, you don't need to put any markup to enclose the syntax, just draw table as mediawiki do.
markup summary
* means that the markup must be on a new line
* | {| | start table |
* | {| para | start table with parameters |
* | |+ caption | table caption; only one per table and between table start and first row1) |
* | |+ para | caption | table caption with parameters |
* | |- | table row |
* | |- para | table row with parameters |
* | ! header | table header cell |
* | ! para | header | table header cell with parameters |
!! header | consecutive table headers | |
!! para | header | consecutive table headers with parameters | |
* | | cell | table data cell; Cell content may follow on same line or on following lines |
* | | para | cell | table data cell with parameters |
|| cell | consecutive table data cell | |
|| para | cell | consecutive table data cell with parameters | |
* | |} | end table; please add an additional empty line to end the whole table2) |
please see http://meta.wikimedia.org/wiki/Help:Table for more detail.
Examples
Simple table
{| |+ caption ! header 1 ! header 2 |- | cell A | cell B |}
{| |+ caption ! header 1 !! header 2 |- | cell A || cell B |}
Table with parameter and wiki markups (formatting, linking, etc.)
{| border="1" style="width:300px" |+ style="color:red"| //caption// |- style="background:green;height:50px" ! style="color:white" |header 1 ! header 2 !! style="color:yellow" | header 3 |- | style="text-align:right" | **bold**, //italic//, ((footnote)) | [[link|a link]] || style="color:yellow" | {{pdf.pdf}} |- | text with \\ new line OK\\ [[http://www.google.com|google]] good\\ | colspan="2" | but ====Headline==== not working! |}
Table with complex wiki markups (listblock, hr, preformatted, code, etc.)
{| border="1" |- | * add an additional * blank line * after the last list | as many lines as you like but don't add any empty line |- | add an additional blank line after the preformatted text | hr fine ---- |- | > quoting must >> add a blank line too | <file> file or code goes here </file> |}
Nested tables
{| border="1" | you | can use {| border="2" style="background:#CCC;" | nested |- | table |} now | . |}
Limits
I use the source of http://meta.wikimedia.org/wiki/Help:Table to test this plugin. It works fine, except:
Column headings with double bar
The following syntax will render heading 2 as normal cell
! Column heading 1 || Column heading 2
Revision History
- 2006-11-06 0.2.0 support nested table and many more…
- 2006-10-04 0.1.0 Initial release
Sources
<?php //ini_set("display_errors", "On"); // for debugging /** * exttab2-Plugin: Parses extended tables (like MediaWiki) * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author disorde chang <disorder.chang@gmail.com> * @date 2007-10-04 */ // must be run within Dokuwiki if(!defined('DOKU_INC')) die(); if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'syntax.php'); /** * All DokuWiki plugins to extend the parser/rendering mechanism * need to inherit from this class */ class syntax_plugin_exttab2 extends DokuWiki_Syntax_Plugin { var $stack = array(); function syntax_plugin_exttab2(){ define("EXTTAB2_TABLE", 0); define("EXTTAB2_CAPTION", 1); define("EXTTAB2_TR", 2); define("EXTTAB2_TD", 3); define("EXTTAB2_TH", 4); $this->tagsmap = array( EXTTAB2_TABLE=> array("table", "", "\n" ), EXTTAB2_CAPTION=> array("caption", "\t", "\n" ), EXTTAB2_TR=> array("tr", "\t", "\n" ), EXTTAB2_TD=> array("td", "\t"."\t", "\n" ), EXTTAB2_TH=> array("th", "\t"."\t", "\n" ), /* // DOKU constant not work when preview EXTTAB2_TABLE=> array("table", "", DOKU_LF ), EXTTAB2_CAPTION=> array("caption", DOKU_TAB, DOKU_LF ), EXTTAB2_TR=> array("tr", DOKU_TAB, DOKU_LF ), EXTTAB2_TD=> array("td", DOKU_TAB.DOKU_TAB, DOKU_LF ), EXTTAB2_TH=> array("th", DOKU_TAB.DOKU_TAB, DOKU_LF ), */ ); } function getInfo(){ return array( 'author' => 'Disorder Chang', 'email' => 'disorder.chang@gmail.com', 'date' => '2007-11-06', 'name' => 'exttab2 Plugin', 'desc' => 'parses MediaWiki-like tables', 'url' => 'http://www.dokuwiki.org/plugin:exttab2', ); } function getType(){ return 'container'; } function getPType(){ return 'block'; } function getAllowedTypes() { return array('container', 'formatting', 'substition', 'disabled', 'protected'); } function getSort(){ return 50; } function connectTo($mode) { $this->Lexer->addEntryPattern('\n\{\|[^\n]*',$mode,'plugin_exttab2'); } function postConnect() { $para = "[^\|\n\[\{\!]+"; // parametes // caption: |+ params | caption $this->Lexer->addPattern("\n\|\+(?:$para\|(?!\|))?",'plugin_exttab2'); // row: |- params $this->Lexer->addPattern('\n\|\-[^\n]*','plugin_exttab2'); // table open $this->Lexer->addPattern('\n\{\|[^\n]*','plugin_exttab2'); // table close $this->Lexer->addPattern('\n\|\}','plugin_exttab2'); // header $this->Lexer->addPattern("(?:\n|\!)\!(?:$para\|(?!\|))?",'plugin_exttab2'); //cell $this->Lexer->addPattern("(?:\n|\|)\|(?:$para\|(?!\|))?",'plugin_exttab2'); //end // $this->Lexer->addExitPattern('\n','plugin_exttab2'); // $this->Lexer->addExitPattern("(?<!\|)\n",'plugin_exttab2'); // not work $this->Lexer->addExitPattern("\n(?=\n)",'plugin_exttab2'); } /** * Handle the match */ function handle($match, $state, $pos, &$handler){ if($state == DOKU_LEXER_EXIT) { return array($state, "end"); } else if($state == DOKU_LEXER_UNMATCHED){ return array($state, "", $match); } else{ $para = "[^\|\n]+"; // parametes if(preg_match ( '/\{\|([^\n]*)/', $match, $m)){ // table open $func = "table_open"; $params = $m[1]; return array($state, $func, $params); } else if($match == "\n|}"){ // table close $func = "table_close"; $params = ""; return array($state, $func, $params); } else if(preg_match ("/^\n\|\+(?:(?:($para)\|)?)$/", $match, $m)){ // caption $func = "caption"; $params = $m[1]; return array($state, $func, $params); } else if(preg_match ( '/\|-([^\n]*)/', $match, $m)){ // row $func = "row"; $params = $m[1]; return array($state, $func, $params); } else if(preg_match("/^(?:\n|\!)\!(?:(?:([^\|\n\!]+)\|)?)$/", $match, $m)){ // header $func = "header"; $params = $m[1]; return array($state, $func, $params); } else if(preg_match("/^(?:\n|\|)\|(?:(?:($para)\|)?)$/", $match, $m)){ // cell $func = "cell"; $params = $m[1]; return array($state, $func, $params); } else{ die("what? ".$match); // for debugging } } } /** * Create output */ function render($mode, &$renderer, $data) { if($mode == 'xhtml'){ list($state, $func, $params) = $data; switch ($state) { case DOKU_LEXER_UNMATCHED : $r = $renderer->_xmlEntities($params); $renderer->doc .= $r; break; case DOKU_LEXER_ENTER : case DOKU_LEXER_MATCHED: $r = $this->$func($params); $renderer->doc .= $r; break; case DOKU_LEXER_EXIT : $r = $this->$func($params); $renderer->doc .= $r; break; } return true; } return false; } function _attrString($attr="", $before=" "){ if(is_null($attr) || trim($attr)=="") $attr = ""; else $attr = $before.trim($attr); return $attr; } var $tagsmap = array(); function _starttag($tag, $params=NULL, $before="", $after=""){ $tagstr = $this->tagsmap[$tag][0]; $before = $this->tagsmap[$tag][1].$before; $after = $this->tagsmap[$tag][2].$after; $r = $before."<".$tagstr.$this->_attrString($params).">". $after; return $r; } function _endtag($tag, $before="", $after=""){ $tagstr = $this->tagsmap[$tag][0]; $before = $this->tagsmap[$tag][1].$before; $after = $this->tagsmap[$tag][2].$after; $r = $before."</".$tagstr.">". $after; return $r; } function table_open($params=NULL){ $r .= $this->_closetags(EXTTAB2_TABLE); $r .= $this->_starttag(EXTTAB2_TABLE, $params); $this->stack[] = EXTTAB2_TABLE; return $r; } function table_close($params=NULL){ $t = end($this->stack); switch($t){ case EXTTAB2_TABLE: array_push($this->stack, EXTTAB2_TR, EXTTAB2_TD); $r .= $this->_starttag(EXTTAB2_TR, $params); $r .= $this->_starttag(EXTTAB2_TD, $params); break; case EXTTAB2_CAPTION: $r .= $this->_endtag(EXTTAB2_CAPTION); array_pop($this->stack); array_push($this->stack, EXTTAB2_TR, EXTTAB2_TD); $r .= $this->_starttag(EXTTAB2_TR, $params); $r .= $this->_starttag(EXTTAB2_TD, $params); break; case EXTTAB2_TR: array_push($this->stack, EXTTAB2_TD); $r = $this->_starttag(EXTTAB2_TD, $params); break; case EXTTAB2_TD: case EXTTAB2_TH: break; } while(($t = end($this->stack)) != EXTTAB2_TABLE){ $r .= $this->_endtag($t); array_pop($this->stack); } array_pop($this->stack); $r .= $this->_endtag(EXTTAB2_TABLE); return $r; } function end($params=NULL){ while(!empty($this->stack)){ $r .= $this->table_close(); } return $r; } function caption($params=NULL){ if(($r = $this->_closetags(EXTTAB2_CAPTION)) === FALSE){ return ""; } $r .= $this->_starttag(EXTTAB2_CAPTION, $params); $this->stack[] = EXTTAB2_CAPTION; return $r; } function row($params=NULL){ $r .= $this->_closetags(EXTTAB2_TR); $r .= $this->_starttag(EXTTAB2_TR, $params); $this->stack[] = EXTTAB2_TR; return $r; } function header($params=NULL){ $r .= $this->_closetags(EXTTAB2_TH); $r .= $this->_starttag(EXTTAB2_TH, $params); $this->stack[] = EXTTAB2_TH; return $r; } function cell($params=NULL){ $r .= $this->_closetags(EXTTAB2_TD); $r .= $this->_starttag(EXTTAB2_TD, $params); $this->stack[] = EXTTAB2_TD; return $r; } function _closetags($tag){ $r = ""; switch($tag){ case EXTTAB2_TD: case EXTTAB2_TH: $t = end($this->stack); switch($t){ case EXTTAB2_TABLE: array_push($this->stack, EXTTAB2_TR); $r .= $this->_starttag(EXTTAB2_TR, $params); break; case EXTTAB2_CAPTION: $r .= $this->_endtag(EXTTAB2_CAPTION); array_pop($this->stack); array_push($this->stack, EXTTAB2_TR); $r .= $this->_starttag(EXTTAB2_TR, $params); break; case EXTTAB2_TR: break; case EXTTAB2_TD: case EXTTAB2_TH: $r .= $this->_endtag($t); array_pop($this->stack); break; } break; case EXTTAB2_TR: $t = end($this->stack); switch($t){ case EXTTAB2_TABLE: break; case EXTTAB2_CAPTION: $r .= $this->_endtag(EXTTAB2_CAPTION); array_pop($this->stack); break; case EXTTAB2_TR: $r .= $this->_starttag(EXTTAB2_TD); $r .= $this->_endtag(EXTTAB2_TD); $r .= $this->_endtag(EXTTAB2_TR); array_pop($this->stack); break; case EXTTAB2_TD: case EXTTAB2_TH: $r .= $this->_endtag($t); $r .= $this->_endtag(EXTTAB2_TR); array_pop($this->stack); array_pop($this->stack); break; } break; case EXTTAB2_TABLE: $t = end($this->stack); if($t === FALSE) break; switch($t){ case EXTTAB2_TABLE: array_push($this->stack, EXTTAB2_TR, EXTTAB2_TD); $r .= $this->_starttag(EXTTAB2_TR, $params); $r .= $this->_starttag(EXTTAB2_TD, $params); break; case EXTTAB2_CAPTION: $r .= $this->_endtag(EXTTAB2_CAPTION); array_pop($this->stack); array_push($this->stack, EXTTAB2_TR, EXTTAB2_TD); $r .= $this->_starttag(EXTTAB2_TR, $params); $r .= $this->_starttag(EXTTAB2_TD, $params); break; case EXTTAB2_TR: array_push($this->stack, EXTTAB2_TD); $r = $this->_starttag(EXTTAB2_TD, $params); break; case EXTTAB2_TD: case EXTTAB2_TH: break; } break; case EXTTAB2_CAPTION: $t = end($this->stack); if($t==EXTTAB2_TABLE){ } else{ return false ; // ignore this, or should echo error? } break; } return $r; } } ?>
Discussion
Yup this is an awesome plug in I love it. One question. I tried using the sorting feature but was unable to. Any hopes of getting this to work? –lenehey [Nov. 27, 2007]
Another question. Any hopes of doing Ettab3 where Ettab3 is Ettab2 + sorting table and a finished plugin a non-programmer can use ?. Philip Hettel [feb. 17, 2008]
Found a bug which prevents you from coloring a cell anything brighter than #999999. For some reason if you include a letter such as “#AA88BB” in the hex-color, it won't parse.
What about VERTICAL ALIGNMENT??? oops, got it- valign=“top” sorry about that
What is the current status of the XSS Vulnerability Issue? – I would like to know, as nothing has been reported here as since 2008-02-07
and the plugin has ended up quite useful to me. — Luis Machuca B. 2009/02/18 17:47
The XSS vulnerability seems a little off-topic doesn't it? (as long as this doesn't even contain JavaScript, at least at my first glance..) Also, extremely useful, big thanks! –exa
Two years and no words on the so-called XSS vulnerability. Is this plugin abandoned? Does anyone know how serious the vulnerability is or what it takes to fix it? Does it even matter
? Is there anyone
using
this plugin? — Luis 2010/01/24 16:13
To Luis : I would like to use this plugin too and still waiting the programmer answers. 2010/01/27
2010/05/01: Skimming through the code, I'm guessing the vulnerability is that the table can take arbitrary event attributes (e.g. onclick=“javascript:{stuff}”
, etc), which allows insertion of malicious javascript code that could execute an XSS attack. This won't matter if only trusted people are editing, though. Again, this is by skimming the code at the high level, pointing out the most obvious “flaw” (feature?) in this plugin's design. I don't know if that's what they were actually referring to.