Table Action
This is the development page for the Table action.
History
The original Table action available in Wikka was from IanAndolina's nontroppo wiki (archived).Note: Searching on the web reveals that Ian is not the original author. Credit goes to Michael Abendroth (IbuKi) for being the original author of IbukiTableAction @ WakkaWiki.
The original Table action provides a nice way to create a simple HTML table.
This Table action has a number of drawbacks, however:
- It is awkward to use for anything but the most simple tables - mainly because it's an action, which is different from the common approach to table markup in many Wikis: Most WikiEngines that support tables provide a table markup instead of a "plugin"-like solution like this.
- Cell contents can't have Wikka markup (if they do, the markup is shown as raw text instead of formatted).
- The action does not actually produce data table markup (which it should).
Preliminary solution
There is an ongoing discussion about providing true table markup in Wikka:Clearly, we need a better solution than the current action, preferably (some kind of) true table markup syntax.
However, it would be unwise to release a "preliminary" markup, as it could easily lead to confusion among our user base (as well as needless conversions down the line), and extra work for the developers.
Instead, we should first determine what we want to be able to do in terms of table markup, and then look if we can find a simpler preliminary solution (as a subset of that markup) that still enables us to get there. And any preliminary solution should at least address the problem of ease-of-use and generating data table markup.
So, until we have come to a clear conclusion about "Wikka table markup", we won't have a preliminary Wikka table markup either. Still, there is a clear (and sometimes pressing) need for an easier way to produce HTML tables from Wikka code. Therefore I've investigated whether it was possible to extend the current Table action to provide more flexibility and address the major drawbacks at least partially. I was glad to find it wasn't actually all that hard.
I'm presenting here a rewrite of the Table action and a minor patch to the main wikka.php file (which could benefit other actions as well). It's far from a perfect solution for producing tables - but it's a lot more flexible and easier to use than the current table action. So I'm hoping this can tide us over until we embark on true table markup.
Patch for ./libs/Wakka.class.php
Two very small changes in ./libs/Wakka.class.php are going to provide us with the framework that enables the new Table action to be much easier to use. Essentially, they make it possible to:- Write any action spanning multiple lines: this is especially handy for all actions that have many, or very long, parameters;
- Allow the content of a parameter to span multiple lines.
In ./libs/Wakka.class.php, find the Action function (line 981 in 1.1.6.3). Then replace this line:
by this: and replace this line: by this:
In both cases, all we do is add an 's' after the final '/' of the regular expression, which will make it match across multiple lines.
The first change allows us to "recognize" an action that spans multiple lines (so you can put each parameter on a new line, if desired); the second change allows parameter content to span multiple lines (a parameter value must still be enclosed in double quotes to be recognized, though). Careful with the latter one: not all actions may be able to handle such multi-line parameter values!!
New Table action
Starting with the current Table action, refactoring, refactoring, refactoring .... many of you won't be surprised that I ended up with a complete rewrite of the table action. It provides a lot more flexibility but is completely backwards-compatible with the current table action. Still, if you want to test only, I'd advise you to store the code under the name of table2.php rather than table.php (as indicated below): that way you can test without influencing the operation of any current table actions on your Wikka site.Replace ./actions/table.php with the following code (or save it as table2.php for testing):
<?php
/**
* Turns a simple list of cell contents into a (data) table.
*
* Wikka markup within cells is interpreted but HTML markup for cell contents is not possible.
*
* There is only one required paramater: cells; if this is missing an error message will be
* displayed, along with the original action markup.
*
* It is possible to define cell contents across multiple lines; surrounding whitespace is trimmed
* before generating output, embedded whitespace (including newlines) is retained.
*
* Cell contents must be separated by a delimiter character; by default this is a semicolon, but if
* this would conflict with cell contents, a delimiter can be defined with the delimiter (or delim)
* parameter. Superfluous delimiters at the start and end of the cells parameter are discarded,
* so it's possible to write each cell row on a separate line, and end each cell with the delimiter:
* this makes it relatively easy to maintain table contents.
*
* A caption can be defined, as well as a summary.
* There is limited support for table headers (th cells): if cell content is embedded in sets of
* 2 to 5 '=' characters (as in heading markup) these will be discarded and a header cell (th) will
* be generated instead of a data cell (td). Row groups (thead, tfoot, tbody) are NOT supported.
* The result is valid (data) table markup although accessibility will be limited.
*
* @package Actions
* @subpackage Formatters
* @name Table
*
* @author {@link http://wikka.jsnx.com/JavaWoman JavaWoman}
* (rewrite of {@link http://wikka.jsnx.com/JsnX JsnX}'s original)
* @copyright Copyright © 2005, Marjolein Katsma
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @since Wikka 1.0.0 (original)
* @since Wikka 1.1.6.x (rewrite)
*
* @input char $delimiter optional: delimiter that will be used to separate cells; must be a single character. default: semicolon (;)
* @input char $delim optional: synonym of $delimiter
* @input string $caption optional: table caption
* @input integer $columns optional: number of columns the table is to have. default: 1
* @input integer $cols optional: synonym of $columns
* @input integer $border optional: borderwidth of table. default: 1
* @input integer $cellpadding optional: cellpadding for the table. default: 3
* @input integer $cellspacing optional: cellspacing for the table. default: 1
* @input string $style optional: in-line style for the table. default: none
* @input string $class optional: alternative for style: use the class to style with the stylesheet. default: none
* @input string $summary optional: summary of what the table contains. default: none
* @input string $cells required: content of the table with cells separated by the (defined) delimiter
* @output XHTML table, optionally with caption and header cells, and with markup within cells
*/
// Constants
define('EMPTY_CELL', '###');
define('METACHARS', '-+*,|[]{}/\\'); # meta characters used in REs must be escaped if used as delimiter
define('MISSING_PARAM', 'Error: required parameter %s is missing'); # i18n
// The following defaults can be overridden with a parameter
$lDelim = ';'; # default delimiter
$lCaption = '';
$lCols = 1;
$lBorder = 1;
$lPadding = 3;
$lSpacing = 1;
$lStyle = ''; # styling for table tag
$lClass = '';
$lSummary = '';
if (is_array($vars))
{
// check presence of required parameters
if (!isset($vars['cells']))
{
$out = '<em class="error">';
$out .= '{{table '.$vars['wikka_vars'].'}}';
$out .= sprintf(' '.MISSING_PARAM, '<tt>cells</tt>');
$out .= '</em>';
}
// all OK, let's go ahead
else
{
// get delimiter first (escape chars that are special chars in REs)
if (isset($vars['delimiter']))
{
if (1 == strlen($vars['delimiter'])) $lDelim = addcslashes($vars['delimiter'],METACHARS);
}
elseif (isset($vars['delim']))
{
if (1 == strlen($vars['delim'])) $lDelim = addcslashes($vars['delim'],METACHARS);
}
// process other parameters
foreach ($vars as $param => $value)
{
// sanitize input
$value = trim($value);
while ($value != strip_tags($value)) $value = strip_tags($value);
// parse input
switch ($param)
{
case 'caption':
$lCaption = $value;
break;
case 'columns':
case 'cols':
if (preg_match('/[0-9]+/',$value)) $lCols = $value;
break;
case 'border':
if (preg_match('/[0-9]+/',$value)) $lBorder = $value;
break;
case 'cellpadding':
if (preg_match('/[0-9]+/',$value)) $lPadding = $value;
break;
case 'cellspacing':
if (preg_match('/[0-9]+/',$value)) $lSpacing = $value;
break;
case 'style':
$lStyle = $value;
break;
case 'class':
$lClass = $value;
break;
case 'summary':
$lSummary = $value;
break;
case 'cells':
// get rid of any surrounding delimiters
if (preg_match('/(.*?)['.$lDelim.']+$/s',$value,$matches)) $value = $matches[1];
if (preg_match('/^['.$lDelim.']+(.*)/s',$value,$matches)) $value = $matches[1];
// split contents into array
$cells = split($lDelim, $value);
break;
}
}
// start table
$out = '<table cellpadding="'.$lPadding.'" cellspacing="'.$lSpacing.'" border="'.$lBorder.'"';
if ('' != $lStyle) $out .= ' style="'.$lStyle.'"';
if ('' != $lClass) $out .= ' class="'.$lClass.'"';
if ('' != $lSummary) $out .= ' summary="'.$lSummary.'"';
$out .= ">\n";
// optional caption
if ('' != $lCaption) $out .= '<caption>'.$lCaption.'</caption>'."\n";
$iCell = 0;
foreach ($cells as $content)
{
// discard surrounding whitespace
$content = trim($content);
// start row
if (($iCell % $lCols) == 0) $out .= "<tr>\n";
// process cells
if ($content == EMPTY_CELL || $content == '')
{
$out .= " <td> </td>\n";
}
elseif (preg_match('/={2,5}(.*?)={2,5}/',$content,$matches))
{
$out .= ' <th>'.trim($this->Format($matches[1]))."</th>\n";
}
else
{
$out .= ' <td>'.trim($this->Format($content))."</td>\n";
}
// end row
if ((++$iCell % $lCols) == 0) $out .= "</tr>\n";
}
// end table
$out .= "</table>\n";
}
}
else
{
$out = '<em class="error">';
$out .= '{{table}}';
$out .= sprintf(' '.MISSING_PARAM, '<tt>cells</tt>');
$out .= '</em>';
}
echo $out;
?>
/**
* Turns a simple list of cell contents into a (data) table.
*
* Wikka markup within cells is interpreted but HTML markup for cell contents is not possible.
*
* There is only one required paramater: cells; if this is missing an error message will be
* displayed, along with the original action markup.
*
* It is possible to define cell contents across multiple lines; surrounding whitespace is trimmed
* before generating output, embedded whitespace (including newlines) is retained.
*
* Cell contents must be separated by a delimiter character; by default this is a semicolon, but if
* this would conflict with cell contents, a delimiter can be defined with the delimiter (or delim)
* parameter. Superfluous delimiters at the start and end of the cells parameter are discarded,
* so it's possible to write each cell row on a separate line, and end each cell with the delimiter:
* this makes it relatively easy to maintain table contents.
*
* A caption can be defined, as well as a summary.
* There is limited support for table headers (th cells): if cell content is embedded in sets of
* 2 to 5 '=' characters (as in heading markup) these will be discarded and a header cell (th) will
* be generated instead of a data cell (td). Row groups (thead, tfoot, tbody) are NOT supported.
* The result is valid (data) table markup although accessibility will be limited.
*
* @package Actions
* @subpackage Formatters
* @name Table
*
* @author {@link http://wikka.jsnx.com/JavaWoman JavaWoman}
* (rewrite of {@link http://wikka.jsnx.com/JsnX JsnX}'s original)
* @copyright Copyright © 2005, Marjolein Katsma
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @since Wikka 1.0.0 (original)
* @since Wikka 1.1.6.x (rewrite)
*
* @input char $delimiter optional: delimiter that will be used to separate cells; must be a single character. default: semicolon (;)
* @input char $delim optional: synonym of $delimiter
* @input string $caption optional: table caption
* @input integer $columns optional: number of columns the table is to have. default: 1
* @input integer $cols optional: synonym of $columns
* @input integer $border optional: borderwidth of table. default: 1
* @input integer $cellpadding optional: cellpadding for the table. default: 3
* @input integer $cellspacing optional: cellspacing for the table. default: 1
* @input string $style optional: in-line style for the table. default: none
* @input string $class optional: alternative for style: use the class to style with the stylesheet. default: none
* @input string $summary optional: summary of what the table contains. default: none
* @input string $cells required: content of the table with cells separated by the (defined) delimiter
* @output XHTML table, optionally with caption and header cells, and with markup within cells
*/
// Constants
define('EMPTY_CELL', '###');
define('METACHARS', '-+*,|[]{}/\\'); # meta characters used in REs must be escaped if used as delimiter
define('MISSING_PARAM', 'Error: required parameter %s is missing'); # i18n
// The following defaults can be overridden with a parameter
$lDelim = ';'; # default delimiter
$lCaption = '';
$lCols = 1;
$lBorder = 1;
$lPadding = 3;
$lSpacing = 1;
$lStyle = ''; # styling for table tag
$lClass = '';
$lSummary = '';
if (is_array($vars))
{
// check presence of required parameters
if (!isset($vars['cells']))
{
$out = '<em class="error">';
$out .= '{{table '.$vars['wikka_vars'].'}}';
$out .= sprintf(' '.MISSING_PARAM, '<tt>cells</tt>');
$out .= '</em>';
}
// all OK, let's go ahead
else
{
// get delimiter first (escape chars that are special chars in REs)
if (isset($vars['delimiter']))
{
if (1 == strlen($vars['delimiter'])) $lDelim = addcslashes($vars['delimiter'],METACHARS);
}
elseif (isset($vars['delim']))
{
if (1 == strlen($vars['delim'])) $lDelim = addcslashes($vars['delim'],METACHARS);
}
// process other parameters
foreach ($vars as $param => $value)
{
// sanitize input
$value = trim($value);
while ($value != strip_tags($value)) $value = strip_tags($value);
// parse input
switch ($param)
{
case 'caption':
$lCaption = $value;
break;
case 'columns':
case 'cols':
if (preg_match('/[0-9]+/',$value)) $lCols = $value;
break;
case 'border':
if (preg_match('/[0-9]+/',$value)) $lBorder = $value;
break;
case 'cellpadding':
if (preg_match('/[0-9]+/',$value)) $lPadding = $value;
break;
case 'cellspacing':
if (preg_match('/[0-9]+/',$value)) $lSpacing = $value;
break;
case 'style':
$lStyle = $value;
break;
case 'class':
$lClass = $value;
break;
case 'summary':
$lSummary = $value;
break;
case 'cells':
// get rid of any surrounding delimiters
if (preg_match('/(.*?)['.$lDelim.']+$/s',$value,$matches)) $value = $matches[1];
if (preg_match('/^['.$lDelim.']+(.*)/s',$value,$matches)) $value = $matches[1];
// split contents into array
$cells = split($lDelim, $value);
break;
}
}
// start table
$out = '<table cellpadding="'.$lPadding.'" cellspacing="'.$lSpacing.'" border="'.$lBorder.'"';
if ('' != $lStyle) $out .= ' style="'.$lStyle.'"';
if ('' != $lClass) $out .= ' class="'.$lClass.'"';
if ('' != $lSummary) $out .= ' summary="'.$lSummary.'"';
$out .= ">\n";
// optional caption
if ('' != $lCaption) $out .= '<caption>'.$lCaption.'</caption>'."\n";
$iCell = 0;
foreach ($cells as $content)
{
// discard surrounding whitespace
$content = trim($content);
// start row
if (($iCell % $lCols) == 0) $out .= "<tr>\n";
// process cells
if ($content == EMPTY_CELL || $content == '')
{
$out .= " <td> </td>\n";
}
elseif (preg_match('/={2,5}(.*?)={2,5}/',$content,$matches))
{
$out .= ' <th>'.trim($this->Format($matches[1]))."</th>\n";
}
else
{
$out .= ' <td>'.trim($this->Format($content))."</td>\n";
}
// end row
if ((++$iCell % $lCols) == 0) $out .= "</tr>\n";
}
// end table
$out .= "</table>\n";
}
}
else
{
$out = '<em class="error">';
$out .= '{{table}}';
$out .= sprintf(' '.MISSING_PARAM, '<tt>cells</tt>');
$out .= '</em>';
}
echo $out;
?>
Comments?
See the TableActionInfo page for documentation and examples [later]. Please try out the various new options, and see if this helps with producing tables (as a preliminary solution).Comments and suggestions are welcome, as always. But please take into account that this is definitely not intended as a complete solution, only to tide us over until we get actual table markup.
A workaround for better table headers management
I modified the table action adding the header parameter so a user can specify if he wants a normal table or a table with the first row rendered as th or a table with the first row and the first cell of every row rendered as th.
{{table columns="3" cellpadding="1" cells="BIG;GREEN;FROGS;yes;yes;no;no;no;###" header="0"}} {{table columns="3" cellpadding="1" cells="BIG;GREEN;FROGS;yes;yes;no;no;no;###" header="1"}} {{table columns="3" cellpadding="1" cells="BIG;GREEN;FROGS;yes;yes;no;no;no;###" header="2"}}
Go to my wiki to see it in action.
This is the modified table action:
<?php
//$vars = array('columns' => '3', 'cellpadding' => '1', 'cells' => '**BIG**;**GREEN**;**FROGS**;yes;yes;no;no;yes;yes');
// Init:
$delimiter=';';
$empty_cell='###';
$row=1;
$cellpadding=1;
$cellspacing=1;
$border=1;
$columns=1;
$header = 0; // this is the new parameter. the default value is 0 so if no parameter is defined there will be no table header
$row_cont = 1; // this is a row counter
// $style='border-spacing: 2px;border:2px outset #876;width:auto;margin:0 auto;';
$style='';
if (is_array($vars))
{
foreach ($vars as $param => $value)
{
if ($param == 'style') {$style=$value;}
if ($param == 'columns') {$columns=$value;}
if ($param == 'header') {$header=$value;}
if ($param == 'cellpadding')
{
$cellpadding=$value;
$border=$value;
}
if ($param == 'cells') $cells = split($delimiter, $value);
}
$cached_output = "<table cellpadding='".$cellpadding."' cellspacing='".$cellspacing."' border='".$border."' style='".$style."'>\n";
foreach ($cells as $cell_item)
{
if ($row == 1) $cached_output .= " <tr>\n";
if ($cell_item==$empty_cell) $cell_item='<br />';
// If we are not in the first row and header is set to 2 and if this is the first cell, then we render it as th
if ($header == 2 && $row_cont > 1 && $row == 1 ) $cached_output .= " <th> ".$cell_item."</th>\n";
// If header is set to 1 or 2 and we are in the first row, then we rendere this row as a table header
else if (($header == 1 || $header == 2) && $row_cont == 1) $cached_output .= " <th>".$cell_item."</th>\n";
// Else we rendere normal table cells
else $cached_output .= " <td>".$cell_item."</td>\n";
$row ++;
if ($row > $columns)
{
$row = "1";
$row_cont ++; // Let's count rows number
$cached_output .= " </tr>\n";
}
}
$cached_output .= "</table>";
echo $this->ReturnSafeHTML($cached_output);
}
?>
//$vars = array('columns' => '3', 'cellpadding' => '1', 'cells' => '**BIG**;**GREEN**;**FROGS**;yes;yes;no;no;yes;yes');
// Init:
$delimiter=';';
$empty_cell='###';
$row=1;
$cellpadding=1;
$cellspacing=1;
$border=1;
$columns=1;
$header = 0; // this is the new parameter. the default value is 0 so if no parameter is defined there will be no table header
$row_cont = 1; // this is a row counter
// $style='border-spacing: 2px;border:2px outset #876;width:auto;margin:0 auto;';
$style='';
if (is_array($vars))
{
foreach ($vars as $param => $value)
{
if ($param == 'style') {$style=$value;}
if ($param == 'columns') {$columns=$value;}
if ($param == 'header') {$header=$value;}
if ($param == 'cellpadding')
{
$cellpadding=$value;
$border=$value;
}
if ($param == 'cells') $cells = split($delimiter, $value);
}
$cached_output = "<table cellpadding='".$cellpadding."' cellspacing='".$cellspacing."' border='".$border."' style='".$style."'>\n";
foreach ($cells as $cell_item)
{
if ($row == 1) $cached_output .= " <tr>\n";
if ($cell_item==$empty_cell) $cell_item='<br />';
// If we are not in the first row and header is set to 2 and if this is the first cell, then we render it as th
if ($header == 2 && $row_cont > 1 && $row == 1 ) $cached_output .= " <th> ".$cell_item."</th>\n";
// If header is set to 1 or 2 and we are in the first row, then we rendere this row as a table header
else if (($header == 1 || $header == 2) && $row_cont == 1) $cached_output .= " <th>".$cell_item."</th>\n";
// Else we rendere normal table cells
else $cached_output .= " <td>".$cell_item."</td>\n";
$row ++;
if ($row > $columns)
{
$row = "1";
$row_cont ++; // Let's count rows number
$cached_output .= " </tr>\n";
}
}
$cached_output .= "</table>";
echo $this->ReturnSafeHTML($cached_output);
}
?>
--
AlessandroMelandri
CategoryDevelopmentActions