xref: /plugin/mikioplugin/syntax/core.php (revision ec319cf186c32cf5d4e95bd5e6b2fcee37553868)
1<?php
2/**
3 * Mikio Core Syntax Plugin
4 *
5 * @link        http://github.com/nomadjimbob/mikioplugin
6 * @license     GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author      James Collins <james.collins@outlook.com.au>
8 */
9if (!defined('DOKU_INC')) die();
10if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
11
12define('MIKIO_LEXER_AUTO', 0);
13define('MIKIO_LEXER_ENTER', 1);
14define('MIKIO_LEXER_EXIT', 2);
15define('MIKIO_LEXER_SPECIAL', 3);
16
17class syntax_plugin_mikioplugin_core extends DokuWiki_Syntax_Plugin
18{
19    public $pattern_entry       = '';
20    public $pattern             = '';
21    public $pattern_exit        = '';
22    public $tag                 = '';
23    public $hasEndTag           = true;
24    public $options             = array();
25
26    protected $tagPrefix          = ''; //'mikio-';
27    protected $classPrefix        = 'mikiop-';
28    protected $elemClass          = 'mikiop';
29
30    private $values              = array();
31
32
33    function __construct() { }
34    public function getType() { return 'formatting'; }
35    public function getAllowedTypes() { return array('formatting', 'substition', 'disabled', 'paragraphs'); }
36    // public function getAllowedTypes() { return array('formatting', 'substition', 'disabled'); }
37    public function getSort() { return 32; }
38    public function getPType() { return 'stack'; }
39
40
41    public function connectTo($mode)
42    {
43        if ($this->pattern_entry == '' && $this->tag != '') {
44            if ($this->hasEndTag) {
45                $this->pattern_entry = '<(?i:' . $this->tagPrefix . $this->tag . ')(?=[ >]).*?>(?=.*?</(?i:' . $this->tagPrefix . $this->tag . ')>)';
46            } else {
47                $this->pattern_entry = '<(?i:' . $this->tagPrefix . $this->tag . ').*?>';
48            }
49        }
50
51        if ($this->pattern_entry != '') {
52            if ($this->hasEndTag) {
53                $this->Lexer->addEntryPattern($this->pattern_entry, $mode, 'plugin_mikioplugin_' . $this->getPluginComponent());
54            } else {
55                $this->Lexer->addSpecialPattern($this->pattern_entry, $mode, 'plugin_mikioplugin_' . $this->getPluginComponent());
56            }
57        }
58    }
59
60
61    public function postConnect()
62    {
63        if ($this->hasEndTag) {
64            if ($this->pattern_exit == '' && $this->tag != '') {
65                $this->pattern_exit = '</(?i:' . $this->tagPrefix . $this->tag . ')>';
66            }
67
68            if ($this->pattern_exit != '') {
69                $this->Lexer->addExitPattern($this->pattern_exit, 'plugin_mikioplugin_' . $this->getPluginComponent());
70            }
71        }
72    }
73
74    public function handle($match, $state, $pos, Doku_Handler $handler)
75    {
76        switch ($state) {
77            case DOKU_LEXER_ENTER:
78            case DOKU_LEXER_SPECIAL:
79                $match_fix = preg_replace('/\s*=\s*/', '=', trim(substr($match, strlen($this->tagPrefix . $this->tag) + 1, -1)));
80                $optionlist = preg_split('/\s(?=([^"]*"[^"]*")*[^"]*$)/', $match_fix);
81
82                $options = array();
83                foreach ($optionlist as $item) {
84                    $i = strpos($item, '=');
85                    if ($i !== false) {
86                        $value = substr($item, $i + 1);
87
88                        if (substr($value, 0, 1) == '"') $value = substr($value, 1);
89                        if (substr($value, -1) == '"') $value = substr($value, 0, -1);
90
91                        $options[substr($item, 0, $i)] = $value;
92                    } else {
93                        $options[$item] = true;
94                    }
95                }
96
97                if(count($this->options) > 0) {
98                    $options_clean = $this->cleanOptions($options);
99                } else {
100                    $options_clean = $options;
101                }
102
103                $this->values = $options_clean;
104
105                return array($state, $options_clean);
106
107            case DOKU_LEXER_MATCHED:
108                return array($state, $match);
109
110            case DOKU_LEXER_UNMATCHED:
111                return array($state, $match);
112
113            case DOKU_LEXER_EXIT:
114                return array($state, $this->values);
115        }
116
117        return array();
118    }
119
120
121    /*
122    * clean element options to only supported attributes, setting defaults if required
123    *
124    * @param $options   options passed to element
125    * @return           array of options supported with default set
126    */
127    protected function cleanOptions($data, $options=null)
128    {
129        $optionsCleaned = array();
130
131        if($options == null) $options = $this->options;
132
133        // Match DokuWiki passed options to syntax options
134        foreach ($data as $optionKey => $optionValue) {
135            foreach ($options as $syntaxKey => $syntaxValue) {
136                if (strcasecmp($optionKey, $syntaxKey) == 0) {
137                    if (array_key_exists('type', $options[$syntaxKey])) {
138                        $type = $options[$syntaxKey]['type'];
139
140                        switch ($type) {
141                            case 'boolean':
142                                $optionsCleaned[$syntaxKey] = filter_var($optionValue, FILTER_VALIDATE_BOOLEAN);
143                                break;
144                            case 'number':
145                                $optionsCleaned[$syntaxKey] = filter_var($optionValue, FILTER_VALIDATE_INT);
146                                break;
147                            case 'float':
148                                $optionsCleaned[$syntaxKey] = filter_var($optionValue, FILTER_VALIDATE_FLOAT);
149                                break;
150                            case 'text':
151                                $optionsCleaned[$syntaxKey] = $optionValue;
152                                break;
153                            case 'size':
154                                $s = strtolower($optionValue);
155                                $i = '';
156                                if (substr($s, -3) == 'rem') {
157                                    $i = substr($s, 0, -3);
158                                    $s = 'rem';
159                                } elseif (substr($s, -2) == 'em') {
160                                    $i = substr($s, 0, -2);
161                                    $s = 'em';
162                                }
163                                elseif (substr($s, -2) == 'px') {
164                                    $i = substr($s, 0, -2);
165                                    $s = 'px';
166                                }
167                                elseif (substr($s, -1) == '%') {
168                                    $i = substr($s, 0, -1);
169                                    $s = '%';
170                                }
171                                else {
172                                    if($s != 'auto') {
173                                        $i = filter_var($s, FILTER_VALIDATE_INT);
174                                        if ($i == '') $i = '1';
175                                        $s = 'rem';
176                                    }
177                                }
178
179                                $optionsCleaned[$syntaxKey] = $i . $s;
180                                break;
181                            case 'multisize':
182                                $val = '';
183                                $parts = explode(' ', $optionValue);
184                                foreach($parts as &$part) {
185                                    $s = strtolower($part);
186                                    $i = '';
187                                    if (substr($s, -3) == 'rem') {
188                                        $i = substr($s, 0, -3);
189                                        $s = 'rem';
190                                    } elseif (substr($s, -2) == 'em') {
191                                        $i = substr($s, 0, -2);
192                                        $s = 'em';
193                                    }
194                                    elseif (substr($s, -2) == 'px') {
195                                        $i = substr($s, 0, -2);
196                                        $s = 'px';
197                                    }
198                                    elseif (substr($s, -2) == 'fr') {
199                                        $i = substr($s, 0, -2);
200                                        $s = 'fr';
201                                    }
202                                    elseif (substr($s, -1) == '%') {
203                                        $i = substr($s, 0, -1);
204                                        $s = '%';
205                                    }
206                                    else {
207                                        if($s != 'auto') {
208                                            $i = filter_var($s, FILTER_VALIDATE_INT);
209                                            if ($i === '') $i = '1';
210                                            if ($i != 0) {
211                                                $s = 'rem';
212                                            } else {
213                                                $s = '';
214                                            }
215                                        }
216                                    }
217
218                                    $part = $i . $s;
219                                }
220
221                                $optionsCleaned[$syntaxKey] = implode(' ' , $parts);
222                                break;
223                            case 'color':
224                                if (strlen($optionValue) == 3 || strlen($optionValue) == 6) {
225                                    preg_match('/([[:xdigit:]]{3}){1,2}/', $optionValue, $matches);
226                                    if (count($matches) > 1) {
227                                        $optionsCleaned[$syntaxKey] = '#' . $matches[0];
228                                    }
229                                } else {
230                                    $optionsCleaned[$syntaxKey] = $optionValue;
231                                }
232                                break;
233                            case 'url':
234                                $optionsCleaned[$syntaxKey] = $this->buildLink($optionValue);
235                                break;
236                            case 'media':
237                                $optionsCleaned[$syntaxKey] = $this->buildMediaLink($optionValue);
238                                break;
239                            case 'choice':
240                                if (array_key_exists('data', $options[$syntaxKey])) {
241                                    foreach ($options[$syntaxKey]['data'] as $choiceKey => $choiceValue) {
242                                        if (strcasecmp($optionValue, $choiceKey) == 0) {
243                                            $optionsCleaned[$syntaxKey] = $choiceKey;
244                                            break;
245                                        }
246
247                                        if (is_array($choiceValue)) {
248                                            foreach ($choiceValue as $choiceItem) {
249                                                if (strcasecmp($optionValue, $choiceItem) == 0) {
250                                                    $optionsCleaned[$syntaxKey] = $choiceKey;
251                                                    break 2;
252                                                }
253                                            }
254                                        } else {
255                                            if (strcasecmp($optionValue, $choiceValue) == 0) {
256                                                $optionsCleaned[$syntaxKey] = $choiceValue;
257                                                break;
258                                            }
259                                        }
260                                    }
261                                }
262                                break;
263                            case 'set':
264                                if (array_key_exists('option', $options[$syntaxKey]) && array_key_exists('data', $options[$syntaxKey])) {
265                                    $optionsCleaned[$options[$syntaxKey]['option']] = $options[$syntaxKey]['data'];
266                                }
267                                break;
268                        }
269                    }
270
271                    break;
272                }
273            }
274        }
275
276
277        foreach ($data as $optionKey => $optionValue) {
278          if (!array_key_exists($optionKey, $optionsCleaned)) {
279            foreach ($options as $syntaxKey => $syntaxValue) {
280              if (array_key_exists('type', $options[$syntaxKey])) {
281                if (array_key_exists('data', $options[$syntaxKey]) && is_array($options[$syntaxKey][data])) {
282                  file_put_contents('output44.txt', $optionKey." - ", FILE_APPEND);
283                  foreach ($options[$syntaxKey]['data'] as $choiceKey => $choiceValue) {
284                    file_put_contents('output44.txt', $choiceValue.", ", FILE_APPEND);
285                    if(is_array($choiceValue)) {
286                      if(in_array($optionKey, $choiceValue)) {
287                        $optionsCleaned[$syntaxKey] = $choiceKey;
288                      }
289                    } else {
290                      if(strcasecmp($choiceValue, $optionKey) == 0) {
291                        $optionsCleaned[$syntaxKey] = $choiceValue;
292                      }
293                    }
294                  }
295                }
296              }
297            }
298          }
299        }
300
301        // Add in syntax options that are missing
302        foreach ($options as $optionKey => $optionValue) {
303            if (!array_key_exists($optionKey, $optionsCleaned)) {
304                if (array_key_exists('default', $options[$optionKey])) {
305                    switch ($options[$optionKey]['type']) {
306                        case 'boolean':
307                            $optionsCleaned[$optionKey] = filter_var($options[$optionKey]['default'], FILTER_VALIDATE_BOOLEAN);
308                            break;
309                        case 'number':
310                            $optionsCleaned[$optionKey] = filter_var($options[$optionKey]['default'], FILTER_VALIDATE_INT);
311                            break;
312                        default:
313                            $optionsCleaned[$optionKey] = $options[$optionKey]['default'];
314                            break;
315                    }
316                }
317            }
318        }
319
320        return $optionsCleaned;
321    }
322
323    /* Lexer renderers */
324    protected function render_lexer_enter(Doku_Renderer $renderer, $data) { }
325    protected function render_lexer_unmatched(Doku_Renderer $renderer, $data) { $renderer->doc .= $renderer->_xmlEntities($data); }
326    protected function render_lexer_exit(Doku_Renderer $renderer, $data) { }
327    protected function render_lexer_special(Doku_Renderer $renderer, $data) { }
328    protected function render_lexer_match(Doku_Renderer $renderer, $data) { }
329
330    /* Renderer */
331    public function render($mode, Doku_Renderer $renderer, $data)
332    {
333        if ($mode == 'xhtml') {
334            list($state, $match) = $data;
335
336            switch ($state) {
337                case DOKU_LEXER_ENTER:
338                    $this->render_lexer_enter($renderer, $match);
339                    return true;
340
341                case DOKU_LEXER_UNMATCHED:
342                    $this->render_lexer_unmatched($renderer, $match);
343                    return true;
344
345                case DOKU_LEXER_MATCHED:
346                    $this->render_lexer_match($renderer, $match);
347                    return true;
348
349                case DOKU_LEXER_EXIT:
350                    $this->render_lexer_exit($renderer, $match);
351                    return true;
352
353                case DOKU_LEXER_SPECIAL:
354                    $this->render_lexer_special($renderer, $match);
355                    return true;
356            }
357
358            return true;
359        }
360
361        return false;
362    }
363
364    /*
365    * return a class list with mikiop- prefix
366    *
367    * @param $options       options of syntax element. Options with key 'class'=true are automatically added
368    * @param $classes       classes to build from options as array
369    * @param $inclAttr      include class="" in the return string
370    * @param $optionsTemplate   allow a different options template instead of $this->options (for findTags)
371    * @return               a string of classes from options/classes variable
372    */
373    public function buildClass($options = null, $classes = null, $inclAttr = false, $optionsTemplate = null)
374    {
375        $s = array();
376
377        if (is_array($options)) {
378            if($classes == null) $classes = array();
379            if($optionsTemplate == null) $optionsTemplate = $this->options;
380
381            foreach($optionsTemplate as $key => $value) {
382                if(array_key_exists('class', $value) && $value['class'] == TRUE) {
383                    array_push($classes, $key);
384                }
385            }
386
387            foreach ($classes as $class) {
388                if (array_key_exists($class, $options) && $options[$class] !== FALSE && $options[$class] != '') {
389                    $prefix = $this->classPrefix;
390
391                    if (array_key_exists($class, $optionsTemplate) && array_key_exists('prefix', $optionsTemplate[$class])) {
392                        $prefix .= $optionsTemplate[$class]['prefix'];
393                    }
394
395                    if (array_key_exists($class, $optionsTemplate) && array_key_exists('classNoSuffix', $optionsTemplate[$class]) && $optionsTemplate[$class]['classNoSuffix'] == TRUE) {
396                        $s[] = $prefix . $class;
397                    } else {
398                        $s[] = $prefix . $class . ($options[$class] !== TRUE ? '-' . $options[$class] : '');
399                    }
400                }
401            }
402
403        }
404
405        $s = implode(' ', $s);
406        if($s != '') $s = ' ' . $s;
407
408        if($inclAttr) $s = ' classes="' . $s . '"';
409
410        return $s;
411    }
412
413
414
415
416    /*
417    * build style string
418    *
419    * @param $list          style list as key => value. Empty values are not included
420    * @param $inclAttr      include style="" in the return string
421    * @return               style list string
422    */
423    public function buildStyle($list, $inclAttr = false)
424    {
425        $s = '';
426
427        if (is_array($list) && count($list) > 0) {
428            foreach ($list as $key => $value) {
429                if($value != '') {
430                    $s .= $key . ':' . $value . ';';
431                }
432            }
433        }
434
435        if($s != '' && $inclAttr) {
436            $s = ' style="' . $s . '"';
437        }
438
439        return $s;
440    }
441
442
443    public function buildTooltipString($options)
444    {
445        $dataPlacement = 'top';
446        $dataHtml = false;
447        $title = '';
448
449        if ($options != null) {
450            if (array_key_exists('tooltip-html-top', $options) && $options['tooltip-html-top'] != '') {
451                $title = $options['tooltip-html-top'];
452                $dataPlacement = 'top';
453            }
454
455            if (array_key_exists('tooltip-html-left', $options) && $options['tooltip-html-left'] != '') {
456                $title = $options['tooltip-html-left'];
457                $dataPlacement = 'left';
458            }
459
460            if (array_key_exists('tooltip-html-bottom', $options) && $options['tooltip-html-bottom'] != '') {
461                $title = $options['tooltip-html-bottom'];
462                $dataPlacement = 'bottom';
463            }
464
465            if (array_key_exists('tooltip-html-right', $options) && $options['tooltip-html-right'] != '') {
466                $title = $options['tooltip-html-right'];
467                $dataPlacement = 'right';
468            }
469
470            if (array_key_exists('tooltip-top', $options) && $options['tooltip-top'] != '') {
471                $title = $options['tooltip-top'];
472                $dataPlacement = 'top';
473            }
474
475            if (array_key_exists('tooltip-left', $options) && $options['tooltip-left'] != '') {
476                $title = $options['tooltip-left'];
477                $dataPlacement = 'left';
478            }
479
480            if (array_key_exists('tooltip-bottom', $options) && $options['tooltip-bottom'] != '') {
481                $title = $options['tooltip-bottom'];
482                $dataPlacement = 'bottom';
483            }
484
485            if (array_key_exists('tooltip-right', $options) && $options['tooltip-right'] != '') {
486                $title = $options['tooltip-right'];
487                $dataPlacement = 'right';
488            }
489
490            if (array_key_exists('tooltip-html', $options) && $options['tooltip-html'] != '') {
491                $title = $options['tooltip-html'];
492                $dataPlacement = 'top';
493            }
494
495            if (array_key_exists('tooltip', $options) && $options['tooltip'] != '') {
496                $title = $options['tooltip'];
497                $dataPlacement = 'top';
498            }
499        }
500
501        if ($title != '') {
502            return ' data-toggle="tooltip" data-placement="' . $dataPlacement . '" ' . ($dataHtml == true ? 'data-html="true" ' : '') . 'title="' . $title . '" ';
503        }
504
505        return '';
506    }
507
508    /*
509    * convert the URL to a DokuWiki media link (if required)
510    *
511    * @param $url   url to parse
512    * @return       url string
513    */
514    public function buildMediaLink($url)
515    {
516        $i = strpos($url, '?');
517        if ($i !== FALSE) $url = substr($url, 0, $i);
518
519        $url = preg_replace('/[^\da-zA-Z:_.-]+/', '', $url);
520
521        return (tpl_getMediaFile(array($url), FALSE));
522    }
523
524
525    /*
526    * returns either a url or dokuwiki link
527    *
528    * @param    $url    link to build from
529    * @return           built link
530    */
531    public function buildLink($url)
532    {
533        $i = strpos($url, '://');
534        if ($i !== FALSE || substr($url, 0, 1) == '#') return $url;
535
536        return wl($url);
537    }
538
539    /*
540    * Call syntax renderer of mikio syntax plugin
541    *
542    * @param $renderer          DokuWiki renderer object
543    * @param $className         mikio syntax class to call
544    * @param $text              unmatched text to pass outside of lexer. Only used when $lexer=MIKIO_LEXER_AUTO
545    * @param $data              tag options to pass to syntax class. Runs through cleanOptions to validate first
546    * @param $lexer             which lexer to call
547    */
548    public function syntaxRender(Doku_Renderer $renderer, $className, $text, $data = null, $lexer = MIKIO_LEXER_AUTO)
549    {
550        $className = 'syntax_plugin_mikioplugin_'.$className;
551
552        if(class_exists($className)) {
553            $class = new $className;
554
555            if(!is_array($data)) $data = array();
556
557
558            if(count($class->options) > 0) {
559                $data = $class->cleanOptions($data, $class->options);
560            }
561
562            switch($lexer) {
563                case MIKIO_LEXER_AUTO:
564                    if ($class->hasEndTag) {
565                        if(method_exists($class, 'render_lexer_enter')) $class->render_lexer_enter($renderer, $data);
566                        $renderer->doc .= $text;
567                        if(method_exists($class, 'render_lexer_exit')) $class->render_lexer_exit($renderer, $data);
568                    } else {
569                        if(method_exists($class, 'render_lexer_special')) $class->render_lexer_special($renderer, $data);
570                    }
571
572                    break;
573                case MIKIO_LEXER_ENTER:
574                    if(method_exists($class, 'render_lexer_enter')) $class->render_lexer_enter($renderer, $data);
575                    break;
576                case MIKIO_LEXER_EXIT:
577                    if(method_exists($class, 'render_lexer_exit')) $class->render_lexer_exit($renderer, $data);
578                    break;
579                case MIKIO_LEXER_SPECIAL:
580                    if(method_exists($class, 'render_lexer_special')) $class->render_lexer_special($renderer, $data);
581                    break;
582            }
583        }
584    }
585
586
587    protected function callMikioTag($className, $data) {
588        // $className = 'syntax_plugin_mikioplugin_'.$className;
589
590
591        // if(class_exists($className)) {
592            //$class = new $className;
593            if(!plugin_isdisabled('mikioplugin')) {
594                $class = plugin_load('syntax', 'mikioplugin_' . $className);
595                // echo '^^'.$className.'^^';
596
597
598                if(method_exists($class, 'mikioCall')) return $class->mikioCall($data);
599
600            }
601
602        // }
603
604        return '';
605    }
606
607
608    protected function callMikioOptionDefault($className, $option) {
609        $className = 'syntax_plugin_mikioplugin_'.$className;
610
611        if(class_exists($className)) {
612            $class = new $className;
613
614            if(array_key_exists($option, $class->options) && array_key_exists('default', $class->options[$option])) {
615                return $class->options[$option]['default'];
616            }
617        }
618
619        return '';
620    }
621
622
623    protected function buildTooltip($text) {
624        if($text != '') {
625            return ' data-tooltip="' . $text . '"';
626        }
627
628        return '';
629    }
630
631    /*
632    * Create array with passed elements and include them if their values are not empty
633    *
634    * @param ...        array items
635    */
636    protected function arrayRemoveEmpties($items) {
637        $result = array();
638
639        foreach($items as $key => $value) {
640            if($value != '') {
641                $result[$key] = $value;
642            }
643        }
644
645        return $result;
646    }
647
648    public function getFirstArrayKey($data)
649    {
650        if (!function_exists('array_key_first')) {
651            foreach ($data as $key => $unused) {
652                return $key;
653            }
654        }
655
656        return array_key_first($data);
657    }
658
659
660    /*
661    * add common options to options
662    *
663    * @param $typelist      common option to add
664    * @param $options   save in options
665    */
666    public function addCommonOptions($typelist) {
667        $types = explode(' ', $typelist);
668        foreach($types as $type) {
669            if(strcasecmp($type, 'shadow') == 0) {
670                $this->options['shadow'] =          array('type'     => 'choice',
671                                                          'data'     => array('large'=> array('shadow-large', 'shadow-lg'), 'small' => array('shadow-small', 'shadow-sm'), true),
672                                                          'default'  => '',
673                                                          'class'    => true);
674            }
675
676            if(strcasecmp($type, 'width') == 0) {
677                $this->options['width'] =           array('type'     => 'size',
678                                                          'default'  => '');
679            }
680
681            if(strcasecmp($type, 'height') == 0) {
682                $this->options['height'] =          array('type'     => 'size',
683                                                          'default'  => '');
684            }
685
686            if(strcasecmp($type, 'text-color') == 0) {
687                $this->options['text-color'] =          array('type'      => 'color',
688                                                          'default'  => '');
689            }
690
691            if(strcasecmp($type, 'type') == 0) {
692                $this->options['type'] =            array('type'     => 'text',
693                                                          'data'     => array('primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark'),
694                                                          'default'  => '',
695                                                          'class'    => true);
696            }
697
698            if(strcasecmp($type, 'text-align') == 0) {
699                $this->options['text-align'] =      array('type'     => 'choice',
700                                                          'data'     => array('left' => array('text-left'), 'center' => array('text-center'), 'right' => array('text-right')),
701                                                          'default'  => '',
702                                                          'class'    => true);
703            }
704
705            if(strcasecmp($type, 'align') == 0) {
706                $this->options['align'] =           array('type'     => 'choice',
707                                                          'data'     => array('left' => array('align-left'), 'center' => array('align-center'), 'right' => array('align-right')),
708                                                          'default'  => '',
709                                                          'class'    => true);
710            }
711
712            if(strcasecmp($type, 'tooltip') == 0) {
713                $this->options['tooltip'] =         array('type'     => 'text',
714                                                          'default'  => '',
715                                                          'class'    => true,
716                                                          'classNoSuffix'   => true);
717            }
718
719            if(strcasecmp($type, 'vertical-align') == 0) {
720                $this->options['vertical-align'] =  array('type'    => 'choice',
721                                                          'data'    => array('top' => array('align-top'), 'middle' => array('align-middle'), 'bottom' => array('align-bottom')),
722                                                          'default' => '',
723                                                          'class'   => true);
724            }
725
726            if(strcasecmp($type, 'links-match') == 0) {
727                $this->options['links-match'] =     array('type'    => 'boolean',
728                                                          'default' => 'false',
729                                                          'class'   => true);
730            }
731
732        }
733    }
734
735
736    /*
737    * Find HTML tags in string. Parse tags options. Used in parsing subtags
738    *
739    * @param $tagName       tagName to search for. Name is exclusive
740    * @param $content       search within content
741    * @param $options       parse options similar to syntax element options
742    * @param $hasEndTag     tagName search also looks for an end tag
743    * @return               array of tags containing 'options' => array of 'name' => 'value', 'content' => content inside the tag
744    */
745    protected function findTags($tagName, $content, $options, $hasEndTag = true) {
746        $items = array();
747        $search = '/<(?i:' . $tagName . ')(.*?)>(.*?)<\/(?i:' . $tagName . ')>/s';
748
749        if(!$hasEndTag) {
750            $search = '/<(?i:' . $tagName . ')(.*?)>/s';
751        }
752
753        if(preg_match_all($search, $content, $match)) {
754            if(count($match) >= 2) {
755                for($i = 0; $i < count($match[1]); $i++) {
756                    $item = array('options' => array(), 'content' => $this->render_text($match[2][$i]));
757
758                    $optionlist = preg_split('/\s(?=([^"]*"[^"]*")*[^"]*$)/', trim($match[1][$i]));
759
760                    foreach ($optionlist as $option) {
761                        $j = strpos($option, '=');
762                        if ($j !== false) {
763                            $value = substr($option, $j + 1);
764
765                            if (substr($value, 0, 1) == '"') $value = substr($value, 1);
766                            if (substr($value, -1) == '"') $value = substr($value, 0, -1);
767
768                            $item['options'][substr($option, 0, $j)] = $value;
769                        } else {
770                            $item['options'][$option] = true;
771                        }
772                    }
773
774                    $item['options'] = $this->cleanOptions($item['options'], $options);
775
776                    $items[] = $item;
777                }
778            }
779        }
780
781        return $items;
782    }
783}