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