Initial commit
This commit is contained in:
39
content/inc/Parsing/Handler/AbstractRewriter.php
Normal file
39
content/inc/Parsing/Handler/AbstractRewriter.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
211
content/inc/Parsing/Handler/Block.php
Normal file
211
content/inc/Parsing/Handler/Block.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
40
content/inc/Parsing/Handler/CallWriter.php
Normal file
40
content/inc/Parsing/Handler/CallWriter.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
30
content/inc/Parsing/Handler/CallWriterInterface.php
Normal file
30
content/inc/Parsing/Handler/CallWriterInterface.php
Normal file
@@ -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();
|
||||
}
|
186
content/inc/Parsing/Handler/Lists.php
Normal file
186
content/inc/Parsing/Handler/Lists.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
82
content/inc/Parsing/Handler/Nest.php
Normal file
82
content/inc/Parsing/Handler/Nest.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
49
content/inc/Parsing/Handler/Preformatted.php
Normal file
49
content/inc/Parsing/Handler/Preformatted.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
86
content/inc/Parsing/Handler/Quote.php
Normal file
86
content/inc/Parsing/Handler/Quote.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
37
content/inc/Parsing/Handler/ReWriterInterface.php
Normal file
37
content/inc/Parsing/Handler/ReWriterInterface.php
Normal file
@@ -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();
|
||||
}
|
320
content/inc/Parsing/Handler/Table.php
Normal file
320
content/inc/Parsing/Handler/Table.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
349
content/inc/Parsing/Lexer/Lexer.php
Normal file
349
content/inc/Parsing/Lexer/Lexer.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
203
content/inc/Parsing/Lexer/ParallelRegex.php
Normal file
203
content/inc/Parsing/Lexer/ParallelRegex.php
Normal file
@@ -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");
|
||||
}
|
||||
}
|
60
content/inc/Parsing/Lexer/StateStack.php
Normal file
60
content/inc/Parsing/Lexer/StateStack.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
128
content/inc/Parsing/Parser.php
Normal file
128
content/inc/Parsing/Parser.php
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
40
content/inc/Parsing/ParserMode/AbstractMode.php
Normal file
40
content/inc/Parsing/ParserMode/AbstractMode.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
68
content/inc/Parsing/ParserMode/Acronym.php
Normal file
68
content/inc/Parsing/ParserMode/Acronym.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
31
content/inc/Parsing/ParserMode/Base.php
Normal file
31
content/inc/Parsing/ParserMode/Base.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
23
content/inc/Parsing/ParserMode/Camelcaselink.php
Normal file
23
content/inc/Parsing/ParserMode/Camelcaselink.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
25
content/inc/Parsing/ParserMode/Code.php
Normal file
25
content/inc/Parsing/ParserMode/Code.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
20
content/inc/Parsing/ParserMode/Emaillink.php
Normal file
20
content/inc/Parsing/ParserMode/Emaillink.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
50
content/inc/Parsing/ParserMode/Entity.php
Normal file
50
content/inc/Parsing/ParserMode/Entity.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
25
content/inc/Parsing/ParserMode/Eol.php
Normal file
25
content/inc/Parsing/ParserMode/Eol.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
44
content/inc/Parsing/ParserMode/Externallink.php
Normal file
44
content/inc/Parsing/ParserMode/Externallink.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
25
content/inc/Parsing/ParserMode/File.php
Normal file
25
content/inc/Parsing/ParserMode/File.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
39
content/inc/Parsing/ParserMode/Filelink.php
Normal file
39
content/inc/Parsing/ParserMode/Filelink.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
50
content/inc/Parsing/ParserMode/Footnote.php
Normal file
50
content/inc/Parsing/ParserMode/Footnote.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
115
content/inc/Parsing/ParserMode/Formatting.php
Normal file
115
content/inc/Parsing/ParserMode/Formatting.php
Normal file
@@ -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'];
|
||||
}
|
||||
}
|
24
content/inc/Parsing/ParserMode/Header.php
Normal file
24
content/inc/Parsing/ParserMode/Header.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
19
content/inc/Parsing/ParserMode/Hr.php
Normal file
19
content/inc/Parsing/ParserMode/Hr.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
27
content/inc/Parsing/ParserMode/Html.php
Normal file
27
content/inc/Parsing/ParserMode/Html.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
20
content/inc/Parsing/ParserMode/Internallink.php
Normal file
20
content/inc/Parsing/ParserMode/Internallink.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
19
content/inc/Parsing/ParserMode/Linebreak.php
Normal file
19
content/inc/Parsing/ParserMode/Linebreak.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
44
content/inc/Parsing/ParserMode/Listblock.php
Normal file
44
content/inc/Parsing/ParserMode/Listblock.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
20
content/inc/Parsing/ParserMode/Media.php
Normal file
20
content/inc/Parsing/ParserMode/Media.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
46
content/inc/Parsing/ParserMode/ModeInterface.php
Normal file
46
content/inc/Parsing/ParserMode/ModeInterface.php
Normal file
@@ -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);
|
||||
}
|
27
content/inc/Parsing/ParserMode/Multiplyentity.php
Normal file
27
content/inc/Parsing/ParserMode/Multiplyentity.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
19
content/inc/Parsing/ParserMode/Nocache.php
Normal file
19
content/inc/Parsing/ParserMode/Nocache.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
19
content/inc/Parsing/ParserMode/Notoc.php
Normal file
19
content/inc/Parsing/ParserMode/Notoc.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
27
content/inc/Parsing/ParserMode/Php.php
Normal file
27
content/inc/Parsing/ParserMode/Php.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
8
content/inc/Parsing/ParserMode/Plugin.php
Normal file
8
content/inc/Parsing/ParserMode/Plugin.php
Normal file
@@ -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 {}
|
31
content/inc/Parsing/ParserMode/Preformatted.php
Normal file
31
content/inc/Parsing/ParserMode/Preformatted.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
41
content/inc/Parsing/ParserMode/Quote.php
Normal file
41
content/inc/Parsing/ParserMode/Quote.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
51
content/inc/Parsing/ParserMode/Quotes.php
Normal file
51
content/inc/Parsing/ParserMode/Quotes.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
19
content/inc/Parsing/ParserMode/Rss.php
Normal file
19
content/inc/Parsing/ParserMode/Rss.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
48
content/inc/Parsing/ParserMode/Smiley.php
Normal file
48
content/inc/Parsing/ParserMode/Smiley.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
47
content/inc/Parsing/ParserMode/Table.php
Normal file
47
content/inc/Parsing/ParserMode/Table.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
28
content/inc/Parsing/ParserMode/Unformatted.php
Normal file
28
content/inc/Parsing/ParserMode/Unformatted.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
31
content/inc/Parsing/ParserMode/Windowssharelink.php
Normal file
31
content/inc/Parsing/ParserMode/Windowssharelink.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
52
content/inc/Parsing/ParserMode/Wordblock.php
Normal file
52
content/inc/Parsing/ParserMode/Wordblock.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user