Re: [cowiki-dev] Extension to coWiki text formatting
Archie Campbell <[email protected]>
| Newsgroups | gmane.comp.php.cowiki.devel |
|---|---|
| Message-ID | <[email protected]> |
Last debugging I'm going to do from my own suggestions. I had a time
getting ampersands to parse / reparse.
They should be ok with these files... :-)
I'm going to work on the math plugin. Something like this...
<plugin TexMath math="y = x / 2">
It will involve compilation from OCaml source of the tex engine. This is
a fairly simple operation, but the dependencies are fairly lengthy. I
quote...
OCaml 3.06 or later is required to compile texvc; this can be acquired
from http://caml.inria.fr/ if your system doesn't have it available.
The makefile requires GNU make.
Rasterization is done via LaTeX, dvips, and ImageMagick. These need
to be installed and in the PATH: latex, dvips, convert
To work properly with rendering non-ASCII Unicode characters, a
supplemental TeX package is needed (cjk-latex in Debian)
However, the OCaml was easy to hack to remove the first item from the
texvc TODO file (requiring image transparency), and it should be simple
to incorporate this widget. texvc is GPL.
I'm going to need a directory for the code (/includes/math) and one for
output (/cowiki/htdocs/math) ?
From then on in it's basically a case of using texvc to find its own
ouput in the htdocs/math directory, named by generated md5 hash of the
input math formula.
Of course, I'm available for Parser / ReParser bugs at any time.
Regards,
Archie
P.S. I can't get HEAD to run properly. I've provided core.conf RESOURCE
strings and removed install_pending, but I get nothing from index.php or
admin.php. Help!
Archie Campbell wrote:
> Er, there were some fairly humourous bugs.
>
> And now there aren't.
>
> P.S. Until you find some more... ;)
>
> Archie Campbell wrote:
>
>>Hi there!
>>
>>I've refactored the WikiParser and the WikiReverseParser.
>>
>>The former parses through a progressive window, and the latter uses the
>>php xml parser.
>>
>>It's quite noticeable how much more condensed the code is, now.
>>
>>I've included 'cowikitest.wiki' so that you can see what the parsers are
>>used to thus far.
>>
>>Please break and comment endlessly.
>>
>>I'm still aiming to close issues, and here's a candid commentary...
>>
>>152 - strikethrough markup. DONE
>>174 - underline markup. DONE
>>195 - add <b>, <i>, <u> tags to markup. ON-REQUEST
>> (should be simple enough, if we tolerate <b> -> ** transformation as
>>run-of-the-mill)
>>196 - justification markup. DONE
>> (er, does <fill> exist in other than dreams?)
>>198 - lists within table cell. DONE
>>208 - monospace markup. DONE
>>
>>There are doubtless many things I've forgotten, and I know that handing
>>over a test file is no excuse for documentation, but I'm not in the mood
>>and it's time for bed. I'll document everything later.
>>
>>Oh, just one thing - colorizeQuote in class.Utility.php is failing to
>>find the COLOR_QUOTE_LEVEL Registry keys, and as a result, <posting>
>>doesn't do much.
>>
>>Regards and good-night.
>>
>>Archie
>>
>
> ------------------------------------------------------------------------
>
> so we can mail * you a copy immediately. * *
>
> * Helping hands: Matt Ho
> *
>
> * * @package parse * @subpackage class * @access public * * @author
> Daniel T. Gorski, * @copyright (C) Daniel T. Gorski, {@link
> http://www.develnet.org} * @license
> http://www.gnu.org/licenses/gpl.html * @version $Revision: 1.36 $ * */
> /** * coWiki - Wiki parser class * * @package parse * @subpackage
> class * @access public * * @author Daniel T. Gorski, * @since coWiki
> 0.3.0 * @author Archie Campbell * @since coWiki 0.3.5 */ class
> WikiParser extends Object { protected static $Instance = null;
> protected $aToc = array(), $aRows = array(), $iRow = 0; protected
> $aEmph = array(); protected $RefNodes = null; private $snug = 0; //
> --------------------------------------------------------------------
> /** * Collect referenced nodes to where the document is linking to * *
> @access protected * * @author Daniel T. Gorski, * @since coWiki 0.3.0
> */ protected function addReferencedNode($Obj) {
> $this->RefNodes->add($Obj); } //
> --------------------------------------------------------------------
> /** * Get referenced nodes * * @access public * * @author Daniel T.
> Gorski, * @since coWiki 0.3.0 */ public function getReferencedNodes()
> { return $this->RefNodes; } /** * Get instance * * @access public * *
> @author Daniel T. Gorski, * @since coWiki 0.3.0 */ public function
> getInstance() { if (!self::$Instance) { self::$Instance = new
> WikiParser; } return self::$Instance; } //
> --------------------------------------------------------------------
> /** * @access protected * * @author Daniel T. Gorski, * @since coWiki
> 0.3.0 */ protected function __construct() { $this->resetEmphasis(); }
> // --- Helper methods
> ------------------------------------------------- /** * @access
> protected * * @author Daniel T. Gorski, * @since coWiki 0.3.0 */
> protected function restoreTokens($sStr){ return $sStr; } /** *
> Generate "toc" (Document Table of Contents) * * @access protected * *
> @author Daniel T. Gorski, * @since coWiki 0.3.0 */ protected function
> buildToc(&$i, &$nDepth) { $sStr = ''; // Iterate though all
> toc-entries while (isset($this->aToc[$i]['DEPTH']) &&
> $this->aToc[$i]['DEPTH'] == $nDepth) { $sAlias =
> # $this->aToc[$i]['TEXT']; $sStr .= ''; $sStr .= ' '.$sAlias.''; $i++;
> // Recurse (indent) if toc-entry is nested if
> (isset($this->aToc[$i]['DEPTH'])) { if ($this->aToc[$i]['DEPTH'] >
> $nDepth) { $sStr .= '
>
> '; $sStr .= $this->buildToc($i, $this->aToc[$i]['DEPTH']); $sStr
> .= '
>
> '; } } $sStr .= '
> '; } return $sStr; } //
> --------------------------------------------------------------------
> /** * parse * * @access public * @param string The wiki source string
> * @return string Wiki XML * * @author Archie Campbell * @since coWiki
> 0.3.5 */ public function parse($sStr) { $sRet = ''; $sContent = '';
> $bList = false; $bTable = false; $bBegin = false; $aList = array();
> $aTable = array(); // {{{ DEBUG }}} Logger::info('Start parsing wiki
> document.'); // Init reference collection $this->RefNodes = new
> Vector; $this->resetEmphasis(); // --- $sStr = trim(escape($sStr)); //
> Replace possible tabulators $sStr = str_replace("\t", ' ', $sStr); //
> Replace possible \r\n or \n\r with \n $sStr = str_replace("\r\n",
> "\n", $sStr); $sStr = str_replace("\n\r", "\n", $sStr); $this->aRows =
> explode( "\n",$sStr."\n" ); $this->iRow = 0; $sRow =
> $this->aRows[$this->iRow]; $bBegin = true; $t = 0; $l = -1; while(
> true ) { $sContent = ''; $aMatches = array(); //table of contents
> alone ^$ if( $bBegin && preg_match( '=^<toc(/?)>$=', $sRow, $aMatches
> )) { $sRet .= "\n\t\t\n"; //plugins }else if( $bBegin && preg_match(
> '=^<plugin +([A-Za-z0-9_.]+)( +([^&]*))?>=i', $sRow, $aMatches )) {
> if(isset($aMatches[3])){ $sRet .= ''; }else{ $sRet .= ''; } //headings
> }else if( $bBegin && preg_match( '=^(\+{1,})\s*(.*)$=s', $sRow,
> $aMatches )) { $sContent = $this->processContent($aMatches[2]); if(
> preg_match( '= ([^<]*)=USx', $sContent, $aLinkMatches ) ) { $sAlias =
> !empty($aLinkMatches[2]) ? $aLinkMatches[2] : $aLinkMatches[1] ; }
> else { $sAlias = $sContent; } $i = sizeof($this->aToc);
> $this->aToc[$i]['TEXT'] = $sAlias; $this->aToc[$i]['DEPTH'] = strlen(
> $aMatches[1] ); $sTag = 'h'.strlen($aMatches[1]); $sRet .= '<'. $sTag
> .'>'. $sContent . ''; //end of table } else if( $bBegin && ($t > 0) &&
> preg_match( '=^</table>=i', $sRow, $aMatches )) { $sParam =
> trim($aTable[$t][0]); if( $sParam == '') { $sRet .= "\n".'
> '.$this->buildTable($aTable[$t]). '
>
> '."\n"; } else { $sRet .= "\n".'
> '. $this->buildTable($aTable[$t]).'
>
> '."\n"; } if( --$t == 0 ){ $bTable = false; } //beginning of table }
> else if( $bBegin && preg_match( '=^<table([^>]*)>$=Ui', $sRow,
> $aMatches )) { $bTable = true; $aTable[++$t] = array(); array_push(
> $aTable[$t], $aMatches[1] ); //beginning of list } else if( $bBegin &&
> preg_match( '=^([\s*#]{0,}[*#]) ([^\n]*)$=', $sRow, $aMatches )) {
> $bList = true; $aList[++$l] = array(); $aList[$l]['TEXT'] =
> $this->processContent( $aMatches[2] ); $aList[$l]['DEPTH'] = strlen(
> $aMatches[1] ); $aList[$l]['TYPE'] = (substr($aMatches[1],-1)=='*'?
> 'ul':'ol'); //horizontal rule } else if ($bBegin && preg_match(
> '=^-{3,}(\s|$)=', $sRow, $aMatches ) ) { $sRet .= '
> ------------------------------------------------------------------------
> '."\n"; /* //noop } else if (preg_match( '=((.*)<noop>)=i', $sRow,
> $aMatches )) { $sContent = $this->processContent( $aMatches[2], false
> ) . ''; $sRow = substr( $sRow, strlen($aMatches[1]) ); do { if
> (preg_match( '=(.*)</noop>=' , $sRow, $aMatches )) { $sContent .=
> $aMatches[1] . ''; break; } else { $sContent .= $sRow; }
> if(++$this->iRowaRows)){ $sRow = $this->aRows[$this->iRow]; } else {
> break; } } while(true); $sRet .= $sContent; */ } else { //end of list
> if($bList) { $d = 0; $sListType = $aList[$d]['TYPE']; $nDepth =
> $aList[$d]['DEPTH']; $sContent .= "\n".''. '<'.$sListType.'>'.
> $this->buildList($d,$nDepth,$aList). ''. ''."\n"; $bList = false;
> $aList = array(); $l = -1; if($bTable){ array_push( $aTable[$t],
> $sContent ); }else{ $sRet .= $sContent; } //more table }else
> if($bTable) { array_push( $aTable[$t], $sRow ); $sRow = ''; //non
> table/list row }else{ $sContent = $this->processContent( $sRow ); if(
> $sContent != '' ){ $sRet .= /*'
>
> '.*/$sContent/*.'
>
> '*/; } $sRow = ''; } } if(isset($aMatches[0])) { $sRow = substr(
> $sRow, strlen( $aMatches[0] ) ); $bBegin = false; } if ( trim($sRow)
> == '' ) { if( ++$this->iRow < sizeof($this->aRows) ) { $sRow =
> $this->aRows[$this->iRow]; $bBegin = true; } else { break; } } } if(
> preg_match( '=^(.*)\n\t\t\n(.*)$=s', $sRet, $aMatches )) { $t = 0; if
> (sizeof($this->aToc) > 0) { $sToc = $this->buildToc($t,
> $this->aToc[$t]['DEPTH']); if ($sToc != '') { $sToc = '
>
> ' . $sToc . '
>
> '; } else { $sToc = ''; } } return $aMatches[1] . $sToc .
> $aMatches[2]; } // {{{ DEBUG }}} Logger::info('Finished parsing wiki
> document.'); return $sRet; } /** * reset Emphasis * * @access
> protected * * @author Archie Campbell * @since coWiki 0.3.5 */
> protected function resetEmphasis() { $this->aEmph = array( "b" =>
> false, "u" => false, "i" => false, "f" => false, "s" => false, "j" =>
> '', "p" => '' ); } /** * processContent * * @access protected *
> @return string * * @author Archie Campbell * @since coWiki 0.3.5 */
> protected function processContent( $sStr ) { $sRet = ''; $aMatches =
> array(); $this->resetEmphasis(); $sStr = ltrim( $sStr ); while( trim(
> $sStr ) != '' ) { //noop if (preg_match( '=^<noop>=is', $sStr,
> $aMatches )) { //greedily munch all until $sStr = substr( $sStr,
> strlen( $aMatches[0] ) ); $sContent = ''; while( !preg_match(
> '=^</noop>=is', $sStr, $aMatches ) ) { preg_match(
> '=([^&]|(\&(?![lg])))*=is', $sStr, $aMatches ); $sContent .=
> $aMatches[0]; $sStr = substr( $sStr, strlen( $aMatches[0] ) ); if (
> $sStr == "\n" ) { if ( ++$this->iRow < sizeof($this->aRows) ) { $sStr
> = $this->aRows[$this->iRow]; } else { $sStr = ''; break; } } } $sRet
> .= '' . $sContent . ''; //justification } else if( preg_match(
> '=^<(/?)(left|center|right)>=i', $sStr, $aMatches )) { $sRet .=
> '<'.$aMatches[1].$aMatches[2].'>'; /* if( $aMatches[1] == '/' &&
> $this->aEmph['j'] == $aMatches[2]{0} ) { $sRet .= '';
> $this->aEmph['j'] = ''; } else if ( $aMatches[1] == '' &&
> $this->aEmph['j'] == '' ) { $sRet .= '<'. $aMatches[2] .'>';
> $this->aEmph['j'] = $aMatches[2]{0}; } */ //pre code posting q start
> at BOL, are greedy until // closing tag anywhere on a line } else if
> (preg_match( '=^<(pre|code|posting|q)>=i', $sStr, $aMatch ) ) { $sStr
> = substr( $sStr, strlen($aMatch[0]) ); do { if (preg_match(
> '=(.*)</'.$aMatch[1].'>=i', $sStr, $aMatches )) { $sRet .=
> '<'.$aMatch[1].'>'.$sContent . $aMatches[1].''; break; } else {
> $sContent .= $sStr ."\n"; } if( ++$this->iRow < sizeof($this->aRows)
> ){ $sStr = $this->aRows[$this->iRow]; }else{ break; } } while(true);
> if( $this->iRow == sizeof($this->aRows) ) { $sRet .=
> '<'.$aMatch[1].'>'. $sContent . ''; break; } //line-break }else if(
> preg_match( '=^<br([ /]+)>=is', $sStr, $aMatches )) { $sRet .= '
> '; //variables }else if( preg_match( '=^%([A-Z0-9_]+)%=U', $sStr,
> $aMatches )) { $sRet .= ''; //links (()()) & [[][]] }else if(
> preg_match( '=^\(\(([^\(\)]+|[^\(\)]+' . '\([^\(\)]+\)[^\(\)]*|' .
> '[^\(\)]+\)\([^\(\)]+)\)\)=Usx', $sStr, $aMatches )) { $sRet .=
> $this->createLinkElement( $aMatches[1] ); /* $aLink = explode( ')(',
> $aMatches[1] ); $aLink[0] = str_replace( '|', '¦', $aLink[0] ); $sRet
> .= ' '; if ( isset( $aLink[1]) ) { $sRet .= trim($aLink[1]); } else {
> $sRet .= trim($aLink[0]); } $sRet .= ''; */ /* //WikiWords }else if(
> preg_match( '=^([A-Z][a-z]+([A-Z][a-z]+)+)=s', $sStr, $aMatches )) {
> $sLink = trim(preg_replace( '=([A-Z])=', ' \1', $aMatches[1] )); $sRet
> .= ' ' . $sLink . ''; */ /* //urls }else if( preg_match( '=^<url(\s+)(
> (http://|https://|ftp://|mailto:|news:) ([-_A-Z0-9\S]*)
> (\*\s|\=\s|"|<|>|<|>|\(|\)|\s|$)?? )>=six', $sStr, $aMatches ) ) { */
> //URIs }else if( preg_match(
> '=^(http://|https://|ftp://|mailto:|news:) ([-_A-Z0-9\S]*)
> (\*\s|\=\s|"|<|>|<|>|\(|\)|\s|$)?? =six', $sStr, $aMatches )) { $sRet
> .= ''; //subscript, superscript }else if( preg_match(
> '=^<(/??)(sub|sup)>=is', $sStr, $aMatches ) ) { if( $aMatches[1] ==
> '/' && $this->aEmph['p'] == $aMatches[2] ) { $sRet .= '';
> $this->aEmph['p'] = ''; }else if ( $aMatches[1] == '' &&
> $this->aEmph['p'] == '' ) { $sRet .= '<' . $aMatches[2] . '>';
> $this->aEmph['p'] = $aMatches[2]; } //emphasis }else if( preg_match(
> '#^([-=\*_/]{2})#', $sStr, $aMatches ) ) { $sEmph = '';
> switch($aMatches[1]{0}){ case '-':
> $sEmph=($this->aEmph["s"]?'/':'').'strike'; $this->aEmph["s"] =
> !$this->aEmph["s"]; break; case '=':
> $sEmph=($this->aEmph["f"]?'/':'').'tt'; $this->aEmph["f"] =
> !$this->aEmph["f"]; break; case '*':
> $sEmph=($this->aEmph["b"]?'/':'').'b'; $this->aEmph["b"] =
> !$this->aEmph["b"]; break; case '_':
> $sEmph=($this->aEmph["u"]?'/':'').'u'; $this->aEmph["u"] =
> !$this->aEmph["u"]; break; case '/':
> $sEmph=($this->aEmph["i"]?'/':'').'i'; $this->aEmph["i"] =
> !$this->aEmph["i"]; break; } $sRet .= '<'.$sEmph.'>'; }else if(
> preg_match( '#^((([-=\*_/]{1})(?!\3))|[^-=\*_/\&]|(\&(?![lg])))*#',
> $sStr, $aMatches ) ) { $sRet .= $aMatches[0]; } if(
> isset($aMatches[0]) ) { $sStr = substr( $sStr, strlen($aMatches[0]) );
> } } return $sRet; } /** * &build table * * @access protected * @return
> string * * @author Archie Campbell * @since coWiki 0.3.5 */ protected
> function buildTable( &$aTable ) { $sRet = ''; $sContent = ''; $sRow =
> ''; $bPriorRow = false; $aMatches = array(); $j = 0; $v =
> sizeof($aTable); while( ++$j < $v ) { /* if
> (!strlen(trim($aTable[$j]))) { return ''.$sStr.''; } */ //rows
> expected to hold table syntax (pipes), may instead //make room for
> ongoing content (tables, lists, etc) $sRow = $aTable[$j]; //find table
> syntax within a (long?) row while( strlen($sRow) && preg_match(
> '=^([\|!][-+]?)( {0,1}[^\|!\n]*)?=', $sRow, $aMatches) ) { //found
> exploded row begins with pipe syntax, poss params $sRow = substr(
> $sRow, strlen($aMatches[0]) ); $nType = 0; //tables made of cells,
> headers, rows, caption if ($aMatches[1]=="|") { $sRet .= '\n";
> continue 1; } //content can be large, room for it here $sContent = '';
> if (isset($aMatches[2]) && $aMatches[2]{0}==' ') { //left a space to
> denote no params $sRet .= '>'; $sContent .= substr($aMatches[2],1);
> //expect double pipe, bang or newline } elseif (isset($aMatches[2])) {
> //got params. look for pipe syntax that blocked last preg $sRet .= '
> '.unescape($aMatches[2]).'>'; if
> (preg_match('=^\|([^\|!\n]*)=',$sRow,$aMatches)) { //got rest of pipe
> syntax (...|text) $sContent .= $aMatches[1]; $sRow = substr( $sRow,
> strlen($aMatches[0]) ); } } else { $sRet .= '>'; } //look ahead now.
> if (trim($sRow) == '') { //look ahead to close our content while(
> ++$j<$v ){ $sRow = $aTable[$j]; //pipe syntax (will force $sContent)
> to continue if ($sRow{0} == '|' || $sRow{0} == '!') { $sRet .=
> $this->processContent($sContent); switch($nType){ case 1: $sRet .=
> "\n"; break; case 2: $sRet .= "\n"; break; } --$j; continue 3; } else
> { //non-pipe (ever-increasing) content $sContent .= $sRow; } } $j =
> $v; $sRet .= $this->processContent($sContent); switch($nType){ case 1:
> $sRet .= "\n"; break; case 2: $sRet .= "\n"; break; } continue 2; }
> elseif ( ( $sRow{0} == "|" || $sRow{0} == "!") && ($sRow{1} == "|" ||
> $sRow{1} == "!") ) { $sRow = substr($sRow, 1); $sRet .=
> $this->processContent($sContent); switch($nType){ case 1: $sRet .=
> "\n"; break; case 2: $sRet .= "\n"; break; } continue 1; } } } if(
> $bPriorRow ) { return $sRet.""; } else { return $sRet; } } /** *
> &build list * * @access protected * @return string * * @author Archie
> Campbell * @since coWiki 0.3.5 */ protected function buildList(&$i,
> &$nDepth, &$aList) { $sStr = ''; // Iterate though all list-entries
> while (isset($aList[$i]['DEPTH']) && $aList[$i]['DEPTH'] == $nDepth) {
> # $sStr .= ''; $sStr .= $aList[$i]['TEXT']; $i++; // Recurse (indent) if
> list-entry is nested if (isset($aList[$i]['DEPTH'])) { if
> ($aList[$i]['DEPTH'] > $nDepth) { // Correct items that are indented
> too deep $aList[$i]['DEPTH'] = $nDepth + 1; $sListType =
> $aList[$i]['TYPE']; $sStr .= '<'.$sListType.'>'; $sStr .=
> $this->buildList( $i, $aList[$i]['DEPTH'], $aList ); $sStr .= ''; } }
> $sStr .= '
> '; } return $sStr; } //
> -------------------------------------------------------------------- }
> // of class /* Prospero: That cross you wear around your neck; is it
> only a decoration, or are you a true Christian believer? Francesca:
> Yes, I believe - truly. Prospero: Then I want you to remove it at
> once! - and never to wear it within this castle again! Do you know how
> a falcon is trained my dear? Her eyes are sown shut. Blinded
> temporarily she suffers the whims of her God patiently, until her will
> is submerged and she learns to serve - as your God taught and blinded
> you with crosses. Francesca: You had me take off my cross because it
> offended ... Prospero: It offended no-one. No - it simply appears to
> me to be discourteous to ... to wear the symbol of a deity long dead.
> My ancestors tried to find it. And to open the door that seperates us
> from our Creator. Francesca: But you need no doors to find God. If you
> believe ... Prospero: Believe?! If you believe you are gullible. Can
> you look around this world and believe in the goodness of a god who
> rules it? Famine, Pestilence, War, Disease and Death! They rule this
> world. Francesca: There is also love and life and hope. Prospero: Very
> little hope I assure you. No. If a god of love and life ever did exist
> ... he is long since dead. Someone ... something rules in his place.
> */ ?>
> ------------------------------------------------------------------------
>
> so we can mail * you a copy immediately. * * @package parse *
> @subpackage class * @access public * * @author Daniel T. Gorski, *
> @copyright (C) Daniel T. Gorski, {@link http://www.develnet.org} *
> @license http://www.gnu.org/licenses/gpl.html * @version $Revision:
> 1.17 $ * */ /** * coWiki - Wiki reverse parser class * * @package
> parse * @subpackage class * @access public * * @author Daniel T.
> Gorski, * @since coWiki 0.3.0 * @author Archie Campbell * @since
> coWiki 0.3.5 * * @todo [D11N] Complete documentation */ class
> WikiReverseParser extends Object { protected static $Instance = null,
> $Context = null, $DocDAO = null; private $rParser = null; private
> $ignoreToc = false, $aList = array(), $nDepth = 0, $aType = array(),
> $aTable = array(), // $nTable = null; $sLink = array(), $fLink =
> array(), $aState = array(0), $sContent = '', $aRow = array(),
> $nListIndent = null; //
> --------------------------------------------------------------------
> /** * Get instance * * @access public * @return mixed * * @author
> Daniel T. Gorski, * @since coWiki 0.3.0 * * @todo [D11N] Check
> description * @todo [D11N] Check return type */ public function
> getInstance() { if (!self::$Instance) { self::$Instance = new
> WikiReverseParser; } return self::$Instance; } //
> --------------------------------------------------------------------
> /** * Parse * * @access protected * @param string * @return void * *
> @author Daniel T. Gorski, * @since coWiki 0.3.0 * * @todo [D11N] Check
> description */ protected function __construct() { $this->Context =
> RuntimeContext::getInstance(); $this->DocDAO =
> $this->Context->getDocumentDAO(); } //
> --------------------------------------------------------------------
> // FIX: THIS METHOD DO NOT COVER ALL POSSIBLE OCCURANCES OF DELIMITERS
> // YET! THIS HAS TO BE CHECKED AND FIXED. /** * If a string reference
> contains delimiters, escape them with * * @access protected * @param
> string * @return mixed * * @author Daniel T. Gorski, * @since coWiki
> 0.3.0 * * @todo [D11N] Check return type */ protected function
> noopDelimiters(&$sStr) { // Escape leading delimiters if
> (substr($sStr, 0, 2) == '()') { $sStr =
> '<noop>()</noop>'.substr($sStr, 2); } else if (substr($sStr, 0, 1) ==
> '(') { $sStr = '<noop>(</noop>'. substr($sStr, 1); } // Escape
> trailing delimiters if (substr($sStr, -2) == '()') { $sStr =
> substr($sStr, 0, -2).'<noop>()</noop>'; } else if (substr($sStr, -1)
> == ')') { $sStr = substr($sStr, 0, -1).'<noop>)</noop>'; } // Escape
> double closing $sStr = str_replace('))', '<noop>))</noop>', $sStr); //
> Replace web/document delimiter return str_replace('¦', '|', $sStr); }
> //
> --------------------------------------------------------------------
> /** * Helper functions * * @author Archie Campbell * @since coWiki
> 0.3.5 * */ protected function appendRow($sStr){
> array_push($this->aRow,$sStr); $this->clearContent(); } protected
> function clearContent(){ $this->sContent = ''; } protected function
> clearRow() { if($this->sContent!=''){
> $this->appendRow($this->sContent); } } protected function
> textListItem( $sData ) { $this->aList[sizeof($this->aList)-1]['TEXT']
> .= $sData; } protected function pushTableItem( $sData ) {
> array_push($this->aTable[sizeof($this->aTable)-1],$sData); } /** *
> Parse * * @access public * @param string * @return mixed * * @author
> Archie Campbell * @since coWiki 0.3.5 * */ public function
> parse($sStr) { //return $this->dummy($sStr); if
> (!function_exists('xml_parser_create')) { return false; }
> $this->rParser = @xml_parser_create(); if (!$this->rParser) { return
> false; } xml_parser_set_option($this->rParser,
> XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($this->rParser,
> XML_OPTION_SKIP_WHITE, 1); // Replace possible tabulators $sStr =
> str_replace("\t", ' ', $sStr); // Replace possible \r\n or \n\r with
> \n $sStr = str_replace("\r\n", "\n", $sStr); $sStr =
> str_replace("\n\r", "\n", $sStr); // Prepare XML, add root element
> $sStr = '' . $sStr . ''; xml_set_default_handler( $this->rParser,
> array(&$this,'xml_default_handler') ); xml_set_element_handler(
> $this->rParser, array(&$this,'xml_start_handler'),
> array(&$this,'xml_end_handler') ); xml_set_character_data_handler(
> $this->rParser, array(&$this,'xml_cdata_handler') ); if(!xml_parse(
> $this->rParser, $sStr )){ echo
> xml_error_string(xml_get_error_code($this->rParser)); } return
> implode("\n",$this->aRow); } /** * xml element start handler * *
> @access protected * * @author Archie Campbell * @since coWiki 0.3.5 *
> */ protected function xml_start_handler( &$parser, $sName, $aAttrib )
> { if($this->ignoreToc){ return; } switch($sName){ case 'document':
> $this->aRow = array(); break; case 'toc': $this->clearRow();
> $this->ignoreToc = true; break; case 'plugin': $this->clearRow();
> $sStr = '1){ foreach($aAttrib as $attr => $value){ if($attr !=
> 'name'){ $sStr .= ' '.$attr.'='.$value; } } } $sStr .= '>';
> $this->appendRow($sStr); break; //table syntax case 'table':
> $this->clearRow(); array_push($this->aState,6);/*WRP_TABL*/
> array_push($this->aTable,array($aAttrib));//[][0] break; case
> 'caption': array_push($this->aState,7);/*WRP_CAPN*/
> $this->clearContent(); break; case 'tr':
> array_push($this->aState,8);/*WRP_TROW*/ $sStr = '|-';
> if(sizeof($aAttrib)){ foreach($aAttrib as $attr => $value){ $sStr .=
> $attr.'="'.$value.'" '; } } $this->pushTableItem( $sStr ); break; case
> 'th': array_push($this->aState,9);/*WRP_THED*/ $sStr = '!';
> if(sizeof($aAttrib)){ foreach($aAttrib as $attr => $value){ $sStr .=
> $attr.'="'.$value.'" '; } } $sStr .= ' '; $this->pushTableItem( $sStr
> ); break; case 'td': array_push($this->aState,10);/*WRP_TCEL*/ $sStr =
> '|'; if(sizeof($aAttrib)){ foreach($aAttrib as $attr => $value){ $sStr
> .= $attr.'="'.$value.'" '; } } $sStr .= ' '; $this->pushTableItem(
> $sStr ); break; //list syntax case 'list': $this->clearRow();
> array_push($this->aState,1);/*WRP_LIST*/ break; case 'ol': case 'ul':
> $this->nDepth++; array_push($this->aType,$sName); break; case 'li':
> switch(end($this->aState)){ case 1: if(strlen($this->sContent)){
> $this->textListItem($this->sContent); } array_push( $this->aList,
> array( 'DEPTH'=>$this->nDepth, 'TYPE'=>(end($this->aType)=='ol'?
> "#":"*"), 'TEXT'=>'' )); break; default: echo $sName . "insanity";
> exit; } $this->clearContent(); break; //paragraphs case 'p':
> $this->clearRow(); break; //noop case 'noop': $this->sContent .= '';
> array_push($this->aState,5); break; //justification case 'left': case
> 'right': case 'center': $this->sContent .= '<'.$sName.'>'; break;
> //pre, code, posting, quote case 'pre': case 'code': case 'posting':
> case 'q': $this->clearRow(); array_push($this->aState,4); break;
> //line-break case 'br': $this->clearRow(); break; //variables case
> 'var': $this->sContent .= "%".$aAttrib['name']."%"; break; //headings
> case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
> array_push($this->aState,3); break; //wiki links case 'uri':
> $this->sContent .= $aAttrib['strref']; break; case 'link':
> array_push($this->aState,2); if( isset($aAttrib['strref']) ) {
> $this->fLink = array(&$this, 'buildStrRefLink'); $this->sLink[1] =
> $aAttrib['strref']; }else if( isset($aAttrib['href']) ) { $this->fLink
> = array(&$this, 'buildHyperRefLink'); $this->sLink[1] =
> $aAttrib['href']; }else if( isset($aAttrib['idref']) ) { $this->fLink
> = array(&$this, 'buildIdRefLink'); $this->sLink[1] =
> $aAttrib['idref']; } break; //subscript & superscript case 'sub': case
> 'sup': $this->sContent .= '<'.$sName.'>'; break; //horizontal rule
> case 'hr':break; //emphasis (strike,tt,b,u,i) case 'strike':
> $this->sContent .= "--"; break; case 'tt': $this->sContent .= "==";
> break; case 'b': $this->sContent .= "**"; break; case 'u':
> $this->sContent .= "__"; break; case 'i': $this->sContent .= '//';
> break; default: echo $sName . " start" . "\n"; } } /** * xml element
> end handler * * @access protected * * @author Archie Campbell * @since
> coWiki 0.3.5 * */ protected function xml_end_handler( &$parser, $sName
> ) { if($this->ignoreToc){ if($sName == 'toc'){ $this->appendRow("");
> $this->ignoreToc = false; return; } } switch($sName){ case 'document':
> $this->clearRow(); break; case 'plugin': break; //table syntax case
> 'table': if(array_pop($this->aState)!=6) { echo "insanity in table";
> exit; } $aTable = array_pop($this->aTable); $sStr = '
> $value ){ $sStr .= " ".$attr.'="'.$value.'"'; } $sStr .=
> (sizeof($aTable)==1?'/>':'>'); $this->appendRow( $sStr ); for ($i = 1;
> $i < sizeof($aTable) ; $i++){ $this->appendRow( $aTable[$i] ); }
> $this->appendRow('
>
> '); break; //caption case 'caption': if(array_pop($this->aState)!=7) {
> echo "insanity in caption"; exit; } $this->pushTableItem( "|+
> ".$this->sContent ); $this->clearContent(); break; case 'tr':
> if(array_pop($this->aState)!=8) { echo "insanity in trow"; exit; }
> break; case 'th': if(array_pop($this->aState)!=9) { echo "insanity in
> thead"; exit; } if($this->sContent != ''){
> $this->pushTableItem($this->sContent); $this->clearContent(); } break;
> case 'td': if(array_pop($this->aState)!=10) { echo "insanity in
> tcell"; exit; } if($this->sContent != ''){
> $this->pushTableItem($this->sContent); $this->clearContent(); } break;
> //list syntax case 'list': if(array_pop($this->aState)!=1) { echo
> "insanity in list"; exit; }; foreach($this->aList as $li){ $sStr =
> str_repeat($li['TYPE'],$li['DEPTH']). " ".$li['TEXT'];
> switch(end($this->aState)){ case 9: case 10: $this->pushTableItem(
> $sStr ); break; default: $this->appendRow( $sStr ); } }
> $this->clearContent(); $this->aList = array(); break; case 'ol': case
> 'ul': $this->nDepth--; array_pop($this->aType); break; case 'li':
> $this->textListItem($this->sContent); $this->clearContent(); break;
> //paragraphs case 'p': $this->appendRow( $this->sContent ); break;
> //noop case 'noop': $this->sContent .= ''; array_pop($this->aState);
> break; //justification case 'left': case 'right': case 'center':
> $this->sContent .= ''; break; //pre, code, posting, quote case 'pre':
> case 'code': case 'posting': case 'q': $this->sContent =
> '<'.$sName.'>'. $this->sContent. ''; array_pop($this->aState);
> if(end($this->aState)!=1){ $this->clearRow(); } break; //line-break
> case 'br': $this->appendRow( '
> ' ); break; //variables case 'var': break; //headings case 'h1': case
> 'h2': case 'h3': case 'h4': case 'h5': case 'h6': $this->appendRow(
> str_repeat("+",substr($sName,1,1)). " ". $this->sContent); array_pop(
> $this->aState ); break; //wiki links case 'uri': break; case 'link':
> array_pop($this->aState); $sStr = call_user_func($this->fLink,
> $this->sLink); $this->sContent .= $sStr; break; //subscript &
> superscript case 'sub': case 'sup': $this->sContent .= ''; break;
> //horizontal rule case 'hr': $this->appendRow("---"); break;
> //emphasis (strike,tt,b,u,i) case 'strike': $this->sContent .= "--";
> break; case 'tt': $this->sContent .= "=="; break; case 'b':
> $this->sContent .= "**"; break; case 'u': $this->sContent .= "__";
> break; case 'i': $this->sContent .= '//'; break; default: echo $sName
> . " end" . "\n"; } } /** * xml element default handler * * @access
> protected * * @author Archie Campbell * @since coWiki 0.3.5 * */
> protected function xml_default_handler( &$parser, $sData ) {
> if(!$this->ignoreToc){ echo "Don't Panic :" . $sData . "\n"; } } /** *
> xml cdata handler * * @access protected * * @author Archie Campbell *
> @since coWiki 0.3.5 * */ protected function xml_cdata_handler(
> &$parser, $sData ) { if($this->ignoreToc){ return; } if( trim($sData)
> != '' ) { switch( end($this->aState) ){ case 1:/*WRP_LIST*/ case
> 3:/*WRP_HEAD*/ case 4:/*WRP_PCPQ*/ case 5:/*WRP_NOOP*/ case
> 7:/*WRP_CAPT*/ case 9:/*WRP_THED*/ $this->sContent .= $sData; break;
> case 2:/*WRP_LINK*/ $this->sLink[2]=trim($sData); break;
> default:/*text*/ $this->sContent .= $sData; } } } //
> --------------------------------------------------------------------
> /** * Build str ref link * * @access protected * @param array *
> @return mixed * * @author Daniel T. Gorski, * @since coWiki 0.3.0 * *
> @todo [D11N] Check description */ protected function
> buildStrRefLink(&$aMatches) { if ($aMatches[1] == $aMatches[2] ||
> trim($aMatches[2]) == '') { return '((' .
> $this->noopDelimiters($aMatches[1]) . '))'; } return '((' .
> $this->noopDelimiters($aMatches[1]) . ')' .'(' . $aMatches[2] . '))';
> } } // of class ?>
>
>------------------------------------------------------------------------
>
>---------------------------------------------------------------------
>To unsubscribe, e-mail: [email protected]
>For additional commands, e-mail: [email protected]
>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
class.WikiParser.php
(text/html, 21.2 KB)
<?php /** * * $Id: class.WikiParser.php,v 1.36 2005/02/14 22:23:53 dgorski Exp $ * * This file is part of coWiki. coWiki is free software under the terms of * the GNU General Public License (GPL). Read the LICENSE file. If you did * not receive a copy of the license and are not able to obtain it through * the internet, please send a note to <[email protected]> so we can mail * you a copy immediately. * * <pre> * Helping hands: Matt Ho <[email protected]> * </pre> * * @package parse * @subpackage class * @access public * * @author Daniel T. Gorski, <[email protected]> * @copyright (C) Daniel T. Gorski, {@link http://www.develnet.org} * @license http://www.gnu.org/licenses/gpl.html * @version $Revision: 1.36 $ * */ /** * coWiki - Wiki parser class * * @package parse * @subpackage class * @access public * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 */ class WikiParser extends Object { protected static $Instance = null; protected $aToc = array(), $aRows = array(), $iRow = 0; protected $aEmph = array(); protected $RefNodes = null; private $snug = 0; // -------------------------------------------------------------------- /** * Collect referenced nodes to where the document is linking to * * @access protected * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 */ protected function addReferencedNode($Obj) { $this->RefNodes->add($Obj); } // -------------------------------------------------------------------- /** * Get referenced nodes * * @access public * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 */ public function getReferencedNodes() { return $this->RefNodes; } /** * Get instance * * @access public * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 */ public function getInstance() { if (!self::$Instance) { self::$Instance = new WikiParser; } return self::$Instance; } // -------------------------------------------------------------------- /** * @access protected * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 */ protected function __construct() { $this->resetEmphasis(); } // --- Helper methods ------------------------------------------------- /** * @access protected * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 */ protected function restoreTokens($sStr){ return $sStr; } /** * Generate "toc" (Document Table of Contents) * * @access protected * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 */ protected function buildToc(&$i, &$nDepth) { $sStr = ''; // Iterate though all toc-entries while (isset($this->aToc[$i]['DEPTH']) && $this->aToc[$i]['DEPTH'] == $nDepth) { $sAlias = $this->aToc[$i]['TEXT']; $sStr .= '<li>'; $sStr .= '<link topicref="'.($i+1).'">'.$sAlias.'</link>'; $i++; // Recurse (indent) if toc-entry is nested if (isset($this->aToc[$i]['DEPTH'])) { if ($this->aToc[$i]['DEPTH'] > $nDepth) { $sStr .= '<ul>'; $sStr .= $this->buildToc($i, $this->aToc[$i]['DEPTH']); $sStr .= '</ul>'; } } $sStr .= '</li>'; } return $sStr; } // -------------------------------------------------------------------- /** * parse * * @access public * @param string The wiki source string * @return string Wiki XML * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 */ public function parse($sStr) { $sRet = ''; $sContent = ''; $bList = false; $bTable = false; $bBegin = false; $aList = array(); $aTable = array(); // {{{ DEBUG }}} Logger::info('Start parsing wiki document.'); // Init reference collection $this->RefNodes = new Vector; $this->resetEmphasis(); // --- $sStr = trim(escape($sStr)); // Replace possible tabulators $sStr = str_replace("\t", ' ', $sStr); // Replace possible \r\n or \n\r with \n $sStr = str_replace("\r\n", "\n", $sStr); $sStr = str_replace("\n\r", "\n", $sStr); $this->aRows = explode( "\n",$sStr."\n" ); $this->iRow = 0; $sRow = $this->aRows[$this->iRow]; $bBegin = true; $t = 0; $l = -1; while( true ) { $sContent = ''; $aMatches = array(); //table of contents alone ^<toc/>$ if( $bBegin && preg_match( '=^<toc(/?)>$=', $sRow, $aMatches )) { $sRet .= "\n\t\t\n"; //plugins }else if( $bBegin && preg_match( '=^<plugin +([A-Za-z0-9_.]+)( +([^&]*))?>=i', $sRow, $aMatches )) { if(isset($aMatches[3])){ $sRet .= '<plugin name="'. $aMatches[1].'" '.$aMatches[3].'/>'; }else{ $sRet .= '<plugin name="'.$aMatches[1].'"/>'; } //headings }else if( $bBegin && preg_match( '=^(\+{1,})\s*(.*)$=s', $sRow, $aMatches )) { $sContent = $this->processContent($aMatches[2]); if( preg_match( '=<link\s+strref\="([^>]+)"> ([^<]*)</link>=USx', $sContent, $aLinkMatches ) ) { $sAlias = !empty($aLinkMatches[2]) ? $aLinkMatches[2] : $aLinkMatches[1] ; } else { $sAlias = $sContent; } $i = sizeof($this->aToc); $this->aToc[$i]['TEXT'] = $sAlias; $this->aToc[$i]['DEPTH'] = strlen( $aMatches[1] ); $sTag = 'h'.strlen($aMatches[1]); $sRet .= '<'. $sTag .'>'. $sContent . '</'. $sTag .'>'; //end of table } else if( $bBegin && ($t > 0) && preg_match( '=^</table>=i', $sRow, $aMatches )) { $sParam = trim($aTable[$t][0]); if( $sParam == '') { $sRet .= "\n".'<table>'.$this->buildTable($aTable[$t]). '</table>'."\n"; } else { $sRet .= "\n".'<table '.trim(unescape($sParam)).'>'. $this->buildTable($aTable[$t]).'</table>'."\n"; } if( --$t == 0 ){ $bTable = false; } //beginning of table } else if( $bBegin && preg_match( '=^<table([^>]*)>$=Ui', $sRow, $aMatches )) { $bTable = true; $aTable[++$t] = array(); array_push( $aTable[$t], $aMatches[1] ); //beginning of list } else if( $bBegin && preg_match( '=^([\s*#]{0,}[*#]) ([^\n]*)$=', $sRow, $aMatches )) { $bList = true; $aList[++$l] = array(); $aList[$l]['TEXT'] = $this->processContent( $aMatches[2] ); $aList[$l]['DEPTH'] = strlen( $aMatches[1] ); $aList[$l]['TYPE'] = (substr($aMatches[1],-1)=='*'? 'ul':'ol'); //horizontal rule } else if ($bBegin && preg_match( '=^-{3,}(\s|$)=', $sRow, $aMatches ) ) { $sRet .= '<hr/>'."\n"; /* //noop } else if (preg_match( '=((.*)<noop>)=i', $sRow, $aMatches )) { $sContent = $this->processContent( $aMatches[2], false ) . '<noop>'; $sRow = substr( $sRow, strlen($aMatches[1]) ); do { if (preg_match( '=(.*)</noop>=' , $sRow, $aMatches )) { $sContent .= $aMatches[1] . '</noop>'; break; } else { $sContent .= $sRow; } if(++$this->iRow<sizeof($this->aRows)){ $sRow = $this->aRows[$this->iRow]; } else { break; } } while(true); $sRet .= $sContent; */ } else { //end of list if($bList) { $d = 0; $sListType = $aList[$d]['TYPE']; $nDepth = $aList[$d]['DEPTH']; $sContent .= "\n".'<list>'. '<'.$sListType.'>'. $this->buildList($d,$nDepth,$aList). '</'.$sListType.'>'. '</list>'."\n"; $bList = false; $aList = array(); $l = -1; if($bTable){ array_push( $aTable[$t], $sContent ); }else{ $sRet .= $sContent; } //more table }else if($bTable) { array_push( $aTable[$t], $sRow ); $sRow = ''; //non table/list row }else{ $sContent = $this->processContent( $sRow ); if( $sContent != '' ){ $sRet .= /*'<p>'.*/$sContent/*.'</p>'*/; } $sRow = ''; } } if(isset($aMatches[0])) { $sRow = substr( $sRow, strlen( $aMatches[0] ) ); $bBegin = false; } if ( trim($sRow) == '' ) { if( ++$this->iRow < sizeof($this->aRows) ) { $sRow = $this->aRows[$this->iRow]; $bBegin = true; } else { break; } } } if( preg_match( '=^(.*)\n\t\t\n(.*)$=s', $sRet, $aMatches )) { $t = 0; if (sizeof($this->aToc) > 0) { $sToc = $this->buildToc($t, $this->aToc[$t]['DEPTH']); if ($sToc != '') { $sToc = '<toc><ul>' . $sToc . '</ul></toc>'; } else { $sToc = '<toc/>'; } } return $aMatches[1] . $sToc . $aMatches[2]; } // {{{ DEBUG }}} Logger::info('Finished parsing wiki document.'); return $sRet; } /** * reset Emphasis * * @access protected * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 */ protected function resetEmphasis() { $this->aEmph = array( "b" => false, "u" => false, "i" => false, "f" => false, "s" => false, "j" => '', "p" => '' ); } /** * processContent * * @access protected * @return string * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 */ protected function processContent( $sStr ) { $sRet = ''; $aMatches = array(); $this->resetEmphasis(); $sStr = ltrim( $sStr ); while( trim( $sStr ) != '' ) { //noop if (preg_match( '=^<noop>=is', $sStr, $aMatches )) { //greedily munch all until </noop> $sStr = substr( $sStr, strlen( $aMatches[0] ) ); $sContent = ''; while( !preg_match( '=^</noop>=is', $sStr, $aMatches ) ) { preg_match( '=([^&]|(\&(?![lg])))*=is', $sStr, $aMatches ); $sContent .= $aMatches[0]; $sStr = substr( $sStr, strlen( $aMatches[0] ) ); if ( $sStr == "\n" ) { if ( ++$this->iRow < sizeof($this->aRows) ) { $sStr = $this->aRows[$this->iRow]; } else { $sStr = ''; break; } } } $sRet .= '<noop>' . $sContent . '</noop>'; //justification } else if( preg_match( '=^<(/?)(left|center|right)>=i', $sStr, $aMatches )) { $sRet .= '<'.$aMatches[1].$aMatches[2].'>'; /* if( $aMatches[1] == '/' && $this->aEmph['j'] == $aMatches[2]{0} ) { $sRet .= '</'. $aMatches[2] .'>'; $this->aEmph['j'] = ''; } else if ( $aMatches[1] == '' && $this->aEmph['j'] == '' ) { $sRet .= '<'. $aMatches[2] .'>'; $this->aEmph['j'] = $aMatches[2]{0}; } */ //pre code posting q start at BOL, are greedy until // closing tag anywhere on a line } else if (preg_match( '=^<(pre|code|posting|q)>=i', $sStr, $aMatch ) ) { $sStr = substr( $sStr, strlen($aMatch[0]) ); do { if (preg_match( '=(.*)</'.$aMatch[1].'>=i', $sStr, $aMatches )) { $sRet .= '<'.$aMatch[1].'>'.$sContent . $aMatches[1].'</'.$aMatch[1].'>'; break; } else { $sContent .= $sStr ."\n"; } if( ++$this->iRow < sizeof($this->aRows) ){ $sStr = $this->aRows[$this->iRow]; }else{ break; } } while(true); if( $this->iRow == sizeof($this->aRows) ) { $sRet .= '<'.$aMatch[1].'>'. $sContent . '</'.$aMatch[1].'>'; break; } //line-break }else if( preg_match( '=^<br([ /]+)>=is', $sStr, $aMatches )) { $sRet .= '<br/>'; //variables }else if( preg_match( '=^%([A-Z0-9_]+)%=U', $sStr, $aMatches )) { $sRet .= '<var name="'.$aMatches[1].'"/>'; //links (()()) & [[][]] }else if( preg_match( '=^\(\(([^\(\)]+|[^\(\)]+' . '\([^\(\)]+\)[^\(\)]*|' . '[^\(\)]+\)\([^\(\)]+)\)\)=Usx', $sStr, $aMatches )) { $sRet .= $this->createLinkElement( $aMatches[1] ); /* $aLink = explode( ')(', $aMatches[1] ); $aLink[0] = str_replace( '|', '¦', $aLink[0] ); $sRet .= '<link strref="' . trim($aLink[0]) . '">'; if ( isset( $aLink[1]) ) { $sRet .= trim($aLink[1]); } else { $sRet .= trim($aLink[0]); } $sRet .= '</link>'; */ /* //WikiWords }else if( preg_match( '=^([A-Z][a-z]+([A-Z][a-z]+)+)=s', $sStr, $aMatches )) { $sLink = trim(preg_replace( '=([A-Z])=', ' \1', $aMatches[1] )); $sRet .= '<link strref="' . $sLink . '">' . $sLink . '</link>'; */ /* //urls }else if( preg_match( '=^<url(\s+)( (http://|https://|ftp://|mailto:|news:) ([-_A-Z0-9\S]*) (\*\s|\=\s|"|<|>|<|>|\(|\)|\s|$)?? )>=six', $sStr, $aMatches ) ) { */ //URIs }else if( preg_match( '=^(http://|https://|ftp://|mailto:|news:) ([-_A-Z0-9\S]*) (\*\s|\=\s|"|<|>|<|>|\(|\)|\s|$)?? =six', $sStr, $aMatches )) { $sRet .= '<uri strref="' . $aMatches[1].$aMatches[2] . '"/>'; //subscript, superscript }else if( preg_match( '=^<(/??)(sub|sup)>=is', $sStr, $aMatches ) ) { if( $aMatches[1] == '/' && $this->aEmph['p'] == $aMatches[2] ) { $sRet .= '</' . $aMatches[2] . '>'; $this->aEmph['p'] = ''; }else if ( $aMatches[1] == '' && $this->aEmph['p'] == '' ) { $sRet .= '<' . $aMatches[2] . '>'; $this->aEmph['p'] = $aMatches[2]; } //emphasis }else if( preg_match( '#^([-=\*_/]{2})#', $sStr, $aMatches ) ) { $sEmph = ''; switch($aMatches[1]{0}){ case '-': $sEmph=($this->aEmph["s"]?'/':'').'strike'; $this->aEmph["s"] = !$this->aEmph["s"]; break; case '=': $sEmph=($this->aEmph["f"]?'/':'').'tt'; $this->aEmph["f"] = !$this->aEmph["f"]; break; case '*': $sEmph=($this->aEmph["b"]?'/':'').'b'; $this->aEmph["b"] = !$this->aEmph["b"]; break; case '_': $sEmph=($this->aEmph["u"]?'/':'').'u'; $this->aEmph["u"] = !$this->aEmph["u"]; break; case '/': $sEmph=($this->aEmph["i"]?'/':'').'i'; $this->aEmph["i"] = !$this->aEmph["i"]; break; } $sRet .= '<'.$sEmph.'>'; }else if( preg_match( '#^((([-=\*_/]{1})(?!\3))|[^-=\*_/&]|(&(?![lg])))*#', $sStr, $aMatches ) ) { $sRet .= $aMatches[0]; } if( isset($aMatches[0]) ) { $sStr = substr( $sStr, strlen($aMatches[0]) ); } } return $sRet; } /** * &build table * * @access protected * @return string * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 */ protected function buildTable( &$aTable ) { $sRet = ''; $sContent = ''; $sRow = ''; $bPriorRow = false; $aMatches = array(); $j = 0; $v = sizeof($aTable); while( ++$j < $v ) { /* if (!strlen(trim($aTable[$j]))) { return '<tr valign="top"><td colspan="1">'.$sStr.'</td></tr>'; } */ //rows expected to hold table syntax (pipes), may instead //make room for ongoing content (tables, lists, etc) $sRow = $aTable[$j]; //find table syntax within a (long?) row while( strlen($sRow) && preg_match( '=^([\|!][-+]?)( {0,1}[^\|!\n]*)?=', $sRow, $aMatches) ) { //found exploded row begins with pipe syntax, poss params $sRow = substr( $sRow, strlen($aMatches[0]) ); $nType = 0; //tables made of cells, headers, rows, caption if ($aMatches[1]=="|") { $sRet .= '<td'; $nType = 1; }else if ($aMatches[1]=="!") { $sRet .= '<th'; $nType = 2; }else if ($aMatches[1]=="|-") { if ($bPriorRow) { $sRet .= "</tr>\n<tr"; } else { $sRet .= '<tr'; } $nType = 3; $bPriorRow = true; }else if ($aMatches[1]=="|+") { $sRet .= '<caption>'.trim($aMatches[2])."</caption>\n"; continue 1; } //content can be large, room for it here $sContent = ''; if (isset($aMatches[2]) && $aMatches[2]{0}==' ') { //left a space to denote no params $sRet .= '>'; $sContent .= substr($aMatches[2],1); //expect double pipe, bang or newline } elseif (isset($aMatches[2])) { //got params. look for pipe syntax that blocked last preg $sRet .= ' '.unescape($aMatches[2]).'>'; if (preg_match('=^\|([^\|!\n]*)=',$sRow,$aMatches)) { //got rest of pipe syntax (...|text) $sContent .= $aMatches[1]; $sRow = substr( $sRow, strlen($aMatches[0]) ); } } else { $sRet .= '>'; } //look ahead now. if (trim($sRow) == '') { //look ahead to close our content while( ++$j<$v ){ $sRow = $aTable[$j]; //pipe syntax (will force $sContent) to continue if ($sRow{0} == '|' || $sRow{0} == '!') { $sRet .= $this->processContent($sContent); switch($nType){ case 1: $sRet .= "</td>\n"; break; case 2: $sRet .= "</th>\n"; break; } --$j; continue 3; } else { //non-pipe (ever-increasing) content $sContent .= $sRow; } } $j = $v; $sRet .= $this->processContent($sContent); switch($nType){ case 1: $sRet .= "</td>\n"; break; case 2: $sRet .= "</th>\n"; break; } continue 2; } elseif ( ( $sRow{0} == "|" || $sRow{0} == "!") && ($sRow{1} == "|" || $sRow{1} == "!") ) { $sRow = substr($sRow, 1); $sRet .= $this->processContent($sContent); switch($nType){ case 1: $sRet .= "</td>\n"; break; case 2: $sRet .= "</th>\n"; break; } continue 1; } } } if( $bPriorRow ) { return $sRet."</tr>"; } else { return $sRet; } } /** * &build list * * @access protected * @return string * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 */ protected function buildList(&$i, &$nDepth, &$aList) { $sStr = ''; // Iterate though all list-entries while (isset($aList[$i]['DEPTH']) && $aList[$i]['DEPTH'] == $nDepth) { $sStr .= '<li>'; $sStr .= $aList[$i]['TEXT']; $i++; // Recurse (indent) if list-entry is nested if (isset($aList[$i]['DEPTH'])) { if ($aList[$i]['DEPTH'] > $nDepth) { // Correct items that are indented too deep $aList[$i]['DEPTH'] = $nDepth + 1; $sListType = $aList[$i]['TYPE']; $sStr .= '<'.$sListType.'>'; $sStr .= $this->buildList( $i, $aList[$i]['DEPTH'], $aList ); $sStr .= '</'.$sListType.'>'; } } $sStr .= '</li>'; } return $sStr; } // -------------------------------------------------------------------- } // of class /* Prospero: That cross you wear around your neck; is it only a decoration, or are you a true Christian believer? Francesca: Yes, I believe - truly. Prospero: Then I want you to remove it at once! - and never to wear it within this castle again! Do you know how a falcon is trained my dear? Her eyes are sown shut. Blinded temporarily she suffers the whims of her God patiently, until her will is submerged and she learns to serve - as your God taught and blinded you with crosses. Francesca: You had me take off my cross because it offended ... Prospero: It offended no-one. No - it simply appears to me to be discourteous to ... to wear the symbol of a deity long dead. My ancestors tried to find it. And to open the door that seperates us from our Creator. Francesca: But you need no doors to find God. If you believe ... Prospero: Believe?! If you believe you are gullible. Can you look around this world and believe in the goodness of a god who rules it? Famine, Pestilence, War, Disease and Death! They rule this world. Francesca: There is also love and life and hope. Prospero: Very little hope I assure you. No. If a god of love and life ever did exist ... he is long since dead. Someone ... something rules in his place. */ ?>
class.WikiReverseParser.php
(text/html, 15.8 KB)
<?php /** * * $Id: class.WikiReverseParser.php,v 1.17 2005/04/09 23:34:07 dgorski Exp $ * * This file is part of coWiki. coWiki is free software under the terms of * the GNU General Public License (GPL). Read the LICENSE file. If you did * not receive a copy of the license and are not able to obtain it through * the internet, please send a note to <[email protected]> so we can mail * you a copy immediately. * * @package parse * @subpackage class * @access public * * @author Daniel T. Gorski, <[email protected]> * @copyright (C) Daniel T. Gorski, {@link http://www.develnet.org} * @license http://www.gnu.org/licenses/gpl.html * @version $Revision: 1.17 $ * */ /** * coWiki - Wiki reverse parser class * * @package parse * @subpackage class * @access public * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 * * @todo [D11N] Complete documentation */ class WikiReverseParser extends Object { protected static $Instance = null, $Context = null, $DocDAO = null; private $rParser = null; private $ignoreToc = false, $aList = array(), $nDepth = 0, $aType = array(), $aTable = array(), // $nTable = null; $sLink = array(), $fLink = array(), $aState = array(0), $sContent = '', $aRow = array(), $nListIndent = null; // -------------------------------------------------------------------- /** * Get instance * * @access public * @return mixed * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 * * @todo [D11N] Check description * @todo [D11N] Check return type */ public function getInstance() { if (!self::$Instance) { self::$Instance = new WikiReverseParser; } return self::$Instance; } // -------------------------------------------------------------------- /** * Parse * * @access protected * @param string * @return void * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 * * @todo [D11N] Check description */ protected function __construct() { $this->Context = RuntimeContext::getInstance(); $this->DocDAO = $this->Context->getDocumentDAO(); } // -------------------------------------------------------------------- // FIX: THIS METHOD DO NOT COVER ALL POSSIBLE OCCURANCES OF DELIMITERS // YET! THIS HAS TO BE CHECKED AND FIXED. /** * If a string reference contains delimiters, escape them with <noop> * * @access protected * @param string * @return mixed * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 * * @todo [D11N] Check return type */ protected function noopDelimiters(&$sStr) { // Escape leading delimiters if (substr($sStr, 0, 2) == '()') { $sStr = '<noop>()</noop>'.substr($sStr, 2); } else if (substr($sStr, 0, 1) == '(') { $sStr = '<noop>(</noop>'. substr($sStr, 1); } // Escape trailing delimiters if (substr($sStr, -2) == '()') { $sStr = substr($sStr, 0, -2).'<noop>()</noop>'; } else if (substr($sStr, -1) == ')') { $sStr = substr($sStr, 0, -1).'<noop>)</noop>'; } // Escape double closing $sStr = str_replace('))', '<noop>))</noop>', $sStr); // Replace web/document delimiter return str_replace('¦', '|', $sStr); } // -------------------------------------------------------------------- /** * Helper functions * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 * */ protected function appendRow($sStr){ array_push($this->aRow,$sStr); $this->clearContent(); } protected function clearContent(){ $this->sContent = ''; } protected function clearRow() { if($this->sContent!=''){ $this->appendRow($this->sContent); } } protected function textListItem( $sData ) { $this->aList[sizeof($this->aList)-1]['TEXT'] .= $sData; } protected function pushTableItem( $sData ) { array_push($this->aTable[sizeof($this->aTable)-1],$sData); } /** * Parse * * @access public * @param string * @return mixed * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 * */ public function parse($sStr) { //return $this->dummy($sStr); if (!function_exists('xml_parser_create')) { return false; } $this->rParser = @xml_parser_create(); if (!$this->rParser) { return false; } xml_parser_set_option($this->rParser, XML_OPTION_CASE_FOLDING, 0); xml_parser_set_option($this->rParser, XML_OPTION_SKIP_WHITE, 1); // Replace possible tabulators $sStr = str_replace("\t", ' ', $sStr); // Replace possible \r\n or \n\r with \n $sStr = str_replace("\r\n", "\n", $sStr); $sStr = str_replace("\n\r", "\n", $sStr); // Prepare XML, add root element $sStr = '<document>' . $sStr . '</document>'; xml_set_default_handler( $this->rParser, array(&$this,'xml_default_handler') ); xml_set_element_handler( $this->rParser, array(&$this,'xml_start_handler'), array(&$this,'xml_end_handler') ); xml_set_character_data_handler( $this->rParser, array(&$this,'xml_cdata_handler') ); if(!xml_parse( $this->rParser, $sStr )){ echo xml_error_string(xml_get_error_code($this->rParser)); } return implode("\n",$this->aRow); } /** * xml element start handler * * @access protected * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 * */ protected function xml_start_handler( &$parser, $sName, $aAttrib ) { if($this->ignoreToc){ return; } switch($sName){ case 'document': $this->aRow = array(); break; case 'toc': $this->clearRow(); $this->ignoreToc = true; break; case 'plugin': $this->clearRow(); $sStr = '<plugin '.$aAttrib['name']; if(sizeof($aAttrib)>1){ foreach($aAttrib as $attr => $value){ if($attr != 'name'){ $sStr .= ' '.$attr.'='.$value; } } } $sStr .= '>'; $this->appendRow($sStr); break; //table syntax case 'table': $this->clearRow(); array_push($this->aState,6);/*WRP_TABL*/ array_push($this->aTable,array($aAttrib));//[][0] break; case 'caption': array_push($this->aState,7);/*WRP_CAPN*/ $this->clearContent(); break; case 'tr': array_push($this->aState,8);/*WRP_TROW*/ $sStr = '|-'; if(sizeof($aAttrib)){ foreach($aAttrib as $attr => $value){ $sStr .= $attr.'="'.$value.'" '; } } $this->pushTableItem( $sStr ); break; case 'th': array_push($this->aState,9);/*WRP_THED*/ $sStr = '!'; if(sizeof($aAttrib)){ foreach($aAttrib as $attr => $value){ $sStr .= $attr.'="'.$value.'" '; } } $sStr .= ' '; $this->pushTableItem( $sStr ); break; case 'td': array_push($this->aState,10);/*WRP_TCEL*/ $sStr = '|'; if(sizeof($aAttrib)){ foreach($aAttrib as $attr => $value){ $sStr .= $attr.'="'.$value.'" '; } } $sStr .= ' '; $this->pushTableItem( $sStr ); break; //list syntax case 'list': $this->clearRow(); array_push($this->aState,1);/*WRP_LIST*/ break; case 'ol': case 'ul': $this->nDepth++; array_push($this->aType,$sName); break; case 'li': switch(end($this->aState)){ case 1: if(strlen($this->sContent)){ $this->textListItem($this->sContent); } array_push( $this->aList, array( 'DEPTH'=>$this->nDepth, 'TYPE'=>(end($this->aType)=='ol'? "#":"*"), 'TEXT'=>'' )); break; default: echo $sName . "insanity"; exit; } $this->clearContent(); break; //paragraphs case 'p': $this->clearRow(); break; //noop case 'noop': $this->sContent .= '<noop>'; array_push($this->aState,5); break; //justification case 'left': case 'right': case 'center': $this->sContent .= '<'.$sName.'>'; break; //pre, code, posting, quote case 'pre': case 'code': case 'posting': case 'q': $this->clearRow(); array_push($this->aState,4); break; //line-break case 'br': $this->clearRow(); break; //variables case 'var': $this->sContent .= "%".$aAttrib['name']."%"; break; //headings case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': array_push($this->aState,3); break; //wiki links case 'uri': $this->sContent .= $aAttrib['strref']; break; case 'link': array_push($this->aState,2); if( isset($aAttrib['strref']) ) { $this->fLink = array(&$this, 'buildStrRefLink'); $this->sLink[1] = $aAttrib['strref']; }else if( isset($aAttrib['href']) ) { $this->fLink = array(&$this, 'buildHyperRefLink'); $this->sLink[1] = $aAttrib['href']; }else if( isset($aAttrib['idref']) ) { $this->fLink = array(&$this, 'buildIdRefLink'); $this->sLink[1] = $aAttrib['idref']; } break; //subscript & superscript case 'sub': case 'sup': $this->sContent .= '<'.$sName.'>'; break; //horizontal rule case 'hr':break; //emphasis (strike,tt,b,u,i) case 'strike': $this->sContent .= "--"; break; case 'tt': $this->sContent .= "=="; break; case 'b': $this->sContent .= "**"; break; case 'u': $this->sContent .= "__"; break; case 'i': $this->sContent .= '//'; break; default: echo $sName . " start" . "\n"; } } /** * xml element end handler * * @access protected * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 * */ protected function xml_end_handler( &$parser, $sName ) { if($this->ignoreToc){ if($sName == 'toc'){ $this->appendRow("<toc>"); $this->ignoreToc = false; return; } } switch($sName){ case 'document': $this->clearRow(); break; case 'plugin': break; //table syntax case 'table': if(array_pop($this->aState)!=6) { echo "insanity in table"; exit; } $aTable = array_pop($this->aTable); $sStr = '<table'; foreach($aTable[0] as $attr => $value ){ $sStr .= " ".$attr.'="'.$value.'"'; } $sStr .= (sizeof($aTable)==1?'/>':'>'); $this->appendRow( $sStr ); for ($i = 1; $i < sizeof($aTable) ; $i++){ $this->appendRow( $aTable[$i] ); } $this->appendRow('</table>'); break; //caption case 'caption': if(array_pop($this->aState)!=7) { echo "insanity in caption"; exit; } $this->pushTableItem( "|+ ".$this->sContent ); $this->clearContent(); break; case 'tr': if(array_pop($this->aState)!=8) { echo "insanity in trow"; exit; } break; case 'th': if(array_pop($this->aState)!=9) { echo "insanity in thead"; exit; } if($this->sContent != ''){ $this->pushTableItem($this->sContent); $this->clearContent(); } break; case 'td': if(array_pop($this->aState)!=10) { echo "insanity in tcell"; exit; } if($this->sContent != ''){ $this->pushTableItem($this->sContent); $this->clearContent(); } break; //list syntax case 'list': if(array_pop($this->aState)!=1) { echo "insanity in list"; exit; }; foreach($this->aList as $li){ $sStr = str_repeat($li['TYPE'],$li['DEPTH']). " ".$li['TEXT']; switch(end($this->aState)){ case 9: case 10: $this->pushTableItem( $sStr ); break; default: $this->appendRow( $sStr ); } } $this->clearContent(); $this->aList = array(); break; case 'ol': case 'ul': $this->nDepth--; array_pop($this->aType); break; case 'li': $this->textListItem($this->sContent); $this->clearContent(); break; //paragraphs case 'p': $this->appendRow( $this->sContent ); break; //noop case 'noop': $this->sContent .= '</noop>'; array_pop($this->aState); break; //justification case 'left': case 'right': case 'center': $this->sContent .= '</'.$sName.'>'; break; //pre, code, posting, quote case 'pre': case 'code': case 'posting': case 'q': $this->sContent = '<'.$sName.'>'. $this->sContent. '</'.$sName.'>'; array_pop($this->aState); if(end($this->aState)!=1){ $this->clearRow(); } break; //line-break case 'br': $this->appendRow( '<br/>' ); break; //variables case 'var': break; //headings case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': $this->appendRow( str_repeat("+",substr($sName,1,1)). " ". $this->sContent); array_pop( $this->aState ); break; //wiki links case 'uri': break; case 'link': array_pop($this->aState); $sStr = call_user_func($this->fLink, $this->sLink); $this->sContent .= $sStr; break; //subscript & superscript case 'sub': case 'sup': $this->sContent .= '</'.$sName.'>'; break; //horizontal rule case 'hr': $this->appendRow("---"); break; //emphasis (strike,tt,b,u,i) case 'strike': $this->sContent .= "--"; break; case 'tt': $this->sContent .= "=="; break; case 'b': $this->sContent .= "**"; break; case 'u': $this->sContent .= "__"; break; case 'i': $this->sContent .= '//'; break; default: echo $sName . " end" . "\n"; } } /** * xml element default handler * * @access protected * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 * */ protected function xml_default_handler( &$parser, $sData ) { if(!$this->ignoreToc){ $this->sContent .= html_entity_decode($sData); } } /** * xml cdata handler * * @access protected * * @author Archie Campbell <[email protected]> * @since coWiki 0.3.5 * */ protected function xml_cdata_handler( &$parser, $sData ) { if($this->ignoreToc){ return; } if( trim($sData) != '' ) { switch( end($this->aState) ){ case 1:/*WRP_LIST*/ case 3:/*WRP_HEAD*/ case 4:/*WRP_PCPQ*/ case 5:/*WRP_NOOP*/ case 7:/*WRP_CAPT*/ case 9:/*WRP_THED*/ $this->sContent .= $sData; break; case 2:/*WRP_LINK*/ $this->sLink[2]=trim($sData); break; default:/*text*/ $this->sContent .= $sData; } } } // -------------------------------------------------------------------- /** * Build str ref link * * @access protected * @param array * @return mixed * * @author Daniel T. Gorski, <[email protected]> * @since coWiki 0.3.0 * * @todo [D11N] Check description */ protected function buildStrRefLink(&$aMatches) { if ($aMatches[1] == $aMatches[2] || trim($aMatches[2]) == '') { return '((' . $this->noopDelimiters($aMatches[1]) . '))'; } return '((' . $this->noopDelimiters($aMatches[1]) . ')' .'(' . $aMatches[2] . '))'; } } // of class ?>