1*4b3ddbf5SSyntaxseed<?php 2*4b3ddbf5SSyntaxseed 3*4b3ddbf5SSyntaxseed/** 4*4b3ddbf5SSyntaxseed * Plugin AVMathTable 5*4b3ddbf5SSyntaxseed * 6*4b3ddbf5SSyntaxseed * Adds math to columns for Dokuwiki tables. 7*4b3ddbf5SSyntaxseed * Supported Math: 8*4b3ddbf5SSyntaxseed * AVG - Calculate average of the column. 9*4b3ddbf5SSyntaxseed * SUM - Calculate total of the column. 10*4b3ddbf5SSyntaxseed * CNT - Number of numeric values in the column above this cell. 11*4b3ddbf5SSyntaxseed * 12*4b3ddbf5SSyntaxseed * USAGE: 13*4b3ddbf5SSyntaxseed<mathtable> 14*4b3ddbf5SSyntaxseed^ Name ^ Deposited ^ Balance ^ 15*4b3ddbf5SSyntaxseed| John | 25 | 500 | 16*4b3ddbf5SSyntaxseed| Mary | 40 | 680 | 17*4b3ddbf5SSyntaxseed| Lex | 10 | 140 | 18*4b3ddbf5SSyntaxseed| TOTAL| =AVG | =SUM | 19*4b3ddbf5SSyntaxseed</mathtable> 20*4b3ddbf5SSyntaxseed * 21*4b3ddbf5SSyntaxseed * @license GPL-2.0 (https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 22*4b3ddbf5SSyntaxseed * @author Sherri W. (http://syntaxseed.com) 23*4b3ddbf5SSyntaxseed */ 24*4b3ddbf5SSyntaxseed 25*4b3ddbf5SSyntaxseedif (!defined('DOKU_INC')) { 26*4b3ddbf5SSyntaxseed define('DOKU_INC', realpath(dirname(__FILE__) . '/../../') . '/'); 27*4b3ddbf5SSyntaxseed} 28*4b3ddbf5SSyntaxseedif (!defined('DOKU_PLUGIN')) { 29*4b3ddbf5SSyntaxseed define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 30*4b3ddbf5SSyntaxseed} 31*4b3ddbf5SSyntaxseed 32*4b3ddbf5SSyntaxseed 33*4b3ddbf5SSyntaxseed/** 34*4b3ddbf5SSyntaxseed * All DokuWiki plugins to extend the parser/rendering mechanism 35*4b3ddbf5SSyntaxseed * need to inherit from this class 36*4b3ddbf5SSyntaxseed */ 37*4b3ddbf5SSyntaxseedclass syntax_plugin_avmathtable extends DokuWiki_Syntax_Plugin 38*4b3ddbf5SSyntaxseed{ 39*4b3ddbf5SSyntaxseed private array $infoTable = []; 40*4b3ddbf5SSyntaxseed 41*4b3ddbf5SSyntaxseed /** 42*4b3ddbf5SSyntaxseed * What kind of syntax are we? 43*4b3ddbf5SSyntaxseed */ 44*4b3ddbf5SSyntaxseed public function getType() 45*4b3ddbf5SSyntaxseed { 46*4b3ddbf5SSyntaxseed return 'substition'; 47*4b3ddbf5SSyntaxseed } 48*4b3ddbf5SSyntaxseed 49*4b3ddbf5SSyntaxseed /** 50*4b3ddbf5SSyntaxseed * Where to sort in? 51*4b3ddbf5SSyntaxseed */ 52*4b3ddbf5SSyntaxseed public function getSort() 53*4b3ddbf5SSyntaxseed { 54*4b3ddbf5SSyntaxseed return 999; 55*4b3ddbf5SSyntaxseed } 56*4b3ddbf5SSyntaxseed 57*4b3ddbf5SSyntaxseed 58*4b3ddbf5SSyntaxseed /** 59*4b3ddbf5SSyntaxseed * Connect pattern to lexer 60*4b3ddbf5SSyntaxseed */ 61*4b3ddbf5SSyntaxseed public function connectTo($mode) 62*4b3ddbf5SSyntaxseed { 63*4b3ddbf5SSyntaxseed $this->Lexer->addEntryPattern('\<mathtable\>', $mode, 'plugin_avmathtable'); 64*4b3ddbf5SSyntaxseed } 65*4b3ddbf5SSyntaxseed 66*4b3ddbf5SSyntaxseed public function postConnect() 67*4b3ddbf5SSyntaxseed { 68*4b3ddbf5SSyntaxseed $this->Lexer->addExitPattern('\</mathtable\>', 'plugin_avmathtable'); 69*4b3ddbf5SSyntaxseed } 70*4b3ddbf5SSyntaxseed 71*4b3ddbf5SSyntaxseed 72*4b3ddbf5SSyntaxseed /** 73*4b3ddbf5SSyntaxseed * Handle the match 74*4b3ddbf5SSyntaxseed */ 75*4b3ddbf5SSyntaxseed public function handle($match, $state, $pos, Doku_Handler $handler) 76*4b3ddbf5SSyntaxseed { 77*4b3ddbf5SSyntaxseed switch ($state) { 78*4b3ddbf5SSyntaxseed case DOKU_LEXER_ENTER: 79*4b3ddbf5SSyntaxseed return array($state, ''); 80*4b3ddbf5SSyntaxseed case DOKU_LEXER_MATCHED: 81*4b3ddbf5SSyntaxseed break; 82*4b3ddbf5SSyntaxseed case DOKU_LEXER_UNMATCHED: 83*4b3ddbf5SSyntaxseed 84*4b3ddbf5SSyntaxseed $tables = $this->parseTable($match); 85*4b3ddbf5SSyntaxseed [$table, $info] = $tables; 86*4b3ddbf5SSyntaxseed 87*4b3ddbf5SSyntaxseed $this->infoTable = $info; 88*4b3ddbf5SSyntaxseed //$match = $table; 89*4b3ddbf5SSyntaxseed 90*4b3ddbf5SSyntaxseed return array($state, $tables); 91*4b3ddbf5SSyntaxseed 92*4b3ddbf5SSyntaxseed case DOKU_LEXER_EXIT: 93*4b3ddbf5SSyntaxseed return array($state, ''); 94*4b3ddbf5SSyntaxseed case DOKU_LEXER_SPECIAL: 95*4b3ddbf5SSyntaxseed break; 96*4b3ddbf5SSyntaxseed } 97*4b3ddbf5SSyntaxseed return array(); 98*4b3ddbf5SSyntaxseed } 99*4b3ddbf5SSyntaxseed 100*4b3ddbf5SSyntaxseed 101*4b3ddbf5SSyntaxseed /** 102*4b3ddbf5SSyntaxseed * Create output 103*4b3ddbf5SSyntaxseed */ 104*4b3ddbf5SSyntaxseed public function render($mode, Doku_Renderer $renderer, $data) 105*4b3ddbf5SSyntaxseed { 106*4b3ddbf5SSyntaxseed if ($mode == 'xhtml') { 107*4b3ddbf5SSyntaxseed 108*4b3ddbf5SSyntaxseed if (empty($data[1])) { 109*4b3ddbf5SSyntaxseed return; 110*4b3ddbf5SSyntaxseed } 111*4b3ddbf5SSyntaxseed 112*4b3ddbf5SSyntaxseed list($state, $tables) = $data; 113*4b3ddbf5SSyntaxseed 114*4b3ddbf5SSyntaxseed // echo('<pre>'); 115*4b3ddbf5SSyntaxseed // var_dump($state, $tables); 116*4b3ddbf5SSyntaxseed // echo('</pre>'); 117*4b3ddbf5SSyntaxseed 118*4b3ddbf5SSyntaxseed [$match, $info] = $tables; 119*4b3ddbf5SSyntaxseed $this->infoTable = $info; 120*4b3ddbf5SSyntaxseed 121*4b3ddbf5SSyntaxseed switch ($state) { 122*4b3ddbf5SSyntaxseed case DOKU_LEXER_ENTER: 123*4b3ddbf5SSyntaxseed //$renderer->doc .= "<div class='avMathTable'>"; 124*4b3ddbf5SSyntaxseed break; 125*4b3ddbf5SSyntaxseed 126*4b3ddbf5SSyntaxseed case DOKU_LEXER_MATCHED: 127*4b3ddbf5SSyntaxseed break; 128*4b3ddbf5SSyntaxseed 129*4b3ddbf5SSyntaxseed case DOKU_LEXER_UNMATCHED: 130*4b3ddbf5SSyntaxseed $info = []; 131*4b3ddbf5SSyntaxseed 132*4b3ddbf5SSyntaxseed $output = $this->renderArrayIntoTable($match); 133*4b3ddbf5SSyntaxseed $html = p_render('xhtml', p_get_instructions($output), $info); 134*4b3ddbf5SSyntaxseed 135*4b3ddbf5SSyntaxseed $renderer->doc .= "<div class='avmathtable'>" . $html . "</div>"; 136*4b3ddbf5SSyntaxseed 137*4b3ddbf5SSyntaxseed break; 138*4b3ddbf5SSyntaxseed 139*4b3ddbf5SSyntaxseed case DOKU_LEXER_EXIT: 140*4b3ddbf5SSyntaxseed //$renderer->doc .= "</div>"; 141*4b3ddbf5SSyntaxseed break; 142*4b3ddbf5SSyntaxseed 143*4b3ddbf5SSyntaxseed case DOKU_LEXER_SPECIAL: 144*4b3ddbf5SSyntaxseed break; 145*4b3ddbf5SSyntaxseed } 146*4b3ddbf5SSyntaxseed return true; 147*4b3ddbf5SSyntaxseed } 148*4b3ddbf5SSyntaxseed return false; 149*4b3ddbf5SSyntaxseed } 150*4b3ddbf5SSyntaxseed 151*4b3ddbf5SSyntaxseed /** 152*4b3ddbf5SSyntaxseed * Parse the table syntax into an array. 153*4b3ddbf5SSyntaxseed */ 154*4b3ddbf5SSyntaxseed private function parseTable(string $tableSyntax): array 155*4b3ddbf5SSyntaxseed { 156*4b3ddbf5SSyntaxseed // 1) Parse the wiki table text into a collection of instructions. 157*4b3ddbf5SSyntaxseed $calls = p_get_instructions($tableSyntax); 158*4b3ddbf5SSyntaxseed 159*4b3ddbf5SSyntaxseed // echo('<pre>'); 160*4b3ddbf5SSyntaxseed // var_dump($calls); 161*4b3ddbf5SSyntaxseed // echo('</pre>'); 162*4b3ddbf5SSyntaxseed 163*4b3ddbf5SSyntaxseed // 2) Convert to array 164*4b3ddbf5SSyntaxseed $table = []; 165*4b3ddbf5SSyntaxseed $row = []; 166*4b3ddbf5SSyntaxseed $cell = null; 167*4b3ddbf5SSyntaxseed 168*4b3ddbf5SSyntaxseed $infoTable = []; // Keep track of things like if it's a header cell or a regular cell, alignment, etc. 169*4b3ddbf5SSyntaxseed $infoRow = []; 170*4b3ddbf5SSyntaxseed $infoCell = ['type' => 'plain', 'alignment' => 'left']; 171*4b3ddbf5SSyntaxseed 172*4b3ddbf5SSyntaxseed foreach ($calls as $call) { 173*4b3ddbf5SSyntaxseed [$cmd, $data] = $call; 174*4b3ddbf5SSyntaxseed 175*4b3ddbf5SSyntaxseed switch ($cmd) { 176*4b3ddbf5SSyntaxseed 177*4b3ddbf5SSyntaxseed case 'tableheader_open': 178*4b3ddbf5SSyntaxseed $cell = ''; 179*4b3ddbf5SSyntaxseed $infoCell = ['type' => 'header', 'alignment' => (is_null($data[1]) ? 'left' : $data[1])]; 180*4b3ddbf5SSyntaxseed 181*4b3ddbf5SSyntaxseed break; 182*4b3ddbf5SSyntaxseed case 'tablecell_open': 183*4b3ddbf5SSyntaxseed $cell = ''; 184*4b3ddbf5SSyntaxseed $infoCell = ['type' => 'plain', 'alignment' => (is_null($data[1]) ? 'left' : $data[1])]; 185*4b3ddbf5SSyntaxseed break; 186*4b3ddbf5SSyntaxseed 187*4b3ddbf5SSyntaxseed case 'cdata': 188*4b3ddbf5SSyntaxseed if ($cell !== null) { 189*4b3ddbf5SSyntaxseed $cell .= $data[0]; 190*4b3ddbf5SSyntaxseed } 191*4b3ddbf5SSyntaxseed break; 192*4b3ddbf5SSyntaxseed 193*4b3ddbf5SSyntaxseed case 'tableheader_close': 194*4b3ddbf5SSyntaxseed case 'tablecell_close': 195*4b3ddbf5SSyntaxseed $row[] = trim($cell); 196*4b3ddbf5SSyntaxseed $infoRow[] = $infoCell; 197*4b3ddbf5SSyntaxseed $cell = null; // Reset 198*4b3ddbf5SSyntaxseed $infoCell = ['type' => 'plain', 'alignment' => 'left']; // Reset 199*4b3ddbf5SSyntaxseed break; 200*4b3ddbf5SSyntaxseed 201*4b3ddbf5SSyntaxseed case 'tablerow_close': 202*4b3ddbf5SSyntaxseed $table[] = $row; 203*4b3ddbf5SSyntaxseed $infoTable[] = $infoRow; 204*4b3ddbf5SSyntaxseed $row = []; 205*4b3ddbf5SSyntaxseed $infoRow = []; 206*4b3ddbf5SSyntaxseed break; 207*4b3ddbf5SSyntaxseed } 208*4b3ddbf5SSyntaxseed } 209*4b3ddbf5SSyntaxseed 210*4b3ddbf5SSyntaxseed return [$table, $infoTable]; 211*4b3ddbf5SSyntaxseed } 212*4b3ddbf5SSyntaxseed 213*4b3ddbf5SSyntaxseed 214*4b3ddbf5SSyntaxseed private function renderArrayIntoTable(array $table): string 215*4b3ddbf5SSyntaxseed { 216*4b3ddbf5SSyntaxseed $output = ''; 217*4b3ddbf5SSyntaxseed 218*4b3ddbf5SSyntaxseed $columnData = []; 219*4b3ddbf5SSyntaxseed $rowNum = 1; 220*4b3ddbf5SSyntaxseed // Create each row: 221*4b3ddbf5SSyntaxseed foreach ($table as $i => $row) { 222*4b3ddbf5SSyntaxseed // Create each cell: 223*4b3ddbf5SSyntaxseed foreach ($row as $j => $cell) { 224*4b3ddbf5SSyntaxseed // Initialize info about this column. 225*4b3ddbf5SSyntaxseed if ($rowNum == 1) { 226*4b3ddbf5SSyntaxseed $columnData[$j] = ['sum' => 0, 'count' => 0, 'precision' => 0]; 227*4b3ddbf5SSyntaxseed } 228*4b3ddbf5SSyntaxseed 229*4b3ddbf5SSyntaxseed // Open up the cell wiki syntax. 230*4b3ddbf5SSyntaxseed if ($this->infoTable[$i][$j]['type'] == 'header') { 231*4b3ddbf5SSyntaxseed $output .= "^ "; 232*4b3ddbf5SSyntaxseed } else { 233*4b3ddbf5SSyntaxseed $output .= "| "; 234*4b3ddbf5SSyntaxseed } 235*4b3ddbf5SSyntaxseed if ($this->infoTable[$i][$j]['alignment'] == 'right' || $this->infoTable[$i][$j]['alignment'] == 'center') { 236*4b3ddbf5SSyntaxseed $output .= " "; 237*4b3ddbf5SSyntaxseed } 238*4b3ddbf5SSyntaxseed 239*4b3ddbf5SSyntaxseed // Gather info about the numbers in this cell. 240*4b3ddbf5SSyntaxseed if (is_numeric($cell)) { 241*4b3ddbf5SSyntaxseed if ((int)$cell == $cell) { 242*4b3ddbf5SSyntaxseed $columnData[$j]['sum'] += intval($cell); 243*4b3ddbf5SSyntaxseed $columnData[$j]['count'] += 1; 244*4b3ddbf5SSyntaxseed } elseif ((float)$cell == $cell) { 245*4b3ddbf5SSyntaxseed $columnData[$j]['sum'] += floatval($cell); 246*4b3ddbf5SSyntaxseed $columnData[$j]['count'] += 1; 247*4b3ddbf5SSyntaxseed $columnData[$j]['precision'] = max($columnData[$j]['precision'], $this->countDecimalPlaces((float)$cell)); 248*4b3ddbf5SSyntaxseed } 249*4b3ddbf5SSyntaxseed } 250*4b3ddbf5SSyntaxseed 251*4b3ddbf5SSyntaxseed // Insert the cell value. TODO : Handle special math features. 252*4b3ddbf5SSyntaxseed $output .= $this->insertCellData($cell, $columnData, $rowNum, $j); 253*4b3ddbf5SSyntaxseed 254*4b3ddbf5SSyntaxseed 255*4b3ddbf5SSyntaxseed // Close up the cell wiki syntax. 256*4b3ddbf5SSyntaxseed if ($this->infoTable[$i][$j]['alignment'] == 'left' || $this->infoTable[$i][$j]['alignment'] == 'center') { 257*4b3ddbf5SSyntaxseed $output .= " "; 258*4b3ddbf5SSyntaxseed } 259*4b3ddbf5SSyntaxseed if ($this->infoTable[$i][$j]['type'] == 'header') { 260*4b3ddbf5SSyntaxseed $output .= " ^"; 261*4b3ddbf5SSyntaxseed } else { 262*4b3ddbf5SSyntaxseed $output .= " |"; 263*4b3ddbf5SSyntaxseed } 264*4b3ddbf5SSyntaxseed } 265*4b3ddbf5SSyntaxseed $output .= "\n"; // End of a row. 266*4b3ddbf5SSyntaxseed $rowNum++; 267*4b3ddbf5SSyntaxseed } 268*4b3ddbf5SSyntaxseed 269*4b3ddbf5SSyntaxseed //$dump = var_export($table, true); 270*4b3ddbf5SSyntaxseed 271*4b3ddbf5SSyntaxseed return $output; 272*4b3ddbf5SSyntaxseed } 273*4b3ddbf5SSyntaxseed 274*4b3ddbf5SSyntaxseed /** 275*4b3ddbf5SSyntaxseed * Put the value back in the cell. Substitute math where applicable. 276*4b3ddbf5SSyntaxseed */ 277*4b3ddbf5SSyntaxseed private function insertCellData(mixed $cell, array $columnData, int $rowNum, int $colNum) 278*4b3ddbf5SSyntaxseed { 279*4b3ddbf5SSyntaxseed 280*4b3ddbf5SSyntaxseed // echo('<pre>'); 281*4b3ddbf5SSyntaxseed // var_dump($columnData); 282*4b3ddbf5SSyntaxseed // echo('</pre>'); 283*4b3ddbf5SSyntaxseed 284*4b3ddbf5SSyntaxseed switch (trim($cell)) { 285*4b3ddbf5SSyntaxseed case '=SUM': 286*4b3ddbf5SSyntaxseed return '<span class="avmathtablevalue">' . $columnData[$colNum]['sum'] . '</span>'; 287*4b3ddbf5SSyntaxseed break; 288*4b3ddbf5SSyntaxseed case '=CNT': 289*4b3ddbf5SSyntaxseed return '<span class="avmathtablevalue">' . $columnData[$colNum]['count'] . '</span>'; 290*4b3ddbf5SSyntaxseed break; 291*4b3ddbf5SSyntaxseed case '=AVG': 292*4b3ddbf5SSyntaxseed return '<span class="avmathtablevalue">' . round(($columnData[$colNum]['sum'] / $columnData[$colNum]['count']), $columnData[$colNum]['precision']+1) . '</span>'; 293*4b3ddbf5SSyntaxseed break; 294*4b3ddbf5SSyntaxseed default: 295*4b3ddbf5SSyntaxseed return $cell; 296*4b3ddbf5SSyntaxseed } 297*4b3ddbf5SSyntaxseed } 298*4b3ddbf5SSyntaxseed 299*4b3ddbf5SSyntaxseed private function countDecimalPlaces(float $num): int 300*4b3ddbf5SSyntaxseed { 301*4b3ddbf5SSyntaxseed $fNumber = floatval($num); 302*4b3ddbf5SSyntaxseed for ($iDecimals = 0; $fNumber != round($fNumber, $iDecimals); $iDecimals++); 303*4b3ddbf5SSyntaxseed return $iDecimals; 304*4b3ddbf5SSyntaxseed } 305*4b3ddbf5SSyntaxseed} // End class 306