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