diff options
author | andi <andi@splitbrain.org> | 2005-03-31 16:57:49 +0200 |
---|---|---|
committer | andi <andi@splitbrain.org> | 2005-03-31 16:57:49 +0200 |
commit | 0cecf9d507451346a32ddf45a85b425784fbb0f8 (patch) | |
tree | 076dd2d128f55022792b4bfab42c1d2d2bec4fb8 /inc/parser/handler.php | |
parent | c53ea5f2d3d1019fd4e1956796bc329af499a86d (diff) | |
download | rpg-0cecf9d507451346a32ddf45a85b425784fbb0f8.tar.gz rpg-0cecf9d507451346a32ddf45a85b425784fbb0f8.tar.bz2 |
new parser added (define DOKU_USENEWPARSER to use it)
darcs-hash:20050331145749-9977f-f011ea6c65a278197e9087b685c635c60a204cd2.gz
Diffstat (limited to 'inc/parser/handler.php')
-rw-r--r-- | inc/parser/handler.php | 1510 |
1 files changed, 1510 insertions, 0 deletions
diff --git a/inc/parser/handler.php b/inc/parser/handler.php new file mode 100644 index 000000000..78826007e --- /dev/null +++ b/inc/parser/handler.php @@ -0,0 +1,1510 @@ +<?php +if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); + +class Doku_Handler { + + var $Renderer = NULL; + + var $CallWriter = NULL; + + var $calls = array(); + + var $meta = array( + 'section' => FALSE, + 'toc' => TRUE, + ); + + function Doku_Handler() { + $this->CallWriter = & new Doku_Handler_CallWriter($this); + } + + function __addCall($handler, $args, $pos) { + $call = array($handler,$args, $pos); + $this->CallWriter->writeCall($call); + } + + function __finalize(){ + if ( $this->meta['section'] ) { + $S = & new Doku_Handler_Section(); + $this->calls = $S->process($this->calls); + } + + $B = & new Doku_Handler_Block(); + $this->calls = $B->process($this->calls); + + if ( $this->meta['toc'] ) { + $T = & new Doku_Handler_Toc(); + $this->calls = $T->process($this->calls); + } + + array_unshift($this->calls,array('document_start',array(),0)); + $last_call = end($this->calls); + array_push($this->calls,array('document_end',array(),$last_call[2])); + } + + function fetch() { + $call = each($this->calls); + if ( $call ) { + return $call['value']; + } + return FALSE; + } + + function base($match, $state, $pos) { + switch ( $state ) { + case DOKU_LEXER_UNMATCHED: + $this->__addCall('cdata',array($match), $pos); + return TRUE; + break; + + } + } + + function header($match, $state, $pos) { + $match = trim($match); + $levels = array( + '======'=>1, + '====='=>2, + '===='=>3, + '==='=>4, + '=='=>5, + ); + $hsplit = preg_split( '/(={2,})/u', $match,-1, + PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); + + // Locate the level - default to level 1 if no match (title contains == signs) + if ( isset($hsplit[0]) && array_key_exists($hsplit[0], $levels) ) { + $level = $levels[$hsplit[0]]; + } else { + $level = 1; + } + + // Strip of the marker for the header, based on the level - the rest is the title + $iLevels = array_flip($levels); + $markerLen = strlen($iLevels[$level]); + $title = substr($match, $markerLen, strlen($match)-($markerLen*2)); + + $this->__addCall('header',array($title,$level), $pos); + $this->meta['section'] = TRUE; + return TRUE; + } + + function notoc($match, $state, $pos) { + $this->meta['toc'] = FALSE; + return TRUE; + } + + function linebreak($match, $state, $pos) { + $this->__addCall('linebreak',array(),$pos); + return TRUE; + } + + function eol($match, $state, $pos) { + $this->__addCall('eol',array(),$pos); + return TRUE; + } + + function hr($match, $state, $pos) { + $this->__addCall('hr',array(),$pos); + return TRUE; + } + + function __nestingTag($match, $state, $pos, $name) { + switch ( $state ) { + case DOKU_LEXER_ENTER: + $this->__addCall($name.'_open', array(), $pos); + break; + case DOKU_LEXER_EXIT: + $this->__addCall($name.'_close', array(), $pos); + break; + case DOKU_LEXER_UNMATCHED: + $this->__addCall('cdata',array($match), $pos); + break; + } + } + + function strong($match, $state, $pos) { + $this->__nestingTag($match, $state, $pos, 'strong'); + return TRUE; + } + + function emphasis($match, $state, $pos) { + $this->__nestingTag($match, $state, $pos, 'emphasis'); + return TRUE; + } + + function underline($match, $state, $pos) { + $this->__nestingTag($match, $state, $pos, 'underline'); + return TRUE; + } + + function monospace($match, $state, $pos) { + $this->__nestingTag($match, $state, $pos, 'monospace'); + return TRUE; + } + + function subscript($match, $state, $pos) { + $this->__nestingTag($match, $state, $pos, 'subscript'); + return TRUE; + } + + function superscript($match, $state, $pos) { + $this->__nestingTag($match, $state, $pos, 'superscript'); + return TRUE; + } + + function deleted($match, $state, $pos) { + $this->__nestingTag($match, $state, $pos, 'deleted'); + return TRUE; + } + + + function footnote($match, $state, $pos) { + $this->__nestingTag($match, $state, $pos, 'footnote'); + return TRUE; + } + + function listblock($match, $state, $pos) { + switch ( $state ) { + case DOKU_LEXER_ENTER: + $ReWriter = & new Doku_Handler_List($this->CallWriter); + $this->CallWriter = & $ReWriter; + $this->__addCall('list_open', array($match), $pos); + break; + case DOKU_LEXER_EXIT: + $this->__addCall('list_close', array(), $pos); + $this->CallWriter->process(); + $ReWriter = & $this->CallWriter; + $this->CallWriter = & $ReWriter->CallWriter; + break; + case DOKU_LEXER_MATCHED: + $this->__addCall('list_item', array($match), $pos); + break; + case DOKU_LEXER_UNMATCHED: + $this->__addCall('cdata', array($match), $pos); + break; + } + return TRUE; + } + + function unformatted($match, $state, $pos) { + if ( $state == DOKU_LEXER_UNMATCHED ) { + $this->__addCall('unformatted',array($match), $pos); + } + return TRUE; + } + + function php($match, $state, $pos) { + if ( $state == DOKU_LEXER_UNMATCHED ) { + $this->__addCall('php',array($match), $pos); + } + return TRUE; + } + + function html($match, $state, $pos) { + if ( $state == DOKU_LEXER_UNMATCHED ) { + $this->__addCall('html',array($match), $pos); + } + return TRUE; + } + + function preformatted($match, $state, $pos) { + switch ( $state ) { + case DOKU_LEXER_ENTER: + $ReWriter = & new Doku_Handler_Preformatted($this->CallWriter); + $this->CallWriter = & $ReWriter; + $this->__addCall('preformatted_start',array(), $pos); + break; + case DOKU_LEXER_EXIT: + $this->__addCall('preformatted_end',array(), $pos); + $this->CallWriter->process(); + $ReWriter = & $this->CallWriter; + $this->CallWriter = & $ReWriter->CallWriter; + break; + case DOKU_LEXER_MATCHED: + $this->__addCall('preformatted_newline',array(), $pos); + break; + case DOKU_LEXER_UNMATCHED: + $this->__addCall('preformatted_content',array($match), $pos); + break; + } + + return TRUE; + } + + function file($match, $state, $pos) { + if ( $state == DOKU_LEXER_UNMATCHED ) { + $this->__addCall('file',array($match), $pos); + } + return TRUE; + } + + function quote($match, $state, $pos) { + + switch ( $state ) { + + case DOKU_LEXER_ENTER: + $ReWriter = & new Doku_Handler_Quote($this->CallWriter); + $this->CallWriter = & $ReWriter; + $this->__addCall('quote_start',array($match), $pos); + break; + + case DOKU_LEXER_EXIT: + $this->__addCall('quote_end',array(), $pos); + $this->CallWriter->process(); + $ReWriter = & $this->CallWriter; + $this->CallWriter = & $ReWriter->CallWriter; + break; + + case DOKU_LEXER_MATCHED: + $this->__addCall('quote_newline',array($match), $pos); + break; + + case DOKU_LEXER_UNMATCHED: + $this->__addCall('cdata',array($match), $pos); + break; + + } + + return TRUE; + } + + function code($match, $state, $pos) { + switch ( $state ) { + case DOKU_LEXER_UNMATCHED: + $matches = preg_split('/>/u',$match,2); + $matches[0] = trim($matches[0]); + if ( trim($matches[0]) == '' ) { + $matches[0] = NULL; + } + # $matches[0] contains name of programming language + # if available + $this->__addCall( + 'code', + array($matches[1],$matches[0]), + $pos + ); + break; + } + return TRUE; + } + + function acronym($match, $state, $pos) { + $this->__addCall('acronym',array($match), $pos); + return TRUE; + } + + function smiley($match, $state, $pos) { + $this->__addCall('smiley',array($match), $pos); + return TRUE; + } + + function wordblock($match, $state, $pos) { + $this->__addCall('wordblock',array($match), $pos); + return TRUE; + } + + function entity($match, $state, $pos) { + $this->__addCall('entity',array($match), $pos); + return TRUE; + } + + function multiplyentity($match, $state, $pos) { + preg_match_all('/\d+/',$match,$matches); + $this->__addCall('multiplyentity',array($matches[0][0],$matches[0][1]), $pos); + return TRUE; + } + + function singlequoteopening($match, $state, $pos) { + $this->__addCall('singlequoteopening',array(), $pos); + return TRUE; + } + + function singlequoteclosing($match, $state, $pos) { + $this->__addCall('singlequoteclosing',array(), $pos); + return TRUE; + } + + function doublequoteopening($match, $state, $pos) { + $this->__addCall('doublequoteopening',array(), $pos); + return TRUE; + } + + function doublequoteclosing($match, $state, $pos) { + $this->__addCall('doublequoteclosing',array(), $pos); + return TRUE; + } + + function camelcaselink($match, $state, $pos) { + $this->__addCall('camelcaselink',array($match), $pos); + return TRUE; + } + + /* + * @TODO What about email? + */ + function internallink($match, $state, $pos) { + // Strip the opening and closing markup + $link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match); + + // Split title from URL + $link = preg_split('/\|/u',$link,2); + if ( !isset($link[1]) ) { + $link[1] = NULL; + + // If the title is an image, convert it to an array containing the image details + } else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) { + $media = Doku_Handler_Parse_Media($link[1]); + + // Check it's really an image, not a link to a file + if ( !strpos($media['type'], 'link') ) { + $link[1] = $media; + } + } + + // Interwiki links + if ( preg_match('/^[a-zA-Z]+>{1}[\w()\/\\#~:.?+=&%@!\-;,]+$/u',$link[0]) ) { + $interwiki = preg_split('/>/u',$link[0]); + $this->__addCall( + 'interwikilink', + // UTF8 problem... + array($link[0],$link[1],strtolower($interwiki[0]),$interwiki[1]), + $pos + ); + + // Link to internal wiki page + } else if ( preg_match('/^[\w:#]+$/u',$link[0]) ) { + $this->__addCall( + 'internallink', + array($link[0],$link[1]), + $pos + ); + + // Otherwise it's some kind of link to elsewhere + } else { + + // file:// + if ( substr($link[0],0,4) == 'file' ) { + $this->__addCall( + 'filelink', + array($link[0],$link[1]), + $pos + ); + + // Check for Windows shares - potential security issues here? + // i.e. mode not loaded but still matched here... + } else if ( preg_match('/\\\\\\\\[\w.:?\-;,]+?\\\\/u',$link[0]) ) { + $this->__addCall( + 'windowssharelink', + array($link[0],$link[1]), + $pos + ); + + // Otherwise it's one of the other valid internal links + } else { + + // Add the scheme as needed... + $leader = substr($link[0],0,3); + if ( $leader == 'www' ) { + $link[0] = 'http://'.$link[0]; + } else if ( $leader == 'ftp' ) { + $link[0] = 'ftp://'.$link[0]; + } + + $this->__addCall( + 'externallink', + array($link[0],$link[1]), + $pos + ); + } + } + return TRUE; + } + + function filelink($match, $state, $pos) { + $this->__addCall('filelink',array($match, NULL), $pos); + return TRUE; + } + + function windowssharelink($match, $state, $pos) { + $this->__addCall('windowssharelink',array($match, NULL), $pos); + return TRUE; + } + + function media($match, $state, $pos) { + $p = Doku_Handler_Parse_Media($match); + + // If it's not an image, build a normal link + if ( strpos($p['type'], 'link') ) { + $this->__addCall($p['type'],array($p['src'], $p['title']), $pos); + } else { + $this->__addCall( + $p['type'], + array($p['src'], $p['title'], $p['align'], $p['width'], $p['height'], $p['cache']), + $pos + ); + } + return TRUE; + } + + function externallink($match, $state, $pos) { + // Prevent use of multibyte strings in URLs + // See: http://www.boingboing.net/2005/02/06/shmoo_group_exploit_.html + // Not worried about other charsets so long as page is output as UTF-8 + /*if ( strlen($match) != utf8_strlen($match) ) { + $this->__addCall('cdata',array($match), $pos); + } else {*/ + + $this->__addCall('externallink',array($match, NULL), $pos); + //} + return TRUE; + } + + function email($match, $state, $pos) { + $email = preg_replace(array('/^</','/>$/'),'',$match); + $this->__addCall('email',array($email, NULL), $pos); + return TRUE; + } + + function table($match, $state, $pos) { + switch ( $state ) { + + case DOKU_LEXER_ENTER: + + $ReWriter = & new Doku_Handler_Table($this->CallWriter); + $this->CallWriter = & $ReWriter; + + $this->__addCall('table_start', array(), $pos); + //$this->__addCall('table_row', array(), $pos); + if ( trim($match) == '^' ) { + $this->__addCall('tableheader', array(), $pos); + } else { + $this->__addCall('tablecell', array(), $pos); + } + break; + + case DOKU_LEXER_EXIT: + $this->__addCall('table_end', array(), $pos); + $this->CallWriter->process(); + $ReWriter = & $this->CallWriter; + $this->CallWriter = & $ReWriter->CallWriter; + break; + + case DOKU_LEXER_UNMATCHED: + if ( trim($match) != '' ) { + $this->__addCall('cdata',array($match), $pos); + } + break; + + case DOKU_LEXER_MATCHED: + if ( preg_match('/.{2}/',$match) ) { + $this->__addCall('table_align', array($match), $pos); + } else if ( $match == "\n|" ) { + $this->__addCall('table_row', array(), $pos); + $this->__addCall('tablecell', array(), $pos); + } else if ( $match == "\n^" ) { + $this->__addCall('table_row', array(), $pos); + $this->__addCall('tableheader', array(), $pos); + } else if ( $match == '|' ) { + $this->__addCall('tablecell', array(), $pos); + } else if ( $match == '^' ) { + $this->__addCall('tableheader', array(), $pos); + } + break; + } + return TRUE; + } +} + +//------------------------------------------------------------------------ +function Doku_Handler_Parse_Media($match) { + + // Strip the opening and closing markup + $link = preg_replace(array('/^\{\{/','/\}\}$/u'),'',$match); + + // Split title from URL + $link = preg_split('/\|/u',$link,2); + + + // Check alignment + $ralign = (bool)preg_match('/^ /',$link[0]); + $lalign = (bool)preg_match('/ $/',$link[0]); + + // Logic = what's that ;)... + if ( $lalign & $ralign ) { + $align = 'center'; + } else if ( $ralign ) { + $align = 'right'; + } else if ( $lalign ) { + $align = 'left'; + } else { + $align = NULL; + } + + // The title... + if ( !isset($link[1]) ) { + $link[1] = NULL; + } + + // img src url from params + // What if it's an external image where URL contains '?' char? + $src = preg_split('/\?/u',$link[0],2); + + // Strip any alignment whitespace + $src[0] = trim($src[0]); + + // Check for width, height and caching params + if ( isset($src[1]) ) { + + if(preg_match('#(\d*)(x(\d*))?#i',$src[1],$matches)){ + + if(isset($matches[1])) { + $width = $matches[1]; + } else { + $width = NULL; + } + + if(isset($matches[3])) { + $height = $matches[3]; + } else { + $height = NULL; + } + + $cache = !(bool)preg_match('/nocache/i',$src[1]); + } + + } else { + $width = NULL; + $height = NULL; + $cache = TRUE; + } + + // Check whether this is a local or remote image + if ( substr($src[0],0,4) == 'http' ) { + $call = 'external'; + } else { + $call = 'internal'; + } + + // Check this is actually an image... + if ( !preg_match('/\.(gif|png|jpe?g)$/',$src[0] ) ) { + // Security implications?... + $call .= 'link'; + } else { + $call .= 'media'; + } + + $params = array( + 'type'=>$call, + 'src'=>$src[0], + 'title'=>$link[1], + 'align'=>$align, + 'width'=>$width, + 'height'=>$height, + 'cache'=>$cache, + ); + + return $params; +} + +//------------------------------------------------------------------------ +class Doku_Handler_CallWriter { + + var $Handler; + + function Doku_Handler_CallWriter(& $Handler) { + $this->Handler = & $Handler; + } + + function writeCall($call) { + $this->Handler->calls[] = $call; + } + + function writeCalls($calls) { + $this->Handler->calls = array_merge($this->Handler->calls, $calls); + } +} + +//------------------------------------------------------------------------ +class Doku_Handler_List { + + var $CallWriter; + + var $calls = array(); + var $listCalls = array(); + var $listStack = array(); + + function Doku_Handler_List(& $CallWriter) { + $this->CallWriter = & $CallWriter; + } + + function writeCall($call) { + $this->calls[] = $call; + } + + // Probably not needed but just in case... + function writeCalls($calls) { + $this->calls = array_merge($this->calls, $calls); + $this->CallWriter->writeCalls($this->calls); + } + + //------------------------------------------------------------------------ + 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); + } + + //------------------------------------------------------------------------ + function listStart($call) { + $depth = $this->interpretSyntax($call[1][0], $listType); + + $this->initialDepth = $depth; + $this->listStack[] = array($listType, $depth); + + $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]); + } + + //------------------------------------------------------------------------ + 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]); + } + } + + //------------------------------------------------------------------------ + function listOpen($call) { + $depth = $this->interpretSyntax($call[1][0], $listType); + $end = end($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]); + + // 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); + } + + //------------------------------------------------------------------------ + // Getting deeper... + } else if ( $depth > $end[1] ) { + + $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]); + + $this->listStack[] = array($listType, $depth); + + //------------------------------------------------------------------------ + // Getting shallower ( $depth < $end[1] ) + } 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]); + + // Throw away the end - done + array_pop($this->listStack); + + while (1) { + $end = end($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]); + + } 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); + } + + 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); + + } + + } + + } + } + + //------------------------------------------------------------------------ + function listContent($call) { + $this->listCalls[] = $call; + } + + //------------------------------------------------------------------------ + function interpretSyntax($match, & $type) { + if ( substr($match,-1) == '*' ) { + $type = 'u'; + } else { + $type = 'o'; + } + return count(explode(' ',str_replace("\t",' ',$match))); + } +} + +//------------------------------------------------------------------------ +class Doku_Handler_Preformatted { + + var $CallWriter; + + var $calls = array(); + var $pos; + var $text =''; + + + + function Doku_Handler_Preformatted(& $CallWriter) { + $this->CallWriter = & $CallWriter; + } + + function writeCall($call) { + $this->calls[] = $call; + } + + // Probably not needed but just in case... + function writeCalls($calls) { + $this->calls = array_merge($this->calls, $calls); + $this->CallWriter->writeCalls($this->calls); + } + + 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': + $this->CallWriter->writeCall(array('preformatted',array($this->text),$this->pos)); + break; + } + } + } +} + +//------------------------------------------------------------------------ +class Doku_Handler_Quote { + + var $CallWriter; + + var $calls = array(); + + var $quoteCalls = array(); + + function Doku_Handler_Quote(& $CallWriter) { + $this->CallWriter = & $CallWriter; + } + + function writeCall($call) { + $this->calls[] = $call; + } + + // Probably not needed but just in case... + function writeCalls($calls) { + $this->calls = array_merge($this->calls, $calls); + $this->CallWriter->writeCalls($this->calls); + } + + function process() { + + $quoteDepth = 1; + + foreach ( $this->calls as $call ) { + switch ($call[0]) { + + case 'quote_start': + + $this->quoteCalls[] = array('quote_open',array(),$call[2]); + + 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]); + } + } else if ( $quoteLength < $quoteDepth ) { + $quoteDiff = $quoteDepth - $quoteLength; + for ( $i = 1; $i <= $quoteDiff; $i++ ) { + $this->quoteCalls[] = array('quote_close',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; + } + } + } + + function getDepth($marker) { + preg_match('/>{1,}/', $marker, $matches); + $quoteLength = strlen($matches[0]); + return $quoteLength; + } +} + +//------------------------------------------------------------------------ +class Doku_Handler_Table { + + var $CallWriter; + + var $calls = array(); + var $tableCalls = array(); + var $maxCols = 0; + var $maxRows = 1; + var $currentCols = 0; + var $firstCell = FALSE; + var $lastCellType = 'tablecell'; + + function Doku_Handler_Table(& $CallWriter) { + $this->CallWriter = & $CallWriter; + } + + function writeCall($call) { + $this->calls[] = $call; + } + + // Probably not needed but just in case... + function writeCalls($calls) { + $this->calls = array_merge($this->calls, $calls); + $this->CallWriter->writeCalls($this->calls); + } + + //------------------------------------------------------------------------ + function process() { + foreach ( $this->calls as $call ) { + switch ( $call[0] ) { + case 'table_start': + $this->tableStart($call); + break; + case 'table_row': + $this->tableRowClose(array('tablerow_close',$call[1],$call[2])); + $this->tableRowOpen(array('tablerow_open',$call[1],$call[2])); + break; + case 'tableheader': + case 'tablecell': + $this->tableCell($call); + break; + case 'table_end': + $this->tableRowClose(array('tablerow_close',$call[1],$call[2])); + $this->tableEnd($call); + break; + default: + $this->tableDefault($call); + break; + } + } + $this->CallWriter->writeCalls($this->tableCalls); + } + + function tableStart($call) { + $this->tableCalls[] = array('table_open',array(),$call[2]); + $this->tableCalls[] = array('tablerow_open',array(),$call[2]); + $this->firstCell = TRUE; + } + + function tableEnd($call) { + $this->tableCalls[] = array('table_close',array(),$call[2]); + $this->finalizeTable(); + } + + function tableRowOpen($call) { + $this->tableCalls[] = $call; + $this->currentCols = 0; + $this->firstCell = TRUE; + $this->lastCellType = 'tablecell'; + $this->maxRows++; + } + + function tableRowClose($call) { + // Strip off final cell opening and anything after it + while ( $discard = array_pop($this->tableCalls ) ) { + + if ( $discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') { + + // Its a spanning element - put it back and close it + if ( $discard[1][0] > 1 ) { + + $this->tableCalls[] = $discard; + if ( strstr($discard[0],'cell') ) { + $name = 'tablecell'; + } else { + $name = 'tableheader'; + } + $this->tableCalls[] = array($name.'_close',array(),$call[2]); + } + + break; + } + } + $this->tableCalls[] = $call; + + if ( $this->currentCols > $this->maxCols ) { + $this->maxCols = $this->currentCols; + } + } + + function tableCell($call) { + 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),$call[2]); + $this->lastCellType = $call[0]; + + } else { + + $this->tableCalls[] = array($call[0].'_open',array(1,NULL),$call[2]); + $this->lastCellType = $call[0]; + $this->firstCell = FALSE; + + } + + $this->currentCols++; + } + + function tableDefault($call) { + $this->tableCalls[] = $call; + } + + 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; + } else { + trigger_error('First element in table call list is not table_open'); + } + + $lastRow = 0; + $lastCell = 0; + $toDelete = array(); + + // 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 + foreach ( $this->tableCalls as $key => $call ) { + + if ( $call[0] == 'tablerow_open' ) { + + $lastRow = $key; + + } else if ( $call[0] == 'tablecell_open' || $call[0] == 'tableheader_open' ) { + + $lastCell = $key; + + } else if ( $call[0] == 'table_align' ) { + + // If the previous element was a cell open, align right + if ( $this->tableCalls[$key-1][0] == 'tablecell_open' || $this->tableCalls[$key-1][0] == 'tableheader_open' ) { + $this->tableCalls[$key-1][1][1] = 'right'; + + // If the next element if the close of an element, align either center or left + } else if ( $this->tableCalls[$key+1][0] == 'tablecell_close' || $this->tableCalls[$key+1][0] == 'tableheader_close' ) { + if ( $this->tableCalls[$lastCell][1][1] == 'right' ) { + $this->tableCalls[$lastCell][1][1] = 'center'; + } else { + $this->tableCalls[$lastCell][1][1] = 'left'; + } + + } + + // Now convert the whitespace back to cdata + $this->tableCalls[$key][0] = 'cdata'; + + } else if ( $call[0] == 'colspan' ) { + + $this->tableCalls[$key-1][1][0] = FALSE; + + for($i = $key-2; $i > $lastRow; $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; + } + } + + foreach ( $toDelete as $delete ) { + unset($this->tableCalls[$delete]); + } + + $this->tableCalls = array_values($this->tableCalls); + } +} + +//------------------------------------------------------------------------ +class Doku_Handler_Section { + + function process($calls) { + + $sectionCalls = array(); + $inSection = FALSE; + + foreach ( $calls as $call ) { + + if ( $call[0] == 'header' ) { + + if ( $inSection ) { + $sectionCalls[] = array('section_close',array(), $call[2]); + } + + $sectionCalls[] = $call; + $sectionCalls[] = array('section_open',array($call[1][1]), $call[2]); + $inSection = TRUE; + + } else { + $sectionCalls[] = $call; + } + } + + if ( $inSection ) { + $sectionCalls[] = array('section_close',array(), $call[2]); + } + + return $sectionCalls; + } + +} + +//------------------------------------------------------------------------ +class Doku_Handler_Block { + + var $calls = array(); + + var $blockStack = array(); + + var $inParagraph = FALSE; + var $atStart = TRUE; + + // Blocks don't contain linefeeds + var $blockOpen = array( + 'header', + 'listu_open','listo_open','listitem_open', + 'table_open','tablerow_open','tablecell_open','tableheader_open', + 'quote_open', + 'section_open', // Needed to prevent p_open between header and section_open + 'code','file','php','html','hr','preformatted', + ); + + var $blockClose = array( + 'header', + 'listu_close','listo_close','listitem_close', + 'table_close','tablerow_close','tablecell_close','tableheader_close', + 'quote_close', + 'section_close', // Needed to prevent p_close after section_close + 'code','file','php','html','hr','preformatted', + ); + + // Stacks can contain linefeeds + var $stackOpen = array( + 'footnote_open','section_open', + ); + + var $stackClose = array( + 'footnote_close','section_close', + ); + + function process($calls) { + foreach ( $calls as $key => $call ) { + + // Process blocks which are stack like... (contain linefeeds) + if ( in_array($call[0],$this->stackOpen ) ) { + /* + if ( $this->atStart ) { + $this->calls[] = array('p_open',array(), $call[2]); + $this->atStart = FALSE; + $this->inParagraph = TRUE; + } + */ + $this->calls[] = $call; + + // Hack - footnotes shouldn't immediately contain a p_open + if ( $call[0] != 'footnote_open' ) { + $this->addToStack(); + } else { + $this->addToStack(FALSE); + } + continue; + } + + if ( in_array($call[0],$this->stackClose ) ) { + + if ( $this->inParagraph ) { + $this->calls[] = array('p_close',array(), $call[2]); + } + $this->calls[] = $call; + $this->removeFromStack(); + continue; + } + + if ( !$this->atStart ) { + + if ( $call[0] == 'eol' ) { + + if ( $this->inParagraph ) { + $this->calls[] = array('p_close',array(), $call[2]); + } + $this->calls[] = array('p_open',array(), $call[2]); + $this->inParagraph = TRUE; + + } else { + + $storeCall = TRUE; + + if ( $this->inParagraph && in_array($call[0], $this->blockOpen) ) { + $this->calls[] = array('p_close',array(), $call[2]); + $this->inParagraph = FALSE; + $this->calls[] = $call; + $storeCall = FALSE; + } + + if ( in_array($call[0], $this->blockClose) ) { + if ( $this->inParagraph ) { + $this->calls[] = array('p_close',array(), $call[2]); + $this->inParagraph = FALSE; + } + if ( $storeCall ) { + $this->calls[] = $call; + $storeCall = FALSE; + } + + // This really sucks and suggests this whole class sucks but... + if ( isset($calls[$key+1]) + && + !in_array($calls[$key+1][0], $this->blockOpen) + && + !in_array($calls[$key+1][0], $this->blockClose) + ) { + + $this->calls[] = array('p_open',array(), $call[2]); + $this->inParagraph = TRUE; + } + } + + if ( $storeCall ) { + $this->calls[] = $call; + } + + } + + + } else { + + // Unless there's already a block at the start, start a paragraph + if ( !in_array($call[0],$this->blockOpen) ) { + $this->calls[] = array('p_open',array(), $call[2]); + if ( $call[0] != 'eol' ) { + $this->calls[] = $call; + } + $this->atStart = FALSE; + $this->inParagraph = TRUE; + } else { + $this->calls[] = $call; + $this->atStart = FALSE; + } + + } + + } + + if ( $this->inParagraph ) { + if ( $call[0] == 'p_open' ) { + // Ditch the last call + array_pop($this->calls); + } else if ( !in_array($call[0], $this->blockClose) ) { + $this->calls[] = array('p_close',array(), $call[2]); + } else { + $last_call = array_pop($this->calls); + $this->calls[] = array('p_close',array(), $call[2]); + $this->calls[] = $last_call; + } + } + + return $this->calls; + } + + function addToStack($newStart = TRUE) { + $this->blockStack[] = array($this->atStart, $this->inParagraph); + $this->atStart = $newStart; + $this->inParagraph = FALSE; + } + + function removeFromStack() { + $state = array_pop($this->blockStack); + $this->atStart = $state[0]; + $this->inParagraph = $state[1]; + } +} +//------------------------------------------------------------------------ +define('DOKU_TOC_OPEN',1); +define('DOKU_TOCBRANCH_OPEN',2); +define('DOKU_TOCITEM_OPEN',3); +define('DOKU_TOC_ELEMENT',4); +define('DOKU_TOCITEM_CLOSE',5); +define('DOKU_TOCBRANCH_CLOSE',6); +define('DOKU_TOC_CLOSE',7); + +class Doku_Handler_Toc { + + var $calls = array(); + var $tocStack = array(); + var $toc = array(); + var $numHeaders = 0; + + function process($calls) { + + foreach ( $calls as $call ) { + if ( $call[0] == 'header' && $call[1][1] < 4 ) { + $this->numHeaders++; + $this->addToToc($call); + } + $this->calls[] = $call; + } + + // Complete the table of contents then prepend to the calls + $this->finalizeToc($call); + return $this->calls; + } + + function addToToc($call) { + + $depth = $call[1][1]; + + // If it's the opening item... + if ( count ( $this->toc) == 0 ) { + + $this->addTocCall($call, DOKU_TOC_OPEN); + + for ( $i = 1; $i <= $depth; $i++ ) { + + $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_OPEN); + + if ( $i != $depth ) { + $this->addTocCall(array($call[0],array($call[1][0], $i, TRUE),$call[2]), DOKU_TOCITEM_OPEN); + } else { + $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOCITEM_OPEN); + $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOC_ELEMENT); + } + + $this->tocStack[] = $i; + + } + return; + } + + $currentDepth = end($this->tocStack); + $initialDepth = $currentDepth; + + // Create new branches as needed + if ( $depth > $currentDepth ) { + + for ($i = $currentDepth+1; $i <= $depth; $i++ ) { + $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_OPEN); + // It's just a filler + if ( $i != $depth ) { + $this->addTocCall(array($call[0],array($call[1][0], $i, TRUE),$call[2]), DOKU_TOCITEM_OPEN); + } else { + $this->addTocCall(array($call[0],array($call[1][0], $i),$call[2]), DOKU_TOCITEM_OPEN); + } + $this->tocStack[] = $i; + } + + $currentDepth = $i-1; + + } + + // Going down + if ( $depth < $currentDepth ) { + for ( $i = $currentDepth; $i >= $depth; $i-- ) { + if ( $i != $depth ) { + array_pop($this->tocStack); + $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_CLOSE); + $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCBRANCH_CLOSE); + } else { + $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_CLOSE); + $this->addTocCall(array($call[0],array($call[1][0],$i),$call[2]), DOKU_TOCITEM_OPEN); + $this->addTocCall($call, DOKU_TOC_ELEMENT); + return; + } + } + } + + if ( $depth == $initialDepth ) { + $this->addTocCall($call, DOKU_TOCITEM_CLOSE); + $this->addTocCall($call, DOKU_TOCITEM_OPEN); + } + + $this->addTocCall($call, DOKU_TOC_ELEMENT); + + + } + + function addTocCall($call, $type) { + switch ( $type ) { + case DOKU_TOC_OPEN: + $this->toc[] = array('toc_open',array(),$call[2]); + break; + + case DOKU_TOCBRANCH_OPEN: + $this->toc[] = array('tocbranch_open',array($call[1][1]),$call[2]); + break; + + case DOKU_TOCITEM_OPEN: + if ( isset( $call[1][2] ) ) { + $this->toc[] = array('tocitem_open',array($call[1][1], TRUE),$call[2]); + } else { + $this->toc[] = array('tocitem_open',array($call[1][1]),$call[2]); + } + break; + + case DOKU_TOC_ELEMENT: + $this->toc[] = array('tocelement',array($call[1][1],$call[1][0]),$call[2]); + break; + + case DOKU_TOCITEM_CLOSE: + $this->toc[] = array('tocitem_close',array($call[1][1]),$call[2]); + break; + + case DOKU_TOCBRANCH_CLOSE: + $this->toc[] = array('tocbranch_close',array($call[1][1]),$call[2]); + break; + + case DOKU_TOC_CLOSE: + if ( count($this->toc) > 0 ) { + $this->toc[] = array('toc_close',array(),$call[2]); + } + break; + } + } + + function finalizeToc($call) { + if ( $this->numHeaders < 3 ) { + return; + } + if ( count ($this->tocStack) > 0 ) { + while ( NULL !== ($toc = array_pop($this->tocStack)) ) { + $this->addTocCall(array($call[0],array('',$toc),$call[2]), DOKU_TOCITEM_CLOSE); + $this->addTocCall(array($call[0],array('',$toc),$call[2]), DOKU_TOCBRANCH_CLOSE); + } + } + $this->addTocCall($call, DOKU_TOC_CLOSE); + $this->calls = array_merge($this->toc, $this->calls); + } + +} + +?> |