[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 use dokuwiki\Extension\Event; 4 use dokuwiki\Extension\SyntaxPlugin; 5 use dokuwiki\Parsing\Handler\Block; 6 use dokuwiki\Parsing\Handler\CallWriter; 7 use dokuwiki\Parsing\Handler\CallWriterInterface; 8 use dokuwiki\Parsing\Handler\Lists; 9 use dokuwiki\Parsing\Handler\Nest; 10 use dokuwiki\Parsing\Handler\Preformatted; 11 use dokuwiki\Parsing\Handler\Quote; 12 use dokuwiki\Parsing\Handler\Table; 13 14 /** 15 * Class Doku_Handler 16 */ 17 class Doku_Handler { 18 /** @var CallWriterInterface */ 19 protected $callWriter = null; 20 21 /** @var array The current CallWriter will write directly to this list of calls, Parser reads it */ 22 public $calls = array(); 23 24 /** @var array internal status holders for some modes */ 25 protected $status = array( 26 'section' => false, 27 'doublequote' => 0, 28 ); 29 30 /** @var bool should blocks be rewritten? FIXME seems to always be true */ 31 protected $rewriteBlocks = true; 32 33 /** 34 * Doku_Handler constructor. 35 */ 36 public function __construct() { 37 $this->callWriter = new CallWriter($this); 38 } 39 40 /** 41 * Add a new call by passing it to the current CallWriter 42 * 43 * @param string $handler handler method name (see mode handlers below) 44 * @param mixed $args arguments for this call 45 * @param int $pos byte position in the original source file 46 */ 47 public function addCall($handler, $args, $pos) { 48 $call = array($handler,$args, $pos); 49 $this->callWriter->writeCall($call); 50 } 51 52 /** 53 * Accessor for the current CallWriter 54 * 55 * @return CallWriterInterface 56 */ 57 public function getCallWriter() { 58 return $this->callWriter; 59 } 60 61 /** 62 * Set a new CallWriter 63 * 64 * @param CallWriterInterface $callWriter 65 */ 66 public function setCallWriter($callWriter) { 67 $this->callWriter = $callWriter; 68 } 69 70 /** 71 * Return the current internal status of the given name 72 * 73 * @param string $status 74 * @return mixed|null 75 */ 76 public function getStatus($status) { 77 if (!isset($this->status[$status])) return null; 78 return $this->status[$status]; 79 } 80 81 /** 82 * Set a new internal status 83 * 84 * @param string $status 85 * @param mixed $value 86 */ 87 public function setStatus($status, $value) { 88 $this->status[$status] = $value; 89 } 90 91 /** @deprecated 2019-10-31 use addCall() instead */ 92 public function _addCall($handler, $args, $pos) { 93 dbg_deprecated('addCall'); 94 $this->addCall($handler, $args, $pos); 95 } 96 97 /** 98 * Similar to addCall, but adds a plugin call 99 * 100 * @param string $plugin name of the plugin 101 * @param mixed $args arguments for this call 102 * @param int $state a LEXER_STATE_* constant 103 * @param int $pos byte position in the original source file 104 * @param string $match matched syntax 105 */ 106 public function addPluginCall($plugin, $args, $state, $pos, $match) { 107 $call = array('plugin',array($plugin, $args, $state, $match), $pos); 108 $this->callWriter->writeCall($call); 109 } 110 111 /** 112 * Finishes handling 113 * 114 * Called from the parser. Calls finalise() on the call writer, closes open 115 * sections, rewrites blocks and adds document_start and document_end calls. 116 * 117 * @triggers PARSER_HANDLER_DONE 118 */ 119 public function finalize(){ 120 $this->callWriter->finalise(); 121 122 if ( $this->status['section'] ) { 123 $last_call = end($this->calls); 124 array_push($this->calls,array('section_close',array(), $last_call[2])); 125 } 126 127 if ( $this->rewriteBlocks ) { 128 $B = new Block(); 129 $this->calls = $B->process($this->calls); 130 } 131 132 Event::createAndTrigger('PARSER_HANDLER_DONE',$this); 133 134 array_unshift($this->calls,array('document_start',array(),0)); 135 $last_call = end($this->calls); 136 array_push($this->calls,array('document_end',array(),$last_call[2])); 137 } 138 139 /** 140 * fetch the current call and advance the pointer to the next one 141 * 142 * @fixme seems to be unused? 143 * @return bool|mixed 144 */ 145 public function fetch() { 146 $call = current($this->calls); 147 if($call !== false) { 148 next($this->calls); //advance the pointer 149 return $call; 150 } 151 return false; 152 } 153 154 155 /** 156 * Internal function for parsing highlight options. 157 * $options is parsed for key value pairs separated by commas. 158 * A value might also be missing in which case the value will simple 159 * be set to true. Commas in strings are ignored, e.g. option="4,56" 160 * will work as expected and will only create one entry. 161 * 162 * @param string $options space separated list of key-value pairs, 163 * e.g. option1=123, option2="456" 164 * @return array|null Array of key-value pairs $array['key'] = 'value'; 165 * or null if no entries found 166 */ 167 protected function parse_highlight_options($options) { 168 $result = array(); 169 preg_match_all('/(\w+(?:="[^"]*"))|(\w+(?:=[^\s]*))|(\w+[^=\s\]])(?:\s*)/', $options, $matches, PREG_SET_ORDER); 170 foreach ($matches as $match) { 171 $equal_sign = strpos($match [0], '='); 172 if ($equal_sign === false) { 173 $key = trim($match[0]); 174 $result [$key] = 1; 175 } else { 176 $key = substr($match[0], 0, $equal_sign); 177 $value = substr($match[0], $equal_sign+1); 178 $value = trim($value, '"'); 179 if (strlen($value) > 0) { 180 $result [$key] = $value; 181 } else { 182 $result [$key] = 1; 183 } 184 } 185 } 186 187 // Check for supported options 188 $result = array_intersect_key( 189 $result, 190 array_flip(array( 191 'enable_line_numbers', 192 'start_line_numbers_at', 193 'highlight_lines_extra', 194 'enable_keyword_links') 195 ) 196 ); 197 198 // Sanitize values 199 if(isset($result['enable_line_numbers'])) { 200 if($result['enable_line_numbers'] === 'false') { 201 $result['enable_line_numbers'] = false; 202 } 203 $result['enable_line_numbers'] = (bool) $result['enable_line_numbers']; 204 } 205 if(isset($result['highlight_lines_extra'])) { 206 $result['highlight_lines_extra'] = array_map('intval', explode(',', $result['highlight_lines_extra'])); 207 $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']); 208 $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']); 209 } 210 if(isset($result['start_line_numbers_at'])) { 211 $result['start_line_numbers_at'] = (int) $result['start_line_numbers_at']; 212 } 213 if(isset($result['enable_keyword_links'])) { 214 if($result['enable_keyword_links'] === 'false') { 215 $result['enable_keyword_links'] = false; 216 } 217 $result['enable_keyword_links'] = (bool) $result['enable_keyword_links']; 218 } 219 if (count($result) == 0) { 220 return null; 221 } 222 223 return $result; 224 } 225 226 /** 227 * Simplifies handling for the formatting tags which all behave the same 228 * 229 * @param string $match matched syntax 230 * @param int $state a LEXER_STATE_* constant 231 * @param int $pos byte position in the original source file 232 * @param string $name actual mode name 233 */ 234 protected function nestingTag($match, $state, $pos, $name) { 235 switch ( $state ) { 236 case DOKU_LEXER_ENTER: 237 $this->addCall($name.'_open', array(), $pos); 238 break; 239 case DOKU_LEXER_EXIT: 240 $this->addCall($name.'_close', array(), $pos); 241 break; 242 case DOKU_LEXER_UNMATCHED: 243 $this->addCall('cdata', array($match), $pos); 244 break; 245 } 246 } 247 248 249 /** 250 * The following methods define the handlers for the different Syntax modes 251 * 252 * The handlers are called from dokuwiki\Parsing\Lexer\Lexer\invokeParser() 253 * 254 * @todo it might make sense to move these into their own class or merge them with the 255 * ParserMode classes some time. 256 */ 257 // region mode handlers 258 259 /** 260 * Special plugin handler 261 * 262 * This handler is called for all modes starting with 'plugin_'. 263 * An additional parameter with the plugin name is passed. The plugin's handle() 264 * method is called here 265 * 266 * @author Andreas Gohr <andi@splitbrain.org> 267 * 268 * @param string $match matched syntax 269 * @param int $state a LEXER_STATE_* constant 270 * @param int $pos byte position in the original source file 271 * @param string $pluginname name of the plugin 272 * @return bool mode handled? 273 */ 274 public function plugin($match, $state, $pos, $pluginname){ 275 $data = array($match); 276 /** @var SyntaxPlugin $plugin */ 277 $plugin = plugin_load('syntax',$pluginname); 278 if($plugin != null){ 279 $data = $plugin->handle($match, $state, $pos, $this); 280 } 281 if ($data !== false) { 282 $this->addPluginCall($pluginname,$data,$state,$pos,$match); 283 } 284 return true; 285 } 286 287 /** 288 * @param string $match matched syntax 289 * @param int $state a LEXER_STATE_* constant 290 * @param int $pos byte position in the original source file 291 * @return bool mode handled? 292 */ 293 public function base($match, $state, $pos) { 294 switch ( $state ) { 295 case DOKU_LEXER_UNMATCHED: 296 $this->addCall('cdata', array($match), $pos); 297 return true; 298 break; 299 } 300 return false; 301 } 302 303 /** 304 * @param string $match matched syntax 305 * @param int $state a LEXER_STATE_* constant 306 * @param int $pos byte position in the original source file 307 * @return bool mode handled? 308 */ 309 public function header($match, $state, $pos) { 310 // get level and title 311 $title = trim($match); 312 $level = 7 - strspn($title,'='); 313 if($level < 1) $level = 1; 314 $title = trim($title,'='); 315 $title = trim($title); 316 317 if ($this->status['section']) $this->addCall('section_close', array(), $pos); 318 319 $this->addCall('header', array($title, $level, $pos), $pos); 320 321 $this->addCall('section_open', array($level), $pos); 322 $this->status['section'] = true; 323 return true; 324 } 325 326 /** 327 * @param string $match matched syntax 328 * @param int $state a LEXER_STATE_* constant 329 * @param int $pos byte position in the original source file 330 * @return bool mode handled? 331 */ 332 public function notoc($match, $state, $pos) { 333 $this->addCall('notoc', array(), $pos); 334 return true; 335 } 336 337 /** 338 * @param string $match matched syntax 339 * @param int $state a LEXER_STATE_* constant 340 * @param int $pos byte position in the original source file 341 * @return bool mode handled? 342 */ 343 public function nocache($match, $state, $pos) { 344 $this->addCall('nocache', array(), $pos); 345 return true; 346 } 347 348 /** 349 * @param string $match matched syntax 350 * @param int $state a LEXER_STATE_* constant 351 * @param int $pos byte position in the original source file 352 * @return bool mode handled? 353 */ 354 public function linebreak($match, $state, $pos) { 355 $this->addCall('linebreak', array(), $pos); 356 return true; 357 } 358 359 /** 360 * @param string $match matched syntax 361 * @param int $state a LEXER_STATE_* constant 362 * @param int $pos byte position in the original source file 363 * @return bool mode handled? 364 */ 365 public function eol($match, $state, $pos) { 366 $this->addCall('eol', array(), $pos); 367 return true; 368 } 369 370 /** 371 * @param string $match matched syntax 372 * @param int $state a LEXER_STATE_* constant 373 * @param int $pos byte position in the original source file 374 * @return bool mode handled? 375 */ 376 public function hr($match, $state, $pos) { 377 $this->addCall('hr', array(), $pos); 378 return true; 379 } 380 381 /** 382 * @param string $match matched syntax 383 * @param int $state a LEXER_STATE_* constant 384 * @param int $pos byte position in the original source file 385 * @return bool mode handled? 386 */ 387 public function strong($match, $state, $pos) { 388 $this->nestingTag($match, $state, $pos, 'strong'); 389 return true; 390 } 391 392 /** 393 * @param string $match matched syntax 394 * @param int $state a LEXER_STATE_* constant 395 * @param int $pos byte position in the original source file 396 * @return bool mode handled? 397 */ 398 public function emphasis($match, $state, $pos) { 399 $this->nestingTag($match, $state, $pos, 'emphasis'); 400 return true; 401 } 402 403 /** 404 * @param string $match matched syntax 405 * @param int $state a LEXER_STATE_* constant 406 * @param int $pos byte position in the original source file 407 * @return bool mode handled? 408 */ 409 public function underline($match, $state, $pos) { 410 $this->nestingTag($match, $state, $pos, 'underline'); 411 return true; 412 } 413 414 /** 415 * @param string $match matched syntax 416 * @param int $state a LEXER_STATE_* constant 417 * @param int $pos byte position in the original source file 418 * @return bool mode handled? 419 */ 420 public function monospace($match, $state, $pos) { 421 $this->nestingTag($match, $state, $pos, 'monospace'); 422 return true; 423 } 424 425 /** 426 * @param string $match matched syntax 427 * @param int $state a LEXER_STATE_* constant 428 * @param int $pos byte position in the original source file 429 * @return bool mode handled? 430 */ 431 public function subscript($match, $state, $pos) { 432 $this->nestingTag($match, $state, $pos, 'subscript'); 433 return true; 434 } 435 436 /** 437 * @param string $match matched syntax 438 * @param int $state a LEXER_STATE_* constant 439 * @param int $pos byte position in the original source file 440 * @return bool mode handled? 441 */ 442 public function superscript($match, $state, $pos) { 443 $this->nestingTag($match, $state, $pos, 'superscript'); 444 return true; 445 } 446 447 /** 448 * @param string $match matched syntax 449 * @param int $state a LEXER_STATE_* constant 450 * @param int $pos byte position in the original source file 451 * @return bool mode handled? 452 */ 453 public function deleted($match, $state, $pos) { 454 $this->nestingTag($match, $state, $pos, 'deleted'); 455 return true; 456 } 457 458 /** 459 * @param string $match matched syntax 460 * @param int $state a LEXER_STATE_* constant 461 * @param int $pos byte position in the original source file 462 * @return bool mode handled? 463 */ 464 public function footnote($match, $state, $pos) { 465 if (!isset($this->_footnote)) $this->_footnote = false; 466 467 switch ( $state ) { 468 case DOKU_LEXER_ENTER: 469 // footnotes can not be nested - however due to limitations in lexer it can't be prevented 470 // we will still enter a new footnote mode, we just do nothing 471 if ($this->_footnote) { 472 $this->addCall('cdata', array($match), $pos); 473 break; 474 } 475 $this->_footnote = true; 476 477 $this->callWriter = new Nest($this->callWriter, 'footnote_close'); 478 $this->addCall('footnote_open', array(), $pos); 479 break; 480 case DOKU_LEXER_EXIT: 481 // check whether we have already exitted the footnote mode, can happen if the modes were nested 482 if (!$this->_footnote) { 483 $this->addCall('cdata', array($match), $pos); 484 break; 485 } 486 487 $this->_footnote = false; 488 $this->addCall('footnote_close', array(), $pos); 489 490 /** @var Nest $reWriter */ 491 $reWriter = $this->callWriter; 492 $this->callWriter = $reWriter->process(); 493 break; 494 case DOKU_LEXER_UNMATCHED: 495 $this->addCall('cdata', array($match), $pos); 496 break; 497 } 498 return true; 499 } 500 501 /** 502 * @param string $match matched syntax 503 * @param int $state a LEXER_STATE_* constant 504 * @param int $pos byte position in the original source file 505 * @return bool mode handled? 506 */ 507 public function listblock($match, $state, $pos) { 508 switch ( $state ) { 509 case DOKU_LEXER_ENTER: 510 $this->callWriter = new Lists($this->callWriter); 511 $this->addCall('list_open', array($match), $pos); 512 break; 513 case DOKU_LEXER_EXIT: 514 $this->addCall('list_close', array(), $pos); 515 /** @var Lists $reWriter */ 516 $reWriter = $this->callWriter; 517 $this->callWriter = $reWriter->process(); 518 break; 519 case DOKU_LEXER_MATCHED: 520 $this->addCall('list_item', array($match), $pos); 521 break; 522 case DOKU_LEXER_UNMATCHED: 523 $this->addCall('cdata', array($match), $pos); 524 break; 525 } 526 return true; 527 } 528 529 /** 530 * @param string $match matched syntax 531 * @param int $state a LEXER_STATE_* constant 532 * @param int $pos byte position in the original source file 533 * @return bool mode handled? 534 */ 535 public function unformatted($match, $state, $pos) { 536 if ( $state == DOKU_LEXER_UNMATCHED ) { 537 $this->addCall('unformatted', array($match), $pos); 538 } 539 return true; 540 } 541 542 /** 543 * @param string $match matched syntax 544 * @param int $state a LEXER_STATE_* constant 545 * @param int $pos byte position in the original source file 546 * @return bool mode handled? 547 */ 548 public function php($match, $state, $pos) { 549 if ( $state == DOKU_LEXER_UNMATCHED ) { 550 $this->addCall('php', array($match), $pos); 551 } 552 return true; 553 } 554 555 /** 556 * @param string $match matched syntax 557 * @param int $state a LEXER_STATE_* constant 558 * @param int $pos byte position in the original source file 559 * @return bool mode handled? 560 */ 561 public function phpblock($match, $state, $pos) { 562 if ( $state == DOKU_LEXER_UNMATCHED ) { 563 $this->addCall('phpblock', array($match), $pos); 564 } 565 return true; 566 } 567 568 /** 569 * @param string $match matched syntax 570 * @param int $state a LEXER_STATE_* constant 571 * @param int $pos byte position in the original source file 572 * @return bool mode handled? 573 */ 574 public function html($match, $state, $pos) { 575 if ( $state == DOKU_LEXER_UNMATCHED ) { 576 $this->addCall('html', array($match), $pos); 577 } 578 return true; 579 } 580 581 /** 582 * @param string $match matched syntax 583 * @param int $state a LEXER_STATE_* constant 584 * @param int $pos byte position in the original source file 585 * @return bool mode handled? 586 */ 587 public function htmlblock($match, $state, $pos) { 588 if ( $state == DOKU_LEXER_UNMATCHED ) { 589 $this->addCall('htmlblock', array($match), $pos); 590 } 591 return true; 592 } 593 594 /** 595 * @param string $match matched syntax 596 * @param int $state a LEXER_STATE_* constant 597 * @param int $pos byte position in the original source file 598 * @return bool mode handled? 599 */ 600 public function preformatted($match, $state, $pos) { 601 switch ( $state ) { 602 case DOKU_LEXER_ENTER: 603 $this->callWriter = new Preformatted($this->callWriter); 604 $this->addCall('preformatted_start', array(), $pos); 605 break; 606 case DOKU_LEXER_EXIT: 607 $this->addCall('preformatted_end', array(), $pos); 608 /** @var Preformatted $reWriter */ 609 $reWriter = $this->callWriter; 610 $this->callWriter = $reWriter->process(); 611 break; 612 case DOKU_LEXER_MATCHED: 613 $this->addCall('preformatted_newline', array(), $pos); 614 break; 615 case DOKU_LEXER_UNMATCHED: 616 $this->addCall('preformatted_content', array($match), $pos); 617 break; 618 } 619 620 return true; 621 } 622 623 /** 624 * @param string $match matched syntax 625 * @param int $state a LEXER_STATE_* constant 626 * @param int $pos byte position in the original source file 627 * @return bool mode handled? 628 */ 629 public function quote($match, $state, $pos) { 630 631 switch ( $state ) { 632 633 case DOKU_LEXER_ENTER: 634 $this->callWriter = new Quote($this->callWriter); 635 $this->addCall('quote_start', array($match), $pos); 636 break; 637 638 case DOKU_LEXER_EXIT: 639 $this->addCall('quote_end', array(), $pos); 640 /** @var Lists $reWriter */ 641 $reWriter = $this->callWriter; 642 $this->callWriter = $reWriter->process(); 643 break; 644 645 case DOKU_LEXER_MATCHED: 646 $this->addCall('quote_newline', array($match), $pos); 647 break; 648 649 case DOKU_LEXER_UNMATCHED: 650 $this->addCall('cdata', array($match), $pos); 651 break; 652 653 } 654 655 return true; 656 } 657 658 /** 659 * @param string $match matched syntax 660 * @param int $state a LEXER_STATE_* constant 661 * @param int $pos byte position in the original source file 662 * @return bool mode handled? 663 */ 664 public function file($match, $state, $pos) { 665 return $this->code($match, $state, $pos, 'file'); 666 } 667 668 /** 669 * @param string $match matched syntax 670 * @param int $state a LEXER_STATE_* constant 671 * @param int $pos byte position in the original source file 672 * @param string $type either 'code' or 'file' 673 * @return bool mode handled? 674 */ 675 public function code($match, $state, $pos, $type='code') { 676 if ( $state == DOKU_LEXER_UNMATCHED ) { 677 $matches = explode('>',$match,2); 678 // Cut out variable options enclosed in [] 679 preg_match('/\[.*\]/', $matches[0], $options); 680 if (!empty($options[0])) { 681 $matches[0] = str_replace($options[0], '', $matches[0]); 682 } 683 $param = preg_split('/\s+/', $matches[0], 2, PREG_SPLIT_NO_EMPTY); 684 while(count($param) < 2) array_push($param, null); 685 // We shortcut html here. 686 if ($param[0] == 'html') $param[0] = 'html4strict'; 687 if ($param[0] == '-') $param[0] = null; 688 array_unshift($param, $matches[1]); 689 if (!empty($options[0])) { 690 $param [] = $this->parse_highlight_options ($options[0]); 691 } 692 $this->addCall($type, $param, $pos); 693 } 694 return true; 695 } 696 697 /** 698 * @param string $match matched syntax 699 * @param int $state a LEXER_STATE_* constant 700 * @param int $pos byte position in the original source file 701 * @return bool mode handled? 702 */ 703 public function acronym($match, $state, $pos) { 704 $this->addCall('acronym', array($match), $pos); 705 return true; 706 } 707 708 /** 709 * @param string $match matched syntax 710 * @param int $state a LEXER_STATE_* constant 711 * @param int $pos byte position in the original source file 712 * @return bool mode handled? 713 */ 714 public function smiley($match, $state, $pos) { 715 $this->addCall('smiley', array($match), $pos); 716 return true; 717 } 718 719 /** 720 * @param string $match matched syntax 721 * @param int $state a LEXER_STATE_* constant 722 * @param int $pos byte position in the original source file 723 * @return bool mode handled? 724 */ 725 public function wordblock($match, $state, $pos) { 726 $this->addCall('wordblock', array($match), $pos); 727 return true; 728 } 729 730 /** 731 * @param string $match matched syntax 732 * @param int $state a LEXER_STATE_* constant 733 * @param int $pos byte position in the original source file 734 * @return bool mode handled? 735 */ 736 public function entity($match, $state, $pos) { 737 $this->addCall('entity', array($match), $pos); 738 return true; 739 } 740 741 /** 742 * @param string $match matched syntax 743 * @param int $state a LEXER_STATE_* constant 744 * @param int $pos byte position in the original source file 745 * @return bool mode handled? 746 */ 747 public function multiplyentity($match, $state, $pos) { 748 preg_match_all('/\d+/',$match,$matches); 749 $this->addCall('multiplyentity', array($matches[0][0], $matches[0][1]), $pos); 750 return true; 751 } 752 753 /** 754 * @param string $match matched syntax 755 * @param int $state a LEXER_STATE_* constant 756 * @param int $pos byte position in the original source file 757 * @return bool mode handled? 758 */ 759 public function singlequoteopening($match, $state, $pos) { 760 $this->addCall('singlequoteopening', array(), $pos); 761 return true; 762 } 763 764 /** 765 * @param string $match matched syntax 766 * @param int $state a LEXER_STATE_* constant 767 * @param int $pos byte position in the original source file 768 * @return bool mode handled? 769 */ 770 public function singlequoteclosing($match, $state, $pos) { 771 $this->addCall('singlequoteclosing', array(), $pos); 772 return true; 773 } 774 775 /** 776 * @param string $match matched syntax 777 * @param int $state a LEXER_STATE_* constant 778 * @param int $pos byte position in the original source file 779 * @return bool mode handled? 780 */ 781 public function apostrophe($match, $state, $pos) { 782 $this->addCall('apostrophe', array(), $pos); 783 return true; 784 } 785 786 /** 787 * @param string $match matched syntax 788 * @param int $state a LEXER_STATE_* constant 789 * @param int $pos byte position in the original source file 790 * @return bool mode handled? 791 */ 792 public function doublequoteopening($match, $state, $pos) { 793 $this->addCall('doublequoteopening', array(), $pos); 794 $this->status['doublequote']++; 795 return true; 796 } 797 798 /** 799 * @param string $match matched syntax 800 * @param int $state a LEXER_STATE_* constant 801 * @param int $pos byte position in the original source file 802 * @return bool mode handled? 803 */ 804 public function doublequoteclosing($match, $state, $pos) { 805 if ($this->status['doublequote'] <= 0) { 806 $this->doublequoteopening($match, $state, $pos); 807 } else { 808 $this->addCall('doublequoteclosing', array(), $pos); 809 $this->status['doublequote'] = max(0, --$this->status['doublequote']); 810 } 811 return true; 812 } 813 814 /** 815 * @param string $match matched syntax 816 * @param int $state a LEXER_STATE_* constant 817 * @param int $pos byte position in the original source file 818 * @return bool mode handled? 819 */ 820 public function camelcaselink($match, $state, $pos) { 821 $this->addCall('camelcaselink', array($match), $pos); 822 return true; 823 } 824 825 /** 826 * @param string $match matched syntax 827 * @param int $state a LEXER_STATE_* constant 828 * @param int $pos byte position in the original source file 829 * @return bool mode handled? 830 */ 831 public function internallink($match, $state, $pos) { 832 // Strip the opening and closing markup 833 $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match); 834 835 // Split title from URL 836 $link = explode('|',$link,2); 837 if ( !isset($link[1]) ) { 838 $link[1] = null; 839 } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) { 840 // If the title is an image, convert it to an array containing the image details 841 $link[1] = Doku_Handler_Parse_Media($link[1]); 842 } 843 $link[0] = trim($link[0]); 844 845 //decide which kind of link it is 846 847 if ( link_isinterwiki($link[0]) ) { 848 // Interwiki 849 $interwiki = explode('>',$link[0],2); 850 $this->addCall( 851 'interwikilink', 852 array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]), 853 $pos 854 ); 855 }elseif ( preg_match('/^\\\\\\\\[^\\\\]+?\\\\/u',$link[0]) ) { 856 // Windows Share 857 $this->addCall( 858 'windowssharelink', 859 array($link[0],$link[1]), 860 $pos 861 ); 862 }elseif ( preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ) { 863 // external link (accepts all protocols) 864 $this->addCall( 865 'externallink', 866 array($link[0],$link[1]), 867 $pos 868 ); 869 }elseif ( preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ) { 870 // E-Mail (pattern above is defined in inc/mail.php) 871 $this->addCall( 872 'emaillink', 873 array($link[0],$link[1]), 874 $pos 875 ); 876 }elseif ( preg_match('!^#.+!',$link[0]) ){ 877 // local link 878 $this->addCall( 879 'locallink', 880 array(substr($link[0],1),$link[1]), 881 $pos 882 ); 883 }else{ 884 // internal link 885 $this->addCall( 886 'internallink', 887 array($link[0],$link[1]), 888 $pos 889 ); 890 } 891 892 return true; 893 } 894 895 /** 896 * @param string $match matched syntax 897 * @param int $state a LEXER_STATE_* constant 898 * @param int $pos byte position in the original source file 899 * @return bool mode handled? 900 */ 901 public function filelink($match, $state, $pos) { 902 $this->addCall('filelink', array($match, null), $pos); 903 return true; 904 } 905 906 /** 907 * @param string $match matched syntax 908 * @param int $state a LEXER_STATE_* constant 909 * @param int $pos byte position in the original source file 910 * @return bool mode handled? 911 */ 912 public function windowssharelink($match, $state, $pos) { 913 $this->addCall('windowssharelink', array($match, null), $pos); 914 return true; 915 } 916 917 /** 918 * @param string $match matched syntax 919 * @param int $state a LEXER_STATE_* constant 920 * @param int $pos byte position in the original source file 921 * @return bool mode handled? 922 */ 923 public function media($match, $state, $pos) { 924 $p = Doku_Handler_Parse_Media($match); 925 926 $this->addCall( 927 $p['type'], 928 array($p['src'], $p['title'], $p['align'], $p['width'], 929 $p['height'], $p['cache'], $p['linking']), 930 $pos 931 ); 932 return true; 933 } 934 935 /** 936 * @param string $match matched syntax 937 * @param int $state a LEXER_STATE_* constant 938 * @param int $pos byte position in the original source file 939 * @return bool mode handled? 940 */ 941 public function rss($match, $state, $pos) { 942 $link = preg_replace(array('/^\{\{rss>/','/\}\}$/'),'',$match); 943 944 // get params 945 list($link,$params) = explode(' ',$link,2); 946 947 $p = array(); 948 if(preg_match('/\b(\d+)\b/',$params,$match)){ 949 $p['max'] = $match[1]; 950 }else{ 951 $p['max'] = 8; 952 } 953 $p['reverse'] = (preg_match('/rev/',$params)); 954 $p['author'] = (preg_match('/\b(by|author)/',$params)); 955 $p['date'] = (preg_match('/\b(date)/',$params)); 956 $p['details'] = (preg_match('/\b(desc|detail)/',$params)); 957 $p['nosort'] = (preg_match('/\b(nosort)\b/',$params)); 958 959 if (preg_match('/\b(\d+)([dhm])\b/',$params,$match)) { 960 $period = array('d' => 86400, 'h' => 3600, 'm' => 60); 961 $p['refresh'] = max(600,$match[1]*$period[$match[2]]); // n * period in seconds, minimum 10 minutes 962 } else { 963 $p['refresh'] = 14400; // default to 4 hours 964 } 965 966 $this->addCall('rss', array($link, $p), $pos); 967 return true; 968 } 969 970 /** 971 * @param string $match matched syntax 972 * @param int $state a LEXER_STATE_* constant 973 * @param int $pos byte position in the original source file 974 * @return bool mode handled? 975 */ 976 public function externallink($match, $state, $pos) { 977 $url = $match; 978 $title = null; 979 980 // add protocol on simple short URLs 981 if(substr($url,0,3) == 'ftp' && (substr($url,0,6) != 'ftp://')){ 982 $title = $url; 983 $url = 'ftp://'.$url; 984 } 985 if(substr($url,0,3) == 'www' && (substr($url,0,7) != 'http://')){ 986 $title = $url; 987 $url = 'http://'.$url; 988 } 989 990 $this->addCall('externallink', array($url, $title), $pos); 991 return true; 992 } 993 994 /** 995 * @param string $match matched syntax 996 * @param int $state a LEXER_STATE_* constant 997 * @param int $pos byte position in the original source file 998 * @return bool mode handled? 999 */ 1000 public function emaillink($match, $state, $pos) { 1001 $email = preg_replace(array('/^</','/>$/'),'',$match); 1002 $this->addCall('emaillink', array($email, null), $pos); 1003 return true; 1004 } 1005 1006 /** 1007 * @param string $match matched syntax 1008 * @param int $state a LEXER_STATE_* constant 1009 * @param int $pos byte position in the original source file 1010 * @return bool mode handled? 1011 */ 1012 public function table($match, $state, $pos) { 1013 switch ( $state ) { 1014 1015 case DOKU_LEXER_ENTER: 1016 1017 $this->callWriter = new Table($this->callWriter); 1018 1019 $this->addCall('table_start', array($pos + 1), $pos); 1020 if ( trim($match) == '^' ) { 1021 $this->addCall('tableheader', array(), $pos); 1022 } else { 1023 $this->addCall('tablecell', array(), $pos); 1024 } 1025 break; 1026 1027 case DOKU_LEXER_EXIT: 1028 $this->addCall('table_end', array($pos), $pos); 1029 /** @var Table $reWriter */ 1030 $reWriter = $this->callWriter; 1031 $this->callWriter = $reWriter->process(); 1032 break; 1033 1034 case DOKU_LEXER_UNMATCHED: 1035 if ( trim($match) != '' ) { 1036 $this->addCall('cdata', array($match), $pos); 1037 } 1038 break; 1039 1040 case DOKU_LEXER_MATCHED: 1041 if ( $match == ' ' ){ 1042 $this->addCall('cdata', array($match), $pos); 1043 } else if ( preg_match('/:::/',$match) ) { 1044 $this->addCall('rowspan', array($match), $pos); 1045 } else if ( preg_match('/\t+/',$match) ) { 1046 $this->addCall('table_align', array($match), $pos); 1047 } else if ( preg_match('/ {2,}/',$match) ) { 1048 $this->addCall('table_align', array($match), $pos); 1049 } else if ( $match == "\n|" ) { 1050 $this->addCall('table_row', array(), $pos); 1051 $this->addCall('tablecell', array(), $pos); 1052 } else if ( $match == "\n^" ) { 1053 $this->addCall('table_row', array(), $pos); 1054 $this->addCall('tableheader', array(), $pos); 1055 } else if ( $match == '|' ) { 1056 $this->addCall('tablecell', array(), $pos); 1057 } else if ( $match == '^' ) { 1058 $this->addCall('tableheader', array(), $pos); 1059 } 1060 break; 1061 } 1062 return true; 1063 } 1064 1065 // endregion modes 1066 } 1067 1068 //------------------------------------------------------------------------ 1069 function Doku_Handler_Parse_Media($match) { 1070 1071 // Strip the opening and closing markup 1072 $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match); 1073 1074 // Split title from URL 1075 $link = explode('|',$link,2); 1076 1077 // Check alignment 1078 $ralign = (bool)preg_match('/^ /',$link[0]); 1079 $lalign = (bool)preg_match('/ $/',$link[0]); 1080 1081 // Logic = what's that ;)... 1082 if ( $lalign & $ralign ) { 1083 $align = 'center'; 1084 } else if ( $ralign ) { 1085 $align = 'right'; 1086 } else if ( $lalign ) { 1087 $align = 'left'; 1088 } else { 1089 $align = null; 1090 } 1091 1092 // The title... 1093 if ( !isset($link[1]) ) { 1094 $link[1] = null; 1095 } 1096 1097 //remove aligning spaces 1098 $link[0] = trim($link[0]); 1099 1100 //split into src and parameters (using the very last questionmark) 1101 $pos = strrpos($link[0], '?'); 1102 if($pos !== false){ 1103 $src = substr($link[0],0,$pos); 1104 $param = substr($link[0],$pos+1); 1105 }else{ 1106 $src = $link[0]; 1107 $param = ''; 1108 } 1109 1110 //parse width and height 1111 if(preg_match('#(\d+)(x(\d+))?#i',$param,$size)){ 1112 !empty($size[1]) ? $w = $size[1] : $w = null; 1113 !empty($size[3]) ? $h = $size[3] : $h = null; 1114 } else { 1115 $w = null; 1116 $h = null; 1117 } 1118 1119 //get linking command 1120 if(preg_match('/nolink/i',$param)){ 1121 $linking = 'nolink'; 1122 }else if(preg_match('/direct/i',$param)){ 1123 $linking = 'direct'; 1124 }else if(preg_match('/linkonly/i',$param)){ 1125 $linking = 'linkonly'; 1126 }else{ 1127 $linking = 'details'; 1128 } 1129 1130 //get caching command 1131 if (preg_match('/(nocache|recache)/i',$param,$cachemode)){ 1132 $cache = $cachemode[1]; 1133 }else{ 1134 $cache = 'cache'; 1135 } 1136 1137 // Check whether this is a local or remote image or interwiki 1138 if (media_isexternal($src) || link_isinterwiki($src)){ 1139 $call = 'externalmedia'; 1140 } else { 1141 $call = 'internalmedia'; 1142 } 1143 1144 $params = array( 1145 'type'=>$call, 1146 'src'=>$src, 1147 'title'=>$link[1], 1148 'align'=>$align, 1149 'width'=>$w, 1150 'height'=>$h, 1151 'cache'=>$cache, 1152 'linking'=>$linking, 1153 ); 1154 1155 return $params; 1156 } 1157
title
Description
Body
title
Description
Body
title
Description
Body
title
Body