xref: /plugin/avmathtable/syntax.php (revision 4b3ddbf591e3349073dbca861af625be957f8ea7)
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