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