1<?php
2/**
3 * DokuWiki Syntax Plugin Canvas canvas
4 *
5 *  html5 canvas functionality
6 *
7 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
8 * @author  Sahara Satoshi <sahara.satoshi@gmail.com>
9 *
10 * REMARK: depends on InlineJS embedder (syntax component)
11 * SYNTAX:
12 *        <canvas[:rgraph|:jqplot] chartid width,height>
13 *         ... javascript ...
14 *        </canvas>
15 */
16// must be run within Dokuwiki
17if (!defined('DOKU_INC')) die();
18if (!defined('NL')) define('NL',"\n");
19
20/**
21 * All DokuWiki plugins to extend the parser/rendering mechanism
22 * need to inherit from this class
23 */
24class syntax_plugin_canvas_canvas extends DokuWiki_Syntax_Plugin {
25
26    protected $mode;
27    protected $entry_pattern = '<canvas\b.*?>(?=.*?</canvas>)';
28    protected $exit_pattern  = '</canvas>';
29
30    function __construct() {
31        $this->mode = substr(get_class($this), 7);
32    }
33
34    function getType()  { return 'protected'; }
35    //function getPType() { return 'block'; }
36    function getSort()  { return 160; }
37    function connectTo($mode) {
38        $this->Lexer->addEntryPattern($this->entry_pattern, $mode, $this->mode);
39    }
40    function postConnect() {
41        $this->Lexer->addExitPattern($this->exit_pattern, $this->mode);
42    }
43
44 /**
45  * handle syntax
46  */
47    public function handle($match, $state, $pos, Doku_Handler $handler){
48
49        global $conf;
50        // check whether inlinejs plugin exists
51        if (!plugin_isdisabled('inlinejs')) {
52            $inlinejs = plugin_load('syntax', 'inlinejs_embedder');
53            if ($inlinejs->getConf('follow_htmlok') && !$conf['htmlok']) return false;
54        } else {
55            msg($this->getPluginName().': Plugin InlineJS disabled.',-1);
56            return false;
57        }
58
59        switch ($state) {
60            case DOKU_LEXER_ENTER:
61                // at least one delimiter required to have unique canvas id.
62                //if (strpos($match,' ') === false) {
63                //    msg($this->getPluginName().': syntax wrong.',-1);
64                //    return false;
65                //}
66                return array($state, $match);
67
68            case DOKU_LEXER_UNMATCHED:
69                // javascript code
70                return array($state, $match);
71
72            case DOKU_LEXER_EXIT:
73                return array($state, '');
74        }
75        return false;
76    }
77
78 /**
79  * Render
80  */
81    public function render($format, Doku_Renderer $renderer, $indata) {
82
83        if (empty($indata)) return false;
84        list($state, $data) = $indata;
85
86        if ($format != 'xhtml') return false;
87
88        switch ($state) {
89            case DOKU_LEXER_ENTER:
90                // get canvas type and id
91                if ( substr($data, 1, 7) == 'canvas:') {
92                    $match = trim(substr($data, 8, -1));
93                } else {
94                    $match = trim(substr($data, 1, -1));
95                }
96                list($ctype, $cid, $cparam) = explode(' ', $match, 3);
97                if (empty($cid)) {
98                    msg($this->getPluginName().': syntax wrong -- '.hsc($data),-1);
99                }
100                $param = $this->getArguments($cparam, 'width');
101
102                // prepare canvas
103                $renderer->doc.= $this->_htmlCanvas($cid, $ctype, $param);
104                // open script tag to output embedded javascript
105                $renderer->doc.= '<script type="text/javascript">'.NL.'/*<![CDATA[*/';
106                break;
107
108            case DOKU_LEXER_UNMATCHED:
109                // output javascript
110                $renderer->doc.= $data;
111                break;
112
113            case DOKU_LEXER_EXIT:
114                // close script tag
115                $renderer->doc.=  '/*!]]>*/'.NL.'</script>'.NL;
116                break;
117        }
118        return true;
119    }
120
121
122    /* ---------------------------------------------------------
123     * build html of canvas
124     *
125     * @param $cid   (string) canvas id
126     * @param $ctype (string) canvas type
127     * @param $opts  (array)  canvas options (width, height, ...)
128     * ---------------------------------------------------------
129     */
130    protected function _htmlCanvas($cid, $ctype, $opts) {
131
132        // check whether canvas id is given?
133        if (empty($cid)) return false;
134
135        // set default canvas size
136        if (!array_key_exists('width', $opts)) $opts['width']  = '300px';
137        if (!array_key_exists('height',$opts)) $opts['height'] = '150px';
138
139        // prepare plot container
140        switch ($ctype) {
141            case "jqplot":
142                // see its project page https://bitbucket.org/cleonello/jqplot/overview
143                // jqPlot is currently available for use in all personal or commercial projects
144                // under both the MIT and GPL version 2.0 licenses. This means that you can
145                // choose the license that best suits your project and use it accordingly.
146                $html.= '<div class="jqplot-target"';
147                $html.= ' id="'.$cid.'"';
148                $html.= ' style="width: '.$opts['width'].'; height: '.$opts['height'].';"> ';
149                $html.= '</div>'.NL;
150                $html.= '<div class="jqplot-license-note"';
151                $html.= ' style="width: '.$opts['width'].'">';
152                $html.= '<a href="http://www.jqplot.com/" title="Powered by jqPlot">Powered by jQplot</a>';
153                $html.= '</div>'.NL;
154                break;
155            case "rgraph":
156                // see http://www.rgraph.net/license
157                // RGraph can be used free-of-charge by both commercial and non-commercial
158                // entities (eg business, personal, charity, educational etc) on either
159                // internal or external websites or in software that they make under the terms
160                // of the Creative Commons Attribution 3.0 This means that you may use RGraph
161                // for both commercial and non-commercial purposes as long as you link back to
162                // this website (eg underneath the chart).
163                $html.= '<canvas class="canvasbox"';
164                $html.= ' id="'.$cid.'"';
165                $html.= ' width="'.substr($opts['width'],0,-2).'"';
166                $html.= ' height="'.substr($opts['height'],0,-2).'"';
167                $html.= '>'.'[No canvas support]'.'</canvas>'.NL;
168                $html.= '<div class="rgraph-license-note"';
169                $html.= ' style="width: '.$opts['width'].'">';
170                $html.= '<a href="http://www.rgraph.net/" title="Powered by RGraph">Powered by RGraph</a>';
171                $html.= '</div>'.NL;
172                break;
173            default:
174                $html.= '<canvas class="canvasbox"';
175                $html.= ' id="'.$cid.'"';
176                $html.= ' width="'.substr($opts['width'],0,-2).'"';
177                $html.= ' height="'.substr($opts['height'],0,-2).'"';
178                $html.= '>'.'[No canvas support]'.'</canvas>'.NL;
179                break;
180        }
181        return $html;
182    }
183
184
185    /* ---------------------------------------------------------
186     * get each named/non-named arguments as array variable
187     *
188     * Named arguments is to be given as key="value" (quoted).
189     * Non-named arguments is assumed as boolean.
190     *
191     * @param $args (string) arguments
192     * @param $singlekey (string) key name if single numeric value was given
193     * @return (array) parsed arguments in $arg['key']=value
194     * ---------------------------------------------------------
195     */
196    protected function getArguments($args='', $singlekey='height') {
197        $arg = array();
198        // get named arguments (key="value"), ex: width="100"
199        // value must be quoted in argument string.
200        $val = "([\"'`])(?:[^\\\\\"'`]|\\\\.)*\g{-1}";
201        $pattern = "/(\w+)\s*=\s*($val)/";
202        preg_match_all($pattern, $args, $matches, PREG_SET_ORDER);
203        foreach ($matches as $match) {
204            $arg[$match[1]] = substr($match[2], 1, -1); // drop quates from value string
205            $args = str_replace($match[0], '', $args); // remove parsed substring
206        }
207
208        // get named numeric value argument, ex width=100
209        // numeric value may not be quoted in argument string.
210        $val = '\d+';
211        $pattern = "/(\w+)\s*=\s*($val)/";
212        preg_match_all($pattern, $args, $matches, PREG_SET_ORDER);
213        foreach ($matches as $match) {
214            $arg[$match[1]] = (int)$match[2];
215            $args = str_replace($match[0], '', $args); // remove parsed substring
216        }
217
218        // get width and/or height, specified as non-named arguments
219        $unit = 'px';
220        $pattern = '/(?:^| )(\d+(%|em|pt|px)?)\s*([,xX]?(\d+(%|em|pt|px)?))?(?: |$)/';
221        if (preg_match($pattern, $args, $matches)) {
222            if ($matches[4]) {
223                // width and height with unit was given
224                $arg['width'] = $matches[1];
225                if (!$matches[2]) $arg['width'].= $unit;
226                $arg['height'] = $matches[4];
227                if (!$matches[5]) $arg['height'].= $unit;
228            } elseif ($matches[2]) {
229                // width or height(=assumed as default) with unit was given
230                // preferred key name given as second parameter of this function
231                $arg[$singlekey] = $matches[1];
232                if (!$matches[2]) $arg[$singlekey].= $unit;
233            } elseif ($matches[1]) {
234                // numeric token is assumed as width or height
235                $arg[$singlekey] = $matches[1].$unit;
236            }
237            $args = str_replace($matches[0], '', $args); // remove parsed substring
238        }
239
240        // get flags or non-named arguments, ex: showdate, noshowfooter
241        $tokens = preg_split('/\s+/', $args);
242        foreach ($tokens as $token) {
243            if (preg_match('/^(?:!|not?)(.+)/',$token, $matches)) {
244                // denyed/negative prefixed token
245                $arg[$matches[1]] = false;
246            } elseif (preg_match('/^[A-Za-z]/',$token)) {
247                $arg[$token] = true;
248            }
249        }
250        return $arg;
251    }
252
253}
254