<?php


defined('WikyBlog') or die("Not an entry point...");

/*
	Order
------------------------------
	(1) class parser
	(2) function getXmlError
	


	XML_PARSE ERROR NOTES
	
	"JUNK AT THE END"... the closing tag of the first opening tag was found and there was still data after it
							
	"Mimatched".... either an open tag without a closing or a closing tag without an open


*/

////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//	MY PARSER
//		- WIKI PARSING!
//
//	- 2006 June 03
//		combined two parser classes into a single
//
//		might want to undo that... we're not going to need the xmlParser for all of the requests anymore
//
// 	- 2006 June 13
//		no external img suppor
//
//
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//		wikiParser
//



class Parser{
	
	//In general, these variables don't need to be given values here
	//	the default values are assigned by setState() and clearState()
	
	var $mDTopen;
	//var $aStripState;
	var $bStripState, $cStripState, $mLastSection;
	var $linkNum, $includes;
	var $variables = array();
	var $includeCache, $file_id = false;
	var $includeCacheSTD = array();

	
	var $xmlParser, $warn, $htmlDoc, $errors, $tidyErrors;
	var $foundUnsafe; //will be set to false if there are <a href="javascript: > and such
	
	var $isSafe; //can be set to true if we know the code is free of harmful code
	var $isAdmin;
	var $object;
	
	var $openTags, $lastOpen, $closeTags;
	
	var $cache = array();
	
	//	HTML ELEMENTS
	//	array('ELEMENT'=>'element') 
	//
	//<style> //security holes with this one
	//<ADDRESS>
	//'VAR','SAMP','DFN','CITE','CODE'
	var $htmlTags = array(
					'DIV'=>'div','PRE'=>'pre','P'=>'p'
					,'TABLE'=>'table','CAPTION'=>'caption','TBODY'=>'tbody','TR'=>'tr','TH'=>'th','TD'=>'td'
					,'H1'=>'h1','H2'=>'h2','H3'=>'h3','H4'=>'h4','H5'=>'h5','H6'=>'h6'
					,'OL'=>'ol','UL'=>'ul','MENU'=>'menu','DIR'=>'dir','LI'=>'li','DL'=>'dl','DT'=>'dt','DD'=>'dd'
					,'NOBR'=>'nobr','WBR'=>'wbr','BLOCKQUOTE'=>'blockquote','CENTER'=>'center'
					,'FIELDSET'=>'fieldset','LEGEND'=>'legend'
					,'MAP'=>'map'
					,'LINK'=>'link'
					);

	var $htmlTagsHead = array('HTML'=>'html','HEAD'=>'head','TITLE'=>'title','BODY'=>'body','META'=>'meta');
					
	var $htmlTagsSingle = array('BR'=>'br', 'HR'=>'hr','META'=>'meta','IMG'=>'img','BASEFONT'=>'basefont','AREA'=>'area','LINK'=>'link');
	
	
	var $htmlTagsNonBlock = array(
					'SPAN'=>'span'
					,'FONT'=>'font','EM'=>'em','STRONG'=>'strong','B'=>'b','I'=>'i','TT'=>'tt','BIG'=>'big','SMALL'=>'small'
					,'SUB'=>'sub','SUP'=>'sup','STRIKE'=>'strike'
					,'A'=>'a'
					);
					
					
	
	var $htmlAttr = array(
				// Allowed attributes--no scripting, etc.
				/* BR */ 'NOSHADE'=>'noshade',
				/* HR */ 'CITE'=>'cite',
				/* BLOCKQUOTE, Q */
				'SUMMARY'=>'summary','WIDTH'=>'width','BORDER'=>'border','FRAME'=>'frame','RULES'=>'rules',
				'CHAR'=>'char',
				'CHAROFF'=>'charoff','COLGROUP'=>'colgroup','COL'=>'col','SPAN'=>'span','ABBR'=>'abbr','AXIS'=>'axis',
				'HEADERS'=>'headers','SCOPE'=>'scope',
				/* LISTS */	'TYPE'=>'type','START'=>'start','VALUE'=>'value','COMPACT'=>'compact',
				/* Tables */ 'ROWSPAN'=>'rowspan','COLSPAN'=>'colspan','CELLSPACING'=>'cellspacing','CELLPADDING'=>'cellpadding','VALIGN'=>'valign',
				/* FONT */ 'SIZE'=>'size','FACE'=>'face','COLOR'=>'color',
				/* Meta */ 'CONTENT'=>'content',
				/* A */ 'HREF'=>'href','REL'=>'rel', 'REV'=>'rev','TARGET'=>'target',
				/* GENERAL */ 'ID'=>'id','CLASS'=>'class','NAME'=>'name','STYLE'=>'style',
				'TITLE'=>'title','ALIGN'=>'align','LANG'=>'lang','DIR'=>'dir','WIDTH'=>'width','HEIGHT'=>'height',
				'BGCOLOR'=>'bgcolor','CLEAR'=>'clear',
				/* IMG */ 'SRC'=>'src', 'ALT'=>'alt', //the most dangerous... wary of src="http://foo.com/malicous.js"
				/* HTML */ 'XMLNS'=>'xmlns', 'LANG'=>'lang', 'XMLNS:V'=>'xmlns:v',
				'SCROLLING'=>'scrolling'
				);
				
	var $smiles = array ( ':D' => 'icon_biggrin.gif', ':)' => 'icon_smile.gif', ':(' => 'icon_sad.gif', ':o' => 'icon_surprised.gif', ':shock:' => 'icon_eek.gif', ':?' => 'icon_confused.gif', '8)' => 'icon_cool.gif', ':lol:' => 'icon_lol.gif', ':x' => 'icon_mad.gif', ':P' => 'icon_razz.gif', ':oops:' => 'icon_redface.gif', ':cry:' => 'icon_cry.gif', ':evil:' => 'icon_evil.gif', ':twisted:' => 'icon_twisted.gif', ':roll:' => 'icon_rolleyes.gif', ':wink:' => 'icon_wink.gif', ':!:' => 'icon_exclaim.gif', ':?:' => 'icon_question.gif', ':idea:' => 'icon_idea.gif', ':arrow:' => 'icon_arrow.gif', ':|' => 'icon_neutral.gif', ':mrgreen:' => 'icon_mrgreen.gif' );


	
	////////////////////////////////////////////////////////////////////////////////////
	//
	//		Parse, clearstate, fixTags
	//

	
	function Parser() {
		$this->htmlTags += $this->htmlTagsSingle;
		$this->htmlTags += $this->htmlTagsNonBlock;
		$this->htmlTags += $this->htmlTagsHead;
		$this->setState();
		$this->clearState();
		$this->random =  'NaodW29xYE-' . Parser::getRandomString();
		
	}

	//called by initiateParser()
	//	these variables are needed even after the parse is finished
	function setState($variables=array()){
		global $pageOwner;
		
		$this->errors = false; //used by myParserErrors.php
		$this->tidyErrors = false;
		$this->foundUnsafe = false;
		$this->foundError = false;
		$this->includes = array();
		
		//set variables, set them here for registration
		$this->variables['pageowner'] = $pageOwner['username'];
		if( isset($_SESSION['username']) ){
			$this->variables['username'] = $_SESSION['username'];
		}else{
			$this->variables['username'] = 'Anonymous';
		}		
		$this->variables += $variables;
	}
	
	//called at the end of each parse
	// and with the creation of the parser object
	function clearState(){
		global $page;
		
		$this->nofollow = $page->nofollow;
		
		if( $this->object ){
			$this->object->includes = $this->includes;
		}
		unset($this->object);//Important!
		$this->object = $this->includeCache = false;
		$this->includes = array();
		
		//$this->aStripState = array();
		$this->openTags = $this->closeTags = $this->cStripState = $this->bStripState = array();
		$this->mDTopen = $this->htmlDoc = $this->isSafe = $this->isAdmin = $this->file_id = false;
		$this->linkNum = 1;
		
		//
		$this->lastOpen = '';
	}
	
	////////////////////////////////////////////////////////
	//
	//		Entry Points
	//
	
	
	function justParse($text,$flags,$file_id=false){
		$this->setFlagOptions($flags);
		$this->warn = false;
		$this->file_id = $file_id;
		return $this->doParse($text);
	}
	
	function parse($text, $warn=true, &$object){
		//	Options
		$this->setState();
		$this->object =& $object;
		$this->file_id = $object->file_id;
		
		$this->setFlagOptions($this->object->flags);
		$this->warn = $warn;
		if( $this->warn ){
			$this->isSafe = false;
		}
		return $this->doParse($text);
	}
		
	function doParse(&$text){
		global $pageOwner;
		//$start = microtime();
		
		//	Strip
		$this->stripAll($text);
		
		$this->getHtmlLinks($text);
		
		
		//Content Includes
		// $empty = array();
		$this->replaceVariables($text,$this->variables);
		$this->includeContent($text);
		
		//	Constants
		//$this->setConstants();
		
		//	Fix
		$this->doRegularExp( $text );
		
		
		//	Line by Line
		//			- Lists, tables, (headings?)
		
		$text = explode ( "\n" , $text );
		$text = $this->doBlocks($text ); //!
		

		
		//	Links... maybe this should be done with the line by line?
		//			- or within doHTML?
		//			- OR these are done with preg_replace!.. 
		
		//Unstrip B
		Parser::unStripState( $text, $this->bStripState);
		
		//	Parse
		// 			-HTML Tags
		$this->doXML( $text );
		
		
		//	Unstrip C
		Parser::unStripState( $text, $this->cStripState);
		
		$this->clearState();
		
		// $duration = microtime_diff($start, microtime());
		// message('time: '.$duration);

		return trim($text);
	}
	
	//Entry Point #2
	//	does not do all of the same wiki syntax
	//	Used by CLASSmap.php and CLASStemplate.php for the moment
	//		not used an a request-by-request basis... used when saving files
	function parseXML($text,$htmlDoc=false,&$object,$warn =false){
		//	Options
		$this->setState();
		$this->htmlDoc = $htmlDoc;
		$this->setFlagOptions($object->flags);
		$this->object =& $object;
		$this->file_id = $object->file_id;
		$this->warn = $warn;
		if( $this->warn ){
			$this->isSafe = false; //if we want warnings, then we want to test everything
		}
		
		//	Strip
		$this->stripAll($text);
		
		//Content Includes
		$empty = array();
		$this->replaceVariables($text,$this->variables);
		$this->includeContent($text);
			
		$this->doRegularExp($text);
		
		//Unstrip All
		Parser::unStripState( $text, $this->bStripState);
		Parser::unStripState( $text, $this->cStripState);		
		
		$this->doXML($text);
		
		$this->clearState();
		return trim($text);
	}
	
	
	// get local links formatted with htmls
	function getHtmlLinks(&$text){
		global $wbLinkPrefix;
		
		if( !$this->object ){
			return;
		}
		
		$prefixLen = strlen($wbLinkPrefix);
		
		$links = explode('<a',$text);
		foreach($links as $link){
			$pos = strpos($link,'>');
			if( $pos === false){
				continue;
			}
			$link = substr($link,0,$pos);
			$pos = strpos(strtolower($link),'href=');
			if( $pos === false ){
				continue;
			}
			$link = substr($link,$pos+5);
			
			//get quote type
			if( $link{0} == '"' ){
				$quote = '"';
			}elseif( $link{0} == '\'' ){
				$quote = '\'';
			}else{
				continue;
			}
			
			//inside of quotes
			$link = substr($link,1);
			$pos = strpos($link,$quote);
			if( $pos === false ){
				continue;
			}
			$link = substr($link,0,$pos);
			if( $link{0} != '/' ){
				continue;
			}
			
			$this->object->inLinks[] = substr($link,$prefixLen);
		}
	}	
	
	////////////////////////////////////////////////////////
	//
	//		Working Functions
	//
	
	
	function stripAll(&$text,$first=true){
		
		
		//$text = Parser::extractTags('condcomments', $text ,$this->cStripState );
		$text = $this->extractTags('comments', $text );
		$text = $this->extractTags('nowiki', $text ); // should go before <pre>
		$text = $this->extractTags('pre', $text );
		$text = $this->extractTags('code', $text );
		//$text = $this->extractTags('quote', $text );
		if( $first ){
			$text = $this->extractTags('includeonly', $text, true );
			$text = $this->extractTags('noinclude', $text );
		}else{
			$text = $this->extractTags('includeonly', $text );
			$text = $this->extractTags('noinclude', $text, true );
		}
	}
	
	
	//Determine if safe/admin..
	//
	function setFlagOptions(&$flags){
		global $wbNofollowUnchecked;
		
		if( strpos($flags,'safe') !== false){
			$this->isSafe = true;
		}
		if( strpos($flags,'admin') !== false){
			$this->isAdmin = true;
		}
		if( !$this->nofollow ){
			if( strpos($flags,'nofollow') !== false ){
				$this->nofollow = true;
			}elseif( $wbNofollowUnchecked && strpos($flags,'notchecked') !== false){
				$this->nofollow = true;
			}
		}
	}
	
// 	function setConstants(){
// 		global $pageOwner,$page;
// 		$this->cStripState['&PageOwner&'] =& $pageOwner['username'];
// 		$this->cStripState['&DisplayTitle&'] =& $page->displayTitle;
// 	}
	
	
	////////////////////////////////////////////////////////////////////////////////////
	//
	//		Content Includes	{{...}}
	//			--	Flaws: selects `content` .. so datatype must have a `content` column
	//			--	how do we get templates from another user account..
	//			--	how would this work with maps, comments, templates?
	//			--	how to show the templates when editing..
	//			--	Can't use $dbObject within this function because of /BrowsePages!
	//
	
	function includeContent(&$text,$values=array()){
		global $wbTablePrefix,$pageOwner,$wbTables,$page,$langA,$jsNum;
		
		//limit includes to 10
		if( count($this->includes) >= 10){
			return;
		}
		
		$pos = strpos($text,'{{');
		if( $pos === false){
			return;
		}
		
		$pos2 = strpos($text,'}}',$pos);
		
		if( $pos2 === false){
			return;
		}
		
		$start = $pos;
		$len = ($pos2-$pos+2);
		$temp = substr($text,$start,$len);
		$temp = substr($temp,2,-2);
		$args = explode('|',$temp); //this needs to merge with the existing $values
		$titleArg = trim(array_shift($args));
		$title = 'Template:'.$titleArg;
		$title = str_replace(array("\r","\n"),' ',$title);
			
		//get argument=values
		$index = 1;
		foreach($args as $arg){
			$eqpos = strpos( $arg, '=' );
			if ( $eqpos === false ) {
				$values[$index++] = $arg;
			}else{
				$index++;
				$name = trim( substr( $arg, 0, $eqpos ) );
				$value = trim( substr( $arg, $eqpos+1 ) );
				if ( $value === false ) {
					$value = '';
				}
				if ( $name !== false ) {
					$values[$name] = $value;
				}
			}
		}
		
		$replacement = $this->getIncludeText($titleArg,$values);
		if( $replacement === false ){
			$replacement = '[['.$title.'?cmd=edit|'.$title.']]';
			$this->includes[$title] = false;
			
		}else{
			$this->stripAll($replacement,false);
			$this->replaceVariables($replacement,$values);
			$this->includeContent($replacement,$values);
		}
		
		$text = substr_replace($text,$replacement,$start,$len);
		
		$this->includeContent($text);
	}
	
	function fillCacheSTD(){
		global $langA,$page;
		
		$menu = '<table class="collapsible {{{class|}}}" style="{{{table-style|}}}">';
		$menu .= '<tr><th>';
			$menu .= '<div class="collapsible_title">{{{title|}}}</div>';
		$menu .= '</th></tr>';
		$menu .= '<tr><td>';
			$menu .= '<div class="collapsible_content" style="{{{content-style|}}}">';
			$menu .= '{{{content|}}}';
			$menu .= '</div>';
		$menu .= '</td></tr>';
		$menu .= '</table>';
		$this->includeCacheSTD['collapsible'] = $menu;
		
		
		
		//we wrap it in a div because the scriptOnly css will make it display:block
		$toc = '<div class="scriptOnly"><table class="collapsible toc_table collapsed" style="{{{table-style|}}}"><tr><th><div class="collapsible_title" >'.$langA['page_contents'].'</div></th></tr><tr><td></td></tr></table></div>';
		$this->includeCacheSTD['toc'] = $toc;

	}
	
	
	
	//get the text of the included file from the $includeCache, else attempt to find it in the database
	function getIncludeText($title,&$variables){
		global $wbTablePrefix,$pageOwner,$wbTables,$page,$jsNum,$wbVideoUrls;
		
		if( $this->warn ){
			return false;
		}
		
		if( empty($this->includeCacheSTD) ){
			$this->fillCacheSTD();
		}
		
		
		//preset templates
		$title = wbStrtolower($title);
		if( isset($this->includeCacheSTD[$title]) ){
			
			if( $title == 'toc' ){
				$page->scripts[] = '/include/js/contentTable.js?'.$jsNum;
			}
			
			return $this->includeCacheSTD[$title];
		}
		
		//embed video
		if( $title == 'video' ){
			
			$service =& $variables[1];
			$id =& $variables[2];
			$width =& $variables[3];
			
			
			//check service & id
			if( empty($service) || empty($id) ){
				return '[[Help:Text Formatting/Embed Video#WBheaderInvalid_Parameters|Invalid Parameters]]';
			}
			
			if( preg_match('%[^A-Za-z0-9_\\-]%',$id) ){
				return '[[Help:Text Formatting/Embed Video#WBheaderInvalid_Video_Id|Invalid Video Id]]';
			}
			
			//check width
			$ratio = 425 / 350;
			if( !empty($width) && is_numeric($width) ){
				$height = round($width / $ratio);
			}else{
				$width = 425;
				$height = 350;
			}
			
			//this list can be added to from the wiki.php file
			$wbVideoUrls['12seconds'] = 'http://embed.12seconds.tv/players/remotePlayer.swf?vid=%s';
			$wbVideoUrls['5min'] = 'http://www.5min.com/Embeded/%s/';
			$wbVideoUrls['bambuser'] = 'http://bambuser.com/r/player.swf?vid=%s&amp;context=external';
			$wbVideoUrls['bliptv'] = 'http://blip.tv/scripts/flash/showplayer.swf?file=http%3A%2F%2Fblip.tv/rss/flash/%s';
			$wbVideoUrls['blogtv'] = 'http://www.blogtv.com/vb/%s';
			$wbVideoUrls['clipfish'] = 'http://www.clipfish.de/videoplayer.swf?as=0&amp;videoid=%s&r=1';
			$wbVideoUrls['current'] = 'http://current.com/e/%s';
			$wbVideoUrls['dailymotion'] = 'http://www.dailymotion.com/swf/%s';
			$wbVideoUrls['eyespot'] = 'http://eyespot.com/flash/medialoader.swf?vurl=http%3A%2F%2Fdownloads.eyespot.com%2Fplay%3Fr%3D%s&amp;_autoPlay=false';
			$wbVideoUrls['fabchannel'] = 'http://www.fabchannel.com/embed/player.swf?ap=%s';
			$wbVideoUrls['googlevideo'] = 'http://video.google.com/googleplayer.swf?docid=%s';
			$wbVideoUrls['gtrailers'] = 'http://www.gametrailers.com/remote_wrap.php?mid=%s';
			$wbVideoUrls['jumpcut'] = 'http://www.jumpcut.com/media/flash/jump.swf?id=%s&amp;asset_type=movie&amp;asset_id=%s&amp;eb=1';
			$wbVideoUrls['justintv'] = 'http://www.justin.tv/widgets/jtv_tip_embed.swf?tip_id=%s&amp;channel=justin.tv';
			$wbVideoUrls['metacafe'] = 'http://www.metacafe.com/fplayer/%s/foo.swf';
			$wbVideoUrls['mogulus'] = 'http://www.mogulus.com/grid/PlayerV2.swf?layout=playerEmbedDefault&channel=%s';
			$wbVideoUrls['myspacetv'] = 'http://lads.myspace.com/videos/vplayer.swf?m=%s&amp;v=2&amp;type=video';
			$wbVideoUrls['revver'] = 'http://flash.revver.com/player/1.0/player.swf?mediaId=%s';
			$wbVideoUrls['rcmovie'] = 'http://www.rcmovie.de/embed/%s';
			$wbVideoUrls['scivee'] = 'http://www.scivee.tv/flash/embedPlayer.swf?id=%s&type=3';
			$wbVideoUrls['seesmic'] = 'http://seesmic.com/embeds/wrapper.swf?video=%s&version=threadedplayer';
			$wbVideoUrls['sevenload'] = 'http://page.sevenload.com/swf/en_GB/player.swf?id=%s';
			$wbVideoUrls['slideshare'] = 'http://static.slideshare.net/swf/ssplayer2.swf?doc=%s';
			$wbVideoUrls['stickam'] = 'http://player.stickam.com/flashVarMediaPlayer/%s';
			$wbVideoUrls['superdeluxe'] = 'http://i.cdn.turner.com/sdx/static/swf/share_vidplayer.swf?id=%s';
			$wbVideoUrls['ustream'] = 'http://www.ustream.tv/flash/video/%s';
			$wbVideoUrls['veoh'] = 'http://www.veoh.com/videodetails2.swf?player=videodetailsembedded&amp;type=v&amp;permalinkId=%s&amp;id=anonymous';
			$wbVideoUrls['vimeo'] = 'http://www.vimeo.com/moogaloop.swf?clip_id=%s';
			$wbVideoUrls['youtube'] = 'http://www.youtube.com/v/%s';
			
			if( empty($wbVideoUrls[$service]) ){
				return '[[Help:Text Formatting/Embed Video#WBheaderInvalid_Service|Invalid Service]]';
			}
			
			$url = sprintf($wbVideoUrls[$service],$id);
			
			$embed = '<object height="%2$s" width="%3$s" ><param name="movie" value="%1$s"></param><param name="wmode" value="transparent"></param><embed src="%1$s" type="application/x-shockwave-flash" wmode="transparent" height="%2$s" width="%3$s" ></embed></object>';
			
			$marker = $this->getMarker();
			$this->cStripState += array( $marker => sprintf($embed, $url, $height, $width) );
			return $marker;
		}
		
		
		
		//we don't want the parse to check the included text because links will be stripped of their local status
		if( $title == 'blog' ){
			$marker = $this->getMarker();
			$this->cStripState += array( $marker => includeBlog() );
			return $marker;
			//return includeBlog();
			
		}elseif( $title == 'children' ){
			includeFile('tool/Children.php',false);
			$marker = $this->getMarker();
			$this->cStripState += array( $marker => fileChildren::includeChildren() );
			return $marker;
			//return fileChildren::includeChildren();
		}
// 		if( $title == 'toc' ){
// 			$marker = $this->getMarker();
// 			$this->cStripState += array( $marker => '<table class="scriptOnly"><tr><td class="WB_TOC"><div class="WB_TOC_TITLE"><a href="javascript:void(0)" onclick="WB.TOC(this);return false;">'.$langA['page_contents'].'</a></div></td></tr></table>' );
// 			$page->scripts[] = '/include/js/contentTable.js?'.$jsNum;
// 			return $marker;
// 		}
		
		
		
		
		//custom templates
		if( $this->includeCache === false ){
			$this->initiateIncludes();
		}
		
		
		$title = 'template:'.$title;
		if( isset($this->includeCache[$title]) ){
			return $this->includeCache[$title];
		}
		
		
		
		// Fetch include
		//	.. what to do about redirects?
		$pages = '`'.$wbTablePrefix.'pages`';
		$query = 'SELECT `content`, `modified`, '.$pages.'.`file_id` ';
		$query .= ' FROM '.$pages.' ';
		$query .= ' LEFT JOIN '.$wbTables['all_files'];
		$query .= ' ON '.$wbTables['all_files'].'.`file_id` = '.$pages.'.`file_id` ';
		$query .= ' WHERE ';
		$query .= ' `owner`="'.wbDB::escape($pageOwner['username']).'" ';
		$query .= ' AND `title`="'.wbDB::escape(toStorage($title)).'" ';
		$query .= ' AND '.$wbTables['all_files'].'.`visible` = 1 ';
		$query .= ' AND !FIND_IN_SET("redirect", '.$wbTables['all_files'].'.flags) ';
		$query .= ' LIMIT 1 OFFSET 0';
		$result = wbDB::runQuery($query);
			
		if(mysql_num_rows($result) != 1){
			return false;
		}
		
		$row = mysql_fetch_assoc($result);
		$page->setLastModified($row['modified']);
		$this->includes[$title] = $row['file_id'];
		$this->includeCache[$title] = $row['content'];
		return $row['content'];
	}
		
	
	//fill the includeCache for any files stored in all_include
	function initiateIncludes(){
		global $wbTables, $wbTablePrefix,$page;
		
		$this->includeCache = array();
		
		if( !$this->file_id ){
			return;
		}
		
		$pages = '`'.$wbTablePrefix.'pages`';
		$query = 'SELECT `content`, `modified`, '.$wbTables['all_included'].'.`incl_id`, LOWER('.$pages.'.`title`) AS `title` ';
		$query .= ' FROM '.$wbTables['all_included'];
		$query .= ' JOIN '.$wbTables['all_files'];
		$query .= ' ON '.$wbTables['all_included'].'.`incl_id` = '.$wbTables['all_files'].'.`file_id` ';
		$query .= ' JOIN '.$pages;
		$query .= ' ON '.$wbTables['all_files'].'.`file_id` = '.$pages.'.`file_id` ';
		$query .= ' WHERE ';
		$query .= $wbTables['all_included'].'.`file_id` = "'.$this->file_id.'" ';
		$query .= ' AND '.$wbTables['all_files'].'.`visible` = 1 ';
		$query .= ' AND !FIND_IN_SET("redirect", '.$wbTables['all_files'].'.flags) ';
		$result = wbDB::runQuery($query);
		while( $row = mysql_fetch_assoc($result) ){
			$this->includes[$row['title']] = $row['incl_id'];
			$this->includeCache[$row['title']] = $row['content'];
			$page->setLastModified($row['modified']);
		}
	}
	
	function replaceVariables(&$text,&$values,$i=1){
		global $page;
		
		
		$pos = strpos($text,'{{{');
		if( $pos === false){
			return;
		}
		if( strpos($text,'{{{{{') === $pos){
			$pos+=2;
		}
		
		$pos2 = strpos($text,'}}}',$pos);
		
		if( $pos2 === false){
			return;
		}
		$temp = substr($text,$pos,($pos2-$pos+3));
		$temp = substr($temp,3,-3);
		
		
		//get default value
		$default = false;
		$pos3 = strpos($temp,'|');
		if($pos3 > 0 ){
			if( !($default = substr($temp,$pos3+1))){
				$default = ''; //substr returns false when start position is the same length as the string
			}
			$temp = substr($temp,0,$pos3);
		}

		
		if( isset($values[$temp]) ){
			$replacement = $values[$temp];
		}elseif( isset($values[$i]) ){
			$replacement = $values[$i];
		}elseif( $default !== false ){
			$replacement = $default;
		}else{
			$replacement = $this->getMarker();
			$this->bStripState += array( $replacement => '{{{'.$temp.'}}}' ); //hmm where do I want this to go back in?
		}
		$text = substr_replace($text,$replacement,$pos,($pos2-$pos+3));
		$this->replaceVariables($text,$values,++$i);
	}
	
	////////////////////////////////////////////////////////////////////////////////////
	//
	//		PREG_REPLACE ONCE
	//			- Character Entities
	//			- FIX TAGS
	//
	
	function doRegularExp(&$text,$doLinks=true){
		// both of these are used with images
		global $page,$wbConfig;

		// the costliest regular expressions are the unanchored ones... without ^ or $!
		// unfortunately, these aren't the easiest to take care of with doBlocks()
		
		
		
		////////////////////////////////////////////////////////////////
		//
		//		Escaping for the xmlParser
		//
		if( !$this->isSafe && !$this->isAdmin ){
			if( !isset($wbConfig['tidy']) || $wbConfig['tidy'] == 'Off'){
				//These are things that tidy will take care of
				//
				//
				//	1)	Character Entities
				
				//	<![CDATA[ and anything that's not a valid token for opening a regular tag
				/* 	<? is needed for <?duplicate entry?> and <?lang translate?>
					<! is needed for <!DOCTYPE
				*/
				$patterns[] = '#<(?![a-z/?!])#i';
				//$patterns[] = '#<(?![a-z/?])#i';
				//$patterns[] = '#<(?![a-z/?][a-z])#i';// <a href wont work!
				$replacements[] = '&lt;\1';
				
				$patterns[] = '#\]\]>#';
				$replacements[] = ']]&gt;';
			
			}
		}
		
		// Lone Ampersands .. required for xhtml/xml validity
		$patterns[] = '/&(?![#a-zA-Z0-9]{2,6};)/';
		$replacements[] = '&amp;';
		
		
		////	2)	Tags
		
		//	UPPERCASE TAGS
		//	needs to be done for parseXML()
		//	this could be an even more accurate function and have all the tags listed in the pattern
		//	or could even have a pattern that changes < to &lt;
		//	.. could even send the whole tag to a function and return a "<tag..>" or "&lt;tag..&gt;" depending on whats allowed
		
		$patterns[] = '#<(/?[a-zA-Z]*)#ie'; // and since < is an illegal character, this will only affect tags!
		$replacements[] = "'<'.strtolower('\\1')";
		
		
		// HR, BR, IMG, META, BASEFONT, AREA, ?? INPUT, BASE :: all single tags
		$patterns[] = '#<((?:br|hr|img|meta|basefont|area|input|base)(?:\s[^>]*?)?)/?>#i';
		$replacements[] = '<\1 />';
		
		//Quotes
// 		$patterns[] = '#<(?:quote[^>]*)>(.*?)</quote>#is';
// 		$replacements[] = '<div class="quote">\1</div>';
		$patterns[] = '#<(?:quote[^>]*)>#is';
		$replacements[] = '<div class="quote">';
		
		$patterns[] = '#</quote>#is';
		$replacements[] = '</div>';
		
		////////////////////////////////////////////////////////////////
		//
		//		[[....]]	SYNTAX FOR LOCAL LINKS
		//
		if( !$this->htmlDoc ){
			
			////	Images, specialLinks..
				//$patterns[] = '#\[\[([a-z]*):([^[:space:]\]:]+?)\]\]#ie'; //excludes spaces
				$patterns[] = '#\[\[([a-z]*):([^\n\r\]]+?)\]\]#ie';
				//$replacements[] = '$this->wikiCommand("$1","$2")'; //single quotes will be escaped
				$replacements[] = '$this->wikiCommand(\'$1\',\'$2\')'; //double quotes will be escaped
			
			////	INTERNAL LINKS
				$acceptableText = '^|\[\]\n\r';
				$patterns[] = '#\[\[(['.$acceptableText.']+)\|?([^|\r\n\]\[]+?)?\]\]#ise';
				$replacements[] = '$this->formatLinks(\'$1\',\'$2\',\'internal\')';
		}
		

// 		////	EXTERNAL LINKS
// 		// 			external 1: http
// 		// 			A note about characters allowed before and after:
// 		// 				href="http:.." should not be converted here so " cannot be before or after
// 		 	//$terminate = "[,;\.\":[:space:]]";
// 			//$patterns[] = '#\b((?:http://|mailto:|https://|ftp://|file://)[^[:space:]\]]+?)(?='.$terminate.'?[[:space:]])#is'; // \b indicates a word boundary
// 			//$patterns[] = '#(?<!\[)((?:http://|mailto:|https://|ftp://|file://)[^[:space:]\]]+?)(?='.$terminate.'?[[:space:]])#ise'; 
// 			//$patterns[] = '#[[:space:]]((?:http://|mailto:|https://|ftp://|file://)[^[:space:]\]]+?)[[:space:]]#ise'; 
// 			$patterns[] = '#(?<=[[:space:],\;])((?:http://|mailto:|https://|ftp://|file://)[^[:space:]\]]+?)(?=[[:space:],\;])#ise';
// 			$replacements[] = '$this->formatLinks(\'$1\',\'$2\',\'external2\')';
// 		
// 			
// 			
// 		////	external 2
// 			$patterns[] = '#\[((?:http://|mailto:|https://|ftp://|file://)[^[:space:]\]\r\n]+?)(?: ([^\]\r\n]+?))?\]#ise';
// 			$replacements[] = '$this->formatLinks(\'$1\',\'$2\',\'external1\')';


		////	EXTERNAL LINKS
		//			Links get stored in bStripState
		//
		//			1) do the [http:// ....] links first..
		//			2) then do http://... link
		//		
		
			// 1)
			$patterns[] = '#\[((?:http://|mailto:|https://|ftp://|file://)[^[:space:]\]\r\n]+?)(?: ([^\]\r\n]+?))?\]#ise';
			$replacements[] = '$this->formatLinks(\'$1\',\'$2\',\'external1\')';
		
			// 2)
			//	the lookahead/behind require characters to be [:space:],\;\[\] or the end/beginning of the content
			$patterns[] = '#(?<=(?:[[:space:],\;\]\[])|^)((?:http://|mailto:|https://|ftp://|file://)[^[:space:]\]<>]+)(?=(?:[[:space:],\;\[\]])|$)#ise';
			$replacements[] = '$this->formatLinks(\'$1\',\'$2\',\'external2\')';
			

	 
	
		
		////	wikiwikilinks
			// $patterns[] = '#[,;[:space:]]([A-Z][a-z]+(?:[A-Z][a-z]+)+)[[:space:],.]#e';
			// $replacements[] = "wikiWikiLinks('\\1')";

		////	Smileys
		// 
			$patterns[] = '#(?<=[[:space:]])(:D|:\)|:\(|:shock:|8\)|:lol:|:x|:P|:oops:|:o|:cry:|:evil:|:twisted:|:roll:|:wink:|:!:|:\?:|:\?|:idea:|:arrow:|:\||:mrgreen:)#e';
			$replacements[] = '$this->doSmileys(\'$1\')';
						
			
		$text = preg_replace($patterns,$replacements,$text);
		
	}
	
	////////////////////////////////////////////////////////////////////////////////////
	//
	//		Functions that are called from doRegularExp();
	//
	//
	
	function doSmileys($arg){
		global $langA;
		if( !isset($this->smiles[$arg]) ){
			return $arg;
		}
		if( isset($langA['smiles'][$arg]) ){
			$alt = $langA['smiles'][$arg];
		}else{
			$alt = $arg;
		}
		
		$marker = $this->getMarker();
		$loc = wbLinks::getDir('/imgs/smiles/'.$this->smiles[$arg]);
		$this->bStripState += array( $marker => '<img src="'.$loc.'" height="15" width="15" alt="'.$alt.'" />');
		return $marker;
		
	}
	
	//	quotes will be escaped depending on which quotes are used in the preg_replace
	//
	function wikiCommand($arg1,$arg2){
		global $pageOwner,$userLanguage;
		
		//message('one: '.$arg1.' :: two: '.$arg2);
		
		$marker = $this->getMarker();
		$argLower = wbStrtolower($arg1);
		$pieces = wbExplode('|',$arg2);
		
		if( count($pieces) < 1){
			return '[['.$arg1.':'.$arg2.']]';
		}
		
		//links
		//	doQuotes()
		//	attrs
		$attrs = '';
		switch($argLower){
			case 'speciallink':
			case 'locallink':
			case 'maplink':
			case 'map':
			case 'help':
				if( isset($pieces[1]) ){
					$pieces[1] = $this->doQuotes($pieces[1]);
				}
				if( !$this->isSafe && !$this->isAdmin){
					$attrs = ' WBCHECK="'.$this->random.'" ';
				}
			break;
		}
		if( !isset($pieces[1]) ){
			$pieces[1] = $pieces[0];
		}
		
		
		//$pieces[1] = toDisplay($pieces[1]); //changes _ to ' ' and / to >.. don't want that here
		$pieces[0] = wbHtmlspecialchars($pieces[0]);
		
		//put the results in the first stripState, ie $this->bStripState
		switch($argLower){
// 			case 'attach':
// 			$loc = '/include/tool/Files.php?o='.toStorage($pageOwner['username'],true).'&f='.$pieces[0];
// 			$loc = wbLinks::getDir($loc);
// 			$this->bStripState += array( $marker => $this->format'<a href="'.$loc.'">'.$pieces[1].'</a>');
// 			return $marker;
			
			case 'attach':
			$this->bStripState += array( $marker => $this->formatAttachment($pieces) );
			return $marker;
			
			case 'image':
			$this->bStripState += array( $marker => $this->formatImage($pieces));
			return $marker;
			
			case 'speciallink':
			$this->bStripState += array( $marker => wbLinks::special(wbHtmlspecialchars($pieces[0]),$pieces[1],$attrs));
			return $marker;
			
			case 'locallink':
			$this->bStripState += array( $marker => wbLinks::local(wbHtmlspecialchars($pieces[0]),$pieces[1],$attrs));
			return $marker;
			
			case 'maplink':
			case 'map':
			$this->bStripState += array( $marker => wbLinks::local('/Map/'.$pageOwner['username'].'/'.wbHtmlspecialchars($pieces[0]),$pieces[1],$attrs));
			return $marker;
			
			case 'help':
			//use cStripState to skip the xml check -> skip REV and WBCHECK
			$this->cStripState += array( $marker => wbLinks::help(wbHtmlspecialchars($pieces[0]),wbHtmlspecialchars($pieces[1])));
			//$this->bStripState += array( $marker => wbLinks::local('/Help/'.$userLanguage.'/'.wbHtmlspecialchars($pieces[0]),$pieces[1],$attrs));
			return $marker;
			
			case 'wbimage':
			$this->bStripState += array( $marker => '[['.$arg1.':'.$arg2.']]<!-- wbimage Under Development -->');
			return $marker;
		}
		
		return '[['.$arg1.':'.$arg2.']]';
	}

	function formatAttachment(&$args){
		global $pageOwner;
		$fileName = basename(array_shift($args));
		if( count($args) > 0 ){
			$label = array_shift($args);
		}else{
			$label = $fileName;
		}
		
		//add to internal links
		if( $this->object ){
			$link = '/Attach/'.$pageOwner['username'].'/'.$fileName;
			$this->object->inLinks[] = $link;
		}		
		
		$loc = '/include/tool/Files.php?o='.toStorage($pageOwner['username'],true).'&amp;f='.$fileName;
		return '<a href="'.wbLinks::getDir($loc).'">'.$label.'</a>';
	}
	
	//
	//	[[Image:Example.jpg|center|thumb|100px|Sunflowers]]
	//
	function formatImage(&$args){
		global $page,$pageOwner;
		
		$alt = $width = $height = $scale = false;
		$align = '';
		$imgName = array_shift($args);
		$alt = $imgName;
		$imgName = toStorage(basename($imgName));

		
		//add to internal links
		if( $this->object ){
			$link = '/Attach/'.$pageOwner['username'].'/'.$imgName;
			$this->object->inLinks[] = $link;
		}
		
		
		$class = 'wikiImg';
		foreach($args as $var){
			$temp = strtolower($var);
			
			//alignment
			if( $temp == 'right'){
				$align = 'right';
				$class = 'wikiImg wikiImgRight';
				continue;
			}
			
			if($temp == 'left' ){
				$align = 'left';
				$class = 'wikiImg wikiImgLeft';
				continue;
			}
			if($temp == 'center'){
				$align = 'center';
				$class = 'wikiImg wikiImgCenter';
				continue;
			}
			
			//set dimensions
			$temp = substr($temp, -2);
			if( $temp =='px' ){
				$temp = substr($var,0,-2);
				if( is_numeric($temp) ){
					$width = $temp;
					continue;
				}elseif( is_numeric(str_replace('x','',$temp))){
					$pos = strpos($var,'x');
					$height = substr($var,$pos+1,-2);
					$width = substr($var,0,$pos);
					continue;
				}
			}
			
			//scale
			$temp = substr($var, -1);
			if( $temp == 'x'){
				$temp = substr($var,0,-1);
				if( is_numeric($temp) ){
					$scale = (float)$temp;
					continue;
				}
			}
			
			$alt = $var;
		}
		
		
		//attributes string
		$attrs = ' alt="'.wbHtmlspecialchars($alt).'" title="'.wbHtmlspecialchars($alt).'" class="'.$class.'" ';
		if( $align ){
			$attrs .= ' align="'.$align.'" ';
		}
		$fullPath = wbLinks::getDir('/userfiles/'.toStorage($pageOwner['username'],true).'/uploaded/'.$imgName);
		
		//does the image exist?
		$data = $this->getImageData($imgName);
		if( !$data || ($data['width'] == 0) || ($data['height'] == 0) ){
			return '<img src="'.$fullPath.'" '.$attrs.'/>';
		}
		
		//height and width
		$x = max(1,$data['width']);
		$y = max(1,$data['height']);
			
		if( $width !== false ){
			if( $height !== false){
				$y = $height;
			}else{
				$y = round(($y/$x)*$width);
			}
			$x = $width;
			
		}elseif($scale){
			$x = round($scale*$x);
			$y = round($scale*$y);
		}
		
		$r = '<img src="'.$fullPath.'" '.$attrs;
		if( $x > 1 ){
			$r .= ' width="'.$x.'" ';
		}
		if( $y > 1 ){
			$r .= ' height="'.$y.'" ';
		}
		$r .= '/>';
		return $r;
	}
	
	//get the image size if it isn't set
	function getImageData($name){
		global $wbTables, $pageOwner, $wbTablePrefix;
		
		static $imgCache = array();
		
		if( isset($imgCache[$name]) ){
			return $imgCache[$name];
		}
		
		$attachments = '`'.$wbTablePrefix.'attachments`';
		$query = 'SELECT `height`, `width` ';
		$query .= ' FROM '.$attachments;
		$query .= ' WHERE `owner` = "'.wbDB::escape($pageOwner['username']).'" ';
		$query .= ' AND `title` = "'.wbDB::escape(toStorage($name)).'" ';
		$query .= ' AND `is_image` = 1 ';
		$query .= ' LIMIT 1 OFFSET 0 ';
		$result = wbDB::runQuery($query);
		if( $row = mysql_fetch_assoc($result) ){
			$imgCache[$name] = $row;
			return $row;
		}
		return false;
	}
	
	

	//	formatLinks
	//
	//
	//	@param	string	$arg1	the link
	//	@param	string	$arg2	Text to go in the link
	//	@param	mixed	$which	tells the formatLinks() what type of link (local, "[..]" or "http://.."
	//
	function formatLinks($arg1,$arg2,$which='internal'){
		global $pageOwner,$wbConfig,$page;
		$returnPad = '';
		$marker = $this->getMarker();
		
		$arg1 = trim($arg1);
		$arg2 = trim($arg2);	
		$arg1 = stripslashes($arg1);
		$arg1 = wbHtmlspecialchars($arg1);
		$arg2 = stripslashes($arg2);
		//$arg2 = wbHtmlspecialchars($arg2);
		$attrs = '';
		$link = $arg1;
		//$link = str_replace('+','%2B',$arg1);
		$title = '';
		
		if(empty($arg2)){
			//external links
			if( $which ==='external1' ){
				$arg2 = '['.$this->linkNum++.']';
				
			//internal links
			}else{
				//if the comma is the end character
				$lastChar = $link{strlen($link)-1};
				if( ($lastChar === ',') || ($lastChar === '.') ){
					$returnPad = $lastChar;
					$link = $arg1 = substr($link, 0, -1);
				} 
				$arg2 = $arg1;
				$arg2 = trim($arg2,'/\\');
				$arg2 = wbStr_replace('_',' ',$arg2);
				$page->addMeta($arg2);
			}
		}else{
			$arg2 = $this->doQuotes($arg2);
			$page->addMeta($arg2);
		}
	
		//local
		if($which === 'internal'){
			if($link{0} == '#'){
				
			}else{
				
				
				if($link{0} != '/'){
					
					$link = '/'.$pageOwner['username'].'/'.$link;
						$attrs = ' rev="'.$page->revType.'" ';
				}else{
					
					if( $page->revType == 'interwiki' ){
						$attrs = ' rev="'.$page->revType.'" ';
						
					}elseif( strpos(strtolower($link),'/help') === 0 ){
						$attrs = ' rev="'.$page->revType.'" ';
						
					}elseif( $this->isOwnerLink($link) ){
						$attrs = ' rev="'.$page->revType.'" ';
						
					}else{
						$attrs = ' class="otheruser" ';
					}
				}
				
				
				if( $this->object ){
					$this->object->inLinks[] = $link;
				}
				$link = wbLinks::getUrl($link);
			}
		
		//external
		}else{
			
			//interWiki test
			$temp = parse_url($link);
			if( isset($temp['host']) 
				&& isset($temp['scheme'])
				&& isset($wbConfig['inter'][(wbStrtolower($temp['scheme'].'://'.$temp['host']))])){
					$attrs = ' rev="interwiki" ';
			}else{
				$attrs = ' class="external" '; //needed for css.. showing external.png
				if( $this->nofollow ){
					$attrs .= 'rel="nofollow" ';
				}
			}
		}
		if( !$this->isSafe && !$this->isAdmin){
			$attrs .= ' WBCHECK="'.$this->random.'" ';
		}

		$text = '<a href="'.$link.'"'.$title.$attrs.'>'.$arg2.'</a>';
		
		$this->bStripState += array( $marker => '<a href="'.$link.'"'.$title.$attrs.'>'.$arg2.'</a>' );
		return $marker.$returnPad;
	}
	
	function isOwnerLink($link){
		global $dbInfo, $pageOwner;
		
		$link = toStorage($link,true);
		//$link = wbStrtolower($link);
		
		//using explode
		$link = trim($link,'/\\');
		$pathArray = explode('/',$link);
		$piece = array_shift($pathArray);
		
		if( $piece === 'edit' ){
			//begins with /edit
			$piece = array_shift($pathArray);
		}
		
		if( isset($dbInfo[$piece]) ){
			//begins with space: $piece
			$piece = array_shift($pathArray);
			if( isset($dbInfo[$piece]) ){
				//begins with second space: $piece
				$piece = array_shift($pathArray);
			}
		}
		if( wbStrcasecmp($pageOwner['username'],$piece) ){
			return true;
		}
		return false;
	}

	////////////////////////////////////////////////////////////////////////////////////
	//
	//		STRIP, UNSTRIP
	//
	
	
	// used by extractTags function
	function getRandomString(){
		return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff));
	}

	// Replaces all occurrences of <$tag>content</$tag> in the text
	// with a random marker and returns the new text. the output parameter
	// $stripState will be an associative array filled with data on the form
	// $unique_marker => content.

	
	// used by strip function only. for...???
	function extractTags( $tag, &$text,$strip=false ){
		$result = '';
		
		switch($tag){
			// case 'condcomments':
			// 	$openSplit = '/<!--\[if IE\]>/';
			// 	$closeSplit = '/<!\[endif\]-->/';
			// 	$openTag = "<!--[if IE]>";
			// 	$closeTag = "<![endif]-->";
			// break;
			case 'comments':
				$openSplit = '/<!--/';
				$closeSplit = '/-->/';
				$openTag = '<!--';
				$closeTag = '-->';
			break;
			case 'wikivars':
				$openSplit = '/{{{/';
				$closeSplit = '/}}}/';
				$openTag = '{{{';
				$closeTag = '}}}';
			break;
			
			case 'code':
				$openSplit = "/<\\s*$tag/i";
				$closeSplit = "/<\\/\\s*$tag\\s*>/i";
				$openTag = "<$tag";
				$closeTag = "</$tag>";
			break;
			
			
			//case 'quote':
			default:
				$openSplit = "/<\\s*$tag\\s*>/i";
				$closeSplit = "/<\\/\\s*$tag\\s*>/i";
				$openTag = "<$tag>";
				$closeTag = "</$tag>";
			break;
		}
		
		$text = preg_split($openSplit,$text,-1);
		//echo '<h2>'.ucfirst($tag).' - Test</h2>';
		//message(wbHtmlspecialchars($openSplit).showArray($text));
		
		$result = array_shift($text);
		
		foreach( $text as $key => $value){
			$test2 = preg_split( $closeSplit, $value,2);
			$marker = $this->getMarker();
			
			// Maintain number of lines... for debugging!
			// $numberOfNewlines = substr_count($test2[0], "\n");
			// echo '<h1>Test</h1>';
			// echo $numberOfNewlines;
			
			//I want to keep the comments from getting stripped by the xmlParser
			//	<nowiki> is stripped by HTML tidy
			
			switch($tag){
				case 'code':
					$result .= $marker;
					$this->doCode($test2[0],$marker);
				break;
				
				// case 'quote':
				// 	$result .= $marker;
				// 	$this->bStripState += array( $marker => '<div class="quote">'. $test2[0].'</div>' );
				// break;
				case 'includeonly':
				case 'noinclude':
				case 'nowiki':
					if( $strip ){
						break;
					}
					$result .= $marker;
					$this->cStripState += array( $marker => nl2br(wbHtmlspecialchars($test2[0])) ); //we don't want <nowiki> tags in the output html
				break;
				
				case 'comments':
				case 'condcomments':
				case 'wikivars':
					$result .= $marker;
					//$newState = array( $marker => $openTag.$test2[0].$closeTag ); //this messes up conditional comments <!--[if IE]>...<![endif]-->
					$this->cStripState += array( $marker => $openTag.wbHtmlspecialchars($test2[0]).$closeTag );
				break;
				
				default:
					$result .= $openTag.$marker.$closeTag;
					$this->cStripState += array( $marker => wbHtmlspecialchars($test2[0]) );
				break;
			}
			
			if( isset($test2[1]) ){
				$result .= $test2[1];
			}
		}
		return $result;
	}
	
	function getMarker(){
		static $n = 0;
		return $this->random . sprintf('%08X', $n++);
	}
	
	function unStripState(&$text,&$stripState){
		if( count($stripState) == 0){
			return;
		}
		
		$stripState = array_reverse( $stripState );
		
		$text = str_replace( 	array_keys($stripState)
								, array_values($stripState)
								, $text );
		$stripState = array(); //reset the state
	}
	
	
	
	
	
	////////////////////////////////////////////////////////////////////////////////////
	//
	//		LISTS
	//
	
	// getCommon() returns the length of the longest common substring
	// of both arguments, starting at the beginning of both.
	//!
	function getCommon( $st1, $st2 ) {
		$fl = strlen( $st1 );
		$shorter = strlen( $st2 );
		if ( $fl < $shorter ) { $shorter = $fl; }

		for ( $i = 0; $i < $shorter; ++$i ) {
			if ( $st1{$i} != $st2{$i} ) { break; }
		}
		return $i;
	}
		
	function openList( $char ){
		$result='';
		
		if ( '*' == $char ) { $result .= '<ul><li>'; }
		else if ( '#' == $char ) { $result .= '<ol><li>'; }
		else if ( ':' == $char ) { $result .= '<dl><dd>'; }
		else if ( ';' == $char ) {
			$result .= '<dl><dt>';
			$this->mDTopen = true;
		}else{ $result = '<!-- ERR 1 -->'; }

		return $result;
	}
	
	// Some functions here used by doBlocks()::lists
	//!
	function closeList( $char ) {
		if ( '*' == $char ) { $text = '</li></ul>'; }
		else if ( '#' == $char ) { $text = '</li></ol>'; }
		else if ( ':' == $char ) {
			if ( $this->mDTopen ) {
				$this->mDTopen = false;
				$text = '</dt></dl>';
			} else {
				$text = '</dd></dl>';
			}
		}
		else {	return '<!-- ERR 3 -->'; }
		return $text; //keep line numbers the same!
		return $text."\n";
	}
	
	
	function closeLists($pref, $lastPrefix ){
		$output = '';
		$lastPrefixLength = strlen( $lastPrefix );
		$commonPrefixLength = $this->getCommon( $pref, $lastPrefix );
		while( $commonPrefixLength < $lastPrefixLength ) {
			$output .= $this->closeList( $lastPrefix{$lastPrefixLength-1} );
			--$lastPrefixLength;
		}
		return $output;
	}

	
	function nextItem( $char ) {
		if ( '*' == $char || '#' == $char ) { return '</li><li>'; }
		else if ( ':' == $char || ';' == $char ) {
			$close = "</dd>";
			if ( $this->mDTopen ) { $close = '</dt>'; }
			if ( ';' == $char ) {
				$this->mDTopen = true;
				return $close . '<dt>';
			} else {
				$this->mDTopen = false;
				return $close . '<dd>';
			}
		}
		return '<!-- ERR 2 -->';
	}	
	

	function doBlocks( &$text ){
		
		// Parsing through the text line by line.
		// making lists from lines starting with * # : etc.
		//
		// why doesn't this use mbString functions ? !!!
		
		
		// List Variables
		$lastPrefix = $lastLine = '';
		$this->mDTopen = $inBlockElem = $paragraphStack = false;
		$prefixLength = 0;
		$headers = array();

		//Table Variables
		$td = array () ; // Is currently a td tag open?
		$ltd = array () ; // Was it TD or TH?
		$tr = array () ; // Is currently a tr tag open?
		$ltr = array () ; // tr attributes
		
		//Result
		$output = '';
		
		foreach ( $text as $lineText ) {
			
			//Quotes
			$lineText = $this->doQuotes($lineText);
			
			//this isn't quite right.. a line of text could start with "|" and not be a part of a table
			// should set paragraphStack to false with each case below
			$anyPrefix = strspn($lineText, '*#:;=-{|!');
			if( $anyPrefix ){
				$paragraphStack = false;
				$output .= $this->closeParagraph(); 
			}
			
			// List
			$lastPrefixLength = strlen( $lastPrefix );
			// Multiple prefixes may abut each other for nested lists.
			$prefixLength = strspn( $lineText, '*#:;' );
			$pref = substr( $lineText, 0, $prefixLength );

			# eh?
			$pref2 = str_replace( ';', ':', $pref );
			$lineText = substr( $lineText, $prefixLength );

			//
			//
			//	1)	Lists
			//
			//	
			if( $prefixLength && 0 == strcmp( $lastPrefix, $pref2 ) ) { //strcmp returns 0 if str1 = str2
				// Same as the last item, so no need to deal with nesting or opening stuff
				$output .= $this->nextItem( substr( $pref, -1 ) );

				if ( ";" == substr( $pref, -1 ) ) {
					// The one nasty exception: definition lists work like this:
					// ; title : definition text
					// So we check for : in the remainder text to split up the
					// title and definition, without b0rking links.
					// FIXME: This is not foolproof. Something better in Tokenizer might help.
					if( preg_match( '/^(.*?(?:\s|&nbsp;)):(.*)$/', $lineText, $match ) ) {
						$term = $match[1];
						$output .= $term . $this->nextItem( ':' );
						$lineText = $match[2];
					}
				}
				$output .= $lineText."\n";
				continue;
				
			}elseif( $prefixLength ) {
				// Either open or close a level...
				$commonPrefixLength = $this->getCommon( $pref, $lastPrefix );

				$output .= $this->closeLists($pref, $lastPrefix );
				
				if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
					$output .= $this->nextItem( $pref{$commonPrefixLength-1} );
				}
				while ( $prefixLength > $commonPrefixLength ) {
					$char = substr( $pref, $commonPrefixLength, 1 );
					$output .= $this->openList( $char );

					if ( ';' == $char ) {
						// FIXME: This is dupe of code above
						if( preg_match( '/^(.*?(?:\s|&nbsp;)):(.*)$/', $lineText, $match ) ) {
							$term = $match[1];
							$output .= $term . $this->nextItem( ':' );
							$lineText = $match[2];
						}
					}
					++$commonPrefixLength;
				}
				$lastPrefix = $pref2;
				$output .= $lineText."\n";
				continue;
			}
			
			//end list section
			$output .= $this->closeLists($pref,$lastPrefix);
			$lastPrefix = '';

			
			
			//Table Vars
			$fc = substr ( $lineText , 0 , 1 ) ;
			
			//
			//
			//	Horizontal Rule <HR>
			//
			//
			if($fc == '-'){
				$num = strspn( $lineText, '-' );
				if( $num > 3){
					$output .= substr_replace ( $lineText,'<hr />',0,$num)."\n";
					continue;
				}
				// str_replace??
				// $searches = array("\n----","-");
				// $replacements = array('<hr />','');
				// $output .= str_replace($searches,$replacements,"\n".$lineText);
				// continue;
				
				// //preg_replace??
				// $pattern = '/(^|\n)-----*/';
				// $replacement = '\\1<hr />';
				// $output .= preg_replace($pattern,$replacement,$lineText);
				// continue;

			}else if($fc == '='){
				
	 		//
	 		//
			//		2)	HEADERS
			//
			//
				// I wonder how I can make this more efficient that preg_replace?
				$last = strrpos($lineText , '=');
				if( $last < 3){
					$output .= $lineText."\n";
					continue;
				}
				
				$header = substr($lineText,0,$last+1);
				$theRest = substr($lineText,$last+1);
				
				
				$numLeft = strspn($header,'=');
				$numRight = strspn(strrev($header),'=');
				$num = min($numLeft,$numRight,6);
				
				$header = substr($header,$num);
				
				
				// Anchors, 
				$anchor = $header = substr($header,0,-$num);
				
				//if anchor is in a stripState
				while( ($pos = strpos($anchor,$this->random)) !== false ){
					$len = strlen($this->random)+8;
					$stripped = substr($anchor,$pos,$len);
					
					if( isset($this->cStripState[$stripped]) ){
						$stripped = strip_tags($this->cStripState[$stripped]);
						
					}elseif( isset($this->bStripState[$stripped]) ){
						
						$stripped = strip_tags($this->bStripState[$stripped]);
					}else{
						trigger_error('existing stripstate not found');
					}
					
					$anchor = substr_replace($anchor,$stripped,$pos,$len);
					
				}
				
				
				$anchor = strip_tags($anchor);
				$anchor = toStorage($anchor);
				$anchor = str_replace("'",'',$anchor);
				if( isset($headers[$header]) ){
					$headers[$header]++;
					$anchor = $anchor.'_'.$headers[$header];
				}else{
					$headers[$header] = 0;
				}
				
				$tagLeft = '<a name="WBheader'.$anchor.'"></a><h'.$num.'>';
				$tagRight = '</h'.$num.'>';
				
				$output .= $tagLeft.$header.$tagRight.$theRest."\n";
				
				continue;
			}
			
			
			//$lineText = trim ( $lineText );

			//
			//
			//	3)	TABLES
			//
			//
			if ( '{|' == substr ( $lineText , 0 , 2 ) ){
				$output .= "<table " .  substr ( $lineText , 3 ) . ">\n" ;
				array_push ( $td , false ) ;
				array_push ( $ltd , '' ) ;
				array_push ( $tr , false ) ;
				array_push ( $ltr , '' ) ;
				continue;
			// Check for the following table stuff only if count($td) != 0
			}else if ( count ( $td ) != 0 ){
			
				if ( '|}' == substr ( $lineText , 0 , 2 ) ){
					
					$z = "</table>" ;
					$l = array_pop ( $ltd ) ;
					if ( array_pop ( $tr ) ) $z = '</tr>' . $z ;
					if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
					array_pop ( $ltr ) ;
					//$text[$lineKey] = $z ;
					$output .= $z.substr($lineText,2)."\n";
					continue;
				
				//a new row
				}else if ( '|-' == substr ( $lineText , 0 , 2 ) ){ // Allows for |---------------
				
					$lineText = substr ( $lineText , 1 ) ;
					while ( $lineText != '' && substr ( $lineText , 0 , 1 ) == '-' ) $lineText = substr ( $lineText , 1 ) ;
					$z = '' ;
					$l = array_pop ( $ltd ) ;
					if ( array_pop ( $tr ) ) $z = '</tr>' . $z ;
					if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
					array_pop ( $ltr ) ;
					//$text[$lineKey] = $z ;
					$output .= $z;
					array_push ( $tr , false ) ;
					array_push ( $td , false ) ;
					array_push ( $ltd , '' ) ;
					array_push ( $ltr , $lineText ) ;
					continue;
				// Caption	
				}else if ( '|' == $fc || '!' == $fc || '|+' == substr ( $lineText , 0 , 2 ) ){
				
					if ( '|+' == substr ( $lineText , 0 , 2 ) ){
						$fc = '+' ;
						$lineText = substr ( $lineText , 1 ) ;
					}
					$after = substr ( $lineText , 1 ) ;
					if ( $fc == '!' ) $after = str_replace ( '!!' , '||' , $after ) ;
					$after = explode ( '||' , $after ) ;
					//$text[$lineKey] = '' ;
					foreach( $after AS $theline ){
						$z = '' ;
						if ( $fc != '+' ){
							
							$tra = array_pop( $ltr );
							if ( !array_pop( $tr ) ) $z = "<tr{$tra}>\n" ;
							
							array_push ( $tr , true ) ;
							array_push ( $ltr , '' ) ;
						}
	
						$l = array_pop ( $ltd ) ;
						if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
						if ( $fc == '|' ) $l = 'td' ;
						else if ( $fc == '!' ) $l = 'th' ;
						else if ( $fc == '+' ) $l = 'caption' ;
						else $l = '' ;
						array_push ( $ltd , $l ) ;
						$y = explode ( '|' , $theline , 2 ) ;
						if ( count ( $y ) == 1 ) $y = "{$z}<{$l}>{$y[0]}" ;
						else $y = $y = "{$z}<{$l} ".$y[0].">{$y[1]}" ;
						$output .= $y;
						array_push ( $td , true ) ;
					}
					$output .= "\n";
					continue;
				}
			}
			//
			//
			//	Paragraphs
			//
			//
			
			$openmatch = preg_match('#<(table|blockquote|h1|h2|h3|h4|h5|h6|pre|tr|p|ul|li|/tr|/td|/th)#iS', $lineText );
			$closematch = preg_match('#<(/table|/blockquote|/h1|/h2|/h3|/h4|/h5|/h6|td|th|div|/div|hr|/pre|/p|/li|/ul)#iS', $lineText );
			if( $openmatch or $closematch ){
				$paragraphStack = false;
				$output .= $this->closeParagraph();
				if( $closematch ){
					$inBlockElem = false;
				}else{
					$inBlockElem = true;
				}
				
			}elseif ( '' == trim($lineText) ) {
				if ( $paragraphStack ) {
					$output .= $paragraphStack.'<br />';
					$paragraphStack = false;
					$this->mLastSection = 'p';
				} else {
					if ($this->mLastSection != 'p' ) {
						$output .= $this->closeParagraph();
						$this->mLastSection = '';
						$paragraphStack = '<p>';
					} else {
						$paragraphStack = '</p><p>';
					}
				}
			}else{
				if ( $paragraphStack ) {
					$output .= $paragraphStack;
					$paragraphStack = false;
					$this->mLastSection = 'p';
				} else if ($this->mLastSection != 'p') {
					$output .= $this->closeParagraph().'<p>';
					$this->mLastSection = 'p';
				}
			}
			$output .= $lineText."\n";
			
			
		}//end foreach
		
		//clean up open blocks
		if( !empty( $lastPrefix) ){
			$output .= $this->closeLists( '' , $lastPrefix );
		}
		
		//Close Paragraphs
		$output .= $this->closeParagraph();
		
		// Closing open td, tr && table
		while ( count ( $td ) > 0 ){
			if ( array_pop ( $td ) ){
				$output .= "</td>\n";
			}
			if ( array_pop ( $tr ) ){
				$output .= "</tr></table>\n";
			}
		}		
		
		return $output;
	}
	
	/**#@+
	 * Used by doBlockLevels()
	 * @access private
	 */
	function closeParagraph() {
		$result = '';
		if ( '' != $this->mLastSection ) {
			$result = '</' . $this->mLastSection  . ">\n";
		}
		$this->mLastSection = '';
		return $result;
	}	
	
	//quotes
	function doQuotes( &$text ) {
		if( strpos($text,"''") === false){
			return $text;
		}
		$arr = preg_split ("/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
		
		if (count ($arr) == 1)
			return $text;
		else
		{
			// First, do some preliminary work. This may shift some apostrophes from
			// being mark-up to being text. It also counts the number of occurrences
			// of bold and italics mark-ups.
			$i = 0;
			$numbold = 0;
			$numitalics = 0;
			foreach ($arr as $r)
			{
				if (($i % 2) == 1)
				{
					// If there are ever four apostrophes, assume the first is supposed to
					// be text, and the remaining three constitute mark-up for bold text.
					if (strlen ($arr[$i]) == 4)
					{
						$arr[$i-1] .= "'";
						$arr[$i] = "'''";
					}
					// If there are more than 5 apostrophes in a row, assume they're all
					// text except for the last 5.
					else if (strlen ($arr[$i]) > 5)
					{
						$arr[$i-1] .= str_repeat ("'", strlen ($arr[$i]) - 5);
						$arr[$i] = "'''''";
					}
					// Count the number of occurrences of bold and italics mark-ups.
					// We are not counting sequences of five apostrophes.
					if (strlen ($arr[$i]) == 2) $numitalics++;  else
					if (strlen ($arr[$i]) == 3) $numbold++;     else
					if (strlen ($arr[$i]) == 5) { $numitalics++; $numbold++; }
				}
				$i++;
			}

			// If there is an odd number of both bold and italics, it is likely
			// that one of the bold ones was meant to be an apostrophe followed
			// by italics. Which one we cannot know for certain, but it is more
			// likely to be one that has a single-letter word before it.
			if (($numbold % 2 == 1) && ($numitalics % 2 == 1))
			{
				$i = 0;
				$firstsingleletterword = -1;
				$firstmultiletterword = -1;
				$firstspace = -1;
				foreach ($arr as $r)
				{
					if (($i % 2 == 1) and (strlen ($r) == 3))
					{
						$x1 = substr ($arr[$i-1], -1);
						$x2 = substr ($arr[$i-1], -2, 1);
						if ($x1 == " ") {
							if ($firstspace == -1) $firstspace = $i;
						} else if ($x2 == " ") {
							if ($firstsingleletterword == -1) $firstsingleletterword = $i;
						} else {
							if ($firstmultiletterword == -1) $firstmultiletterword = $i;
						}
					}
					$i++;
				}

				// If there is a single-letter word, use it!
				if ($firstsingleletterword > -1)
				{
					$arr [ $firstsingleletterword ] = "''";
					$arr [ $firstsingleletterword-1 ] .= "'";
				}
				// If not, but there's a multi-letter word, use that one.
				else if ($firstmultiletterword > -1)
				{
					$arr [ $firstmultiletterword ] = "''";
					$arr [ $firstmultiletterword-1 ] .= "'";
				}
				// ... otherwise use the first one that has neither.
				// (notice that it is possible for all three to be -1 if, for example,
				// there is only one pentuple-apostrophe in the line)
				else if ($firstspace > -1)
				{
					$arr [ $firstspace ] = "''";
					$arr [ $firstspace-1 ] .= "'";
				}
			}

			// Now let's actually convert our apostrophic mush to HTML!
			$output = '';
			$buffer = '';
			$state = '';
			$i = 0;
			foreach ($arr as $r)
			{
				if (($i % 2) == 0)
				{
					if ($state == 'both')
						$buffer .= $r;
					else
						$output .= $r;
				}
				else
				{
					if (strlen ($r) == 2)
					{
						if ($state == 'em')
						{ $output .= "</em>"; $state = ''; }
						else if ($state == 'strongem')
						{ $output .= "</em>"; $state = 'strong'; }
						else if ($state == 'emstrong')
						{ $output .= "</strong></em><strong>"; $state = 'strong'; }
						else if ($state == 'both')
						{ $output .= "<strong><em>{$buffer}</em>"; $state = 'strong'; }
						else // $state can be 'strong' or ''
						{ $output .= "<em>"; $state .= 'em'; }
					}
					else if (strlen ($r) == 3)
					{
						if ($state == 'strong')
						{ $output .= "</strong>"; $state = ''; }
						else if ($state == 'strongem')
						{ $output .= "</em></strong><em>"; $state = 'em'; }
						else if ($state == 'emstrong')
						{ $output .= "</strong>"; $state = 'em'; }
						else if ($state == 'both')
						{ $output .= "<em><strong>{$buffer}</strong>"; $state = 'em'; }
						else // $state can be 'em' or ''
						{ $output .= "<strong>"; $state .= 'strong'; }
					}
					else if (strlen ($r) == 5)
					{
						if ($state == 'strong')
						{ $output .= "</strong><em>"; $state = 'em'; }
						else if ($state == 'em')
						{ $output .= "</em><strong>"; $state = 'strong'; }
						else if ($state == 'strongem')
						{ $output .= "</em></strong>"; $state = ''; }
						else if ($state == 'emstrong')
						{ $output .= "</strong></em>"; $state = ''; }
						else if ($state == 'both')
						{ $output .= "<em><strong>{$buffer}</strong></em>"; $state = ''; }
						else // ($state == '')
						{ $buffer = ''; $state = 'both'; }
					}
				}
				$i++;
			}
			// Now close all remaining tags.  Notice that the order is important.
			if ($state == 'strong' || $state == 'emstrong')
				$output .= "</strong>";
			if ($state == 'em' || $state == 'strongem' || $state == 'emstrong')
				$output .= "</em>";
			if ($state == 'strongem')
				$output .= "</strong>";
			if ($state == 'both')
				$output .= "<strong><em>{$buffer}</em></strong>";
			return $output;
		}
	}
	//
	//		doBlocks
	//
	////////////////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	//
	//		tidy
	//
	
	function tidyFix(&$text){
		$this->tidyWrap($text);
		
		//message('<textarea style="width:100%" rows="10">'.wbHtmlspecialchars($text).'</textarea>');
		
		$options = array();
		$options['wrap'] = 0;						//keeps tidy from wrapping... want the least amount of space changing as possible.. could get rid of spaces between words with the str_replaces below
		$options['doctype'] = 'transitional';		//omit, auto, strict, transitional, user
		$options['drop-empty-paras'] = false;		//drop empty paragraphs
		$options['lower-literals'] = false;			//keeps tidy from changing html attribute to lowercase... done later in parseXML()
		$options['merge-divs'] = false;				// merge nested <div>'s: <div><div>..</div></div> would become <div>...</div>
		$options['output-xhtml'] = true;			//need this so that <br> will be <br/> .. etc
		
		if( $this->htmlDoc ){
			$options['show-body-only'] = false;
		}else{
			$options['show-body-only'] = true;
		}
		
		
		//
		//	php4
		//
		if( function_exists('tidy_setopt') ){
			$options['char-encoding'] = 'utf8';
			$this->tidyOptions($options);
			$tidy = tidy_parse_string($text);
			tidy_clean_repair();
			
			if( tidy_get_status() === 2){
				// 2 is magic number for fatal error
				// http://www.php.net/manual/en/function.tidy-get-status.php
				$this->tidyErrors[] = 'Tidy found serious XHTML errors: <br/>'.nl2br(wbHtmlspecialchars( tidy_get_error_buffer($tidy)));
				return;
			}
			$text = tidy_get_output();
		
		//	
		//	php5
		//
		}else{
			$tidy = tidy_parse_string($text,$options,'utf8');
			tidy_clean_repair($tidy);
			
			if( tidy_get_status($tidy) === 2){
				// 2 is magic number for fatal error
				// http://www.php.net/manual/en/function.tidy-get-status.php
				$this->tidyErrors[] = 'Tidy found serious XHTML errors: <br/>'.nl2br(wbHtmlspecialchars( tidy_get_error_buffer($tidy)));
				return;
			}
			$text = tidy_get_output($tidy);
		}
			
		$tidyFixes1 = array("<pre>\r","<pre>\n","\n</pre>","\r</pre>");
		$tidyFixes2 = array('<pre>','<pre>','</pre>','</pre>');
		$text = str_replace($tidyFixes1,$tidyFixes2,$text); //tidy adds newlines to <pre>
		//message('<textarea style="width:100%" rows="10">'.wbHtmlspecialchars($text).'</textarea>');
	}
	//for php4
	function tidyOptions($options){
		foreach($options as $key => $value){
			tidy_setopt($key,$value);
		}
	}
		
	function tidyWrap(&$text){
		global $wbConfig;
		
		
		if( $this->htmlDoc){
			return;
		}
		
		$prepend = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
		$prepend .= "<html><head><title></title></head><body>\n";
		$text = $prepend.$text."\n</body></html>";
	}	
	
	//
	//		tidy
	//
	////////////////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	//
	//		doHtml
	//
	
	//
	function getAttributes(&$string){
		$result = array();
		$string = trim($string);
		$p1 = strpos($string,'="');
		while($p1 > 0){
			$len = strcspn($string,'"',$p1+2);
			$result[trim(substr($string,0,$p1))] = substr($string,$p1+2,$len);
			
			$string = substr($string,$p1+3+$len);
			$p1 = strpos($string,'="');
		}
		return $result;
	}

	//	this can add to the parser time by as much as 50%
	//	only used when unsafe and not fixed by tidy
	function fixXML(&$text){
		//message('fix xml: --'.wbHtmlspecialchars($text).'--');
		
		if( $this->isSafe || $this->isAdmin ){
			$lessThan = '&lt;';
		}else{
			$lessThan = '&amp;lt;'; //for the xml parser
		}
		
		$offset = 0;
		$pieces = array();
		$i = 0;
		$result = '';
		$openTags = array();
		
		//while( $this->nextPosition($text,$pos) && ($i < 10) ){
		while( $this->nextPosition($text,$pos) ){
			//message('hmm: ('.$pos.') '.wbHtmlspecialchars($text));
			$i++;
			
			// Test the character after <
			// This is currently fixed by doRegularExp()
			// $nextChar = $text{($pos+1)};
			// if( !ctype_alpha($nextChar) && ($nextChar != '/') ){
			// 	$result .= substr($text,0,$pos).$lessThan;
			// 	$text = substr($text,$pos+1);
			// 	continue;
			// }
			
			$endSymbol = strpos($text,'>',$pos);
			
			//No More >
			if( !$endSymbol ){
				$result .= str_replace('<',$lessThan,$text);
				$this->foundUnsafe = true;
				break; //end loop
			}
			
			$nextStartSymbol = strpos($text,'<',$pos+1);
			$result .= substr($text,0,$pos);
			
			// < before >
			if( ($nextStartSymbol !== false) && ($nextStartSymbol < $endSymbol) ){
				$result .= $lessThan;
				$text = substr($text,$pos+1);
				$this->foundUnsafe = true;
				continue;
			}
			
			
			//We have a tag in between $pos and $endSymbol	
			$tag = substr($text, ($pos+1), ($endSymbol-$pos-1));
			$text = substr($text, ($endSymbol+1));
			
			//Deal with the tags
			if($tag{0} === '/'){
				
				$tag = $this->tagName(substr($tag,1));
				
				//must be opened, else we'll just ignore it
				if( !in_array($tag,$openTags) ){
					$this->foundUnsafe = true;
					continue;
				}
				
				//close the open tags till we match the current closing tag
				while(count($openTags) > 0){
					$openTag = array_pop($openTags);
					$result .= '</'.$openTag.'>';
					if( $openTag == $tag ){
						break;
					}else{
						$this->foundUnsafe = true;
					}
				}
				
			}else{
				$result .= '<'.$tag.'>';
				$lastChar = substr($tag,-1,1);
				$tag = $this->tagName($tag);
				if( !empty($tag) && $lastChar != '/' ){
					$openTags[] = $this->tagName($tag);
				}
			}
		}
		
		//add any left over text
		$text = $result.$text;
		//message('('.$i.')result: --'.wbHtmlspecialchars($result).'--');
	}
	
	function nextPosition(&$text,&$pos){
		$pos = strpos($text,'<');
		if( $pos === false){
			return false;
		}
		return true;
	}
	function tagName($tag){
		$len = strspn(strtolower($tag),'abcdefghijklmnopqrstuvwxyz1234567890');
		return substr($tag,0,$len);
	}

	
	
	function doXML(&$text){
		global $langA,$wbConfig,$wbLinkPrefix;
		$bool = true;
		$fixed = false;
		
		if( isset($wbConfig['tidy']) && $wbConfig['tidy'] == 'On'){
			$this->tidyFix($text);
			$fixed = true;
		}
		
		///////////////////////////////////////////////////////
		//
		//	Don't do the xml_parse
		//
			if( $this->isSafe || $this->isAdmin ){
				return true;
			}
			
			
		///////////////////////////////////////////////////////
		//
		//	Continue with xml_parser
		//
		
			// We want to translate all & so that after we doXML() we get & back
			$text = str_replace('&','&amp;',$text);
		
			//fix tag mismatching
			if( !$fixed ){
				$this->fixXML($text);
			}
		
			//wrap for the parser
			if( !$this->htmlDoc){
				$text = '<WIKYWRAP>'.$text.'</WIKYWRAP>';
			}
			
			//ONLY SUPPORTS ISO-8859-1, UTF-8 and US-ASCII encoding
			$this->xmlParser = xml_parser_create('UTF-8');
			xml_parser_set_option($this->xmlParser, XML_OPTION_CASE_FOLDING, true); // well tags should already be lower case..
			xml_set_element_handler($this->xmlParser, array(&$this, 'startElement'),array(&$this, 'endElement') );
			xml_set_character_data_handler($this->xmlParser, array(&$this, 'characterData') );
			xml_set_processing_instruction_handler( $this->xmlParser, array(&$this, 'instructionHandler') ); 
			//xml_set_external_entity_ref_handler($this->xmlParser, array(&$this, 'externalIdentity') );
			//xml_set_default_handler( $this->xmlParser, array(&$this, 'defaultHandler') );
			
								
			ob_start();
			$bool = xml_parse($this->xmlParser, $text,true); //is_final=true is important
			
			if( !$bool ){
				$this->foundError = $this->foundUnsafe = true;//not safe because we weren't able to check the whole document
				
				//if parse fails, attempt to find the problem
				//	- unstrip to show actual text
				//	- but then line numbers will be off if the stripped was multiline...
				foreach( $this->openTags as $openTag){
					//keep the wrapped html from showing up in myParserErrors
					if( !$this->htmlDoc && $openTag == 'BODY'){
						continue;
					}
					if( isset($this->htmlTags[$openTag]) ){
						$this->closeTags[] = '</'.$this->htmlTags[$openTag].'>';
					}else{
						$this->closeTags[] = '&lt;/'.$openTag.'&gt;';
					}
				}
				echo implode(' ',$this->closeTags);//closes all open tags
				if( $this->warn){
					includeFile('tool/myParserErrors.php');
					$bool = getXmlError( $this, $text );
				}
			}
			
			$text = wb::get_clean();
			
			xml_parser_free($this->xmlParser);
			return $bool;
	}

	
	//	OPENING TAGS
	function startElement(&$parser, &$name, &$attrs){
		global $dbInfo;
		
		if( $name == 'WIKYWRAP'){
			return;
		}
		
		array_unshift($this->openTags,$name);
		$this->lastOpen = $name;
		
		//Links
		if( $name == 'A' ){
			$this->checkLink($attrs);
		}
		

		if( !$lowName = &$this->htmlTags[$name] ){
			echo '&lt;'.strtolower($name);
			echo $this->attributes($attrs);
			echo '&gt;';
			$this->foundUnsafe = true;
			return;
		}
		if( !$this->htmlDoc && isset($this->htmlTagsHead[$name])){
			$this->foundUnsafe = true;
			return;
		}
		
		
		
		echo '<'.$lowName;
		$this->attributes($attrs);
		
		if( isset($this->htmlTagsSingle[$name]) ){
			echo ' /';
		}
		echo '>';
		
	}//startElement()
	
	//
	//	Check for the WBCHECK attribute to make sure it's not a hack attempt
	//
	function checkLink(&$attrs){
		global $page;
		
		//WBCHECK is not set -> so the link was created with HTML -> and cannot have REV
		//	!! test for internal -vs- external links
		if( !isset($attrs['WBCHECK']) ){
			
			//an attempt at a local link
			if( isset($attrs['REV']) ){
				unset($attrs['REV']);
				$this->foundUnsafe = true;
			}
			
		//WBCHECK is set -> so the link was created with wiki Syntax -> REV should match
		}elseif( $attrs['WBCHECK'] != $this->random){
			if( isset($attrs['REV']) ){
				unset($attrs['REV']);
				$this->foundUnsafe = true;
			}
		}
		
		//clean the attrs
		if( isset($attrs['WBCHECK']) ){
			unset($attrs['WBCHECK']);
		}
		
		//nofollow for external links
		if( $this->nofollow && !isset($attrs['REV']) && isset($attrs['HREF']) && (strpos($attrs['HREF'],'://') !== false) ){
			$attrs['REL'] = 'nofollow';
		}
	}
	
	
	//	CLOSING TAGS
	function endElement(&$parser, &$name){
		if( $name == 'WIKYWRAP'){
			return;
		}
		
		$open = array_shift($this->openTags);
		
		if( ($name !== $open) && !empty($open) ){
			array_unshift($this->openTags,$open);
			//There will be an error! do I want to try to correct mistakes here?
			//can we correct it here?
			//!! should keep line numbers associated with each tag so that
			//		more accurate debugging can be done with myParserErrors
		}
		
		
		if( isset($this->htmlTagsSingle[$name]) ){
			return;
		}
		
		if( !$this->htmlDoc && isset($this->htmlTagsHead[$name]) ){
			return;
		}
				
		if( !$lowName = &$this->htmlTags[$name] ){
			echo '&lt;/'.strtolower($name).'&gt;';
			$this->foundUnsafe = true;
			return;
		}
		echo '</'.$lowName.'>';
	}
	
	//admin can do anything
	function attributesAdmin(&$attrs){
		foreach($attrs as $key => $value){
			echo ' '.strtolower($key).'="'.addcslashes($value,'\"').'"'; //just have to escape "
		}
	}
	
	//	CHECK TAG ATTRIBUETES
	function attributes(&$attrs){

		
		//could just be an array intersection ... PHP5
		foreach($attrs as $key => $value){
			
			if( !$key = @$this->htmlAttr[$key]){
				$this->foundUnsafe = true;
				continue;
			}
			
			// SECURITY CHECK, remove potentially malicious attributes
			if( ($key == 'style')||($key == 'src')||($key== 'href') ){
				$replace = array(" ","\t","\n","\r","\v","\s");
				$temp2 = strtolower($value);
				$temp2 = str_replace($replace,'',$temp2);
				
				if( wbStrpos($temp2,'expression') !== false){
					$this->foundUnsafe = true;
					continue;
				}
				if( wbStrpos($temp2,'javascript:') !== false){
					$this->foundUnsafe = true;
					continue;
				}
				if( wbStrpos($temp2,'vbscript:') !== false){
					$this->foundUnsafe = true;
					continue;
				}
				if( wbStrpos($temp2,'mocha:') !== false){
					$this->foundUnsafe = true;
					continue;
				}
			}
			
			echo ' '.$key.'="'.wbStr_replace('"','&quot;',$value).'"'; //just have to change " to &quot;
		}
	}//attributes()

	
	function characterData(&$parser, &$data){
		echo $data;
	}
	
	
	function instructionHandler(&$parser, &$target, &$data){
		global $langA,$pageOwner,$page;
		/*
		//	$parser		resource id
		//	$target		"PRE", "PREDEFINED".. etc
		//	$data		< ?$target $data....? >
		*/
		
		//label as unsafe to force the xmlParse.. even though it could be safe..
		$target = toStorage($target,true);
		switch ($target) {
			case 'php':
				// echo '<pre>';
				// $data = str_replace('&amp;','&',$data);
				// $data = wfUnescapeHTML( $data);
				// highlight_string("<"."?php\n".$data."\n?".">");
				// echo '</pre>';
			return;
			case 'duplicate_entry':
				if( wbStrpos($data,'/') !== 0 ){
					message('BAD_DUPLICATE_ENTRY');
					return;
				}
				$link = '<a href="'.wbLinks::getUrl($data).'" rel="'.$page->revType.'">'.$data.'</a>';
				$txt = '';
				$txt .= '<blockquote style="border:1px solid #e6e6e6;padding:5px;">';
				$txt .= '<b>'.$langA['duplicate_entry'].'</b><br/>';
				$txt .= wbLang::text('DUPLICATE_ENTRY',$link);
				$txt .= '</blockquote>';
				echo $txt;
				$this->foundUnsafe = true;
			return;
			
			case 'lang';
				$data = wbExplode(' ',$data);
				echo call_user_func_array(array('wbLang','text'),$data);
				$this->foundUnsafe = true;
			return;
			
			case 'showlanguage':
				echo showArray($langA);
				$this->foundUnsafe = true;
			return;
			
			case 'message':
				message($data);
				$this->foundUnsafe = true;
			return;
		}			
	}
	
	// function externalIdentity(&$parser, &$open_entity_names, &$base, &$system_id, &$public_id ){
	// 	return 'this is the replacement?';
	// }

	// this is where <!DOCTYPE would be handled..
	
	// function defaultHandler(&$parser, &$data){
	// 	if( !$this->htmlDoc){
	// 		echo $data;
	// 		//message(wbHtmlspecialchars($data));
	// 	}
	// 	// $this->testing[] = $data;
	// 	// $data = 'more text';
	// 	return true;
	// }

	//
	//		doHtml
	//
	////////////////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	//
	//		Third Party
	//
	
	//	<code>
	//	<code lang="php">
	//
	function doCode(&$code,&$marker){
		includeFile('thirdParty/GeSHi/geshi.php');
		
		$pos = strpos($code,'>');
		$arg = substr($code,0,$pos);
		$code = substr($code,$pos+1);
		$args = $this->getAttributes($arg);
		
		$language = 'php';
		if( isset($args['language']) ){
			$language = $args['language'];
		}elseif( isset($args['lang']) ){
			$language = $args['lang'];
		}
		
		$language = wbHtmlspecialchars($language);
		
		$space = '';
		$whiteCount = strspn($code,"\r\n \t\v\s");
		if( $whiteCount > 0 ){
			$white = strrev(substr($code,0,$whiteCount));
			$spaceCount = strspn($white," \t\s");
			$space = strrev(substr($white,0,$spaceCount));
		}
		$code = $space.trim($code);
		
		$geshi = new GeSHi($code,$language);
		$geshi->set_overall_class('code '.toStorage($language,true));
		$this->cStripState += array( $marker => $geshi->parse_code());
	}
	

}



//
//		wikiParser
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	
//		Additional Functions
//

//htmlspecailchars does &,",',<,>
// function wfEscapeHTMLTagsOnly( $in ) {
// 	return str_replace(
// 		array( '"', '>', '<' ),
// 		array( '&quot;', '&gt;', '&lt;' ),
// 		$in );
// 		
// }


// function wfUnescapeHTML( $in ){
// 	return str_replace(
// 		array( '&quot;', '&gt;', '&lt;' ),
// 		array( '"', '>', '<' ),
// 		$in );
// }




// function wikiWikiLinks($arg1){
// 	$pattern = '#([A-Z][a-z]+)(?:([A-Z][a-z]+)+)#';
// 	preg_match($pattern,$arg1,$matches);
// 	unset($matches[0]);
// 	message(showArray($matches));
// 	$arg1 = '<a href="#">';
// 	foreach($matches as $piece){
// 		$arg1 .= $piece.' ';
// 	}
// 	$arg1 .= '</a>';
// 	
// 	
// 	message('Here: %s',$arg1);
// 	return $arg1;
// }








//
//		Additional Functions
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
