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