1<?php
2/**
3 * DokuWiki Plugin latexcaption (Main Syntax Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Ben van Magill <ben.vanmagill16@gmail.com>
7 * @author  Till Biskup <till@till-biskup>
8 */
9
10
11class syntax_plugin_latexcaption_caption extends \dokuwiki\Extension\SyntaxPlugin
12{
13
14    /**
15     * Static variables set to keep track when scope is left.
16     */
17    private static $_types = array('figure', 'table','codeblock','fileblock');
18    private $caption_count = array();
19    private $_type = '';
20    private $_incaption = false;
21    private $_label = '';
22    private $_opts = array();
23    private $_parOpts = array();
24    private $_nested = false;
25
26    /** @var $helper helper_plugin_latexcaption */
27    var $helper = null;
28
29
30    function getInfo(){
31        return confToHash(dirname(__FILE__).'/../plugin.info.txt');
32    }
33
34    public function getType() {
35        return 'container';
36    }
37
38    public function getAllowedTypes() {
39        return array('formatting', 'substition', 'disabled', 'container', 'protected');
40    }
41
42    public function getPType() {
43        return 'block';
44    }
45
46    public function getSort() {
47        return 310;
48    }
49
50
51    public function connectTo($mode) {
52        $this->Lexer->addSpecialPattern('{{setcounter>[a-z0-9=]+?}}',$mode,'plugin_latexcaption_caption');
53        $this->Lexer->addEntryPattern('<figure.*?>(?=.*</figure>)',$mode,'plugin_latexcaption_caption');
54        $this->Lexer->addEntryPattern('<table.*?>(?=.*</table>)',$mode,'plugin_latexcaption_caption');
55        $this->Lexer->addEntryPattern('<codeblock.*?>(?=.*</codeblock>)',$mode,'plugin_latexcaption_caption');
56        $this->Lexer->addEntryPattern('<fileblock.*?>(?=.*</fileblock>)',$mode,'plugin_latexcaption_caption');
57    }
58
59    public function postConnect() {
60        $this->Lexer->addExitPattern('</figure>','plugin_latexcaption_caption');
61        $this->Lexer->addExitPattern('</table>','plugin_latexcaption_caption');
62        $this->Lexer->addExitPattern('</codeblock>','plugin_latexcaption_caption');
63        $this->Lexer->addExitPattern('</fileblock>','plugin_latexcaption_caption');
64
65        $captiontag = $this->getConf('captiontag');
66        $this->Lexer->addPattern('<'.$captiontag.'>(?=.*</'.$captiontag.'>)','plugin_latexcaption_caption');
67        $this->Lexer->addPattern('</'.$captiontag.'>','plugin_latexcaption_caption');
68    }
69
70    private function isSubType($type) {
71        return (substr($type, 0, 3) == 'sub');
72    }
73
74    private function getParType($type) {
75        return substr($type, 3);
76    }
77
78    public function handle($match, $state, $pos, Doku_Handler $handler){
79        global $caption_count;
80
81        $params = [];
82        if ($state == DOKU_LEXER_ENTER){
83            // INPUT $match:<type opts|label>
84            // Strip the <>
85            $match = substr($match,1,-1);
86            // Retrieve the label name if any
87            $match_arr = explode('|', $match, 2);
88            $matches = $match_arr[0];
89            $label = count($match_arr) == 2 ? $match_arr[1] : '';
90
91            // retrieve type and options if any
92            $match_arr = explode(' ', trim($matches), 2);
93            $type = $match_arr[0];
94            $opts = count($match_arr) == 2 ? $match_arr[1] : '';
95            // explode again in the case of multiple options;
96            $opts = (!empty($opts) ? explode(' ', $opts) : ['noalign',]);
97
98            // Set dynamic class counter variable
99            $type_counter_def = '_'.$type.'_count';
100
101            // Increment the counter of relevant type
102            // Store the type in class for caption match to determine what html tag to use
103            // This is ok since we will never have nested figures and more than one caption
104            $this->_type = $type;
105            $this->_label = $label;
106            $this->_opts = $opts;
107            $this->{$type_counter_def} = (!isset($this->{$type_counter_def}) ? 1 : $this->{$type_counter_def}+1);
108
109            // save params to class variables (cached for use in render)
110            $type_counter = $this->{$type_counter_def};
111
112            // Set global variable if theres a label (used for ref)
113            if ($label) {
114                $label = trim($label);
115                // Check if we are counting a subtype to store parent in array for references
116                if ($this->isSubType($type)) {
117                    $partype = $this->getParType($type);
118                    $parcount = $this->{'_'.$partype.'_count'};
119                }
120                // Get caption metadata ready for use in reference syntax. Also store in global array for preview to render correctly
121                $this->caption_count[$label] = array($type, $type_counter, $parcount);
122                $caption_count = $this->caption_count;
123            }
124
125            //Save parent options for use later
126            if (!$this->isSubType($type)){
127                $this->_parOpts = $opts;
128            } else {
129                $this->_nested = true;
130            }
131
132            // Set the params
133            $params['xhtml']['tagtype'] = (in_array($type, ['figure', 'subfigure']) ? 'figure' : 'div');
134            $params['label'] = $label;
135            $params['opts'] = $opts;
136            $params['type'] = $type;
137            $params['meta'] = $this->caption_count;
138
139            return array($state, $match, $pos, $params);
140        }
141        if ($state == DOKU_LEXER_MATCHED){
142            // Case of caption.
143            // Toggle the incaption flag
144            $this->_incaption = !$this->_incaption;
145            $type = $this->_type;
146            $params['label'] = $this->_label;
147            $params['xhtml']['captagtype'] = (in_array($type, ['figure', 'subfigure']) ? 'figcaption' : 'div');
148            $params['incaption'] = $this->_incaption;
149            $params['type_counter'] = $this->{'_'.$type.'_count'};
150            $params['type'] = $type;
151            // Decide what caption options to send to renderer
152            $params['opts'] = ($this->_nested ? $this->_opts : $this->_parOpts);
153
154
155            return array($state, $match, $pos, $params);
156        }
157        if ($state == DOKU_LEXER_UNMATCHED){
158            return array($state, $match, $pos, $params);
159        }
160        if ($state == DOKU_LEXER_EXIT){
161            $type = $this->_type;
162
163            if (substr($type, 0, 3) == 'sub') {
164                // Change environment back to non sub type
165                $this->_type = substr($type, 3);
166                $this->_nested = false;
167            }
168            else {
169                // reset subtype counter
170                $this->{'_sub'.$type.'_count'} = 0;
171            }
172            $params['type'] = $type;
173            $params['xhtml']['tagtype'] = (in_array($type, ['figure', 'subfigure']) ? 'figure' : 'div');
174            return array($state, $match, $pos, $params);
175        }
176        if ($state == DOKU_LEXER_SPECIAL){
177            if (substr($match,0,13) != '{{setcounter>') {
178                return true;
179            }
180
181            $match = substr($match,13,-2);
182            list($type,$num) = explode('=',trim($match));
183
184            $type = trim($type);
185            $num = (int) trim($num);
186
187            if (!in_array($type,$this::$_types)) {
188                return false;
189            }
190
191            // Update the counter. offset by 1 since counter is incremented on caption enter
192            $this->{'_'.$type.'_count'} = $num-1;
193
194            return true;
195        }
196        // this should never be reached
197        return true;
198    }
199
200    public function render($mode, Doku_Renderer $renderer, $data) {
201
202        if (empty($data)) {
203            return false;
204        }
205
206        list($state, $match, $pos, $params) = $data;
207
208        $langset = ($this->getConf('abbrev') ? 'abbrev' : 'long');
209
210        if (!in_array($mode, ['metadata', 'xhtml','odt', 'latex'])) {
211            return true;
212        }
213
214        /** @var Doku_Renderer_metadata $renderer */
215        // Store refs into metadata if there was any labels
216        if ($mode == 'metadata') {
217            if (empty($params['meta'])) return true;
218
219            $renderer->meta['plugin']['latexcaption']['references'] = $params['meta'];
220            return true;
221        }
222
223        /** @var Doku_Renderer_xhtml $renderer */
224        elseif ($mode == 'xhtml') {
225            if ($state == DOKU_LEXER_ENTER) {
226                // We know we already have a valid type on entering this
227                $type = $params['type'];
228                $tagtype = $params['xhtml']['tagtype'];
229                $label = $params['label'];
230                $opts = $params['opts'];
231
232                // print alignment/additional classes
233                $classes = implode(' plugin_latexcaption_', $opts);
234                // Rendering
235                $markup = '<'.$tagtype.' class="plugin_latexcaption_'.$type.' plugin_latexcaption_'.$classes.'" ';
236
237                if ($label){
238                    $markup .= 'id="'.$renderer->_xmlEntities($label).'"';
239                }
240
241                $markup .= '>';
242                $renderer->doc .= $markup;
243                return true;
244            }
245
246            if ($state == DOKU_LEXER_MATCHED) {
247                $captagtype = $params['xhtml']['captagtype'];
248                $incaption = $params['incaption'];
249                $count = $params['type_counter'];
250                $type = $params['type'];
251                $label = $params['label'];
252                $opts = $params['opts'];
253
254                $separator = (in_array('blank', $opts) ? '' : ': ');
255
256                if (!$this->helper)
257                    $this->helper = plugin_load('helper', 'latexcaption');
258
259                // Rendering a caption
260                if ($incaption) {
261                    $markup .= '<'.$captagtype.' class="plugin_latexcaption_caption"><span class="plugin_latexcaption_caption_number"';
262                    // Set title to label
263                    // if ($label) $markup .= ' title="'.$label.'"';
264                    $markup .= '>';
265                    if (substr($type, 0, 3) == 'sub') {
266                        $markup .= '('.$this->helper->number_to_alphabet($count).') ';
267                    }
268                    else {
269                        $markup .= $this->getLang($type.$langset).' '. $count.$separator;
270                    }
271                    $markup .= '</span><span class="plugin_latexcaption_caption_text">';
272
273                    $renderer->doc .= $markup;
274                }
275                else {
276                    $renderer->doc .= "</span></$captagtype>";
277
278                }
279                return true;
280            }
281
282            if ($state == DOKU_LEXER_UNMATCHED) {
283                // return the dokuwiki markup within the figure tags
284                $renderer->doc .= $renderer->_xmlEntities($match);
285            }
286
287            if ($state == DOKU_LEXER_EXIT) {
288                $tagtype = $params['xhtml']['tagtype'];
289                $renderer->doc .= "</$tagtype>";
290                return true;
291            }
292        }
293
294        elseif ($mode == 'latex') {
295            // All Doku $states get type param
296            // Only figure and table supported.
297            $type = $params['type'];
298            if (!in_array($type, ['figure', 'table', 'subfigure'])){
299                return true;
300            }
301
302            if ($state == DOKU_LEXER_ENTER) {
303                // Render
304                $renderer->doc .= "\begin{$type}";
305                return true;
306            }
307            if ($state == DOKU_LEXER_MATCHED) {
308                $incaption = $params['incaption'];
309                $label = $params['label'];
310                if ($incaption) {
311                    $out = '\caption{';
312                } else {
313                    $out = '}';
314                    if ($label){
315                        $out .= "\n" . "\label{$label}";
316                    }
317                }
318                $renderer->doc .= $out;
319                return true;
320            }
321            if ($state == DOKU_LEXER_UNMATCHED) {
322                // Pass it through
323                $renderer->doc .= $match;
324                return true;
325            }
326            if ($state == DOKU_LEXER_EXIT) {
327                $renderer->doc .= "\end{$type}";
328                return true;
329            }
330        }
331
332        /**
333         * WARNING: The odt mode seems to work in general, but strange things happen
334         *          with the tables - therefore, within the table tags only a table
335         *            is allowed, without any additional markup.
336         */
337        elseif ($mode == 'odt') {
338            // All Doku $states get type param
339            // Only figure and table supported.
340            $type = $params['type'];
341            if (!in_array($type, ['figure', 'table'])) {
342                return true;
343            }
344
345            if ($state == DOKU_LEXER_ENTER) {
346                $renderer->p_open();
347                return true;
348            }
349            if ($state == DOKU_LEXER_MATCHED) {
350                $incaption = $params['incaption'];
351                $count = $params['type_counter'];
352                $type = $params['type'];
353                $label = $params['label'];
354
355                if ($incaption) {
356                    $renderer->p_close();
357                    $style_name = ($type == 'figure' ? 'Illustration' : 'Table');
358                    $labelname = ($label ? $label : 'ref'.$style_name.$count);
359
360                    $out = '<text:p text:style-name="'.$style_name.'">';
361                    $out .= $this->getLang($type.$langset);
362                    $out .= '<text:sequence text:ref-name="'.$labelname.'"';
363                    $out .= 'text:name="'.$style_name.'" text:formula="ooow:'.$style_name.'+1" style:num-format="1">';
364                    $out .= ' ' . $count . '</text:sequence>: ';
365
366                    $renderer->doc .= $out;
367                } else {
368                    $renderer->doc .= '</text:p>';
369                    $renderer->p_open();
370                }
371                return true;
372            }
373            if ($state == DOKU_LEXER_UNMATCHED) {
374                // Pass it through
375                $renderer->cdata($match);
376                return true;
377            }
378            if ($state == DOKU_LEXER_EXIT) {
379                $renderer->p_close();
380                return true;
381            }
382        }
383    }
384}
385
386// vim:ts=4:sw=4:et:
387