xref: /plugin/extab4/syntax.php (revision ec44bd345477c6537b6cbcfa2992b97fc97f19e0)
12186d66dSSatoshi Sahara<?php
22186d66dSSatoshi Sahara/**
3*ec44bd34SLeonard Heyman * DokuWiki Plugin Extab4 (Syntax component)
492d5c8a1SSatoshi Sahara *
592d5c8a1SSatoshi Sahara * Allows extended (MediaWiki-style) tables inside DokuWiki
62186d66dSSatoshi Sahara *
72186d66dSSatoshi Sahara * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
892d5c8a1SSatoshi Sahara * @author     Satoshi Sahara <sahara.satoshi@gmail.com>
92186d66dSSatoshi Sahara */
10*ec44bd34SLeonard Heymanclass syntax_plugin_extab4 extends DokuWiki_Syntax_Plugin
111e984f26SSatoshi Sahara{
121e984f26SSatoshi Sahara    public function getType()
131e984f26SSatoshi Sahara    {   // Syntax Type
141e984f26SSatoshi Sahara        return 'container';
151e984f26SSatoshi Sahara    }
162186d66dSSatoshi Sahara
171e984f26SSatoshi Sahara    public function getAllowedTypes()
181e984f26SSatoshi Sahara    {   // Allowed Mode Types
191e984f26SSatoshi Sahara        return array(
201e984f26SSatoshi Sahara            'container',
211e984f26SSatoshi Sahara            'formatting',
221e984f26SSatoshi Sahara            'substition',
231e984f26SSatoshi Sahara            'disabled',
241e984f26SSatoshi Sahara            'protected',
251e984f26SSatoshi Sahara            'paragraphs',
261e984f26SSatoshi Sahara        );
271e984f26SSatoshi Sahara    }
281e984f26SSatoshi Sahara
291e984f26SSatoshi Sahara    public function getPType()
301e984f26SSatoshi Sahara    {   // Paragraph Type
311e984f26SSatoshi Sahara        return 'block';
321e984f26SSatoshi Sahara    }
331e984f26SSatoshi Sahara
341e984f26SSatoshi Sahara
353fb7eb41SSatoshi Sahara    protected $stack = array();  // stack of current open tag - used by handle() method
36bb581870SSatoshi Sahara    protected $tagsmap;
37bb581870SSatoshi Sahara    protected $attrsmap;
382186d66dSSatoshi Sahara
391e984f26SSatoshi Sahara    /**
401e984f26SSatoshi Sahara     * Connect pattern to lexer
411e984f26SSatoshi Sahara     */
421e984f26SSatoshi Sahara    protected $mode;
431e984f26SSatoshi Sahara
441e984f26SSatoshi Sahara    public function preConnect()
451e984f26SSatoshi Sahara    {
461e984f26SSatoshi Sahara        // drop 'syntax_' from class name
471e984f26SSatoshi Sahara        $this->mode = substr(get_class($this), 7);
48e43fcd23SSatoshi Sahara    }
492186d66dSSatoshi Sahara
501e984f26SSatoshi Sahara    public function connectTo($mode)
511e984f26SSatoshi Sahara    {
52e43fcd23SSatoshi Sahara        // table start:  {| attrs
539f716539SSatoshi Sahara        $this->Lexer->addEntryPattern('\n\{\|[^\n]*',$mode, $this->mode);
542186d66dSSatoshi Sahara    }
551e984f26SSatoshi Sahara
561e984f26SSatoshi Sahara    public function postConnect()
571e984f26SSatoshi Sahara    {
58e43fcd23SSatoshi Sahara        // table end:    |}
599f716539SSatoshi Sahara        $this->Lexer->addExitPattern('[ \t]*\n\|\}', $this->mode);
602186d66dSSatoshi Sahara
61cc63ba86SSatoshi Sahara        // match pattern for attributes
62cc63ba86SSatoshi Sahara        $attrs = '[^\n\{\|\!\[]+';
634e16aaf0SSatoshi Sahara
6492d5c8a1SSatoshi Sahara        // caption:      |+ attrs | caption
659f716539SSatoshi Sahara        $this->Lexer->addPattern("\n\|\+ *(?:$attrs\|(?!\|))?", $this->mode);
6692d5c8a1SSatoshi Sahara        // table row:    |- attrs
679f716539SSatoshi Sahara        $this->Lexer->addPattern(' *?\n\|\-+[^\n]*', $this->mode);
6892d5c8a1SSatoshi Sahara        // table header: ! attrs |
699f716539SSatoshi Sahara        $this->Lexer->addPattern("(?: *?\n|\!)\!(?:$attrs\|(?!\|))?", $this->mode);
7092d5c8a1SSatoshi Sahara        // table data:   | attrs |
719f716539SSatoshi Sahara        $this->Lexer->addPattern("(?: *?\n|\|)\|(?:$attrs\|(?!\|))?", $this->mode);
722186d66dSSatoshi Sahara    }
732186d66dSSatoshi Sahara
741e984f26SSatoshi Sahara    public function accepts($mode)
751e984f26SSatoshi Sahara    {   // plugin accepts its own entry syntax
761e984f26SSatoshi Sahara        if ($mode == $this->mode) return true;
771e984f26SSatoshi Sahara        return parent::accepts($mode);
781e984f26SSatoshi Sahara    }
791e984f26SSatoshi Sahara
801e984f26SSatoshi Sahara    public function getSort()
811e984f26SSatoshi Sahara    {   // sort number used to determine priority of this mode
821e984f26SSatoshi Sahara        return 59; // = Doku_Parser_Mode_table-1
831e984f26SSatoshi Sahara    }
841e984f26SSatoshi Sahara
8592d5c8a1SSatoshi Sahara
8692d5c8a1SSatoshi Sahara    /**
8792d5c8a1SSatoshi Sahara     * helper function to simplify writing plugin calls to the instruction list
8892d5c8a1SSatoshi Sahara     * first three arguments are passed to function render as $data
8992d5c8a1SSatoshi Sahara     */
902235f4feSSatoshi Sahara    protected function writeCall($tag, $attr, $state, $pos, $match, $handler)
911e984f26SSatoshi Sahara    {
922235f4feSSatoshi Sahara        $data = array($state, $tag, $attr);
932235f4feSSatoshi Sahara        $handler->addPluginCall($this->getPluginName(), $data, $state, $pos, $match);
942235f4feSSatoshi Sahara    }
952235f4feSSatoshi Sahara
962235f4feSSatoshi Sahara    protected function open($tag, $attr, $pos, $match, $handler)
972235f4feSSatoshi Sahara    {
982235f4feSSatoshi Sahara      //$this->writeCall($tag,$attr,DOKU_LEXER_ENTER, $pos,$match,$handler);
99166bfb80SSatoshi Sahara        $match = array(DOKU_LEXER_ENTER, $tag, $attr);
100166bfb80SSatoshi Sahara        $handler->plugin($match, 'addPluginCall', $pos, $this->getPluginName());
101cc61c42eSSatoshi Sahara    }
102cc61c42eSSatoshi Sahara
1032235f4feSSatoshi Sahara    protected function close($tag, $pos, $match, $handler)
1041e984f26SSatoshi Sahara    {
1052235f4feSSatoshi Sahara      //$this->writeCall($tag,'',DOKU_LEXER_EXIT, $pos,$match,$handler);
106*ec44bd34SLeonard Heyman        $match = array(DOKU_LEXER_EXIT, $tag, '');
107166bfb80SSatoshi Sahara        $handler->plugin($match, 'addPluginCall', $pos, $this->getPluginName());
108cc61c42eSSatoshi Sahara    }
109cc61c42eSSatoshi Sahara
11092d5c8a1SSatoshi Sahara    /**
11192d5c8a1SSatoshi Sahara     * helper function for exttab syntax translation to html
11292d5c8a1SSatoshi Sahara     *
11392d5c8a1SSatoshi Sahara     * @param string $match       matched string
11492bf75d3SSatoshi Sahara     * @return array              tag name and attributes
11592d5c8a1SSatoshi Sahara     */
1161e984f26SSatoshi Sahara    protected function interpret($match = '')
1171e984f26SSatoshi Sahara    {
11862cacab2SSatoshi Sahara        $markup = ltrim($match);
11962cacab2SSatoshi Sahara        $len = 2;
120194fb8c1SSatoshi Sahara        switch (substr($markup, 0, $len)) {
12162cacab2SSatoshi Sahara            case '{|': $tag = 'table';   break;
12262cacab2SSatoshi Sahara            case '|}': $tag = '/table';  break;
12362cacab2SSatoshi Sahara            case '|+': $tag = 'caption'; break;
12462cacab2SSatoshi Sahara            case '|-': $tag = 'tr';      break;
125194fb8c1SSatoshi Sahara            case '||': $tag = 'td';      break;
126194fb8c1SSatoshi Sahara            case '!!': $tag = 'th';      break;
12762cacab2SSatoshi Sahara            default:
12862cacab2SSatoshi Sahara                $len = 1;
129194fb8c1SSatoshi Sahara                switch (substr($markup, 0, $len)) {
130194fb8c1SSatoshi Sahara                    case '!': $tag = 'th'; break;
131194fb8c1SSatoshi Sahara                    case '|': $tag = 'td'; break;
132194fb8c1SSatoshi Sahara                }
13392d5c8a1SSatoshi Sahara        }
13462cacab2SSatoshi Sahara        if (isset($tag)) {
13562cacab2SSatoshi Sahara            $attrs = substr($markup, $len);
13662cacab2SSatoshi Sahara            return array($tag, $attrs);
13792d5c8a1SSatoshi Sahara        } else {
13862cacab2SSatoshi Sahara            msg($this->getPluginName().' ERROR: unknown syntax: '.hsc($markup) ,-1);
13992d5c8a1SSatoshi Sahara            return false;
14092d5c8a1SSatoshi Sahara        }
14192d5c8a1SSatoshi Sahara    }
14292d5c8a1SSatoshi Sahara
1435a49dd0fSSatoshi Sahara    /**
1445a49dd0fSSatoshi Sahara     * append specified class name to attributes
1455a49dd0fSSatoshi Sahara     *
1465a49dd0fSSatoshi Sahara     * @param string $class       class name
1475a49dd0fSSatoshi Sahara     * @param string $attr        attributes of html tag
1485a49dd0fSSatoshi Sahara     * @return string             modified $attr
1495a49dd0fSSatoshi Sahara     */
1501e984f26SSatoshi Sahara    private function appendClass($class, $attr)
1511e984f26SSatoshi Sahara    {
152*ec44bd34SLeonard Heyman        $regex = '/\bclass\s*=\s*(["\'])(.*?)\1/i';
153*ec44bd34SLeonard Heyman        if (preg_match($regex, $attr, $matches) !== 1) {
154194fb8c1SSatoshi Sahara            // class attribute is not specified
155*ec44bd34SLeonard Heyman            return rtrim($attr).' class="'.$class.'"';
1565a49dd0fSSatoshi Sahara        }
157*ec44bd34SLeonard Heyman
158*ec44bd34SLeonard Heyman        $items = preg_split('/\s+/', trim($matches[2]), -1, PREG_SPLIT_NO_EMPTY);
159*ec44bd34SLeonard Heyman        if (in_array($class, $items, true)) {
160*ec44bd34SLeonard Heyman            // class already exists in the class attribute
161*ec44bd34SLeonard Heyman            return $attr;
162*ec44bd34SLeonard Heyman        }
163*ec44bd34SLeonard Heyman
164*ec44bd34SLeonard Heyman        $items[] = $class;
165*ec44bd34SLeonard Heyman        $replace = 'class='.$matches[1].implode(' ', $items).$matches[1];
166*ec44bd34SLeonard Heyman        return preg_replace($regex, $replace, $attr, 1);
1675a49dd0fSSatoshi Sahara    }
1685a49dd0fSSatoshi Sahara
16992d5c8a1SSatoshi Sahara
1702186d66dSSatoshi Sahara    /**
1712186d66dSSatoshi Sahara     * Handle the match
1722186d66dSSatoshi Sahara     */
1731e984f26SSatoshi Sahara    public function handle($match, $state, $pos, Doku_Handler $handler)
1741e984f26SSatoshi Sahara    {
17592d5c8a1SSatoshi Sahara        switch ($state) {
176166bfb80SSatoshi Sahara            case 'addPluginCall':
177bb581870SSatoshi Sahara                // write plugin instruction to call list of the handler
178bb581870SSatoshi Sahara                // Note: $match is array, not matched text
179166bfb80SSatoshi Sahara                return $data = $match;
180166bfb80SSatoshi Sahara
18192d5c8a1SSatoshi Sahara            case DOKU_LEXER_ENTER:
18292d5c8a1SSatoshi Sahara                // table start
1831e984f26SSatoshi Sahara                list($tag, $attr) = $this->interpret($match);
1845a49dd0fSSatoshi Sahara                // ensure that class attribute cotains "exttable"
1851e984f26SSatoshi Sahara                $attr = $this->appendClass('exttable', $attr);
18692d5c8a1SSatoshi Sahara                array_push($this->stack, $tag);
1872235f4feSSatoshi Sahara                $this->open($tag, $attr, $pos, $match, $handler);
18892d5c8a1SSatoshi Sahara                break;
18992bf75d3SSatoshi Sahara            case DOKU_LEXER_EXIT:
19092bf75d3SSatoshi Sahara                do { // rewind table
19192bf75d3SSatoshi Sahara                    $oldtag = array_pop($this->stack);
1922235f4feSSatoshi Sahara                    $this->close($oldtag, $pos, $match, $handler);
19392bf75d3SSatoshi Sahara                } while ($oldtag != 'table');
19492bf75d3SSatoshi Sahara                break;
19592d5c8a1SSatoshi Sahara            case DOKU_LEXER_MATCHED:
19692d5c8a1SSatoshi Sahara                $tag_prev = end($this->stack);
1971e984f26SSatoshi Sahara                list($tag, $attr) = $this->interpret($match);
19892d5c8a1SSatoshi Sahara                switch ($tag_prev) {
19992d5c8a1SSatoshi Sahara                    case 'caption':
200358471aaSSatoshi Sahara                                $oldtag = array_pop($this->stack);
2012235f4feSSatoshi Sahara                                $this->close($oldtag, $pos, $match, $handler);
20292d5c8a1SSatoshi Sahara                    case 'table':
20392d5c8a1SSatoshi Sahara                        switch ($tag) {
20492d5c8a1SSatoshi Sahara                            case 'caption':
20592d5c8a1SSatoshi Sahara                            case 'tr':
20692d5c8a1SSatoshi Sahara                                array_push($this->stack, $tag);
2072235f4feSSatoshi Sahara                                $this->open($tag, $attr, $pos, $match, $handler);
20892d5c8a1SSatoshi Sahara                                break;
20992d5c8a1SSatoshi Sahara                            case 'th':
21092d5c8a1SSatoshi Sahara                            case 'td':
21192d5c8a1SSatoshi Sahara                                array_push($this->stack, 'tr');
2122235f4feSSatoshi Sahara                                $this->open('tr', '', $pos, $match, $handler);
21392d5c8a1SSatoshi Sahara                                array_push($this->stack, $tag);
2142235f4feSSatoshi Sahara                                $this->open($tag, $attr, $pos, $match, $handler);
21592d5c8a1SSatoshi Sahara                                break;
21692d5c8a1SSatoshi Sahara                        }
21792d5c8a1SSatoshi Sahara                        break;
21892d5c8a1SSatoshi Sahara                    case 'tr':
21992d5c8a1SSatoshi Sahara                        switch ($tag) {
22092d5c8a1SSatoshi Sahara                            case 'caption':
22162cacab2SSatoshi Sahara                                msg($this->getPluginName().' Syntax ERROR: match='.hsc(trim($match)) ,-1);
22292d5c8a1SSatoshi Sahara                                break;
22392d5c8a1SSatoshi Sahara                            case 'tr':
22492d5c8a1SSatoshi Sahara                                $oldtag = array_pop($this->stack);
2252235f4feSSatoshi Sahara                                $this->close($oldtag, $pos, $match, $handler);
22692d5c8a1SSatoshi Sahara                                array_push($this->stack, $tag);
2272235f4feSSatoshi Sahara                                $this->open($tag, $attr, $pos, $match, $handler);
22892d5c8a1SSatoshi Sahara                                break;
22992d5c8a1SSatoshi Sahara                            case 'th':
23092d5c8a1SSatoshi Sahara                            case 'td':
23192d5c8a1SSatoshi Sahara                                array_push($this->stack, $tag);
2322235f4feSSatoshi Sahara                                $this->open($tag, $attr, $pos, $match, $handler);
23392d5c8a1SSatoshi Sahara                                break;
23492d5c8a1SSatoshi Sahara                        }
23592d5c8a1SSatoshi Sahara                        break;
23692d5c8a1SSatoshi Sahara                    case 'th':
23792d5c8a1SSatoshi Sahara                    case 'td':
23892d5c8a1SSatoshi Sahara                        switch ($tag) {
23992d5c8a1SSatoshi Sahara                            case 'caption':
24062cacab2SSatoshi Sahara                                msg($this->getPluginName().' Syntax ERROR: match='.hsc(trim($match)) ,-1);
24192d5c8a1SSatoshi Sahara                                break;
24292d5c8a1SSatoshi Sahara                            case 'tr':
24392d5c8a1SSatoshi Sahara                                do { // rewind old row prior to start new row
24492d5c8a1SSatoshi Sahara                                    $oldtag = array_pop($this->stack);
2452235f4feSSatoshi Sahara                                    $this->close($oldtag, $pos, $match, $handler);
24692d5c8a1SSatoshi Sahara                                } while ($oldtag != 'tr');
24792d5c8a1SSatoshi Sahara                                array_push($this->stack, $tag);
2482235f4feSSatoshi Sahara                                $this->open($tag, $attr, $pos, $match, $handler);
24992d5c8a1SSatoshi Sahara                                break;
25092d5c8a1SSatoshi Sahara                            case 'th':
25192d5c8a1SSatoshi Sahara                            case 'td':
25292d5c8a1SSatoshi Sahara                                $oldtag = array_pop($this->stack);
2532235f4feSSatoshi Sahara                                $this->close($oldtag, $pos, $match, $handler);
25492d5c8a1SSatoshi Sahara                                array_push($this->stack, $tag);
2552235f4feSSatoshi Sahara                                $this->open($tag, $attr, $pos, $match, $handler);
25692d5c8a1SSatoshi Sahara                                break;
25792d5c8a1SSatoshi Sahara                        }
25892d5c8a1SSatoshi Sahara                        break;
25992d5c8a1SSatoshi Sahara                }
26092d5c8a1SSatoshi Sahara                break;
26192d5c8a1SSatoshi Sahara            case DOKU_LEXER_UNMATCHED:
26292d5c8a1SSatoshi Sahara                $tag_prev = end($this->stack);
26392d5c8a1SSatoshi Sahara                switch ($tag_prev) {
26492d5c8a1SSatoshi Sahara                    case 'caption':
265e6934c5dSSatoshi Sahara                                // cdata --- use base() instead of $this->_writeCall()
26692d5c8a1SSatoshi Sahara                                $handler->base($match, $state, $pos);
26792d5c8a1SSatoshi Sahara                                break;
26892d5c8a1SSatoshi Sahara                    case 'table':
26992d5c8a1SSatoshi Sahara                                array_push($this->stack, 'tr');
2702235f4feSSatoshi Sahara                                $this->open('tr', '', $pos, $match, $handler);
27192d5c8a1SSatoshi Sahara                    case 'tr':
27292d5c8a1SSatoshi Sahara                                array_push($this->stack, 'td');
2732235f4feSSatoshi Sahara                                $this->open('td', '', $pos, $match, $handler);
27492d5c8a1SSatoshi Sahara                    case 'th':
27592d5c8a1SSatoshi Sahara                    case 'td':
276e6934c5dSSatoshi Sahara                                // cdata --- use base() instead of $this->_writeCall()
277ed8db0b0SSatoshi Sahara                                $handler->base($match, $state, $pos);
27892d5c8a1SSatoshi Sahara                                break;
27992d5c8a1SSatoshi Sahara                }
28092d5c8a1SSatoshi Sahara                break;
2812186d66dSSatoshi Sahara        }
2825c7d1ba6SSatoshi Sahara        return false;
2832186d66dSSatoshi Sahara    }
28492d5c8a1SSatoshi Sahara
2852186d66dSSatoshi Sahara
2862186d66dSSatoshi Sahara   /**
2872186d66dSSatoshi Sahara    * Create output
2882186d66dSSatoshi Sahara    */
2891e984f26SSatoshi Sahara    public function render($format, Doku_Renderer $renderer, $data)
2901e984f26SSatoshi Sahara    {
29192d5c8a1SSatoshi Sahara        if (empty($data)) return false;
2922186d66dSSatoshi Sahara
29392d5c8a1SSatoshi Sahara        switch ($format) {
294c448a253SSatoshi Sahara            case 'xhtml' :
295c448a253SSatoshi Sahara                return $this->render_xhtml($renderer, $data);
296c448a253SSatoshi Sahara            case 'odt'   :
2979d09e2e1SAndreas Ferber            case 'odt_pdf':
298*ec44bd34SLeonard Heyman                $odt = $this->loadHelper('extab4_odt');
299c448a253SSatoshi Sahara                return $odt->render($renderer, $data);
30092d5c8a1SSatoshi Sahara            default:
3012186d66dSSatoshi Sahara                return false;
3022186d66dSSatoshi Sahara        }
303194fb8c1SSatoshi Sahara    }
3042186d66dSSatoshi Sahara
3051e984f26SSatoshi Sahara    protected function render_xhtml(Doku_Renderer $renderer, $data)
3061e984f26SSatoshi Sahara    {
307bb581870SSatoshi Sahara        // prepare class properties
308bb581870SSatoshi Sahara        isset($this->tagsmap) || $this->setTagsmap();
309*ec44bd34SLeonard Heyman        isset($this->attrsmap) || $this->setAllowedAttributes();
310bb581870SSatoshi Sahara
31192d5c8a1SSatoshi Sahara        list($state, $tag, $attr) = $data;
31292d5c8a1SSatoshi Sahara
31392d5c8a1SSatoshi Sahara        switch ($state) {
31492d5c8a1SSatoshi Sahara            case DOKU_LEXER_ENTER:    // open tag
3151e984f26SSatoshi Sahara                $renderer->doc.= $this->tag_open($tag, $attr);
31692d5c8a1SSatoshi Sahara                break;
31792d5c8a1SSatoshi Sahara            case DOKU_LEXER_MATCHED:  // defensive, shouldn't occur
31892d5c8a1SSatoshi Sahara            case DOKU_LEXER_UNMATCHED:
31992d5c8a1SSatoshi Sahara                $renderer->cdata($tag);
32092d5c8a1SSatoshi Sahara                break;
32192d5c8a1SSatoshi Sahara            case DOKU_LEXER_EXIT:     // close tag
3221e984f26SSatoshi Sahara                $renderer->doc.= $this->tag_close($tag);
32392d5c8a1SSatoshi Sahara                break;
32492d5c8a1SSatoshi Sahara        }
3255c7d1ba6SSatoshi Sahara        return true;
32692d5c8a1SSatoshi Sahara    }
32792d5c8a1SSatoshi Sahara
32892d5c8a1SSatoshi Sahara    /**
329bb581870SSatoshi Sahara     * open a exttab tag, used in render_xhtml()
33092d5c8a1SSatoshi Sahara     *
33192d5c8a1SSatoshi Sahara     * @param  string $tag        'table','caption','tr','th' or 'td'
33292d5c8a1SSatoshi Sahara     * @param  string $attr       attibutes of tag element
33392d5c8a1SSatoshi Sahara     * @return string             html used to open the tag
33492d5c8a1SSatoshi Sahara     */
3351e984f26SSatoshi Sahara    protected function tag_open($tag, $attr = null)
3361e984f26SSatoshi Sahara    {
33792d5c8a1SSatoshi Sahara        $before = $this->tagsmap[$tag][0];
33892d5c8a1SSatoshi Sahara        $after  = $this->tagsmap[$tag][1];
3391e984f26SSatoshi Sahara        $attr = $this->cleanAttrString($attr, $this->attrsmap);
34092d5c8a1SSatoshi Sahara        return $before.'<'.$tag.$attr.'>'.$after;
34192d5c8a1SSatoshi Sahara    }
34292d5c8a1SSatoshi Sahara
34392d5c8a1SSatoshi Sahara    /**
344bb581870SSatoshi Sahara     * close a exttab tag, used in render_xhtml()
34592d5c8a1SSatoshi Sahara     *
34692d5c8a1SSatoshi Sahara     * @param  string $tag        'table','caption','tr','th' or 'td'
34792d5c8a1SSatoshi Sahara     * @return string             html used to close the tag
34892d5c8a1SSatoshi Sahara     */
3491e984f26SSatoshi Sahara    protected function tag_close($tag)
3501e984f26SSatoshi Sahara    {
3514bd4a7d9SSatoshi Sahara        $before = $this->tagsmap['/'.$tag][0];
3524bd4a7d9SSatoshi Sahara        $after  = $this->tagsmap['/'.$tag][1];
35392d5c8a1SSatoshi Sahara        return $before.'</'.$tag.'>'.$after;
35492d5c8a1SSatoshi Sahara    }
35592d5c8a1SSatoshi Sahara
356bb581870SSatoshi Sahara    /**
357bb581870SSatoshi Sahara     * prepare tagsmap used in tag_open() and tag_close()
358bb581870SSatoshi Sahara     */
359bb581870SSatoshi Sahara    protected function setTagsmap()
360bb581870SSatoshi Sahara    {
361bb581870SSatoshi Sahara        // define name, prefix and postfix of tags
362bb581870SSatoshi Sahara        $this->tagsmap = array(
363bb581870SSatoshi Sahara                'table'    => array("", "\n" ),     // table start  : {|
364bb581870SSatoshi Sahara                '/table'   => array("", "\n"),      // table end    : |}
365bb581870SSatoshi Sahara                'caption'  => array("", ""),        // caption      : |+
366bb581870SSatoshi Sahara                '/caption' => array("", "\n"),
367bb581870SSatoshi Sahara                'tr'       => array("", "\n"),      // table row    : |-
368bb581870SSatoshi Sahara                '/tr'      => array("", "\n"),
369bb581870SSatoshi Sahara                'th'       => array("", ""),        // table header : !
370bb581870SSatoshi Sahara                '/th'      => array("", "\n"),
371bb581870SSatoshi Sahara                'td'       => array("", ""),        // table data   : |
372bb581870SSatoshi Sahara                '/td'      => array("", "\n"),
373bb581870SSatoshi Sahara        );
374bb581870SSatoshi Sahara    }
375bb581870SSatoshi Sahara
376bb581870SSatoshi Sahara    /**
377bb581870SSatoshi Sahara     * prepare attrsmap used in cleanAttrString()
378bb581870SSatoshi Sahara     */
379bb581870SSatoshi Sahara    protected function setAllowedAttributes()
380bb581870SSatoshi Sahara    {
381bb581870SSatoshi Sahara        // define allowable attibutes for table tags
382bb581870SSatoshi Sahara        $this->attrsmap = array(
383bb581870SSatoshi Sahara            // html5 HTML Global Attributes
384bb581870SSatoshi Sahara            'accesskey', 'class', 'contenteditable', 'contextmenu',
385bb581870SSatoshi Sahara            'dir', 'draggable', 'dropzone', 'hidden', 'id', 'lang',
386bb581870SSatoshi Sahara            'spellcheck', 'style', 'tabindex', 'title', 'translate',
387bb581870SSatoshi Sahara            'xml:lang',
388bb581870SSatoshi Sahara            // html5 table tag
389bb581870SSatoshi Sahara            'border', 'sortable',
390bb581870SSatoshi Sahara            // html5 th and td tag
391bb581870SSatoshi Sahara            'abbr', 'colspan', 'headers', 'rowspan', 'scope', 'sorted',
392bb581870SSatoshi Sahara            // deprecated in html5
393bb581870SSatoshi Sahara            'align', 'valign', 'width', 'height', 'bgcolor', 'nowrap',
394bb581870SSatoshi Sahara        );
395bb581870SSatoshi Sahara    }
39692d5c8a1SSatoshi Sahara
3972186d66dSSatoshi Sahara
3982186d66dSSatoshi Sahara    /**
3992186d66dSSatoshi Sahara     * Make the attribute string safe to avoid XSS attacks.
4003b558e78SSatoshi Sahara     *
4013b558e78SSatoshi Sahara     * @author Ashish Myles <marcianx@gmail.com>
4023b558e78SSatoshi Sahara     *
40392d5c8a1SSatoshi Sahara     * @param  string $attr           attibutes to be checked
40492d5c8a1SSatoshi Sahara     * @param  array  $allowed_keys   allowed attribute name map
40592d5c8a1SSatoshi Sahara     *                                ex: array('border','bgcolor');
40692d5c8a1SSatoshi Sahara     * @return string                 cleaned attibutes
40792d5c8a1SSatoshi Sahara     *
4082186d66dSSatoshi Sahara     * WATCH OUT FOR
4092186d66dSSatoshi Sahara     * - event handlers (e.g. onclick="javascript:...", etc)
4102186d66dSSatoshi Sahara     * - CSS (e.g. background: url(javascript:...))
4112186d66dSSatoshi Sahara     * - closing the tag and opening a new one
4122186d66dSSatoshi Sahara     * WHAT IS DONE
4132186d66dSSatoshi Sahara     * - turn all whitespace into ' ' (to protect from removal)
4142186d66dSSatoshi Sahara     * - remove all non-printable characters and < and >
4152186d66dSSatoshi Sahara     * - parse and filter attributes using a whitelist
4162186d66dSSatoshi Sahara     * - styles with 'url' in them are altogether removed
4172186d66dSSatoshi Sahara     * (I know this is brutally aggressive and doesn't allow
4182186d66dSSatoshi Sahara     * some safe stuff, but better safe than sorry.)
4192186d66dSSatoshi Sahara     * NOTE: Attribute values MUST be in quotes now.
4202186d66dSSatoshi Sahara     */
421*ec44bd34SLeonard Heyman    protected function cleanAttrString($attr, $allowed_keys)
4221e984f26SSatoshi Sahara    {
4231e984f26SSatoshi Sahara        if (is_null($attr)) return null;
4241e984f26SSatoshi Sahara
4251e984f26SSatoshi Sahara        // Keep spaces simple
4262186d66dSSatoshi Sahara        $attr = trim(preg_replace('/\s+/', ' ', $attr));
4271e984f26SSatoshi Sahara        // Remove non-printable characters and angle brackets
4282186d66dSSatoshi Sahara        $attr = preg_replace('/[<>[:^print:]]+/', '', $attr);
4291e984f26SSatoshi Sahara        // This regular expression parses the value of an attribute and
4301e984f26SSatoshi Sahara        // the quotation marks surrounding it.
4311e984f26SSatoshi Sahara        // It assumes that all quotes within the value itself must be escaped,
4321e984f26SSatoshi Sahara        // which is not technically true.
4331e984f26SSatoshi Sahara        // To keep the parsing simple (no look-ahead), the value must be in
4341e984f26SSatoshi Sahara        // quotes.
4352186d66dSSatoshi Sahara        $val = "([\"'`])(?:[^\\\\\"'`]|\\\\.)*\g{-1}";
4362186d66dSSatoshi Sahara
4372186d66dSSatoshi Sahara        $nattr = preg_match_all("/(\w+)\s*=\s*($val)/", $attr, $matches, PREG_SET_ORDER);
4381e984f26SSatoshi Sahara        if (!$nattr) return null;
4392186d66dSSatoshi Sahara
4402186d66dSSatoshi Sahara        $clean_attr = '';
4412186d66dSSatoshi Sahara        for ($i = 0; $i < $nattr; ++$i) {
4422186d66dSSatoshi Sahara            $m = $matches[$i];
4432186d66dSSatoshi Sahara            $attrname = strtolower($m[1]);
4442186d66dSSatoshi Sahara            $attrval  = $m[2];
4451e984f26SSatoshi Sahara            // allow only recognized attributes
44692d5c8a1SSatoshi Sahara            if (in_array($attrname, $allowed_keys, true)) {
4471e984f26SSatoshi Sahara                // make sure that style attributes do not have a url in them
4482186d66dSSatoshi Sahara                if ($attrname != 'style' ||
4491e984f26SSatoshi Sahara                      (stristr($attrval, 'url') === false &&
4501e984f26SSatoshi Sahara                      stristr($attrval, 'import') === false)) {
4512186d66dSSatoshi Sahara                    $clean_attr.= " $attrname=$attrval";
4522186d66dSSatoshi Sahara                }
4532186d66dSSatoshi Sahara            }
4542186d66dSSatoshi Sahara        }
4552186d66dSSatoshi Sahara        return $clean_attr;
4562186d66dSSatoshi Sahara    }
4572186d66dSSatoshi Sahara
4582186d66dSSatoshi Sahara}
459