Инструменты пользователя

Инструменты сайта


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