xref: /plugin/box2/syntax.php (revision 02b649289da0a8ff895058a33823b9402520f0e4)
1*02b64928SChristopher Smith<?php
2*02b64928SChristopher Smith/**
3*02b64928SChristopher Smith * Box Plugin: Draw highlighting boxes around wiki markup
4*02b64928SChristopher Smith *
5*02b64928SChristopher Smith * Syntax:     <box width% classes|title>
6*02b64928SChristopher Smith *   width%    width of the box, must use % unit
7*02b64928SChristopher Smith *   classes   one or more classes used to style the box, several predefined styles included in style.css
8*02b64928SChristopher Smith *   title     (optional) all text after '|' will be rendered above the main code text with a
9*02b64928SChristopher Smith *             different style.
10*02b64928SChristopher Smith *
11*02b64928SChristopher Smith * Acknowledgements:
12*02b64928SChristopher Smith *  Rounded corners based on snazzy borders by Stu Nicholls (http://www.cssplay.co.uk/boxes/snazzy)
13*02b64928SChristopher Smith *  which is in turn based on nifty corners by Alessandro Fulciniti (http://pro.html.it/esempio/nifty/)
14*02b64928SChristopher Smith *
15*02b64928SChristopher Smith * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
16*02b64928SChristopher Smith * @author     Christopher Smith <chris@jalakai.co.uk>
17*02b64928SChristopher Smith */
18*02b64928SChristopher Smith
19*02b64928SChristopher Smithif(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
20*02b64928SChristopher Smithif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
21*02b64928SChristopher Smithrequire_once(DOKU_PLUGIN.'syntax.php');
22*02b64928SChristopher Smith
23*02b64928SChristopher Smith/**
24*02b64928SChristopher Smith * All DokuWiki plugins to extend the parser/rendering mechanism
25*02b64928SChristopher Smith * need to inherit from this class
26*02b64928SChristopher Smith */
27*02b64928SChristopher Smithclass syntax_plugin_box extends DokuWiki_Syntax_Plugin {
28*02b64928SChristopher Smith
29*02b64928SChristopher Smith    var $title_mode = false;
30*02b64928SChristopher Smith
31*02b64928SChristopher Smith    // the following are used in rendering and are set by _xhtml_boxopen()
32*02b64928SChristopher Smith    var $_xb_colours      = '';
33*02b64928SChristopher Smith    var $_content_colours = '';
34*02b64928SChristopher Smith    var $_title_colours   = '';
35*02b64928SChristopher Smith
36*02b64928SChristopher Smith    function getType(){ return 'protected';}
37*02b64928SChristopher Smith    function getAllowedTypes() { return array('container','substition','protected','disabled','formatting','paragraphs'); }
38*02b64928SChristopher Smith    function getPType(){ return 'block';}
39*02b64928SChristopher Smith
40*02b64928SChristopher Smith    // must return a number lower than returned by native 'code' mode (200)
41*02b64928SChristopher Smith    function getSort(){ return 195; }
42*02b64928SChristopher Smith
43*02b64928SChristopher Smith    // override default accepts() method to allow nesting
44*02b64928SChristopher Smith    // - ie, to get the plugin accepts its own entry syntax
45*02b64928SChristopher Smith    function accepts($mode) {
46*02b64928SChristopher Smith        if ($mode == substr(get_class($this), 7)) return true;
47*02b64928SChristopher Smith
48*02b64928SChristopher Smith        return parent::accepts($mode);
49*02b64928SChristopher Smith    }
50*02b64928SChristopher Smith
51*02b64928SChristopher Smith    /**
52*02b64928SChristopher Smith     * Connect pattern to lexer
53*02b64928SChristopher Smith     */
54*02b64928SChristopher Smith    function connectTo($mode) {
55*02b64928SChristopher Smith      $this->Lexer->addEntryPattern('<box>(?=.*?</box.*?>)',$mode,'plugin_box');
56*02b64928SChristopher Smith      $this->Lexer->addEntryPattern('<box\s[^\r\n\|]*?>(?=.*?</box.*?>)',$mode,'plugin_box');
57*02b64928SChristopher Smith      $this->Lexer->addEntryPattern('<box\|(?=[^\r\n]*?\>.*?</box.*?\>)',$mode,'plugin_box');
58*02b64928SChristopher Smith      $this->Lexer->addEntryPattern('<box\s[^\r\n\|]*?\|(?=[^\r\n]*?>.*?</box.*?>)',$mode,'plugin_box');
59*02b64928SChristopher Smith    }
60*02b64928SChristopher Smith
61*02b64928SChristopher Smith    function postConnect() {
62*02b64928SChristopher Smith      $this->Lexer->addPattern('>', 'plugin_box');
63*02b64928SChristopher Smith      $this->Lexer->addExitPattern('</box.*?>', 'plugin_box');
64*02b64928SChristopher Smith    }
65*02b64928SChristopher Smith
66*02b64928SChristopher Smith    /**
67*02b64928SChristopher Smith     * Handle the match
68*02b64928SChristopher Smith     */
69*02b64928SChristopher Smith    function handle($match, $state, $pos, &$handler){
70*02b64928SChristopher Smith
71*02b64928SChristopher Smith        switch ($state) {
72*02b64928SChristopher Smith            case DOKU_LEXER_ENTER:
73*02b64928SChristopher Smith                $data = $this->_boxstyle(trim(substr($match, 4, -1)));
74*02b64928SChristopher Smith                if (substr($match, -1) == '|') {
75*02b64928SChristopher Smith                    $this->title_mode = true;
76*02b64928SChristopher Smith                    return array('title_open',$data);
77*02b64928SChristopher Smith                } else {
78*02b64928SChristopher Smith                    return array('box_open',$data);
79*02b64928SChristopher Smith                }
80*02b64928SChristopher Smith
81*02b64928SChristopher Smith            case DOKU_LEXER_MATCHED:
82*02b64928SChristopher Smith                if ($this->title_mode) {
83*02b64928SChristopher Smith                    $this->title_mode = false;
84*02b64928SChristopher Smith                    return array('box_open','');
85*02b64928SChristopher Smith                } else {
86*02b64928SChristopher Smith                    return array('data', $match);
87*02b64928SChristopher Smith                }
88*02b64928SChristopher Smith
89*02b64928SChristopher Smith            case DOKU_LEXER_UNMATCHED:
90*02b64928SChristopher Smith                $handler->_addCall('cdata',array($match), $pos);
91*02b64928SChristopher Smith                return false;
92*02b64928SChristopher Smith
93*02b64928SChristopher Smith            case DOKU_LEXER_EXIT:
94*02b64928SChristopher Smith                $data = trim(substr($match, 5, -1));
95*02b64928SChristopher Smith                $title =  ($data && $data{0} == "|") ? substr($data,1) : '';
96*02b64928SChristopher Smith
97*02b64928SChristopher Smith                return array('box_close', $title);
98*02b64928SChristopher Smith
99*02b64928SChristopher Smith        }
100*02b64928SChristopher Smith        return false;
101*02b64928SChristopher Smith    }
102*02b64928SChristopher Smith
103*02b64928SChristopher Smith    /**
104*02b64928SChristopher Smith     * Create output
105*02b64928SChristopher Smith     */
106*02b64928SChristopher Smith    function render($mode, &$renderer, $indata) {
107*02b64928SChristopher Smith
108*02b64928SChristopher Smith      if (empty($indata)) return false;
109*02b64928SChristopher Smith      list($instr, $data) = $indata;
110*02b64928SChristopher Smith
111*02b64928SChristopher Smith      if($mode == 'xhtml'){
112*02b64928SChristopher Smith          switch ($instr) {
113*02b64928SChristopher Smith          case 'title_open' :
114*02b64928SChristopher Smith            $this->title_mode = true;
115*02b64928SChristopher Smith            $renderer->doc .= $this->_xhtml_boxopen($data)."<p class='box_title' {$this->_title_colours}>";
116*02b64928SChristopher Smith            break;
117*02b64928SChristopher Smith
118*02b64928SChristopher Smith          case 'box_open' :
119*02b64928SChristopher Smith            if ($this->title_mode) {
120*02b64928SChristopher Smith              $this->title_mode = false;
121*02b64928SChristopher Smith              $renderer->doc .= "</p>\n<div class='box_content' {$this->_content_colours}>";
122*02b64928SChristopher Smith            } else {
123*02b64928SChristopher Smith              $renderer->doc .= $this->_xhtml_boxopen($data)."<div class='box_content' {$this->_content_colours}>";
124*02b64928SChristopher Smith            }
125*02b64928SChristopher Smith            break;
126*02b64928SChristopher Smith
127*02b64928SChristopher Smith          case 'data' :
128*02b64928SChristopher Smith            $renderer->doc .= $renderer->_xmlEntities($data);
129*02b64928SChristopher Smith            break;
130*02b64928SChristopher Smith
131*02b64928SChristopher Smith          case 'box_close' :
132*02b64928SChristopher Smith            $renderer->doc .= "</div>\n";
133*02b64928SChristopher Smith
134*02b64928SChristopher Smith            if ($data) {
135*02b64928SChristopher Smith              $renderer->doc .= "<p class='box_caption' {$this->_title_colours}>".$renderer->_xmlEntities($data)."</p>\n";
136*02b64928SChristopher Smith            }
137*02b64928SChristopher Smith            $renderer->doc .= $this->_xhtml_boxclose();
138*02b64928SChristopher Smith            break;
139*02b64928SChristopher Smith        }
140*02b64928SChristopher Smith
141*02b64928SChristopher Smith        return true;
142*02b64928SChristopher Smith      }
143*02b64928SChristopher Smith      return false;
144*02b64928SChristopher Smith    }
145*02b64928SChristopher Smith
146*02b64928SChristopher Smith    function _boxstyle($str) {
147*02b64928SChristopher Smith      if (!strlen($str)) return array();
148*02b64928SChristopher Smith
149*02b64928SChristopher Smith      $styles = array();
150*02b64928SChristopher Smith
151*02b64928SChristopher Smith      $tokens = preg_split('/\s+/', $str, 9);                      // limit is defensive
152*02b64928SChristopher Smith      foreach ($tokens as $token) {
153*02b64928SChristopher Smith          if (preg_match('/^\d*\.?\d+(%|px|em|ex|pt|cm|mm|pi|in)$/', $token)) {
154*02b64928SChristopher Smith            $styles['width'] = $token;
155*02b64928SChristopher Smith            continue;
156*02b64928SChristopher Smith          }
157*02b64928SChristopher Smith
158*02b64928SChristopher Smith          if (preg_match('/^(
159*02b64928SChristopher Smith              (\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))|        #colorvalue
160*02b64928SChristopher Smith              (rgb\(([0-9]{1,3}%?,){2}[0-9]{1,3}%?\))     #rgb triplet
161*02b64928SChristopher Smith              )$/x', $token)) {
162*02b64928SChristopher Smith            $styles['colour'][] = $token;
163*02b64928SChristopher Smith            continue;
164*02b64928SChristopher Smith          }
165*02b64928SChristopher Smith
166*02b64928SChristopher Smith          // restrict token (class names) characters to prevent any malicious data
167*02b64928SChristopher Smith          if (preg_match('/[^A-Za-z0-9_-]/',$token)) continue;
168*02b64928SChristopher Smith          $styles['class'] = (isset($styles['class']) ? $styles['class'].' ' : '').$token;
169*02b64928SChristopher Smith      }
170*02b64928SChristopher Smith      if (!empty($styles['colour'])) {
171*02b64928SChristopher Smith        $styles['colour'] = $this->_box_colours($styles['colour']);
172*02b64928SChristopher Smith      }
173*02b64928SChristopher Smith
174*02b64928SChristopher Smith      return $styles;
175*02b64928SChristopher Smith    }
176*02b64928SChristopher Smith
177*02b64928SChristopher Smith    function _box_colours($colours) {
178*02b64928SChristopher Smith      $triplets = array();
179*02b64928SChristopher Smith
180*02b64928SChristopher Smith      // only need the first four colours
181*02b64928SChristopher Smith      if (count($colours) > 4) $colours = array_slice($colours,0,4);
182*02b64928SChristopher Smith      foreach ($colours as $colour) {
183*02b64928SChristopher Smith        $triplet[] = $this->_colourToTriplet($colour);
184*02b64928SChristopher Smith      }
185*02b64928SChristopher Smith
186*02b64928SChristopher Smith      // there must be one colour to get here - the primary background
187*02b64928SChristopher Smith      // calculate title background colour if not present
188*02b64928SChristopher Smith      if (empty($triplet[1])) {
189*02b64928SChristopher Smith        $triplet[1] = $triplet[0];
190*02b64928SChristopher Smith      }
191*02b64928SChristopher Smith
192*02b64928SChristopher Smith      // calculate outer background colour if not present
193*02b64928SChristopher Smith      if (empty($triplet[2])) {
194*02b64928SChristopher Smith        $triplet[2] = $triplet[0];
195*02b64928SChristopher Smith      }
196*02b64928SChristopher Smith
197*02b64928SChristopher Smith      // calculate border colour if not present
198*02b64928SChristopher Smith      if (empty($triplet[3])) {
199*02b64928SChristopher Smith        $triplet[3] = $triplet[0];
200*02b64928SChristopher Smith      }
201*02b64928SChristopher Smith
202*02b64928SChristopher Smith      // convert triplets back to style sheet colours
203*02b64928SChristopher Smith      $style_colours['content_background'] = 'rgb('.join(',',$triplet[0]).')';
204*02b64928SChristopher Smith      $style_colours['title_background'] = 'rgb('.join(',',$triplet[1]).')';
205*02b64928SChristopher Smith      $style_colours['outer_background'] = 'rgb('.join(',',$triplet[2]).')';
206*02b64928SChristopher Smith      $style_colours['borders'] = 'rgb('.join(',',$triplet[3]).')';
207*02b64928SChristopher Smith
208*02b64928SChristopher Smith      return $style_colours;
209*02b64928SChristopher Smith    }
210*02b64928SChristopher Smith
211*02b64928SChristopher Smith    function _colourToTriplet($colour) {
212*02b64928SChristopher Smith      if ($colour{0} == '#') {
213*02b64928SChristopher Smith        if (strlen($colour) == 4) {
214*02b64928SChristopher Smith          // format #FFF
215*02b64928SChristopher Smith          return array(hexdec($colour{1}.$colour{1}),hexdec($colour{2}.$colour{2}),hexdec($colour{3}.$colour{3}));
216*02b64928SChristopher Smith        } else {
217*02b64928SChristopher Smith          // format #FFFFFF
218*02b64928SChristopher Smith          return array(hexdec(substr($colour,1,2)),hexdec(substr($colour,3,2)), hexdec(substr($colour,5,2)));
219*02b64928SChristopher Smith        }
220*02b64928SChristopher Smith      } else {
221*02b64928SChristopher Smith        // format rgb(x,y,z)
222*02b64928SChristopher Smith        return explode(',',substr($colour,4,-1));
223*02b64928SChristopher Smith      }
224*02b64928SChristopher Smith    }
225*02b64928SChristopher Smith
226*02b64928SChristopher Smith    function _xhtml_boxopen($styles) {
227*02b64928SChristopher Smith      $class = 'class="box' . (isset($styles['class']) ? ' '.$styles['class'] : '') . '"';
228*02b64928SChristopher Smith      $style = isset($styles['width']) ? "width: {$styles['width']};" : '';
229*02b64928SChristopher Smith
230*02b64928SChristopher Smith      if (isset($styles['colour'])) {
231*02b64928SChristopher Smith        $colours = 'background-color: '.$styles['colour']['outer_background'].'; ';
232*02b64928SChristopher Smith        $colours .= 'border-color: '.$styles['colour']['borders'].';';
233*02b64928SChristopher Smith
234*02b64928SChristopher Smith        $this->_content_colours = 'style="background-color: '.$styles['colour']['content_background'].'; border-color: '.$styles['colour']['borders'].'"';
235*02b64928SChristopher Smith        $this->_title_colours = 'style="background-color: '.$styles['colour']['title_background'].';"';
236*02b64928SChristopher Smith
237*02b64928SChristopher Smith      } else {
238*02b64928SChristopher Smith        $colours = '';
239*02b64928SChristopher Smith
240*02b64928SChristopher Smith        $this->_content_colours = '';
241*02b64928SChristopher Smith        $this->_title_colours = '';
242*02b64928SChristopher Smith      }
243*02b64928SChristopher Smith
244*02b64928SChristopher Smith      if ($style || $colours) $style = ' style="'.$style.' '.$colours.'"';
245*02b64928SChristopher Smith      if ($colours) $colours = ' style="'.$colours.'"';
246*02b64928SChristopher Smith
247*02b64928SChristopher Smith      $this->_xb_colours = $colours;
248*02b64928SChristopher Smith
249*02b64928SChristopher Smith      $html = "<div $class$style>\n";
250*02b64928SChristopher Smith      $html .="  <b class='xtop'><b class='xb1'$colours></b><b class='xb2'$colours></b><b class='xb3'$colours></b><b class='xb4'$colours></b></b>\n";
251*02b64928SChristopher Smith      $html .="  <div class='xbox'$colours>\n";
252*02b64928SChristopher Smith
253*02b64928SChristopher Smith      return $html;
254*02b64928SChristopher Smith    }
255*02b64928SChristopher Smith
256*02b64928SChristopher Smith    function _xhtml_boxclose() {
257*02b64928SChristopher Smith
258*02b64928SChristopher Smith      $colours = $this->_xb_colours;
259*02b64928SChristopher Smith
260*02b64928SChristopher Smith      $html = "  </div>\n";
261*02b64928SChristopher Smith      $html .= "  <b class='xbottom'><b class='xb4'$colours></b><b class='xb3'$colours></b><b class='xb2'$colours></b><b class='xb1'$colours></b></b>\n";
262*02b64928SChristopher Smith      $html .= "</div>\n";
263*02b64928SChristopher Smith
264*02b64928SChristopher Smith      return $html;
265*02b64928SChristopher Smith    }
266*02b64928SChristopher Smith
267*02b64928SChristopher Smith}
268*02b64928SChristopher Smith
269*02b64928SChristopher Smith//Setup VIM: ex: et ts=4 enc=utf-8 :