[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/parser/ -> handler.php (source)

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