Re: [cowiki-dev] Extension to coWiki text formatting

Archie Campbell <[email protected]>
Newsgroups gmane.comp.php.cowiki.devel
Message-ID <[email protected]>
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
>

---------------------------------------------------------------------
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( '=^&lt;toc(/?)&gt;$=',
					$sRow, $aMatches )) {
		$sRet .= "\n\t\t\n";
   	    //plugins
	    }else if( $bBegin && preg_match(
			'=^&lt;plugin +([A-Za-z0-9_.]+)( +([^&]*))?&gt;=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( '=^&lt;/table&gt;=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( '=^&lt;table([^>]*)&gt;$=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( '=((.*)&lt;noop&gt;)=i',
					$sRow, $aMatches )) {
		$sContent = $this->processContent( $aMatches[2], false )
				. '<noop>';
		$sRow = substr( $sRow, strlen($aMatches[1]) );
		do {
		    if (preg_match( '=(.*)&lt;/noop&gt;=' , 
				    $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( '=^&lt;noop&gt;=is',
					$sStr, $aMatches )) {
	        //greedily munch all until </noop>
	        $sStr = substr( $sStr, strlen( $aMatches[0] ) );
	        $sContent = '';
	        while( !preg_match( '=^&lt;/noop&gt;=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( '=^&lt;(/?)(left|center|right)&gt;=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( '=^&lt;(pre|code|posting|q)&gt;=i',
				    $sStr, $aMatch ) ) {
	        $sStr = substr( $sStr, strlen($aMatch[0]) );
		do {
		    if (preg_match( '=(.*)&lt;/'.$aMatch[1].'&gt;=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( '=^&lt;br([ /]+)&gt;=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( '=^&lt;url(\s+)(
                   (http://|https://|ftp://|mailto:|news:)
                   ([-_A-Z0-9\S]*)
                   (\*\s|\=\s|&quot;|&lt;|&gt;|<|>|\(|\)|\s|$)??
				    )&gt;=six',
		    $sStr, $aMatches ) ) {
		*/
	    //URIs
	    }else if( preg_match(
		'=^(http://|https://|ftp://|mailto:|news:)
                   ([-_A-Z0-9\S]*)
                   (\*\s|\=\s|&quot;|&lt;|&gt;|<|>|\(|\)|\s|$)??
                 =six',
				  $sStr, $aMatches )) {
	        $sRet .= '<uri strref="' . 
				$aMatches[1].$aMatches[2] . '"/>';
	    //subscript, superscript
	    }else if( preg_match( '=^&lt;(/??)(sub|sup)&gt;=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 = '&lt;noop&gt;()&lt;/noop&gt;'.substr($sStr, 2);
        } else if (substr($sStr, 0, 1) == '(') {
            $sStr = '&lt;noop&gt;(&lt;/noop&gt;'. substr($sStr, 1);
        }

        // Escape trailing delimiters
        if (substr($sStr, -2) == '()') {
            $sStr = substr($sStr, 0, -2).'&lt;noop&gt;()&lt;/noop&gt;';
        } else if (substr($sStr, -1) == ')') {
            $sStr = substr($sStr, 0, -1).'&lt;noop&gt;)&lt;/noop&gt;';
        }

        // Escape double closing
        $sStr = str_replace('))', '&lt;noop&gt;))&lt;/noop&gt;', $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){
            echo "Don't Panic :" . $sData . "\n";
	}
    }

    /**
     * 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

?>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.