1<?php
2/**
3 * Keyboard Syntax Plugin: Marks text as keyboard key presses.
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Gina Haeussge <osd@foosel.net>
7 * @author     Christopher Arndt
8 */
9
10/**
11 * All DokuWiki plugins to extend the parser/rendering mechanism
12 * need to inherit from this class
13 */
14class syntax_plugin_keyboard extends DokuWiki_Syntax_Plugin {
15    protected $lastClass = null;
16    protected $styles = array ('__keyboard' => array ('display-name' => 'Keyboard',
17                                                      'name' => null),
18                               '__keyboard_keypress' => array ('display-name' => 'Keypress',
19                                                               'name' => null));
20    protected $stylesCreated = false;
21
22    function getType() { return 'formatting'; }
23
24    function getAllowedTypes() {
25        return array('formatting', 'substition', 'disabled');
26    }
27
28    function getSort(){ return 444; }
29
30    function connectTo($mode) {
31         $this->Lexer->addEntryPattern('<key class="[^"]*">', $mode, 'plugin_keyboard');
32         $this->Lexer->addEntryPattern('<kbd class="[^"]*">', $mode, 'plugin_keyboard');
33         $this->Lexer->addEntryPattern('<key>', $mode, 'plugin_keyboard');
34         $this->Lexer->addEntryPattern('<kbd>', $mode, 'plugin_keyboard');
35    }
36
37    function postConnect() {
38        $this->Lexer->addExitPattern('</key>', 'plugin_keyboard');
39        $this->Lexer->addExitPattern('</kbd>', 'plugin_keyboard');
40    }
41
42    /**
43     * Handle the match
44     */
45    function handle($match, $state, $pos, Doku_Handler $handler) {
46        switch ($state) {
47            case DOKU_LEXER_ENTER :
48                if (preg_match('/class="[^"]*"/', $match, $classString) === 1) {
49                    $class = substr($classString[0], 6);
50                    $class = trim($class, '"');
51                } else {
52                    $class = $this->getConf('css_class');
53                }
54                $this->lastClass = $class;
55                return array($state, '', $this->lastClass);
56            case DOKU_LEXER_UNMATCHED :
57                $length = strlen($match);
58                if ($length > 1 &&
59                    !($match[0] == "'" && $match[$length-1] == "'")) {
60                    $mpos = strpos($match, '-');
61                    $ppos = strpos($match, '+');
62                    if(!$mpos)
63                        $separator = '+';
64                    else if(!$ppos)
65                        $separator = '-';
66                    else
67                        $separator = substr($match,($mpos<$ppos)?$mpos:$ppos, 1);
68                    $keys = explode($separator, $match);
69                    $keys = array_map('trim', $keys);
70                } else {
71                    $keys = array($match);
72                }
73                return array($state, $keys, $this->lastClass);
74            case DOKU_LEXER_EXIT:
75                return array($state, '');
76        }
77        return array();
78    }
79
80    /**
81     * Create output
82     */
83    function render($mode, Doku_Renderer $renderer, $data) {
84        if ($mode == 'xhtml') {
85            list($state, $match, $class) = $data;
86            switch ($state) {
87                case DOKU_LEXER_ENTER :
88                    if (empty($class)) {
89                        $renderer->doc .= '<kbd>';
90                    } else {
91                        $renderer->doc .= '<kbd class="'.$class.'">';
92                    }
93                    break;
94                case DOKU_LEXER_UNMATCHED :
95                    foreach ($match as $key) {
96                        if ($this->getConf('disable_translation')) {
97                            $out[] = $renderer->_xmlEntities($key);
98                        } else if (substr($key, 0, 1) == "'" and
99                                   substr($key, -1, 1) == "'" and
100                                   strlen($key) > 1) {
101                            $out[] = $renderer->_xmlEntities(substr($key,1,-1));
102                        } else {
103                            $subst = $this->getLang($key);
104                            if ($subst) {
105                                $out[] = $subst;
106                            } else {
107                                $out[] = $renderer->_xmlEntities(ucfirst($key));
108                            }
109                        }
110                    }
111                    if (empty($class)) {
112                        $renderer->doc .= implode('</kbd>+<kbd>', $out);
113                    } else {
114                        $renderer->doc .= implode('</kbd>+<kbd class="'.$class.'">', $out);
115                    }
116                    break;
117                case DOKU_LEXER_EXIT :
118                    $renderer->doc .= '</kbd>';
119                    break;
120            }
121            return true;
122        }
123        if ($mode == 'odt') {
124            list($state, $match, $class) = $data;
125            switch ($state) {
126                case DOKU_LEXER_ENTER :
127                    if ($this->stylesCreated == false || !array_key_exists ($class, $this->styles)) {
128                        $this->createODTStyles($renderer, $class);
129                    }
130                    $this->renderODTOpenSpan($renderer, $this->styles[$class]['name']);
131                    break;
132                case DOKU_LEXER_UNMATCHED :
133                    foreach ($match as $key) {
134                        if ($this->getConf('disable_translation')) {
135                            $out[] = $key;
136                        } else if (substr($key, 0, 1) == "'" and
137                                   substr($key, -1, 1) == "'" and
138                                   strlen($key) > 1) {
139                            $out[] = substr($key,1,-1);
140                        } else {
141                            $subst = $this->getLang($key);
142                            if ($subst) {
143                                $out[] = $subst;
144                            } else {
145                                $out[] = ucfirst($key);
146                            }
147                        }
148                    }
149                    $max = count($out);
150                    for ($index = 0 ; $index < $max ; $index++) {
151                        $renderer->cdata ($out [$index]);
152                        if ($index+1 < $max) {
153                            $this->renderODTCloseSpan($renderer);
154                            $renderer->cdata ('+');
155                            $this->renderODTOpenSpan($renderer, $this->styles[$class]['name']);
156                        }
157                    }
158                    break;
159                case DOKU_LEXER_EXIT :
160                    $this->renderODTCloseSpan($renderer);
161                    break;
162            }
163            return true;
164        }
165        return false;
166    }
167
168    protected function createODTStyles (Doku_Renderer $renderer, $class = null) {
169        if ( method_exists ($renderer, 'getODTPropertiesFromElement') === true ) {
170            // Create parent style to group the others beneath it
171            if (!$renderer->styleExists('Plugin_Keyboard')) {
172                $parent_properties = array();
173                $parent_properties ['style-parent'] = NULL;
174                $parent_properties ['style-class'] = 'Plugin Keyboard';
175                $parent_properties ['style-name'] = 'Plugin_Keyboard';
176                $parent_properties ['style-display-name'] = 'Plugin Keyboard';
177                $renderer->createTextStyle($parent_properties);
178            }
179
180            if ($this->stylesCreated === false) {
181                $this->stylesCreated = true;
182                foreach ($this->styles as $class => $style) {
183                    // Get CSS properties for ODT export.
184                    // Set parameter $inherit=false to prevent changiung the font-size and family!
185                    $properties = array();
186                    $renderer->getODTPropertiesNew ($properties, 'kbd', 'class="'.$class.'"', NULL, false);
187                    if ($properties['font-family'] == 'inherit') {
188                        unset ($properties['font-family']);
189                    }
190
191                    $style_name = 'Plugin_Keyboard_'.$class;
192                    if (!$renderer->styleExists($style_name)) {
193                        $this->styles[$class]['name'] = $style_name;
194                        $properties ['style-parent'] = 'Plugin_Keyboard';
195                        $properties ['style-class'] = NULL;
196                        $properties ['style-name'] = $style_name;
197                        $properties ['style-display-name'] = $style['display-name'];
198                        $renderer->createTextStyle($properties);
199                    }
200                }
201            }
202
203            if (!empty($class) && !array_key_exists($class, $this->styles)) {
204                // Get CSS properties for ODT export.
205                // Set parameter $inherit=false to prevent changiung the font-size and family!
206                $properties = array();
207                $renderer->getODTPropertiesNew ($properties, 'kbd', 'class="'.$class.'"', NULL, false);
208                if ($properties['font-family'] == 'inherit') {
209                    unset ($properties['font-family']);
210                }
211
212                $style_name = 'Plugin_Keyboard_'.$class;
213                if (!$renderer->styleExists($style_name)) {
214                    $display_name = ucfirst(trim($class, '_'));
215                    $new = array ('name' => $style_name, 'display-name' => $display_name);
216                    $this->styles[$class] = $new;
217
218                    $properties ['style-parent'] = 'Plugin_Keyboard';
219                    $properties ['style-class'] = NULL;
220                    $properties ['style-name'] = $style_name;
221                    $properties ['style-display-name'] = $display_name;
222                    $renderer->createTextStyle($properties);
223                }
224            }
225        }
226    }
227
228    protected function renderODTOpenSpan ($renderer, $class) {
229        if ( method_exists ($renderer, '_odtSpanOpen') === false ) {
230            // Function is not supported by installed ODT plugin version, return.
231            return;
232        }
233        $renderer->_odtSpanOpen($class);
234    }
235
236    protected function renderODTCloseSpan ($renderer) {
237        if ( method_exists ($renderer, '_odtSpanClose') === false ) {
238            // Function is not supported by installed ODT plugin version, return.
239            return;
240        }
241        $renderer->_odtSpanClose();
242    }
243}
244