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( '=^<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){ 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 ?>