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 } else { 273 if (array_key_exists('type', $options[$syntaxKey]) && $options[$syntaxKey]['type'] == 'choice' && array_key_exists('data', $options[$syntaxKey])) { 274 foreach ($options[$syntaxKey]['data'] as $choiceKey => $choiceValue) { 275 if (is_array($choiceValue)) { 276 foreach ($choiceValue as $choiceItem) { 277 if (strcasecmp($optionKey, $choiceItem) == 0) { 278 $optionsCleaned[$syntaxKey] = $choiceKey; 279 break 2; 280 } 281 } 282 } else { 283 if (strcasecmp($optionKey, $choiceValue) == 0) { 284 $optionsCleaned[$syntaxKey] = $choiceValue; 285 break; 286 } 287 } 288 } 289 } 290 } 291 } 292 } 293 294 // Add in syntax options that are missing 295 foreach ($options as $optionKey => $optionValue) { 296 if (!array_key_exists($optionKey, $optionsCleaned)) { 297 if (array_key_exists('default', $options[$optionKey])) { 298 switch ($options[$optionKey]['type']) { 299 case 'boolean': 300 $optionsCleaned[$optionKey] = filter_var($options[$optionKey]['default'], FILTER_VALIDATE_BOOLEAN); 301 break; 302 case 'number': 303 $optionsCleaned[$optionKey] = filter_var($options[$optionKey]['default'], FILTER_VALIDATE_INT); 304 break; 305 default: 306 $optionsCleaned[$optionKey] = $options[$optionKey]['default']; 307 break; 308 } 309 } 310 } 311 } 312 313 return $optionsCleaned; 314 } 315 316 /* Lexer renderers */ 317 protected function render_lexer_enter(Doku_Renderer $renderer, $data) { } 318 protected function render_lexer_unmatched(Doku_Renderer $renderer, $data) { $renderer->doc .= $renderer->_xmlEntities($data); } 319 protected function render_lexer_exit(Doku_Renderer $renderer, $data) { } 320 protected function render_lexer_special(Doku_Renderer $renderer, $data) { } 321 protected function render_lexer_match(Doku_Renderer $renderer, $data) { } 322 323 /* Renderer */ 324 public function render($mode, Doku_Renderer $renderer, $data) 325 { 326 if ($mode == 'xhtml') { 327 list($state, $match) = $data; 328 329 switch ($state) { 330 case DOKU_LEXER_ENTER: 331 $this->render_lexer_enter($renderer, $match); 332 return true; 333 334 case DOKU_LEXER_UNMATCHED: 335 $this->render_lexer_unmatched($renderer, $match); 336 return true; 337 338 case DOKU_LEXER_MATCHED: 339 $this->render_lexer_match($renderer, $match); 340 return true; 341 342 case DOKU_LEXER_EXIT: 343 $this->render_lexer_exit($renderer, $match); 344 return true; 345 346 case DOKU_LEXER_SPECIAL: 347 $this->render_lexer_special($renderer, $match); 348 return true; 349 } 350 351 return true; 352 } 353 354 return false; 355 } 356 357 /* 358 * return a class list with mikiop- prefix 359 * 360 * @param $options options of syntax element. Options with key 'class'=true are automatically added 361 * @param $classes classes to build from options as array 362 * @param $inclAttr include class="" in the return string 363 * @param $optionsTemplate allow a different options template instead of $this->options (for findTags) 364 * @return a string of classes from options/classes variable 365 */ 366 public function buildClass($options = null, $classes = null, $inclAttr = false, $optionsTemplate = null) 367 { 368 $s = array(); 369 370 if (is_array($options)) { 371 if($classes == null) $classes = array(); 372 if($optionsTemplate == null) $optionsTemplate = $this->options; 373 374 foreach($optionsTemplate as $key => $value) { 375 if(array_key_exists('class', $value) && $value['class'] == TRUE) { 376 array_push($classes, $key); 377 } 378 } 379 380 foreach ($classes as $class) { 381 if (array_key_exists($class, $options) && $options[$class] !== FALSE && $options[$class] != '') { 382 $prefix = $this->classPrefix; 383 384 if (array_key_exists($class, $optionsTemplate) && array_key_exists('prefix', $optionsTemplate[$class])) { 385 $prefix .= $optionsTemplate[$class]['prefix']; 386 } 387 388 if (array_key_exists($class, $optionsTemplate) && array_key_exists('classNoSuffix', $optionsTemplate[$class]) && $optionsTemplate[$class]['classNoSuffix'] == TRUE) { 389 $s[] = $prefix . $class; 390 } else { 391 $s[] = $prefix . $class . ($options[$class] !== TRUE ? '-' . $options[$class] : ''); 392 } 393 } 394 } 395 396 } 397 398 $s = implode(' ', $s); 399 if($s != '') $s = ' ' . $s; 400 401 if($inclAttr) $s = ' classes="' . $s . '"'; 402 403 return $s; 404 } 405 406 407 408 409 /* 410 * build style string 411 * 412 * @param $list style list as key => value. Empty values are not included 413 * @param $inclAttr include style="" in the return string 414 * @return style list string 415 */ 416 public function buildStyle($list, $inclAttr = false) 417 { 418 $s = ''; 419 420 if (is_array($list) && count($list) > 0) { 421 foreach ($list as $key => $value) { 422 if($value != '') { 423 $s .= $key . ':' . $value . ';'; 424 } 425 } 426 } 427 428 if($s != '' && $inclAttr) { 429 $s = ' style="' . $s . '"'; 430 } 431 432 return $s; 433 } 434 435 436 public function buildTooltipString($options) 437 { 438 $dataPlacement = 'top'; 439 $dataHtml = false; 440 $title = ''; 441 442 if ($options != null) { 443 if (array_key_exists('tooltip-html-top', $options) && $options['tooltip-html-top'] != '') { 444 $title = $options['tooltip-html-top']; 445 $dataPlacement = 'top'; 446 } 447 448 if (array_key_exists('tooltip-html-left', $options) && $options['tooltip-html-left'] != '') { 449 $title = $options['tooltip-html-left']; 450 $dataPlacement = 'left'; 451 } 452 453 if (array_key_exists('tooltip-html-bottom', $options) && $options['tooltip-html-bottom'] != '') { 454 $title = $options['tooltip-html-bottom']; 455 $dataPlacement = 'bottom'; 456 } 457 458 if (array_key_exists('tooltip-html-right', $options) && $options['tooltip-html-right'] != '') { 459 $title = $options['tooltip-html-right']; 460 $dataPlacement = 'right'; 461 } 462 463 if (array_key_exists('tooltip-top', $options) && $options['tooltip-top'] != '') { 464 $title = $options['tooltip-top']; 465 $dataPlacement = 'top'; 466 } 467 468 if (array_key_exists('tooltip-left', $options) && $options['tooltip-left'] != '') { 469 $title = $options['tooltip-left']; 470 $dataPlacement = 'left'; 471 } 472 473 if (array_key_exists('tooltip-bottom', $options) && $options['tooltip-bottom'] != '') { 474 $title = $options['tooltip-bottom']; 475 $dataPlacement = 'bottom'; 476 } 477 478 if (array_key_exists('tooltip-right', $options) && $options['tooltip-right'] != '') { 479 $title = $options['tooltip-right']; 480 $dataPlacement = 'right'; 481 } 482 483 if (array_key_exists('tooltip-html', $options) && $options['tooltip-html'] != '') { 484 $title = $options['tooltip-html']; 485 $dataPlacement = 'top'; 486 } 487 488 if (array_key_exists('tooltip', $options) && $options['tooltip'] != '') { 489 $title = $options['tooltip']; 490 $dataPlacement = 'top'; 491 } 492 } 493 494 if ($title != '') { 495 return ' data-toggle="tooltip" data-placement="' . $dataPlacement . '" ' . ($dataHtml == true ? 'data-html="true" ' : '') . 'title="' . $title . '" '; 496 } 497 498 return ''; 499 } 500 501 /* 502 * convert the URL to a DokuWiki media link (if required) 503 * 504 * @param $url url to parse 505 * @return url string 506 */ 507 public function buildMediaLink($url) 508 { 509 $i = strpos($url, '?'); 510 if ($i !== FALSE) $url = substr($url, 0, $i); 511 512 $url = preg_replace('/[^\da-zA-Z:_.-]+/', '', $url); 513 514 return (tpl_getMediaFile(array($url), FALSE)); 515 } 516 517 518 /* 519 * returns either a url or dokuwiki link 520 * 521 * @param $url link to build from 522 * @return built link 523 */ 524 public function buildLink($url) 525 { 526 $i = strpos($url, '://'); 527 if ($i !== FALSE || substr($url, 0, 1) == '#') return $url; 528 529 return wl($url); 530 } 531 532 /* 533 * Call syntax renderer of mikio syntax plugin 534 * 535 * @param $renderer DokuWiki renderer object 536 * @param $className mikio syntax class to call 537 * @param $text unmatched text to pass outside of lexer. Only used when $lexer=MIKIO_LEXER_AUTO 538 * @param $data tag options to pass to syntax class. Runs through cleanOptions to validate first 539 * @param $lexer which lexer to call 540 */ 541 public function syntaxRender(Doku_Renderer $renderer, $className, $text, $data = null, $lexer = MIKIO_LEXER_AUTO) 542 { 543 $className = 'syntax_plugin_mikioplugin_'.$className; 544 545 if(class_exists($className)) { 546 $class = new $className; 547 548 if(!is_array($data)) $data = array(); 549 550 551 if(count($class->options) > 0) { 552 $data = $class->cleanOptions($data, $class->options); 553 } 554 555 switch($lexer) { 556 case MIKIO_LEXER_AUTO: 557 if ($class->hasEndTag) { 558 if(method_exists($class, 'render_lexer_enter')) $class->render_lexer_enter($renderer, $data); 559 $renderer->doc .= $text; 560 if(method_exists($class, 'render_lexer_exit')) $class->render_lexer_exit($renderer, $data); 561 } else { 562 if(method_exists($class, 'render_lexer_special')) $class->render_lexer_special($renderer, $data); 563 } 564 565 break; 566 case MIKIO_LEXER_ENTER: 567 if(method_exists($class, 'render_lexer_enter')) $class->render_lexer_enter($renderer, $data); 568 break; 569 case MIKIO_LEXER_EXIT: 570 if(method_exists($class, 'render_lexer_exit')) $class->render_lexer_exit($renderer, $data); 571 break; 572 case MIKIO_LEXER_SPECIAL: 573 if(method_exists($class, 'render_lexer_special')) $class->render_lexer_special($renderer, $data); 574 break; 575 } 576 } 577 } 578 579 580 protected function callMikioTag($className, $data) { 581 // $className = 'syntax_plugin_mikioplugin_'.$className; 582 583 584 // if(class_exists($className)) { 585 //$class = new $className; 586 if(!plugin_isdisabled('mikioplugin')) { 587 $class = plugin_load('syntax', 'mikioplugin_' . $className); 588 // echo '^^'.$className.'^^'; 589 590 591 if(method_exists($class, 'mikioCall')) return $class->mikioCall($data); 592 593 } 594 595 // } 596 597 return ''; 598 } 599 600 601 protected function callMikioOptionDefault($className, $option) { 602 $className = 'syntax_plugin_mikioplugin_'.$className; 603 604 if(class_exists($className)) { 605 $class = new $className; 606 607 if(array_key_exists($option, $class->options) && array_key_exists('default', $class->options[$option])) { 608 return $class->options[$option]['default']; 609 } 610 } 611 612 return ''; 613 } 614 615 616 protected function buildTooltip($text) { 617 if($text != '') { 618 return ' data-tooltip="' . $text . '"'; 619 } 620 621 return ''; 622 } 623 624 /* 625 * Create array with passed elements and include them if their values are not empty 626 * 627 * @param ... array items 628 */ 629 protected function arrayRemoveEmpties($items) { 630 $result = array(); 631 632 foreach($items as $key => $value) { 633 if($value != '') { 634 $result[$key] = $value; 635 } 636 } 637 638 return $result; 639 } 640 641 public function getFirstArrayKey($data) 642 { 643 if (!function_exists('array_key_first')) { 644 foreach ($data as $key => $unused) { 645 return $key; 646 } 647 } 648 649 return array_key_first($data); 650 } 651 652 653 /* 654 * add common options to options 655 * 656 * @param $typelist common option to add 657 * @param $options save in options 658 */ 659 public function addCommonOptions($typelist) { 660 $types = explode(' ', $typelist); 661 foreach($types as $type) { 662 if(strcasecmp($type, 'shadow') == 0) { 663 $this->options['shadow'] = array('type' => 'choice', 664 'data' => array('large'=> array('shadow-large', 'shadow-lg'), 'small' => array('shadow-small', 'shadow-sm'), true), 665 'default' => '', 666 'class' => true); 667 } 668 669 if(strcasecmp($type, 'width') == 0) { 670 $this->options['width'] = array('type' => 'size', 671 'default' => ''); 672 } 673 674 if(strcasecmp($type, 'height') == 0) { 675 $this->options['height'] = array('type' => 'size', 676 'default' => ''); 677 } 678 679 if(strcasecmp($type, 'text-color') == 0) { 680 $this->options['text-color'] = array('type' => 'color', 681 'default' => ''); 682 } 683 684 if(strcasecmp($type, 'type') == 0) { 685 $this->options['type'] = array('type' => 'choice', 686 'data' => array('primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark'), 687 'default' => '', 688 'class' => true); 689 } 690 691 if(strcasecmp($type, 'text-align') == 0) { 692 $this->options['text-align'] = array('type' => 'choice', 693 'data' => array('left' => array('text-left'), 'center' => array('text-center'), 'right' => array('text-right')), 694 'default' => '', 695 'class' => true); 696 } 697 698 if(strcasecmp($type, 'align') == 0) { 699 $this->options['align'] = array('type' => 'choice', 700 'data' => array('left' => array('align-left'), 'center' => array('align-center'), 'right' => array('align-right')), 701 'default' => '', 702 'class' => true); 703 } 704 705 if(strcasecmp($type, 'tooltip') == 0) { 706 $this->options['tooltip'] = array('type' => 'text', 707 'default' => '', 708 'class' => true, 709 'classNoSuffix' => true); 710 } 711 712 if(strcasecmp($type, 'vertical-align') == 0) { 713 $this->options['vertical-align'] = array('type' => 'choice', 714 'data' => array('top' => array('align-top'), 'middle' => array('align-middle'), 'bottom' => array('align-bottom')), 715 'default' => '', 716 'class' => true); 717 } 718 719 if(strcasecmp($type, 'links-match') == 0) { 720 $this->options['links-match'] = array('type' => 'boolean', 721 'default' => 'false', 722 'class' => true); 723 } 724 725 } 726 } 727 728 729 /* 730 * Find HTML tags in string. Parse tags options. Used in parsing subtags 731 * 732 * @param $tagName tagName to search for. Name is exclusive 733 * @param $content search within content 734 * @param $options parse options similar to syntax element options 735 * @param $hasEndTag tagName search also looks for an end tag 736 * @return array of tags containing 'options' => array of 'name' => 'value', 'content' => content inside the tag 737 */ 738 protected function findTags($tagName, $content, $options, $hasEndTag = true) { 739 $items = array(); 740 $search = '/<(?i:' . $tagName . ')(.*?)>(.*?)<\/(?i:' . $tagName . ')>/s'; 741 742 if(!$hasEndTag) { 743 $search = '/<(?i:' . $tagName . ')(.*?)>/s'; 744 } 745 746 if(preg_match_all($search, $content, $match)) { 747 if(count($match) >= 2) { 748 for($i = 0; $i < count($match[1]); $i++) { 749 $item = array('options' => array(), 'content' => $this->render_text($match[2][$i])); 750 751 $optionlist = preg_split('/\s(?=([^"]*"[^"]*")*[^"]*$)/', trim($match[1][$i])); 752 753 foreach ($optionlist as $option) { 754 $j = strpos($option, '='); 755 if ($j !== false) { 756 $value = substr($option, $j + 1); 757 758 if (substr($value, 0, 1) == '"') $value = substr($value, 1); 759 if (substr($value, -1) == '"') $value = substr($value, 0, -1); 760 761 $item['options'][substr($option, 0, $j)] = $value; 762 } else { 763 $item['options'][$option] = true; 764 } 765 } 766 767 $item['options'] = $this->cleanOptions($item['options'], $options); 768 769 $items[] = $item; 770 } 771 } 772 } 773 774 return $items; 775 } 776}