1<?php
2/**
3 * Allow creation of XHTML definition lists:
4 * <dl>
5 *   <dt>term</dt>
6 *   <dd>definition</dd>
7 * </dl>
8 *
9 * Syntax:
10 *   ; term : definition
11 *   ; term
12 *   : definition
13 *
14 * As with other dokuwiki lists, each line must start with 2 spaces or a tab.
15 * Nested definition lists are not supported at this time.
16 *
17 * This plugin is heavily based on the definitions plugin by Pavel Vitis which
18 * in turn drew from the original definition list plugin by Stephane Chamberland.
19 * A huge thanks to both of them.
20 *
21 * Configuration:
22 *
23 * dt_fancy    Whether to wrap DT content in <span class="term">Term</span>.
24 *             Default true.
25 * classname   The html class name to be given to the DL element.
26 *             Default 'plugin_definitionlist'. This is the class used in the
27 *             bundled CSS file.
28 *
29 * ODT support provided by Gabriel Birke and LarsDW223
30 *
31 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
32 * @author     Chris Smith <chris [at] jalakai [dot] co [dot] uk>
33 * @author     Gabriel Birke <birke@d-scribe.de>
34 */
35
36if (!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
37if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
38require_once(DOKU_PLUGIN.'syntax.php');
39
40/**
41 * Settings:
42 *
43 * Define the trigger characters:
44 * ";" & ":" are the mediawiki settings.
45 * "=" & ":" are the settings for the original plugin by Pavel.
46 */
47if (!defined('DL_DT')) define('DL_DT', ';'); // character to indicate a term (dt)
48if (!defined('DL_DD')) define('DL_DD', ':'); // character to indicate a definition (dd)
49
50/**
51 *
52 */
53class syntax_plugin_definitionlist extends DokuWiki_Syntax_Plugin {
54
55    protected $stack = array();    // stack of currently open definition list items - used by handle() method
56
57    public function getType() { return 'container'; }
58    public function getAllowedTypes() { return array('container','substition','protected','disabled','formatting'); }
59    public function getPType() { return 'block'; }          // block, so not surrounded by <p> tags
60    public function getSort() { return 10; }                // before preformatted (20)
61
62    /**
63     * Connect pattern to lexer
64     */
65    public function connectTo($mode) {
66
67        $this->Lexer->addEntryPattern('\n {2,}'.DL_DT, $mode, 'plugin_definitionlist');
68        $this->Lexer->addEntryPattern('\n\t{1,}'.DL_DT, $mode, 'plugin_definitionlist');
69
70        $this->Lexer->addPattern('(?: '.DL_DD.' )', 'plugin_definitionlist');
71        $this->Lexer->addPattern('\n {2,}(?:'.DL_DT.'|'.DL_DD.')', 'plugin_definitionlist');
72        $this->Lexer->addPattern('\n\t{1,}(?:'.DL_DT.'|'.DL_DD.')', 'plugin_definitionlist');
73    }
74
75    public function postConnect() {
76        // we end the definition list when we encounter a blank line
77        $this->Lexer->addExitPattern('\n(?=[ \t]*\n)','plugin_definitionlist');
78    }
79
80    /**
81     * Handle the match
82     */
83    public function handle($match, $state, $pos, Doku_Handler $handler) {
84        switch ( $state ) {
85            case DOKU_LEXER_ENTER:
86                    array_push($this->stack, 'dt');
87                    $this->_writeCall('dl',DOKU_LEXER_ENTER,$pos,$match,$handler);    // open a new DL
88                    $this->_writeCall('dt',DOKU_LEXER_ENTER,$pos,$match,$handler);    // always start with a DT
89                    break;
90
91            case DOKU_LEXER_MATCHED:
92                    $oldtag = array_pop($this->stack);
93                    $newtag = (substr(rtrim($match), -1) == DL_DT) ? 'dt' : 'dd';
94                    array_push($this->stack, $newtag);
95
96                    $this->_writeCall($oldtag,DOKU_LEXER_EXIT,$pos,$match,$handler);  // close the current definition list item...
97                    $this->_writeCall($newtag,DOKU_LEXER_ENTER,$pos,$match,$handler); // ...and open the new dl item
98                    break;
99
100            case DOKU_LEXER_EXIT:
101                    // clean up & close any dl items on the stack
102                    while ($tag = array_pop($this->stack)) {
103                        $this->_writeCall($tag,DOKU_LEXER_EXIT,$pos,$match,$handler);
104                    }
105
106                    // and finally close the surrounding DL
107                    $this->_writeCall('dl',DOKU_LEXER_EXIT,$pos,$match,$handler);
108                    break;
109
110            case DOKU_LEXER_UNMATCHED:
111                    $handler->base($match, $state, $pos);    // cdata --- use base() as _writeCall() is prefixed for private/protected
112                    break;
113        }
114
115        return false;
116    }
117
118    /**
119     * helper function to simplify writing plugin calls to the instruction list
120     *
121     * instruction params are of the format:
122     *    0 => tag    (string)    'dl','dt','dd'
123     *    1 => state  (int)       DOKU_LEXER_??? state constant
124     *    2 => match  (string)    expected to be empty
125     */
126    protected function _writeCall($tag, $state, $pos, $match, &$handler) {
127        $handler->addPluginCall('definitionlist', array($tag, $state, ''), $state, $pos, $match);
128    }
129
130    /**
131     * Create output
132     */
133    public function render($format, Doku_Renderer $renderer, $data) {
134        if (empty($data)) return false;
135
136        switch  ($format) {
137            case 'xhtml' : return $this->render_xhtml($renderer,$data);
138            case 'odt'   :
139                if (!method_exists ($renderer, 'getODTPropertiesFromElement')) {
140                    return $this->render_odt_old($renderer,$data);
141                } else {
142                    return $this->render_odt_new($renderer,$data);
143                }
144            default :
145                //  handle unknown formats generically - map both 'dt' & 'dd' to paragraphs; ingnore the 'dl' container
146                list ($tag, $state, $match) = $data;
147                switch ( $state ) {
148                    case DOKU_LEXER_ENTER:
149                    if ($tag != 'dl') $renderer->p_open();
150                    break;
151                case DOKU_LEXER_MATCHED:                              // fall-thru
152                case DOKU_LEXER_UNMATCHED:                            // defensive, shouldn't occur
153                    $renderer->cdata($match);
154                    break;
155                case DOKU_LEXER_EXIT:
156                    if ($tag != 'dl') $renderer->p_close();
157                    break;
158                }
159                return true;
160        }
161
162        return false;
163    }
164
165    /**
166     * create output for the xhtml renderer
167     *
168     */
169    protected function render_xhtml(Doku_Renderer $renderer, $data) {
170        list($tag,$state,$match) = $data;
171
172        switch ( $state ) {
173            case DOKU_LEXER_ENTER:
174                $renderer->doc .= $this->_open($tag);
175                break;
176            case DOKU_LEXER_MATCHED:
177            case DOKU_LEXER_UNMATCHED:                            // defensive, shouldn't occur
178                $renderer->cdata($tag);
179                break;
180            case DOKU_LEXER_EXIT:
181                $renderer->doc .= $this->_close($tag);
182                break;
183        }
184        return true;
185    }
186
187    /**
188     * create output for ODT renderer
189     *
190     * @author:   Gabriel Birke <birke@d-scribe.de>
191     */
192    protected function render_odt_old(Doku_Renderer $renderer, $data) {
193        static $param_styles = array('dd' => 'def_f5_list', 'dt' => 'def_f5_term');
194        $this->_set_odt_styles_old($renderer);
195
196        list ($tag, $state, $match) = $data;
197
198        switch ( $state ) {
199            case DOKU_LEXER_ENTER:
200                if ($tag == 'dl') {
201                    $renderer->p_close();
202                } else {
203                    $renderer->p_open($param_styles[$tag]);
204                }
205                break;
206            case DOKU_LEXER_MATCHED:
207            case DOKU_LEXER_UNMATCHED:                            // defensive, shouldn't occur
208                $renderer->cdata($match);
209                break;
210            case DOKU_LEXER_EXIT:
211                if ($tag != 'dl') {
212                    $renderer->p_close();
213                } else {
214                    $renderer->p_open();
215                }
216                break;
217        }
218
219        return true;
220    }
221
222    /**
223     * Create output for ODT renderer (newer version)
224     * @author: LarsDW223
225     */
226    protected function render_odt_new(Doku_Renderer $renderer, $data) {
227        static $style_data = array();
228        static $dl_properties = array();
229        $this->_set_odt_styles_new($renderer, $style_data, $dl_properties);
230
231        list ($tag, $state, $match) = $data;
232
233        switch ( $state ) {
234            case DOKU_LEXER_ENTER:
235                if ($tag == 'dl') {
236                    $properties = array();
237                    $renderer->_odtTableOpenUseProperties($dl_properties);
238
239                    $properties ['width'] = $style_data ['margin-left'];
240                    $renderer->_odtTableAddColumnUseProperties($properties);
241                } else {
242                    if ($tag == 'dt') {
243                        $renderer->tablerow_open();
244
245                        $properties = array();
246                        $properties ['border-left'] = 'none';
247                        $properties ['border-right'] = 'none';
248                        $properties ['border-top'] = 'none';
249                        $properties ['border-bottom'] = $style_data ['border-bottom'];
250                        $renderer->_odtTableCellOpenUseProperties ($properties);
251
252                        $renderer->_odtSpanOpen('Plugin_DefinitionList_Term');
253                    } else {
254                        $properties = array();
255                        $properties ['border-left'] = 'none';
256                        $properties ['border-right'] = 'none';
257                        $properties ['border-top'] = 'none';
258                        $properties ['border-bottom'] = $style_data ['border-bottom'];
259                        $renderer->_odtTableCellOpenUseProperties ($properties);
260
261                        if (!empty($style_data ['image'])) {
262                            $properties = array();
263                            $properties ['margin-right'] = $style_data ['padding-left'];
264                            $renderer->_odtAddImageUseProperties($style_data ['image'], $properties);
265                        }
266
267                        $renderer->_odtSpanOpen('Plugin_DefinitionList_Description');
268                    }
269                }
270                break;
271            case DOKU_LEXER_MATCHED:
272            case DOKU_LEXER_UNMATCHED:                            // defensive, shouldn't occur
273                $renderer->cdata($match);
274                break;
275            case DOKU_LEXER_EXIT:
276                if ($tag != 'dl') {
277                    $renderer->_odtSpanClose();
278                    $renderer->tablecell_close();
279                    if ($tag == 'dd') {
280                        $renderer->p_close();
281                        $renderer->tablerow_close();
282                    }
283                } else {
284                    $renderer->table_close();
285                }
286                break;
287        }
288
289        return true;
290    }
291
292    /**
293     * set definition list styles, used by render_odt_old()
294     *
295     * add definition list styles to the renderer's autostyles property (once only)
296     *
297     * @param  $renderer    current (odt) renderer object
298     * @return void
299     */
300    protected function _set_odt_styles_old(Doku_Renderer $renderer) {
301        static $do_once = true;
302
303        if ($do_once) {
304            $renderer->autostyles["def_f5_term"] = '
305                <style:style style:name="def_f5_term" style:display-name="def_term" style:family="paragraph">
306                    <style:paragraph-properties fo:margin-top="0.18cm" fo:margin-bottom="0cm" fo:keep-together="always" style:page-number="auto" fo:keep-with-next="always"/>
307                    <style:text-properties fo:font-weight="bold"/>
308                </style:style>';
309            $renderer->autostyles["def_f5_list"] = '
310                <style:style style:name="def_f5_list" style:display-name="def_list" style:family="paragraph">
311                    <style:paragraph-properties fo:margin-left="0.25cm" fo:margin-right="0cm" fo:text-indent="0cm" style:auto-text-indent="false"/>
312                </style:style>';
313
314            $do_once = false;
315        }
316    }
317
318    /**
319     * Create definition list styles, used by render_odt_new():
320     * Adds definition list styles to the ODT documents common styles (once only)
321     *
322     * @param  Doku_renderer $renderer   current (odt) renderer object
323     * @param  Array         $style_data Array for returning relevant properties to the caller
324     * @author: LarsDW223
325     */
326    protected function _set_odt_styles_new(Doku_Renderer $renderer, &$style_data, &$dl_properties) {
327        static $do_once = true;
328
329        if ($do_once) {
330            // Create parent style to group the others beneath it
331            if (!$renderer->styleExists('Plugin_DivAlign2')) {
332                $parent_properties = array();
333                $parent_properties ['style-parent'] = NULL;
334                $parent_properties ['style-class'] = 'Plugin_DefinitionList';
335                $parent_properties ['style-name'] = 'Plugin_DefinitionList';
336                $parent_properties ['style-display-name'] = 'Plugin DefinitionList';
337                $renderer->createTextStyle($parent_properties);
338            }
339
340            // Get the current HTML stack from the ODT renderer
341            $stack = $renderer->getHTMLStack ();
342
343            // Save state to restore it later
344            $state = array();
345            $stack->getState ($state);
346            // Only for debugging ==> see end of this function
347            //$renderer->dumpHTMLStack ();
348
349            $stack->open('dl', 'class="plugin_definitionlist"', NULL, NULL);
350            $renderer->getODTPropertiesFromElement ($dl_properties, $stack->getCurrentElement(), 'screen', true);
351
352            $stack->open('dd', NULL, NULL, NULL);
353
354            // Get CSS properties for ODT export.
355            $dd_properties = array ();
356            $renderer->getODTPropertiesFromElement ($dd_properties, $stack->getCurrentElement(), 'screen', true);
357
358            $stack->close('dd');
359            $stack->open('dt', NULL, NULL, NULL);
360
361            // Get CSS properties for ODT export.
362            $dt_properties = array ();
363            $renderer->getODTPropertiesFromElement ($dt_properties, $stack->getCurrentElement(), 'screen', true);
364
365            // Set style data to be returned to caller
366            $style_data ['border-bottom'] = $dt_properties ['border-top'];
367            $style_data ['image'] = $dd_properties ['background-image'];
368            $style_data ['margin-left'] = $dd_properties ['margin-left'];
369            $style_data ['padding-left'] = $dd_properties ['padding-left'];
370
371            // Create text style for term
372            $dt_properties ['border-top'] = NULL;
373            $dt_properties ['style-class'] = NULL;
374            $dt_properties ['style-parent'] = 'Plugin_DefinitionList';
375            $dt_properties ['style-name'] = 'Plugin_DefinitionList_Term';
376            $dt_properties ['style-display-name'] = 'Term';
377            $renderer->createTextStyle($dt_properties);
378
379            // Create text style for description
380            $dd_properties ['border-bottom'] = $dt_properties ['border-top'];
381            $dd_properties ['style-class'] = NULL;
382            $dd_properties ['style-parent'] = 'Plugin_DefinitionList';
383            $dd_properties ['style-name'] = 'Plugin_DefinitionList_Description';
384            $dd_properties ['style-display-name'] = 'Description';
385            $dd_properties ['background'] = NULL;
386            $renderer->createTextStyle($dd_properties);
387
388            $stack->restoreState ($state);
389            // Only for debugging to check if the ODT plugins HTML stack
390            // is restored to the start state
391            //$renderer->dumpHTMLStack ();
392
393            $do_once = false;
394        }
395    }
396
397    /**
398     * open a definition list tag, used by render_xhtml()
399     *
400     * @param   $tag  (string)    'dl', 'dt' or 'dd'
401     * @return  (string)          html used to open the tag
402     */
403    protected function _open($tag) {
404        if ($tag == 'dl') {
405            if ($this->getConf('classname')) {
406                $tag .= ' class="'.$this->getConf('classname').'"';
407            }
408            $wrap = NL;
409        } else {
410            $wrap = ($tag == 'dt' && $this->getConf('dt_fancy')) ? '<span class="term">' : '';
411        }
412        return "<$tag>$wrap";
413    }
414
415    /**
416     * close a definition list tag, used by render_xhtml()
417     *
418     * @param   $tag  (string)    'dl', 'dt' or 'dd'
419     * @return  (string)          html used to close the tag
420     */
421    protected function _close($tag) {
422        $wrap = ($tag == 'dt' && $this->getConf('dt_fancy')) ? '</span>' : '';
423        return "$wrap</$tag>\n";
424    }
425
426}
427
428//Setup VIM: ex: et ts=4 enc=utf-8 :
429