diff options
Diffstat (limited to 'platform/www/inc/Parsing')
49 files changed, 3042 insertions, 0 deletions
diff --git a/platform/www/inc/Parsing/Handler/AbstractRewriter.php b/platform/www/inc/Parsing/Handler/AbstractRewriter.php new file mode 100644 index 0000000..d9becbf --- /dev/null +++ b/platform/www/inc/Parsing/Handler/AbstractRewriter.php @@ -0,0 +1,39 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +/** + * Basic implementation of the rewriter interface to be specialized by children + */ +abstract class AbstractRewriter implements ReWriterInterface +{ + /** @var CallWriterInterface original CallWriter */ + protected $callWriter; + + /** @var array[] list of calls */ + public $calls = array(); + + /** @inheritdoc */ + public function __construct(CallWriterInterface $callWriter) + { + $this->callWriter = $callWriter; + } + + /** @inheritdoc */ + public function writeCall($call) + { + $this->calls[] = $call; + } + + /** * @inheritdoc */ + public function writeCalls($calls) + { + $this->calls = array_merge($this->calls, $calls); + } + + /** @inheritDoc */ + public function getCallWriter() + { + return $this->callWriter; + } +} diff --git a/platform/www/inc/Parsing/Handler/Block.php b/platform/www/inc/Parsing/Handler/Block.php new file mode 100644 index 0000000..4cfa686 --- /dev/null +++ b/platform/www/inc/Parsing/Handler/Block.php @@ -0,0 +1,211 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +/** + * Handler for paragraphs + * + * @author Harry Fuecks <hfuecks@gmail.com> + */ +class Block +{ + protected $calls = array(); + protected $skipEol = false; + protected $inParagraph = false; + + // Blocks these should not be inside paragraphs + protected $blockOpen = array( + 'header', + 'listu_open','listo_open','listitem_open','listcontent_open', + 'table_open','tablerow_open','tablecell_open','tableheader_open','tablethead_open', + 'quote_open', + 'code','file','hr','preformatted','rss', + 'htmlblock','phpblock', + 'footnote_open', + ); + + protected $blockClose = array( + 'header', + 'listu_close','listo_close','listitem_close','listcontent_close', + 'table_close','tablerow_close','tablecell_close','tableheader_close','tablethead_close', + 'quote_close', + 'code','file','hr','preformatted','rss', + 'htmlblock','phpblock', + 'footnote_close', + ); + + // Stacks can contain paragraphs + protected $stackOpen = array( + 'section_open', + ); + + protected $stackClose = array( + 'section_close', + ); + + + /** + * Constructor. Adds loaded syntax plugins to the block and stack + * arrays + * + * @author Andreas Gohr <andi@splitbrain.org> + */ + public function __construct() + { + global $DOKU_PLUGINS; + //check if syntax plugins were loaded + if (empty($DOKU_PLUGINS['syntax'])) return; + foreach ($DOKU_PLUGINS['syntax'] as $n => $p) { + $ptype = $p->getPType(); + if ($ptype == 'block') { + $this->blockOpen[] = 'plugin_'.$n; + $this->blockClose[] = 'plugin_'.$n; + } elseif ($ptype == 'stack') { + $this->stackOpen[] = 'plugin_'.$n; + $this->stackClose[] = 'plugin_'.$n; + } + } + } + + protected function openParagraph($pos) + { + if ($this->inParagraph) return; + $this->calls[] = array('p_open',array(), $pos); + $this->inParagraph = true; + $this->skipEol = true; + } + + /** + * Close a paragraph if needed + * + * This function makes sure there are no empty paragraphs on the stack + * + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param string|integer $pos + */ + protected function closeParagraph($pos) + { + if (!$this->inParagraph) return; + // look back if there was any content - we don't want empty paragraphs + $content = ''; + $ccount = count($this->calls); + for ($i=$ccount-1; $i>=0; $i--) { + if ($this->calls[$i][0] == 'p_open') { + break; + } elseif ($this->calls[$i][0] == 'cdata') { + $content .= $this->calls[$i][1][0]; + } else { + $content = 'found markup'; + break; + } + } + + if (trim($content)=='') { + //remove the whole paragraph + //array_splice($this->calls,$i); // <- this is much slower than the loop below + for ($x=$ccount; $x>$i; + $x--) array_pop($this->calls); + } else { + // remove ending linebreaks in the paragraph + $i=count($this->calls)-1; + if ($this->calls[$i][0] == 'cdata') $this->calls[$i][1][0] = rtrim($this->calls[$i][1][0], "\n"); + $this->calls[] = array('p_close',array(), $pos); + } + + $this->inParagraph = false; + $this->skipEol = true; + } + + protected function addCall($call) + { + $key = count($this->calls); + if ($key and ($call[0] == 'cdata') and ($this->calls[$key-1][0] == 'cdata')) { + $this->calls[$key-1][1][0] .= $call[1][0]; + } else { + $this->calls[] = $call; + } + } + + // simple version of addCall, without checking cdata + protected function storeCall($call) + { + $this->calls[] = $call; + } + + /** + * Processes the whole instruction stack to open and close paragraphs + * + * @author Harry Fuecks <hfuecks@gmail.com> + * @author Andreas Gohr <andi@splitbrain.org> + * + * @param array $calls + * + * @return array + */ + public function process($calls) + { + // open first paragraph + $this->openParagraph(0); + foreach ($calls as $key => $call) { + $cname = $call[0]; + if ($cname == 'plugin') { + $cname='plugin_'.$call[1][0]; + $plugin = true; + $plugin_open = (($call[1][2] == DOKU_LEXER_ENTER) || ($call[1][2] == DOKU_LEXER_SPECIAL)); + $plugin_close = (($call[1][2] == DOKU_LEXER_EXIT) || ($call[1][2] == DOKU_LEXER_SPECIAL)); + } else { + $plugin = false; + } + /* stack */ + if (in_array($cname, $this->stackClose) && (!$plugin || $plugin_close)) { + $this->closeParagraph($call[2]); + $this->storeCall($call); + $this->openParagraph($call[2]); + continue; + } + if (in_array($cname, $this->stackOpen) && (!$plugin || $plugin_open)) { + $this->closeParagraph($call[2]); + $this->storeCall($call); + $this->openParagraph($call[2]); + continue; + } + /* block */ + // If it's a substition it opens and closes at the same call. + // To make sure next paragraph is correctly started, let close go first. + if (in_array($cname, $this->blockClose) && (!$plugin || $plugin_close)) { + $this->closeParagraph($call[2]); + $this->storeCall($call); + $this->openParagraph($call[2]); + continue; + } + if (in_array($cname, $this->blockOpen) && (!$plugin || $plugin_open)) { + $this->closeParagraph($call[2]); + $this->storeCall($call); + continue; + } + /* eol */ + if ($cname == 'eol') { + // Check this isn't an eol instruction to skip... + if (!$this->skipEol) { + // Next is EOL => double eol => mark as paragraph + if (isset($calls[$key+1]) && $calls[$key+1][0] == 'eol') { + $this->closeParagraph($call[2]); + $this->openParagraph($call[2]); + } else { + //if this is just a single eol make a space from it + $this->addCall(array('cdata',array("\n"), $call[2])); + } + } + continue; + } + /* normal */ + $this->addCall($call); + $this->skipEol = false; + } + // close last paragraph + $call = end($this->calls); + $this->closeParagraph($call[2]); + return $this->calls; + } +} diff --git a/platform/www/inc/Parsing/Handler/CallWriter.php b/platform/www/inc/Parsing/Handler/CallWriter.php new file mode 100644 index 0000000..2457143 --- /dev/null +++ b/platform/www/inc/Parsing/Handler/CallWriter.php @@ -0,0 +1,40 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +class CallWriter implements CallWriterInterface +{ + + /** @var \Doku_Handler $Handler */ + protected $Handler; + + /** + * @param \Doku_Handler $Handler + */ + public function __construct(\Doku_Handler $Handler) + { + $this->Handler = $Handler; + } + + /** @inheritdoc */ + public function writeCall($call) + { + $this->Handler->calls[] = $call; + } + + /** @inheritdoc */ + public function writeCalls($calls) + { + $this->Handler->calls = array_merge($this->Handler->calls, $calls); + } + + /** + * @inheritdoc + * function is required, but since this call writer is first/highest in + * the chain it is not required to do anything + */ + public function finalise() + { + unset($this->Handler); + } +} diff --git a/platform/www/inc/Parsing/Handler/CallWriterInterface.php b/platform/www/inc/Parsing/Handler/CallWriterInterface.php new file mode 100644 index 0000000..ffc2468 --- /dev/null +++ b/platform/www/inc/Parsing/Handler/CallWriterInterface.php @@ -0,0 +1,30 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +interface CallWriterInterface +{ + /** + * Add a call to our call list + * + * @param array $call the call to be added + */ + public function writeCall($call); + + /** + * Append a list of calls to our call list + * + * @param array[] $calls list of calls to be appended + */ + public function writeCalls($calls); + + /** + * Explicit request to finish up and clean up NOW! + * (probably because document end has been reached) + * + * If part of a CallWriter chain, call finalise on + * the original call writer + * + */ + public function finalise(); +} diff --git a/platform/www/inc/Parsing/Handler/Lists.php b/platform/www/inc/Parsing/Handler/Lists.php new file mode 100644 index 0000000..282ddfb --- /dev/null +++ b/platform/www/inc/Parsing/Handler/Lists.php @@ -0,0 +1,186 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +class Lists extends AbstractRewriter +{ + protected $listCalls = array(); + protected $listStack = array(); + + protected $initialDepth = 0; + + const NODE = 1; + + /** @inheritdoc */ + public function finalise() + { + $last_call = end($this->calls); + $this->writeCall(array('list_close',array(), $last_call[2])); + + $this->process(); + $this->callWriter->finalise(); + unset($this->callWriter); + } + + /** @inheritdoc */ + public function process() + { + + foreach ($this->calls as $call) { + switch ($call[0]) { + case 'list_item': + $this->listOpen($call); + break; + case 'list_open': + $this->listStart($call); + break; + case 'list_close': + $this->listEnd($call); + break; + default: + $this->listContent($call); + break; + } + } + + $this->callWriter->writeCalls($this->listCalls); + return $this->callWriter; + } + + protected function listStart($call) + { + $depth = $this->interpretSyntax($call[1][0], $listType); + + $this->initialDepth = $depth; + // array(list type, current depth, index of current listitem_open) + $this->listStack[] = array($listType, $depth, 1); + + $this->listCalls[] = array('list'.$listType.'_open',array(),$call[2]); + $this->listCalls[] = array('listitem_open',array(1),$call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + } + + + protected function listEnd($call) + { + $closeContent = true; + + while ($list = array_pop($this->listStack)) { + if ($closeContent) { + $this->listCalls[] = array('listcontent_close',array(),$call[2]); + $closeContent = false; + } + $this->listCalls[] = array('listitem_close',array(),$call[2]); + $this->listCalls[] = array('list'.$list[0].'_close', array(), $call[2]); + } + } + + protected function listOpen($call) + { + $depth = $this->interpretSyntax($call[1][0], $listType); + $end = end($this->listStack); + $key = key($this->listStack); + + // Not allowed to be shallower than initialDepth + if ($depth < $this->initialDepth) { + $depth = $this->initialDepth; + } + + if ($depth == $end[1]) { + // Just another item in the list... + if ($listType == $end[0]) { + $this->listCalls[] = array('listcontent_close',array(),$call[2]); + $this->listCalls[] = array('listitem_close',array(),$call[2]); + $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + + // new list item, update list stack's index into current listitem_open + $this->listStack[$key][2] = count($this->listCalls) - 2; + + // Switched list type... + } else { + $this->listCalls[] = array('listcontent_close',array(),$call[2]); + $this->listCalls[] = array('listitem_close',array(),$call[2]); + $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]); + $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); + $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + + array_pop($this->listStack); + $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); + } + } elseif ($depth > $end[1]) { // Getting deeper... + $this->listCalls[] = array('listcontent_close',array(),$call[2]); + $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); + $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + + // set the node/leaf state of this item's parent listitem_open to NODE + $this->listCalls[$this->listStack[$key][2]][1][1] = self::NODE; + + $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); + } else { // Getting shallower ( $depth < $end[1] ) + $this->listCalls[] = array('listcontent_close',array(),$call[2]); + $this->listCalls[] = array('listitem_close',array(),$call[2]); + $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]); + + // Throw away the end - done + array_pop($this->listStack); + + while (1) { + $end = end($this->listStack); + $key = key($this->listStack); + + if ($end[1] <= $depth) { + // Normalize depths + $depth = $end[1]; + + $this->listCalls[] = array('listitem_close',array(),$call[2]); + + if ($end[0] == $listType) { + $this->listCalls[] = array('listitem_open',array($depth-1),$call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + + // new list item, update list stack's index into current listitem_open + $this->listStack[$key][2] = count($this->listCalls) - 2; + } else { + // Switching list type... + $this->listCalls[] = array('list'.$end[0].'_close', array(), $call[2]); + $this->listCalls[] = array('list'.$listType.'_open', array(), $call[2]); + $this->listCalls[] = array('listitem_open', array($depth-1), $call[2]); + $this->listCalls[] = array('listcontent_open',array(),$call[2]); + + array_pop($this->listStack); + $this->listStack[] = array($listType, $depth, count($this->listCalls) - 2); + } + + break; + + // Haven't dropped down far enough yet.... ( $end[1] > $depth ) + } else { + $this->listCalls[] = array('listitem_close',array(),$call[2]); + $this->listCalls[] = array('list'.$end[0].'_close',array(),$call[2]); + + array_pop($this->listStack); + } + } + } + } + + protected function listContent($call) + { + $this->listCalls[] = $call; + } + + protected function interpretSyntax($match, & $type) + { + if (substr($match, -1) == '*') { + $type = 'u'; + } else { + $type = 'o'; + } + // Is the +1 needed? It used to be count(explode(...)) + // but I don't think the number is seen outside this handler + return substr_count(str_replace("\t", ' ', $match), ' ') + 1; + } +} diff --git a/platform/www/inc/Parsing/Handler/Nest.php b/platform/www/inc/Parsing/Handler/Nest.php new file mode 100644 index 0000000..98d2134 --- /dev/null +++ b/platform/www/inc/Parsing/Handler/Nest.php @@ -0,0 +1,82 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +/** + * Generic call writer class to handle nesting of rendering instructions + * within a render instruction. Also see nest() method of renderer base class + * + * @author Chris Smith <chris@jalakai.co.uk> + */ +class Nest extends AbstractRewriter +{ + protected $closingInstruction; + + /** + * @inheritdoc + * + * @param CallWriterInterface $CallWriter the parser's current call writer, i.e. the one above us in the chain + * @param string $close closing instruction name, this is required to properly terminate the + * syntax mode if the document ends without a closing pattern + */ + public function __construct(CallWriterInterface $CallWriter, $close = "nest_close") + { + parent::__construct($CallWriter); + $this->closingInstruction = $close; + } + + /** @inheritdoc */ + public function writeCall($call) + { + $this->calls[] = $call; + } + + /** @inheritdoc */ + public function writeCalls($calls) + { + $this->calls = array_merge($this->calls, $calls); + } + + /** @inheritdoc */ + public function finalise() + { + $last_call = end($this->calls); + $this->writeCall(array($this->closingInstruction,array(), $last_call[2])); + + $this->process(); + $this->callWriter->finalise(); + unset($this->callWriter); + } + + /** @inheritdoc */ + public function process() + { + // merge consecutive cdata + $unmerged_calls = $this->calls; + $this->calls = array(); + + foreach ($unmerged_calls as $call) $this->addCall($call); + + $first_call = reset($this->calls); + $this->callWriter->writeCall(array("nest", array($this->calls), $first_call[2])); + + return $this->callWriter; + } + + /** + * @param array $call + */ + protected function addCall($call) + { + $key = count($this->calls); + if ($key and ($call[0] == 'cdata') and ($this->calls[$key-1][0] == 'cdata')) { + $this->calls[$key-1][1][0] .= $call[1][0]; + } elseif ($call[0] == 'eol') { + // do nothing (eol shouldn't be allowed, to counter preformatted fix in #1652 & #1699) + } else { + $this->calls[] = $call; + } + } + + +} diff --git a/platform/www/inc/Parsing/Handler/Preformatted.php b/platform/www/inc/Parsing/Handler/Preformatted.php new file mode 100644 index 0000000..41beb66 --- /dev/null +++ b/platform/www/inc/Parsing/Handler/Preformatted.php @@ -0,0 +1,49 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +class Preformatted extends AbstractRewriter +{ + + protected $pos; + protected $text =''; + + /** @inheritdoc */ + public function finalise() + { + $last_call = end($this->calls); + $this->writeCall(array('preformatted_end',array(), $last_call[2])); + + $this->process(); + $this->callWriter->finalise(); + unset($this->callWriter); + } + + /** @inheritdoc */ + public function process() + { + foreach ($this->calls as $call) { + switch ($call[0]) { + case 'preformatted_start': + $this->pos = $call[2]; + break; + case 'preformatted_newline': + $this->text .= "\n"; + break; + case 'preformatted_content': + $this->text .= $call[1][0]; + break; + case 'preformatted_end': + if (trim($this->text)) { + $this->callWriter->writeCall(array('preformatted', array($this->text), $this->pos)); + } + // see FS#1699 & FS#1652, add 'eol' instructions to ensure proper triggering of following p_open + $this->callWriter->writeCall(array('eol', array(), $this->pos)); + $this->callWriter->writeCall(array('eol', array(), $this->pos)); + break; + } + } + + return $this->callWriter; + } +} diff --git a/platform/www/inc/Parsing/Handler/Quote.php b/platform/www/inc/Parsing/Handler/Quote.php new file mode 100644 index 0000000..74861b1 --- /dev/null +++ b/platform/www/inc/Parsing/Handler/Quote.php @@ -0,0 +1,86 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +class Quote extends AbstractRewriter +{ + protected $quoteCalls = array(); + + /** @inheritdoc */ + public function finalise() + { + $last_call = end($this->calls); + $this->writeCall(array('quote_end',array(), $last_call[2])); + + $this->process(); + $this->callWriter->finalise(); + unset($this->callWriter); + } + + /** @inheritdoc */ + public function process() + { + + $quoteDepth = 1; + + foreach ($this->calls as $call) { + switch ($call[0]) { + + /** @noinspection PhpMissingBreakStatementInspection */ + case 'quote_start': + $this->quoteCalls[] = array('quote_open',array(),$call[2]); + // fallthrough + case 'quote_newline': + $quoteLength = $this->getDepth($call[1][0]); + + if ($quoteLength > $quoteDepth) { + $quoteDiff = $quoteLength - $quoteDepth; + for ($i = 1; $i <= $quoteDiff; $i++) { + $this->quoteCalls[] = array('quote_open',array(),$call[2]); + } + } elseif ($quoteLength < $quoteDepth) { + $quoteDiff = $quoteDepth - $quoteLength; + for ($i = 1; $i <= $quoteDiff; $i++) { + $this->quoteCalls[] = array('quote_close',array(),$call[2]); + } + } else { + if ($call[0] != 'quote_start') $this->quoteCalls[] = array('linebreak',array(),$call[2]); + } + + $quoteDepth = $quoteLength; + + break; + + case 'quote_end': + if ($quoteDepth > 1) { + $quoteDiff = $quoteDepth - 1; + for ($i = 1; $i <= $quoteDiff; $i++) { + $this->quoteCalls[] = array('quote_close',array(),$call[2]); + } + } + + $this->quoteCalls[] = array('quote_close',array(),$call[2]); + + $this->callWriter->writeCalls($this->quoteCalls); + break; + + default: + $this->quoteCalls[] = $call; + break; + } + } + + return $this->callWriter; + } + + /** + * @param string $marker + * @return int + */ + protected function getDepth($marker) + { + preg_match('/>{1,}/', $marker, $matches); + $quoteLength = strlen($matches[0]); + return $quoteLength; + } +} diff --git a/platform/www/inc/Parsing/Handler/ReWriterInterface.php b/platform/www/inc/Parsing/Handler/ReWriterInterface.php new file mode 100644 index 0000000..2fa7b25 --- /dev/null +++ b/platform/www/inc/Parsing/Handler/ReWriterInterface.php @@ -0,0 +1,37 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +/** + * A ReWriter takes over from the orignal call writer and handles all new calls itself until + * the process method is called and control is given back to the original writer. + * + * @property array[] $calls The list of current calls + */ +interface ReWriterInterface extends CallWriterInterface +{ + /** + * ReWriterInterface constructor. + * + * This rewriter will be registered as the new call writer in the Handler. + * The original is passed as parameter + * + * @param CallWriterInterface $callWriter the original callwriter + */ + public function __construct(CallWriterInterface $callWriter); + + /** + * Process any calls that have been added and add them to the + * original call writer + * + * @return CallWriterInterface the orignal call writer + */ + public function process(); + + /** + * Accessor for this rewriter's original CallWriter + * + * @return CallWriterInterface + */ + public function getCallWriter(); +} diff --git a/platform/www/inc/Parsing/Handler/Table.php b/platform/www/inc/Parsing/Handler/Table.php new file mode 100644 index 0000000..35ff5a8 --- /dev/null +++ b/platform/www/inc/Parsing/Handler/Table.php @@ -0,0 +1,320 @@ +<?php + +namespace dokuwiki\Parsing\Handler; + +class Table extends AbstractRewriter +{ + + protected $tableCalls = array(); + protected $maxCols = 0; + protected $maxRows = 1; + protected $currentCols = 0; + protected $firstCell = false; + protected $lastCellType = 'tablecell'; + protected $inTableHead = true; + protected $currentRow = array('tableheader' => 0, 'tablecell' => 0); + protected $countTableHeadRows = 0; + + /** @inheritdoc */ + public function finalise() + { + $last_call = end($this->calls); + $this->writeCall(array('table_end',array(), $last_call[2])); + + $this->process(); + $this->callWriter->finalise(); + unset($this->callWriter); + } + + /** @inheritdoc */ + public function process() + { + foreach ($this->calls as $call) { + switch ($call[0]) { + case 'table_start': + $this->tableStart($call); + break; + case 'table_row': + $this->tableRowClose($call); + $this->tableRowOpen(array('tablerow_open',$call[1],$call[2])); + break; + case 'tableheader': + case 'tablecell': + $this->tableCell($call); + break; + case 'table_end': + $this->tableRowClose($call); + $this->tableEnd($call); + break; + default: + $this->tableDefault($call); + break; + } + } + $this->callWriter->writeCalls($this->tableCalls); + + return $this->callWriter; + } + + protected function tableStart($call) + { + $this->tableCalls[] = array('table_open',$call[1],$call[2]); + $this->tableCalls[] = array('tablerow_open',array(),$call[2]); + $this->firstCell = true; + } + + protected function tableEnd($call) + { + $this->tableCalls[] = array('table_close',$call[1],$call[2]); + $this->finalizeTable(); + } + + protected function tableRowOpen($call) + { + $this->tableCalls[] = $call; + $this->currentCols = 0; + $this->firstCell = true; + $this->lastCellType = 'tablecell'; + $this->maxRows++; + if ($this->inTableHead) { + $this->currentRow = array('tablecell' => 0, 'tableheader' => 0); + } + } + + protected function tableRowClose($call) + { + if ($this->inTableHead && ($this->inTableHead = $this->isTableHeadRow())) { + $this->countTableHeadRows++; + } + // Strip off final cell opening and anything after it + while ($discard = array_pop($this->tableCalls)) { + if ($discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') { + break; + } + if (!empty($this->currentRow[$discard[0]])) { + $this->currentRow[$discard[0]]--; + } + } + $this->tableCalls[] = array('tablerow_close', array(), $call[2]); + + if ($this->currentCols > $this->maxCols) { + $this->maxCols = $this->currentCols; + } + } + + protected function isTableHeadRow() + { + $td = $this->currentRow['tablecell']; + $th = $this->currentRow['tableheader']; + + if (!$th || $td > 2) return false; + if (2*$td > $th) return false; + + return true; + } + + protected function tableCell($call) + { + if ($this->inTableHead) { + $this->currentRow[$call[0]]++; + } + if (!$this->firstCell) { + // Increase the span + $lastCall = end($this->tableCalls); + + // A cell call which follows an open cell means an empty cell so span + if ($lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open') { + $this->tableCalls[] = array('colspan',array(),$call[2]); + } + + $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]); + $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]); + $this->lastCellType = $call[0]; + } else { + $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]); + $this->lastCellType = $call[0]; + $this->firstCell = false; + } + + $this->currentCols++; + } + + protected function tableDefault($call) + { + $this->tableCalls[] = $call; + } + + protected function finalizeTable() + { + + // Add the max cols and rows to the table opening + if ($this->tableCalls[0][0] == 'table_open') { + // Adjust to num cols not num col delimeters + $this->tableCalls[0][1][] = $this->maxCols - 1; + $this->tableCalls[0][1][] = $this->maxRows; + $this->tableCalls[0][1][] = array_shift($this->tableCalls[0][1]); + } else { + trigger_error('First element in table call list is not table_open'); + } + + $lastRow = 0; + $lastCell = 0; + $cellKey = array(); + $toDelete = array(); + + // if still in tableheader, then there can be no table header + // as all rows can't be within <THEAD> + if ($this->inTableHead) { + $this->inTableHead = false; + $this->countTableHeadRows = 0; + } + + // Look for the colspan elements and increment the colspan on the + // previous non-empty opening cell. Once done, delete all the cells + // that contain colspans + for ($key = 0; $key < count($this->tableCalls); ++$key) { + $call = $this->tableCalls[$key]; + + switch ($call[0]) { + case 'table_open': + if ($this->countTableHeadRows) { + array_splice($this->tableCalls, $key+1, 0, array( + array('tablethead_open', array(), $call[2]))); + } + break; + + case 'tablerow_open': + $lastRow++; + $lastCell = 0; + break; + + case 'tablecell_open': + case 'tableheader_open': + $lastCell++; + $cellKey[$lastRow][$lastCell] = $key; + break; + + case 'table_align': + $prev = in_array($this->tableCalls[$key-1][0], array('tablecell_open', 'tableheader_open')); + $next = in_array($this->tableCalls[$key+1][0], array('tablecell_close', 'tableheader_close')); + // If the cell is empty, align left + if ($prev && $next) { + $this->tableCalls[$key-1][1][1] = 'left'; + + // If the previous element was a cell open, align right + } elseif ($prev) { + $this->tableCalls[$key-1][1][1] = 'right'; + + // If the next element is the close of an element, align either center or left + } elseif ($next) { + if ($this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] == 'right') { + $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'center'; + } else { + $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'left'; + } + } + + // Now convert the whitespace back to cdata + $this->tableCalls[$key][0] = 'cdata'; + break; + + case 'colspan': + $this->tableCalls[$key-1][1][0] = false; + + for ($i = $key-2; $i >= $cellKey[$lastRow][1]; $i--) { + if ($this->tableCalls[$i][0] == 'tablecell_open' || + $this->tableCalls[$i][0] == 'tableheader_open' + ) { + if (false !== $this->tableCalls[$i][1][0]) { + $this->tableCalls[$i][1][0]++; + break; + } + } + } + + $toDelete[] = $key-1; + $toDelete[] = $key; + $toDelete[] = $key+1; + break; + + case 'rowspan': + if ($this->tableCalls[$key-1][0] == 'cdata') { + // ignore rowspan if previous call was cdata (text mixed with :::) + // we don't have to check next call as that wont match regex + $this->tableCalls[$key][0] = 'cdata'; + } else { + $spanning_cell = null; + + // can't cross thead/tbody boundary + if (!$this->countTableHeadRows || ($lastRow-1 != $this->countTableHeadRows)) { + for ($i = $lastRow-1; $i > 0; $i--) { + if ($this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' || + $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open' + ) { + if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) { + $spanning_cell = $i; + break; + } + } + } + } + if (is_null($spanning_cell)) { + // No spanning cell found, so convert this cell to + // an empty one to avoid broken tables + $this->tableCalls[$key][0] = 'cdata'; + $this->tableCalls[$key][1][0] = ''; + break; + } + $this->tableCalls[$cellKey[$spanning_cell][$lastCell]][1][2]++; + + $this->tableCalls[$key-1][1][2] = false; + + $toDelete[] = $key-1; + $toDelete[] = $key; + $toDelete[] = $key+1; + } + break; + + case 'tablerow_close': + // Fix broken tables by adding missing cells + $moreCalls = array(); + while (++$lastCell < $this->maxCols) { + $moreCalls[] = array('tablecell_open', array(1, null, 1), $call[2]); + $moreCalls[] = array('cdata', array(''), $call[2]); + $moreCalls[] = array('tablecell_close', array(), $call[2]); + } + $moreCallsLength = count($moreCalls); + if ($moreCallsLength) { + array_splice($this->tableCalls, $key, 0, $moreCalls); + $key += $moreCallsLength; + } + + if ($this->countTableHeadRows == $lastRow) { + array_splice($this->tableCalls, $key+1, 0, array( + array('tablethead_close', array(), $call[2]))); + } + break; + } + } + + // condense cdata + $cnt = count($this->tableCalls); + for ($key = 0; $key < $cnt; $key++) { + if ($this->tableCalls[$key][0] == 'cdata') { + $ckey = $key; + $key++; + while ($this->tableCalls[$key][0] == 'cdata') { + $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0]; + $toDelete[] = $key; + $key++; + } + continue; + } + } + + foreach ($toDelete as $delete) { + unset($this->tableCalls[$delete]); + } + $this->tableCalls = array_values($this->tableCalls); + } +} diff --git a/platform/www/inc/Parsing/Lexer/Lexer.php b/platform/www/inc/Parsing/Lexer/Lexer.php new file mode 100644 index 0000000..edcd251 --- /dev/null +++ b/platform/www/inc/Parsing/Lexer/Lexer.php @@ -0,0 +1,349 @@ +<?php +/** + * Lexer adapted from Simple Test: http://sourceforge.net/projects/simpletest/ + * For an intro to the Lexer see: + * https://web.archive.org/web/20120125041816/http://www.phppatterns.com/docs/develop/simple_test_lexer_notes + * + * @author Marcus Baker http://www.lastcraft.com + */ + +namespace dokuwiki\Parsing\Lexer; + +/** + * Accepts text and breaks it into tokens. + * + * Some optimisation to make the sure the content is only scanned by the PHP regex + * parser once. Lexer modes must not start with leading underscores. + */ +class Lexer +{ + /** @var ParallelRegex[] */ + protected $regexes; + /** @var \Doku_Handler */ + protected $handler; + /** @var StateStack */ + protected $modeStack; + /** @var array mode "rewrites" */ + protected $mode_handlers; + /** @var bool case sensitive? */ + protected $case; + + /** + * Sets up the lexer in case insensitive matching by default. + * + * @param \Doku_Handler $handler Handling strategy by reference. + * @param string $start Starting handler. + * @param boolean $case True for case sensitive. + */ + public function __construct($handler, $start = "accept", $case = false) + { + $this->case = $case; + $this->regexes = array(); + $this->handler = $handler; + $this->modeStack = new StateStack($start); + $this->mode_handlers = array(); + } + + /** + * Adds a token search pattern for a particular parsing mode. + * + * The pattern does not change the current mode. + * + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + */ + public function addPattern($pattern, $mode = "accept") + { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern); + } + + /** + * Adds a pattern that will enter a new parsing mode. + * + * Useful for entering parenthesis, strings, tags, etc. + * + * @param string $pattern Perl style regex, but ( and ) lose the usual meaning. + * @param string $mode Should only apply this pattern when dealing with this type of input. + * @param string $new_mode Change parsing to this new nested mode. + */ + public function addEntryPattern($pattern, $mode, $new_mode) + { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, $new_mode); + } + + /** + * Adds a pattern that will exit the current mode and re-enter the previous one. + * + * @param string $pattern Perl style regex, but ( and ) lose the usual meaning. + * @param string $mode Mode to leave. + */ + public function addExitPattern($pattern, $mode) + { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, "__exit"); + } + + /** + * Adds a pattern that has a special mode. + * + * Acts as an entry and exit pattern in one go, effectively calling a special + * parser handler for this token only. + * + * @param string $pattern Perl style regex, but ( and ) lose the usual meaning. + * @param string $mode Should only apply this pattern when dealing with this type of input. + * @param string $special Use this mode for this one token. + */ + public function addSpecialPattern($pattern, $mode, $special) + { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, "_$special"); + } + + /** + * Adds a mapping from a mode to another handler. + * + * @param string $mode Mode to be remapped. + * @param string $handler New target handler. + */ + public function mapHandler($mode, $handler) + { + $this->mode_handlers[$mode] = $handler; + } + + /** + * Splits the page text into tokens. + * + * Will fail if the handlers report an error or if no content is consumed. If successful then each + * unparsed and parsed token invokes a call to the held listener. + * + * @param string $raw Raw HTML text. + * @return boolean True on success, else false. + */ + public function parse($raw) + { + if (! isset($this->handler)) { + return false; + } + $initialLength = strlen($raw); + $length = $initialLength; + $pos = 0; + while (is_array($parsed = $this->reduce($raw))) { + list($unmatched, $matched, $mode) = $parsed; + $currentLength = strlen($raw); + $matchPos = $initialLength - $currentLength - strlen($matched); + if (! $this->dispatchTokens($unmatched, $matched, $mode, $pos, $matchPos)) { + return false; + } + if ($currentLength == $length) { + return false; + } + $length = $currentLength; + $pos = $initialLength - $currentLength; + } + if (!$parsed) { + return false; + } + return $this->invokeHandler($raw, DOKU_LEXER_UNMATCHED, $pos); + } + + /** + * Gives plugins access to the mode stack + * + * @return StateStack + */ + public function getModeStack() + { + return $this->modeStack; + } + + /** + * Sends the matched token and any leading unmatched + * text to the parser changing the lexer to a new + * mode if one is listed. + * + * @param string $unmatched Unmatched leading portion. + * @param string $matched Actual token match. + * @param bool|string $mode Mode after match. A boolean false mode causes no change. + * @param int $initialPos + * @param int $matchPos Current byte index location in raw doc thats being parsed + * @return boolean False if there was any error from the parser. + */ + protected function dispatchTokens($unmatched, $matched, $mode, $initialPos, $matchPos) + { + if (! $this->invokeHandler($unmatched, DOKU_LEXER_UNMATCHED, $initialPos)) { + return false; + } + if ($this->isModeEnd($mode)) { + if (! $this->invokeHandler($matched, DOKU_LEXER_EXIT, $matchPos)) { + return false; + } + return $this->modeStack->leave(); + } + if ($this->isSpecialMode($mode)) { + $this->modeStack->enter($this->decodeSpecial($mode)); + if (! $this->invokeHandler($matched, DOKU_LEXER_SPECIAL, $matchPos)) { + return false; + } + return $this->modeStack->leave(); + } + if (is_string($mode)) { + $this->modeStack->enter($mode); + return $this->invokeHandler($matched, DOKU_LEXER_ENTER, $matchPos); + } + return $this->invokeHandler($matched, DOKU_LEXER_MATCHED, $matchPos); + } + + /** + * Tests to see if the new mode is actually to leave the current mode and pop an item from the matching + * mode stack. + * + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + */ + protected function isModeEnd($mode) + { + return ($mode === "__exit"); + } + + /** + * Test to see if the mode is one where this mode is entered for this token only and automatically + * leaves immediately afterwoods. + * + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + */ + protected function isSpecialMode($mode) + { + return (strncmp($mode, "_", 1) == 0); + } + + /** + * Strips the magic underscore marking single token modes. + * + * @param string $mode Mode to decode. + * @return string Underlying mode name. + */ + protected function decodeSpecial($mode) + { + return substr($mode, 1); + } + + /** + * Calls the parser method named after the current mode. + * + * Empty content will be ignored. The lexer has a parser handler for each mode in the lexer. + * + * @param string $content Text parsed. + * @param boolean $is_match Token is recognised rather + * than unparsed data. + * @param int $pos Current byte index location in raw doc + * thats being parsed + * @return bool + */ + protected function invokeHandler($content, $is_match, $pos) + { + if (($content === "") || ($content === false)) { + return true; + } + $handler = $this->modeStack->getCurrent(); + if (isset($this->mode_handlers[$handler])) { + $handler = $this->mode_handlers[$handler]; + } + + // modes starting with plugin_ are all handled by the same + // handler but with an additional parameter + if (substr($handler, 0, 7)=='plugin_') { + list($handler,$plugin) = explode('_', $handler, 2); + return $this->handler->$handler($content, $is_match, $pos, $plugin); + } + + return $this->handler->$handler($content, $is_match, $pos); + } + + /** + * Tries to match a chunk of text and if successful removes the recognised chunk and any leading + * unparsed data. Empty strings will not be matched. + * + * @param string $raw The subject to parse. This is the content that will be eaten. + * @return array|bool Three item list of unparsed content followed by the + * recognised token and finally the action the parser is to take. + * True if no match, false if there is a parsing error. + */ + protected function reduce(&$raw) + { + if (! isset($this->regexes[$this->modeStack->getCurrent()])) { + return false; + } + if ($raw === "") { + return true; + } + if ($action = $this->regexes[$this->modeStack->getCurrent()]->split($raw, $split)) { + list($unparsed, $match, $raw) = $split; + return array($unparsed, $match, $action); + } + return true; + } + + /** + * Escapes regex characters other than (, ) and / + * + * @param string $str + * @return string + */ + public static function escape($str) + { + $chars = array( + '/\\\\/', + '/\./', + '/\+/', + '/\*/', + '/\?/', + '/\[/', + '/\^/', + '/\]/', + '/\$/', + '/\{/', + '/\}/', + '/\=/', + '/\!/', + '/\</', + '/\>/', + '/\|/', + '/\:/' + ); + + $escaped = array( + '\\\\\\\\', + '\.', + '\+', + '\*', + '\?', + '\[', + '\^', + '\]', + '\$', + '\{', + '\}', + '\=', + '\!', + '\<', + '\>', + '\|', + '\:' + ); + return preg_replace($chars, $escaped, $str); + } +} diff --git a/platform/www/inc/Parsing/Lexer/ParallelRegex.php b/platform/www/inc/Parsing/Lexer/ParallelRegex.php new file mode 100644 index 0000000..96f61a1 --- /dev/null +++ b/platform/www/inc/Parsing/Lexer/ParallelRegex.php @@ -0,0 +1,203 @@ +<?php +/** + * Lexer adapted from Simple Test: http://sourceforge.net/projects/simpletest/ + * For an intro to the Lexer see: + * https://web.archive.org/web/20120125041816/http://www.phppatterns.com/docs/develop/simple_test_lexer_notes + * + * @author Marcus Baker http://www.lastcraft.com + */ + +namespace dokuwiki\Parsing\Lexer; + +/** + * Compounded regular expression. + * + * Any of the contained patterns could match and when one does it's label is returned. + */ +class ParallelRegex +{ + /** @var string[] patterns to match */ + protected $patterns; + /** @var string[] labels for above patterns */ + protected $labels; + /** @var string the compound regex matching all patterns */ + protected $regex; + /** @var bool case sensitive matching? */ + protected $case; + + /** + * Constructor. Starts with no patterns. + * + * @param boolean $case True for case sensitive, false + * for insensitive. + */ + public function __construct($case) + { + $this->case = $case; + $this->patterns = array(); + $this->labels = array(); + $this->regex = null; + } + + /** + * Adds a pattern with an optional label. + * + * @param mixed $pattern Perl style regex. Must be UTF-8 + * encoded. If its a string, the (, ) + * lose their meaning unless they + * form part of a lookahead or + * lookbehind assertation. + * @param bool|string $label Label of regex to be returned + * on a match. Label must be ASCII + */ + public function addPattern($pattern, $label = true) + { + $count = count($this->patterns); + $this->patterns[$count] = $pattern; + $this->labels[$count] = $label; + $this->regex = null; + } + + /** + * Attempts to match all patterns at once against a string. + * + * @param string $subject String to match against. + * @param string $match First matched portion of + * subject. + * @return bool|string False if no match found, label if label exists, true if not + */ + public function match($subject, &$match) + { + if (count($this->patterns) == 0) { + return false; + } + if (! preg_match($this->getCompoundedRegex(), $subject, $matches)) { + $match = ""; + return false; + } + + $match = $matches[0]; + $size = count($matches); + // FIXME this could be made faster by storing the labels as keys in a hashmap + for ($i = 1; $i < $size; $i++) { + if ($matches[$i] && isset($this->labels[$i - 1])) { + return $this->labels[$i - 1]; + } + } + return true; + } + + /** + * Attempts to split the string against all patterns at once + * + * @param string $subject String to match against. + * @param array $split The split result: array containing, pre-match, match & post-match strings + * @return boolean True on success. + * + * @author Christopher Smith <chris@jalakai.co.uk> + */ + public function split($subject, &$split) + { + if (count($this->patterns) == 0) { + return false; + } + + if (! preg_match($this->getCompoundedRegex(), $subject, $matches)) { + if (function_exists('preg_last_error')) { + $err = preg_last_error(); + switch ($err) { + case PREG_BACKTRACK_LIMIT_ERROR: + msg('A PCRE backtrack error occured. Try to increase the pcre.backtrack_limit in php.ini', -1); + break; + case PREG_RECURSION_LIMIT_ERROR: + msg('A PCRE recursion error occured. Try to increase the pcre.recursion_limit in php.ini', -1); + break; + case PREG_BAD_UTF8_ERROR: + msg('A PCRE UTF-8 error occured. This might be caused by a faulty plugin', -1); + break; + case PREG_INTERNAL_ERROR: + msg('A PCRE internal error occured. This might be caused by a faulty plugin', -1); + break; + } + } + + $split = array($subject, "", ""); + return false; + } + + $idx = count($matches)-2; + list($pre, $post) = preg_split($this->patterns[$idx].$this->getPerlMatchingFlags(), $subject, 2); + $split = array($pre, $matches[0], $post); + + return isset($this->labels[$idx]) ? $this->labels[$idx] : true; + } + + /** + * Compounds the patterns into a single + * regular expression separated with the + * "or" operator. Caches the regex. + * Will automatically escape (, ) and / tokens. + * + * @return null|string + */ + protected function getCompoundedRegex() + { + if ($this->regex == null) { + $cnt = count($this->patterns); + for ($i = 0; $i < $cnt; $i++) { + /* + * decompose the input pattern into "(", "(?", ")", + * "[...]", "[]..]", "[^]..]", "[...[:...:]..]", "\x"... + * elements. + */ + preg_match_all('/\\\\.|' . + '\(\?|' . + '[()]|' . + '\[\^?\]?(?:\\\\.|\[:[^]]*:\]|[^]\\\\])*\]|' . + '[^[()\\\\]+/', $this->patterns[$i], $elts); + + $pattern = ""; + $level = 0; + + foreach ($elts[0] as $elt) { + /* + * for "(", ")" remember the nesting level, add "\" + * only to the non-"(?" ones. + */ + + switch ($elt) { + case '(': + $pattern .= '\('; + break; + case ')': + if ($level > 0) + $level--; /* closing (? */ + else $pattern .= '\\'; + $pattern .= ')'; + break; + case '(?': + $level++; + $pattern .= '(?'; + break; + default: + if (substr($elt, 0, 1) == '\\') + $pattern .= $elt; + else $pattern .= str_replace('/', '\/', $elt); + } + } + $this->patterns[$i] = "($pattern)"; + } + $this->regex = "/" . implode("|", $this->patterns) . "/" . $this->getPerlMatchingFlags(); + } + return $this->regex; + } + + /** + * Accessor for perl regex mode flags to use. + * @return string Perl regex flags. + */ + protected function getPerlMatchingFlags() + { + return ($this->case ? "msS" : "msSi"); + } +} diff --git a/platform/www/inc/Parsing/Lexer/StateStack.php b/platform/www/inc/Parsing/Lexer/StateStack.php new file mode 100644 index 0000000..325412b --- /dev/null +++ b/platform/www/inc/Parsing/Lexer/StateStack.php @@ -0,0 +1,60 @@ +<?php +/** + * Lexer adapted from Simple Test: http://sourceforge.net/projects/simpletest/ + * For an intro to the Lexer see: + * https://web.archive.org/web/20120125041816/http://www.phppatterns.com/docs/develop/simple_test_lexer_notes + * + * @author Marcus Baker http://www.lastcraft.com + */ + +namespace dokuwiki\Parsing\Lexer; + +/** + * States for a stack machine. + */ +class StateStack +{ + protected $stack; + + /** + * Constructor. Starts in named state. + * @param string $start Starting state name. + */ + public function __construct($start) + { + $this->stack = array($start); + } + + /** + * Accessor for current state. + * @return string State. + */ + public function getCurrent() + { + return $this->stack[count($this->stack) - 1]; + } + + /** + * Adds a state to the stack and sets it to be the current state. + * + * @param string $state New state. + */ + public function enter($state) + { + array_push($this->stack, $state); + } + + /** + * Leaves the current state and reverts + * to the previous one. + * @return boolean false if we attempt to drop off the bottom of the list. + */ + public function leave() + { + if (count($this->stack) == 1) { + return false; + } + array_pop($this->stack); + return true; + } +} diff --git a/platform/www/inc/Parsing/Parser.php b/platform/www/inc/Parsing/Parser.php new file mode 100644 index 0000000..63f0141 --- /dev/null +++ b/platform/www/inc/Parsing/Parser.php @@ -0,0 +1,128 @@ +<?php + +namespace dokuwiki\Parsing; + +use Doku_Handler; +use dokuwiki\Parsing\Lexer\Lexer; +use dokuwiki\Parsing\ParserMode\Base; +use dokuwiki\Parsing\ParserMode\ModeInterface; + +/** + * Sets up the Lexer with modes and points it to the Handler + * For an intro to the Lexer see: wiki:parser + */ +class Parser { + + /** @var Doku_Handler */ + protected $handler; + + /** @var Lexer $lexer */ + protected $lexer; + + /** @var ModeInterface[] $modes */ + protected $modes = array(); + + /** @var bool mode connections may only be set up once */ + protected $connected = false; + + /** + * dokuwiki\Parsing\Doku_Parser constructor. + * + * @param Doku_Handler $handler + */ + public function __construct(Doku_Handler $handler) { + $this->handler = $handler; + } + + /** + * Adds the base mode and initialized the lexer + * + * @param Base $BaseMode + */ + protected function addBaseMode($BaseMode) { + $this->modes['base'] = $BaseMode; + if(!$this->lexer) { + $this->lexer = new Lexer($this->handler, 'base', true); + } + $this->modes['base']->Lexer = $this->lexer; + } + + /** + * Add a new syntax element (mode) to the parser + * + * PHP preserves order of associative elements + * Mode sequence is important + * + * @param string $name + * @param ModeInterface $Mode + */ + public function addMode($name, ModeInterface $Mode) { + if(!isset($this->modes['base'])) { + $this->addBaseMode(new Base()); + } + $Mode->Lexer = $this->lexer; // FIXME should be done by setter + $this->modes[$name] = $Mode; + } + + /** + * Connect all modes with each other + * + * This is the last step before actually parsing. + */ + protected function connectModes() { + + if($this->connected) { + return; + } + + foreach(array_keys($this->modes) as $mode) { + // Base isn't connected to anything + if($mode == 'base') { + continue; + } + $this->modes[$mode]->preConnect(); + + foreach(array_keys($this->modes) as $cm) { + + if($this->modes[$cm]->accepts($mode)) { + $this->modes[$mode]->connectTo($cm); + } + + } + + $this->modes[$mode]->postConnect(); + } + + $this->connected = true; + } + + /** + * Parses wiki syntax to instructions + * + * @param string $doc the wiki syntax text + * @return array instructions + */ + public function parse($doc) { + $this->connectModes(); + // Normalize CRs and pad doc + $doc = "\n" . str_replace("\r\n", "\n", $doc) . "\n"; + $this->lexer->parse($doc); + + if (!method_exists($this->handler, 'finalize')) { + /** @deprecated 2019-10 we have a legacy handler from a plugin, assume legacy _finalize exists */ + + \dokuwiki\Debug\DebugHelper::dbgCustomDeprecationEvent( + 'finalize()', + get_class($this->handler) . '::_finalize()', + __METHOD__, + __FILE__, + __LINE__ + ); + $this->handler->_finalize(); + } else { + $this->handler->finalize(); + } + return $this->handler->calls; + } + +} diff --git a/platform/www/inc/Parsing/ParserMode/AbstractMode.php b/platform/www/inc/Parsing/ParserMode/AbstractMode.php new file mode 100644 index 0000000..15fc9fe --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/AbstractMode.php @@ -0,0 +1,40 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +/** + * This class and all the subclasses below are used to reduce the effort required to register + * modes with the Lexer. + * + * @author Harry Fuecks <hfuecks@gmail.com> + */ +abstract class AbstractMode implements ModeInterface +{ + /** @var \dokuwiki\Parsing\Lexer\Lexer $Lexer will be injected on loading FIXME this should be done by setter */ + public $Lexer; + protected $allowedModes = array(); + + /** @inheritdoc */ + abstract public function getSort(); + + /** @inheritdoc */ + public function preConnect() + { + } + + /** @inheritdoc */ + public function connectTo($mode) + { + } + + /** @inheritdoc */ + public function postConnect() + { + } + + /** @inheritdoc */ + public function accepts($mode) + { + return in_array($mode, (array) $this->allowedModes); + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Acronym.php b/platform/www/inc/Parsing/ParserMode/Acronym.php new file mode 100644 index 0000000..b42a7b5 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Acronym.php @@ -0,0 +1,68 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Acronym extends AbstractMode +{ + // A list + protected $acronyms = array(); + protected $pattern = ''; + + /** + * Acronym constructor. + * + * @param string[] $acronyms + */ + public function __construct($acronyms) + { + usort($acronyms, array($this,'compare')); + $this->acronyms = $acronyms; + } + + /** @inheritdoc */ + public function preConnect() + { + if (!count($this->acronyms)) return; + + $bound = '[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]'; + $acronyms = array_map(['\\dokuwiki\\Parsing\\Lexer\\Lexer', 'escape'], $this->acronyms); + $this->pattern = '(?<=^|'.$bound.')(?:'.join('|', $acronyms).')(?='.$bound.')'; + } + + /** @inheritdoc */ + public function connectTo($mode) + { + if (!count($this->acronyms)) return; + + if (strlen($this->pattern) > 0) { + $this->Lexer->addSpecialPattern($this->pattern, $mode, 'acronym'); + } + } + + /** @inheritdoc */ + public function getSort() + { + return 240; + } + + /** + * sort callback to order by string length descending + * + * @param string $a + * @param string $b + * + * @return int + */ + protected function compare($a, $b) + { + $a_len = strlen($a); + $b_len = strlen($b); + if ($a_len > $b_len) { + return -1; + } elseif ($a_len < $b_len) { + return 1; + } + + return 0; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Base.php b/platform/www/inc/Parsing/ParserMode/Base.php new file mode 100644 index 0000000..5622756 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Base.php @@ -0,0 +1,31 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Base extends AbstractMode +{ + + /** + * Base constructor. + */ + public function __construct() + { + global $PARSER_MODES; + + $this->allowedModes = array_merge( + $PARSER_MODES['container'], + $PARSER_MODES['baseonly'], + $PARSER_MODES['paragraphs'], + $PARSER_MODES['formatting'], + $PARSER_MODES['substition'], + $PARSER_MODES['protected'], + $PARSER_MODES['disabled'] + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 0; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Camelcaselink.php b/platform/www/inc/Parsing/ParserMode/Camelcaselink.php new file mode 100644 index 0000000..ef0b325 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Camelcaselink.php @@ -0,0 +1,23 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Camelcaselink extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern( + '\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b', + $mode, + 'camelcaselink' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 290; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Code.php b/platform/www/inc/Parsing/ParserMode/Code.php new file mode 100644 index 0000000..aa49437 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Code.php @@ -0,0 +1,25 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Code extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('<code\b(?=.*</code>)', $mode, 'code'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('</code>', 'code'); + } + + /** @inheritdoc */ + public function getSort() + { + return 200; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Emaillink.php b/platform/www/inc/Parsing/ParserMode/Emaillink.php new file mode 100644 index 0000000..f9af28c --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Emaillink.php @@ -0,0 +1,20 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Emaillink extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + // pattern below is defined in inc/mail.php + $this->Lexer->addSpecialPattern('<'.PREG_PATTERN_VALID_EMAIL.'>', $mode, 'emaillink'); + } + + /** @inheritdoc */ + public function getSort() + { + return 340; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Entity.php b/platform/www/inc/Parsing/ParserMode/Entity.php new file mode 100644 index 0000000..b670124 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Entity.php @@ -0,0 +1,50 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +use dokuwiki\Parsing\Lexer\Lexer; + +class Entity extends AbstractMode +{ + + protected $entities = array(); + protected $pattern = ''; + + /** + * Entity constructor. + * @param string[] $entities + */ + public function __construct($entities) + { + $this->entities = $entities; + } + + + /** @inheritdoc */ + public function preConnect() + { + if (!count($this->entities) || $this->pattern != '') return; + + $sep = ''; + foreach ($this->entities as $entity) { + $this->pattern .= $sep. Lexer::escape($entity); + $sep = '|'; + } + } + + /** @inheritdoc */ + public function connectTo($mode) + { + if (!count($this->entities)) return; + + if (strlen($this->pattern) > 0) { + $this->Lexer->addSpecialPattern($this->pattern, $mode, 'entity'); + } + } + + /** @inheritdoc */ + public function getSort() + { + return 260; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Eol.php b/platform/www/inc/Parsing/ParserMode/Eol.php new file mode 100644 index 0000000..a5886b5 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Eol.php @@ -0,0 +1,25 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Eol extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $badModes = array('listblock','table'); + if (in_array($mode, $badModes)) { + return; + } + // see FS#1652, pattern extended to swallow preceding whitespace to avoid + // issues with lines that only contain whitespace + $this->Lexer->addSpecialPattern('(?:^[ \t]*)?\n', $mode, 'eol'); + } + + /** @inheritdoc */ + public function getSort() + { + return 370; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Externallink.php b/platform/www/inc/Parsing/ParserMode/Externallink.php new file mode 100644 index 0000000..7475745 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Externallink.php @@ -0,0 +1,44 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Externallink extends AbstractMode +{ + protected $schemes = array(); + protected $patterns = array(); + + /** @inheritdoc */ + public function preConnect() + { + if (count($this->patterns)) return; + + $ltrs = '\w'; + $gunk = '/\#~:.?+=&%@!\-\[\]'; + $punc = '.:?\-;,'; + $host = $ltrs.$punc; + $any = $ltrs.$gunk.$punc; + + $this->schemes = getSchemes(); + foreach ($this->schemes as $scheme) { + $this->patterns[] = '\b(?i)'.$scheme.'(?-i)://['.$any.']+?(?=['.$punc.']*[^'.$any.'])'; + } + + $this->patterns[] = '(?<=\s)(?i)www?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])'; + $this->patterns[] = '(?<=\s)(?i)ftp?(?-i)\.['.$host.']+?\.['.$host.']+?['.$any.']+?(?=['.$punc.']*[^'.$any.'])'; + } + + /** @inheritdoc */ + public function connectTo($mode) + { + + foreach ($this->patterns as $pattern) { + $this->Lexer->addSpecialPattern($pattern, $mode, 'externallink'); + } + } + + /** @inheritdoc */ + public function getSort() + { + return 330; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/File.php b/platform/www/inc/Parsing/ParserMode/File.php new file mode 100644 index 0000000..1491341 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/File.php @@ -0,0 +1,25 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class File extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('<file\b(?=.*</file>)', $mode, 'file'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('</file>', 'file'); + } + + /** @inheritdoc */ + public function getSort() + { + return 210; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Filelink.php b/platform/www/inc/Parsing/ParserMode/Filelink.php new file mode 100644 index 0000000..3cd86cb --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Filelink.php @@ -0,0 +1,39 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Filelink extends AbstractMode +{ + + protected $pattern; + + /** @inheritdoc */ + public function preConnect() + { + + $ltrs = '\w'; + $gunk = '/\#~:.?+=&%@!\-'; + $punc = '.:?\-;,'; + $host = $ltrs.$punc; + $any = $ltrs.$gunk.$punc; + + $this->pattern = '\b(?i)file(?-i)://['.$any.']+?['. + $punc.']*[^'.$any.']'; + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern( + $this->pattern, + $mode, + 'filelink' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 360; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Footnote.php b/platform/www/inc/Parsing/ParserMode/Footnote.php new file mode 100644 index 0000000..c399f98 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Footnote.php @@ -0,0 +1,50 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Footnote extends AbstractMode +{ + + /** + * Footnote constructor. + */ + public function __construct() + { + global $PARSER_MODES; + + $this->allowedModes = array_merge( + $PARSER_MODES['container'], + $PARSER_MODES['formatting'], + $PARSER_MODES['substition'], + $PARSER_MODES['protected'], + $PARSER_MODES['disabled'] + ); + + unset($this->allowedModes[array_search('footnote', $this->allowedModes)]); + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern( + '\x28\x28(?=.*\x29\x29)', + $mode, + 'footnote' + ); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern( + '\x29\x29', + 'footnote' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 150; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Formatting.php b/platform/www/inc/Parsing/ParserMode/Formatting.php new file mode 100644 index 0000000..a3c465c --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Formatting.php @@ -0,0 +1,115 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +/** + * This class sets the markup for bold (=strong), + * italic (=emphasis), underline etc. + */ +class Formatting extends AbstractMode +{ + protected $type; + + protected $formatting = array( + 'strong' => array( + 'entry' => '\*\*(?=.*\*\*)', + 'exit' => '\*\*', + 'sort' => 70 + ), + + 'emphasis' => array( + 'entry' => '//(?=[^\x00]*[^:])', //hack for bugs #384 #763 #1468 + 'exit' => '//', + 'sort' => 80 + ), + + 'underline' => array( + 'entry' => '__(?=.*__)', + 'exit' => '__', + 'sort' => 90 + ), + + 'monospace' => array( + 'entry' => '\x27\x27(?=.*\x27\x27)', + 'exit' => '\x27\x27', + 'sort' => 100 + ), + + 'subscript' => array( + 'entry' => '<sub>(?=.*</sub>)', + 'exit' => '</sub>', + 'sort' => 110 + ), + + 'superscript' => array( + 'entry' => '<sup>(?=.*</sup>)', + 'exit' => '</sup>', + 'sort' => 120 + ), + + 'deleted' => array( + 'entry' => '<del>(?=.*</del>)', + 'exit' => '</del>', + 'sort' => 130 + ), + ); + + /** + * @param string $type + */ + public function __construct($type) + { + global $PARSER_MODES; + + if (!array_key_exists($type, $this->formatting)) { + trigger_error('Invalid formatting type ' . $type, E_USER_WARNING); + } + + $this->type = $type; + + // formatting may contain other formatting but not it self + $modes = $PARSER_MODES['formatting']; + $key = array_search($type, $modes); + if (is_int($key)) { + unset($modes[$key]); + } + + $this->allowedModes = array_merge( + $modes, + $PARSER_MODES['substition'], + $PARSER_MODES['disabled'] + ); + } + + /** @inheritdoc */ + public function connectTo($mode) + { + + // Can't nest formatting in itself + if ($mode == $this->type) { + return; + } + + $this->Lexer->addEntryPattern( + $this->formatting[$this->type]['entry'], + $mode, + $this->type + ); + } + + /** @inheritdoc */ + public function postConnect() + { + + $this->Lexer->addExitPattern( + $this->formatting[$this->type]['exit'], + $this->type + ); + } + + /** @inheritdoc */ + public function getSort() + { + return $this->formatting[$this->type]['sort']; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Header.php b/platform/www/inc/Parsing/ParserMode/Header.php new file mode 100644 index 0000000..854b317 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Header.php @@ -0,0 +1,24 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Header extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + //we're not picky about the closing ones, two are enough + $this->Lexer->addSpecialPattern( + '[ \t]*={2,}[^\n]+={2,}[ \t]*(?=\n)', + $mode, + 'header' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 50; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Hr.php b/platform/www/inc/Parsing/ParserMode/Hr.php new file mode 100644 index 0000000..e4f0b44 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Hr.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Hr extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern('\n[ \t]*-{4,}[ \t]*(?=\n)', $mode, 'hr'); + } + + /** @inheritdoc */ + public function getSort() + { + return 160; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Html.php b/platform/www/inc/Parsing/ParserMode/Html.php new file mode 100644 index 0000000..f5b63ef --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Html.php @@ -0,0 +1,27 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Html extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('<html>(?=.*</html>)', $mode, 'html'); + $this->Lexer->addEntryPattern('<HTML>(?=.*</HTML>)', $mode, 'htmlblock'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('</html>', 'html'); + $this->Lexer->addExitPattern('</HTML>', 'htmlblock'); + } + + /** @inheritdoc */ + public function getSort() + { + return 190; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Internallink.php b/platform/www/inc/Parsing/ParserMode/Internallink.php new file mode 100644 index 0000000..6def0d9 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Internallink.php @@ -0,0 +1,20 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Internallink extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + // Word boundaries? + $this->Lexer->addSpecialPattern("\[\[.*?\]\](?!\])", $mode, 'internallink'); + } + + /** @inheritdoc */ + public function getSort() + { + return 300; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Linebreak.php b/platform/www/inc/Parsing/ParserMode/Linebreak.php new file mode 100644 index 0000000..dd95cc3 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Linebreak.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Linebreak extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern('\x5C{2}(?:[ \t]|(?=\n))', $mode, 'linebreak'); + } + + /** @inheritdoc */ + public function getSort() + { + return 140; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Listblock.php b/platform/www/inc/Parsing/ParserMode/Listblock.php new file mode 100644 index 0000000..eef7627 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Listblock.php @@ -0,0 +1,44 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Listblock extends AbstractMode +{ + + /** + * Listblock constructor. + */ + public function __construct() + { + global $PARSER_MODES; + + $this->allowedModes = array_merge( + $PARSER_MODES['formatting'], + $PARSER_MODES['substition'], + $PARSER_MODES['disabled'], + $PARSER_MODES['protected'] + ); + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('[ \t]*\n {2,}[\-\*]', $mode, 'listblock'); + $this->Lexer->addEntryPattern('[ \t]*\n\t{1,}[\-\*]', $mode, 'listblock'); + + $this->Lexer->addPattern('\n {2,}[\-\*]', 'listblock'); + $this->Lexer->addPattern('\n\t{1,}[\-\*]', 'listblock'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('\n', 'listblock'); + } + + /** @inheritdoc */ + public function getSort() + { + return 10; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Media.php b/platform/www/inc/Parsing/ParserMode/Media.php new file mode 100644 index 0000000..f93f947 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Media.php @@ -0,0 +1,20 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Media extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + // Word boundaries? + $this->Lexer->addSpecialPattern("\{\{(?:[^\}]|(?:\}[^\}]))+\}\}", $mode, 'media'); + } + + /** @inheritdoc */ + public function getSort() + { + return 320; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/ModeInterface.php b/platform/www/inc/Parsing/ParserMode/ModeInterface.php new file mode 100644 index 0000000..7cca038 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/ModeInterface.php @@ -0,0 +1,46 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +/** + * Defines a mode (syntax component) in the Parser + */ +interface ModeInterface +{ + /** + * returns a number used to determine in which order modes are added + * + * @return int; + */ + public function getSort(); + + /** + * Called before any calls to connectTo + * + * @return void + */ + public function preConnect(); + + /** + * Connects the mode + * + * @param string $mode + * @return void + */ + public function connectTo($mode); + + /** + * Called after all calls to connectTo + * + * @return void + */ + public function postConnect(); + + /** + * Check if given mode is accepted inside this mode + * + * @param string $mode + * @return bool + */ + public function accepts($mode); +} diff --git a/platform/www/inc/Parsing/ParserMode/Multiplyentity.php b/platform/www/inc/Parsing/ParserMode/Multiplyentity.php new file mode 100644 index 0000000..89df136 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Multiplyentity.php @@ -0,0 +1,27 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +/** + * Implements the 640x480 replacement + */ +class Multiplyentity extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + + $this->Lexer->addSpecialPattern( + '(?<=\b)(?:[1-9]|\d{2,})[xX]\d+(?=\b)', + $mode, + 'multiplyentity' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 270; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Nocache.php b/platform/www/inc/Parsing/ParserMode/Nocache.php new file mode 100644 index 0000000..fa6db83 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Nocache.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Nocache extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern('~~NOCACHE~~', $mode, 'nocache'); + } + + /** @inheritdoc */ + public function getSort() + { + return 40; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Notoc.php b/platform/www/inc/Parsing/ParserMode/Notoc.php new file mode 100644 index 0000000..5956207 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Notoc.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Notoc extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern('~~NOTOC~~', $mode, 'notoc'); + } + + /** @inheritdoc */ + public function getSort() + { + return 30; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Php.php b/platform/www/inc/Parsing/ParserMode/Php.php new file mode 100644 index 0000000..914648b --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Php.php @@ -0,0 +1,27 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Php extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('<php>(?=.*</php>)', $mode, 'php'); + $this->Lexer->addEntryPattern('<PHP>(?=.*</PHP>)', $mode, 'phpblock'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('</php>', 'php'); + $this->Lexer->addExitPattern('</PHP>', 'phpblock'); + } + + /** @inheritdoc */ + public function getSort() + { + return 180; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Plugin.php b/platform/www/inc/Parsing/ParserMode/Plugin.php new file mode 100644 index 0000000..c885c60 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Plugin.php @@ -0,0 +1,8 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +/** + * @fixme do we need this anymore or could the syntax plugin inherit directly from abstract mode? + */ +abstract class Plugin extends AbstractMode {} diff --git a/platform/www/inc/Parsing/ParserMode/Preformatted.php b/platform/www/inc/Parsing/ParserMode/Preformatted.php new file mode 100644 index 0000000..7dfc474 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Preformatted.php @@ -0,0 +1,31 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Preformatted extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + // Has hard coded awareness of lists... + $this->Lexer->addEntryPattern('\n (?![\*\-])', $mode, 'preformatted'); + $this->Lexer->addEntryPattern('\n\t(?![\*\-])', $mode, 'preformatted'); + + // How to effect a sub pattern with the Lexer! + $this->Lexer->addPattern('\n ', 'preformatted'); + $this->Lexer->addPattern('\n\t', 'preformatted'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('\n', 'preformatted'); + } + + /** @inheritdoc */ + public function getSort() + { + return 20; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Quote.php b/platform/www/inc/Parsing/ParserMode/Quote.php new file mode 100644 index 0000000..65525b2 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Quote.php @@ -0,0 +1,41 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Quote extends AbstractMode +{ + + /** + * Quote constructor. + */ + public function __construct() + { + global $PARSER_MODES; + + $this->allowedModes = array_merge( + $PARSER_MODES['formatting'], + $PARSER_MODES['substition'], + $PARSER_MODES['disabled'], + $PARSER_MODES['protected'] + ); + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('\n>{1,}', $mode, 'quote'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addPattern('\n>{1,}', 'quote'); + $this->Lexer->addExitPattern('\n', 'quote'); + } + + /** @inheritdoc */ + public function getSort() + { + return 220; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Quotes.php b/platform/www/inc/Parsing/ParserMode/Quotes.php new file mode 100644 index 0000000..13db2e6 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Quotes.php @@ -0,0 +1,51 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Quotes extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + global $conf; + + $ws = '\s/\#~:+=&%@\-\x28\x29\]\[{}><"\''; // whitespace + $punc = ';,\.?!'; + + if ($conf['typography'] == 2) { + $this->Lexer->addSpecialPattern( + "(?<=^|[$ws])'(?=[^$ws$punc])", + $mode, + 'singlequoteopening' + ); + $this->Lexer->addSpecialPattern( + "(?<=^|[^$ws]|[$punc])'(?=$|[$ws$punc])", + $mode, + 'singlequoteclosing' + ); + $this->Lexer->addSpecialPattern( + "(?<=^|[^$ws$punc])'(?=$|[^$ws$punc])", + $mode, + 'apostrophe' + ); + } + + $this->Lexer->addSpecialPattern( + "(?<=^|[$ws])\"(?=[^$ws$punc])", + $mode, + 'doublequoteopening' + ); + $this->Lexer->addSpecialPattern( + "\"", + $mode, + 'doublequoteclosing' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 280; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Rss.php b/platform/www/inc/Parsing/ParserMode/Rss.php new file mode 100644 index 0000000..a62d9b8 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Rss.php @@ -0,0 +1,19 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Rss extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern("\{\{rss>[^\}]+\}\}", $mode, 'rss'); + } + + /** @inheritdoc */ + public function getSort() + { + return 310; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Smiley.php b/platform/www/inc/Parsing/ParserMode/Smiley.php new file mode 100644 index 0000000..084ccc9 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Smiley.php @@ -0,0 +1,48 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +use dokuwiki\Parsing\Lexer\Lexer; + +class Smiley extends AbstractMode +{ + protected $smileys = array(); + protected $pattern = ''; + + /** + * Smiley constructor. + * @param string[] $smileys + */ + public function __construct($smileys) + { + $this->smileys = $smileys; + } + + /** @inheritdoc */ + public function preConnect() + { + if (!count($this->smileys) || $this->pattern != '') return; + + $sep = ''; + foreach ($this->smileys as $smiley) { + $this->pattern .= $sep.'(?<=\W|^)'. Lexer::escape($smiley).'(?=\W|$)'; + $sep = '|'; + } + } + + /** @inheritdoc */ + public function connectTo($mode) + { + if (!count($this->smileys)) return; + + if (strlen($this->pattern) > 0) { + $this->Lexer->addSpecialPattern($this->pattern, $mode, 'smiley'); + } + } + + /** @inheritdoc */ + public function getSort() + { + return 230; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Table.php b/platform/www/inc/Parsing/ParserMode/Table.php new file mode 100644 index 0000000..b4b5123 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Table.php @@ -0,0 +1,47 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Table extends AbstractMode +{ + + /** + * Table constructor. + */ + public function __construct() + { + global $PARSER_MODES; + + $this->allowedModes = array_merge( + $PARSER_MODES['formatting'], + $PARSER_MODES['substition'], + $PARSER_MODES['disabled'], + $PARSER_MODES['protected'] + ); + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('[\t ]*\n\^', $mode, 'table'); + $this->Lexer->addEntryPattern('[\t ]*\n\|', $mode, 'table'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addPattern('\n\^', 'table'); + $this->Lexer->addPattern('\n\|', 'table'); + $this->Lexer->addPattern('[\t ]*:::[\t ]*(?=[\|\^])', 'table'); + $this->Lexer->addPattern('[\t ]+', 'table'); + $this->Lexer->addPattern('\^', 'table'); + $this->Lexer->addPattern('\|', 'table'); + $this->Lexer->addExitPattern('\n', 'table'); + } + + /** @inheritdoc */ + public function getSort() + { + return 60; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Unformatted.php b/platform/www/inc/Parsing/ParserMode/Unformatted.php new file mode 100644 index 0000000..1bc2826 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Unformatted.php @@ -0,0 +1,28 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Unformatted extends AbstractMode +{ + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addEntryPattern('<nowiki>(?=.*</nowiki>)', $mode, 'unformatted'); + $this->Lexer->addEntryPattern('%%(?=.*%%)', $mode, 'unformattedalt'); + } + + /** @inheritdoc */ + public function postConnect() + { + $this->Lexer->addExitPattern('</nowiki>', 'unformatted'); + $this->Lexer->addExitPattern('%%', 'unformattedalt'); + $this->Lexer->mapHandler('unformattedalt', 'unformatted'); + } + + /** @inheritdoc */ + public function getSort() + { + return 170; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Windowssharelink.php b/platform/www/inc/Parsing/ParserMode/Windowssharelink.php new file mode 100644 index 0000000..747d4d8 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Windowssharelink.php @@ -0,0 +1,31 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +class Windowssharelink extends AbstractMode +{ + + protected $pattern; + + /** @inheritdoc */ + public function preConnect() + { + $this->pattern = "\\\\\\\\\w+?(?:\\\\[\w\-$]+)+"; + } + + /** @inheritdoc */ + public function connectTo($mode) + { + $this->Lexer->addSpecialPattern( + $this->pattern, + $mode, + 'windowssharelink' + ); + } + + /** @inheritdoc */ + public function getSort() + { + return 350; + } +} diff --git a/platform/www/inc/Parsing/ParserMode/Wordblock.php b/platform/www/inc/Parsing/ParserMode/Wordblock.php new file mode 100644 index 0000000..50b24b2 --- /dev/null +++ b/platform/www/inc/Parsing/ParserMode/Wordblock.php @@ -0,0 +1,52 @@ +<?php + +namespace dokuwiki\Parsing\ParserMode; + +use dokuwiki\Parsing\Lexer\Lexer; + +/** + * @fixme is this actually used? + */ +class Wordblock extends AbstractMode +{ + protected $badwords = array(); + protected $pattern = ''; + + /** + * Wordblock constructor. + * @param $badwords + */ + public function __construct($badwords) + { + $this->badwords = $badwords; + } + + /** @inheritdoc */ + public function preConnect() + { + + if (count($this->badwords) == 0 || $this->pattern != '') { + return; + } + + $sep = ''; + foreach ($this->badwords as $badword) { + $this->pattern .= $sep.'(?<=\b)(?i)'. Lexer::escape($badword).'(?-i)(?=\b)'; + $sep = '|'; + } + } + + /** @inheritdoc */ + public function connectTo($mode) + { + if (strlen($this->pattern) > 0) { + $this->Lexer->addSpecialPattern($this->pattern, $mode, 'wordblock'); + } + } + + /** @inheritdoc */ + public function getSort() + { + return 250; + } +} |