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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user