W3TPL/source

From HTYP, the free directory anyone can edit

Jump to: navigation, search

[edit] Preface

This is just a snapshot from my editor, but it does work except for the <w3tpl> tag, which is in development. Parser interactions may make it difficult to use the existing tags for complex applications, but you can get around many issues by using the "copy" attribute and $variable indirection (to be documented).

[edit] Code

/*
 HISTORY:
	0.01 (Wzl) Mainly proof-of-concept;
		parser should later be optimized for execution time by using PHP intrinsic string fx instead of PHP-code loop
	0.02 (Wzl) Kluge to let <xploop> pull #var value under MW <1.12
 TO DO: If page is not protected, pre should be forced FALSE and post should be forced TRUE
	"pre" can do weird things
	"post" set false allows raw HTML
	0.03 (Wzl) <func> and related tags seem to be working
	0.04 (Wzl) Some debugging; now works with v1.12 {{#tag}} function and template parameters (but not very well)
		names are always lowercased because sometimes the parser does it for you
		names are always trimmed, because sometimes the parser includes extra spaces
	0.05 (Wzl) Added variable indirection ($); removed now-redundant "namer" attribute from <let>
		can we do something similar with pre-parsing? (i.e. a character to indicate the need for it -- @(stuff to parse))
		<if> can now make more sense, i.e. using actual values instead of assuming variable names
	0.06 (Wzl) xploop now using variable indirection; removed listvar parameter
	0.07 (Wzl) Execution trace in <dump>
	0.08 (Wzl) <trace> to set trace options; "input" option in <call>
	0.09 (Wzl) <echo> tag; <call> does not output its contents
	0.10 (Wzl) Code runs ok; still writing <w3tpl> tag
	0.11 (Wzl) Added detection of page-protection, and "raw" attribute for <echo>
	0.12 (Wzl) Fixed minor incompatibilities with MW 1.10
	0.13 (Wzl) <for> tag seems to be working, at least for simple stuff
	0.14 (Wzl) <load> tag fails gracefully when title doesn't exist
	0.15 (Wzl) Support for brackets in variable names, to reference arrays
	0.16 (Wzl) No SQL on unprotected pages; "limit" attribute for retrieving partial data
	0.17 (Wzl) Removed some undefined-var warnings which pop up under certain mysterious circumstances
	0.18 (Wzl) Lots of under-the-hood changes; var names are now parsed by the class clsW3VarName, which may later get renamed to clsW3Var and integrated with the full-blown parser to be written. There's still some ambiguity about the exact circumstances under which <let> overwrites existing data and when it operates on the variable's current value.
	0.19 (Wzl) $wgW3TPLSettings['raw-ok'] now allows raw output even if page not protected
	0.20 (Wzl) added "pre" as a deprecated alias for "parse", for backwards compatibility
	0.21 (Wzl) merged changes from other version 0.19 (accidental fork):
		"user.name" and "user.dbkey" system data
 BUGS:
	$wgW3_func needs to be an array, so you can do <call...><arg><arg></call> and then pass the return value as an argument to another function (2008-10-15: in retrospect, I'm not sure how making it an array would help, though functions probably do need to have the ability to return values)
 ELEMENTS:
	<hide>: Runs the parser on everything in between the tags, but doesn't display the result.
		Useful for doing a lot of "programmish" stuff, so you can format it nicely and comment it without messing up your display
	<let>, <get>: much like PageVars extension, but using XML tags instead of {{#parser}} functions
	<func>: defines a function which can be called with arguments later
		<arg>: optional method of passing arguments to a function
	<call>: call a previously defined function
	<dump>: show list of all variables (with values) and functions (with code)
	<if>, <else>: control structure
	<xploop list="\demarcated\list" repl=string-to-replace sep=separator></xploop>: Same as {{#xploop}}, but uses varname instead of $s$
	TO-DO <w3tpl></w3tpl>: The language itself
*/
 
$wgExtensionCredits['other'][] = array(
        'name' => 'W3TPL',
        'description' => 'Woozle\'s Wacky Wiki Text Processing Language',
        'author' => 'Woozle (Nick) Staddon',
	'url' => 'http://htyp.org/W3TPL', 
	'version' => '0.21 2009-01-08 (alpha)'
);
 
define('ksFuncInit','efW3TPLInit');
 
//Avoid unstubbing $wgParser on setHook() too early on modern (1.12+) MW versions, as per r35980
if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) {
        $wgHooks['ParserFirstCallInit'][] = ksFuncInit;
} else { // Otherwise do things the old fashioned way
        $wgExtensionFunctions[] = ksFuncInit;
}
$wgHooks['LanguageGetMagic'][] = 'efW3_LanguageGetMagic';
 
 
function efW3TPLInit() {
        global $wgParser;
	global $wgExtW3TPL;
	global $wgW3RawOk;
 
// hook in <tag>-style functions:
        $wgParser->setHook( 'hide',	'efW3Hide' );
        $wgParser->setHook( 'let',	'efW3Let' );
        $wgParser->setHook( 'get',	'efW3Get' );
        $wgParser->setHook( 'echo',	'efW3Echo' );
        $wgParser->setHook( 'dump',	'efW3Dump' );
        $wgParser->setHook( 'trace',	'efW3Trace' );
        $wgParser->setHook( 'if',	'efW3If' );
        $wgParser->setHook( 'else',	'efW3Else' );
        $wgParser->setHook( 'for',	'efW3For' );
        $wgParser->setHook( 'func',	'efW3Func' );
        $wgParser->setHook( 'call',	'efW3Call' );
        $wgParser->setHook( 'arg',	'efW3Arg' );
        $wgParser->setHook( 'load',	'efW3Load' );
        $wgParser->setHook( 'xploop',	'efW3Xploop' );
        $wgParser->setHook( 'w3tpl',	'efW3TPLRender' );
 
// hook in {{#parser}}-style functions:
/*
MW 1.13 is totally not happy with "let", and I don't know why.
Abandoning these for now; they didn't turn out to be useful in getting past the parser.
	$wgExtW3TPL = new W3TPL_fx ( );
	$wgParser->setFunctionHook ( 'w3xploop',	array ( &$wgExtW3TPL, 'runXploop'    ) );
	$wgParser->setFunctionHook ( 'w3xpcount',	array ( &$wgExtW3TPL, 'runXpCount'  ) );
	$wgParser->setFunctionHook ( 'let',		array ( &$wgExtW3TPL, 'runLet'  ) );
	$wgParser->setFunctionHook ( 'get',		array ( &$wgExtW3TPL, 'runGet'  ) );
*/
        return true;
}
function efW3_LanguageGetMagic( &$magicWords, $langCode = "en" ) {
    switch ( $langCode ) {
        default:
            $magicWords['w3xploop']	= array ( 0, 'w3xploop' );
            $magicWords['w3xpcount']	= array ( 0, 'w3xpcount' );
    }
    return true;
}
 
 
function TrueFalse($iVar) {
	return $iVar?'TRUE':'FALSE';
}
function W3VarExists($iName) {
	global $wgW3Vars;
 
	$strName = strtolower($iName);
	return isset($wgW3Vars[$strName]);
}
function W3KillVar($iName) {
	global $wgW3Vars;
 
	$strName = strtolower($iName);
	unset($wgW3Vars[$strName]);
}
function W3SetVar($iName, $iValue, $iAppend = FALSE) {
	global $wgW3Vars, $wgW3_doTrace_vars;
 
	$strName = strtolower($iName);
	if ($iAppend && isset($wgW3Vars[$strName])) {
		$wgW3Vars[$strName] .= $iValue;
		if ($wgW3_doTrace_vars) {
			W3AddTrace(' $['.ShowHTML($strName).'] += ['.$iValue.'] => ['.$wgW3Vars[$strName].']');
		}
	} else {
		$wgW3Vars[$strName] = $iValue;
		if ($wgW3_doTrace_vars) {
			W3AddTrace(' $['.ShowHTML($strName).'] = ['.$iValue.']');
		}
	}
}
function W3GetSysData($iName) {
	global $wgParser,$wgTitle,$wgUser;
	global $wgW3_doTrace_vars;
	global $wgW3_data;
 
	$strName = strtolower($iName);
	$strParts =  explode('.', $strName);
	switch ($strParts[0]) {
	case 'title':
		switch ($strParts[1]) {
		case 'id':
			$out = $wgTitle->getArticleID();
			break;
		}
		case 'subject':
			$out = $wgTitle->getText();
			break;
		break;
	case 'row':
		$out = $wgW3_data[$strParts[1]]->$strParts[2];
		break;
	case 'mem':
		$out = memory_get_usage(TRUE);
		break;
	case 'user':
		switch ($strParts[1]) {
		case 'login':
 			$out = $wgUser->getName();
			break;
		case 'dbkey':
			$out = $wgUser->getTitleKey();
			break;
		}
	}
	if ($wgW3_doTrace_vars) {
		W3AddTrace(' GETSYSDATA ['.ShowHTML($iName).']: ['.$out.']');
	}
	return $out;
}
function W3SetSysData($iName,$iValue) {
	global $wgOut;
	global $wgW3Trace_indent;
 
	W3AddTrace(' SETSYSDATA ['.$iName.':'.$iValue.']');
	$strName = strtolower($iName);
	$strParts =  explode('.', $strName);
	switch ($strParts[0]) {
	case 'catg':
		$wgW3Trace_indent++;
		W3AddTrace('CATG');
		$wgW3Trace_indent--;
		$wgOut->mCategoryLinks = array();
		break;
	}
}
function W3GetExpr($iName) {
// check expression for $, meaning it's actually a reference to a variable
// If found, return value of variable - otherwise return original string.
	$objVar = new clsW3VarName($iName);
	$objVar->Trace();
	$objVar->Fetch();
	$strOut = $objVar->Value;
	return $strOut;
 
}
function W3GetVal($iName,$iIndex=NULL) {
// gets value of given variable
// checks function arguments, if function is defined
	$objVar = new clsW3VarName();
	$objVar->ParseName($iName);
	if (!is_null($iIndex)) {
		$objVar->SetIndex($iIndex);
	}
	$objVar->Trace();
	$objVar->Fetch();
	$strVal = $objVar->Value;
	return $strVal;
}
function W3GetEcho() {
	global $wgW3_echoOutput, $wgW3_echoDepth;
	if (isset($wgW3_echoOutput[$wgW3_echoDepth])) {
		$out = $wgW3_echoOutput[$wgW3_echoDepth];
		W3AddTrace('ECHO('.$wgW3_echoDepth.'): CLEARING ['.ShowHTML($out).']');
		unset($wgW3_echoOutput[$wgW3_echoDepth]);
	} else {
		W3AddTrace('ECHO('.$wgW3_echoDepth.'): nothing there');
		$out = NULL;
	}
	return $out;
}
function W3AddEcho($iVal) {
	global $wgW3_echoOutput, $wgW3_echoDepth;
 
	if (isset($wgW3_echoOutput[$wgW3_echoDepth])) {
		W3AddTrace('ECHO('.$wgW3_echoDepth.') was ['.ShowHTML($wgW3_echoOutput[$wgW3_echoDepth]).'], adding ['.$iVal.']');
		$wgW3_echoOutput[$wgW3_echoDepth] .= $iVal;
		W3AddTrace('ECHO('.$wgW3_echoDepth.') now is ['.ShowHTML($wgW3_echoOutput[$wgW3_echoDepth]).']');
	} else {
		$wgW3_echoOutput[$wgW3_echoDepth] = $iVal;
		W3AddTrace('ECHO('.$wgW3_echoDepth.') is ['.ShowHTML($iVal).']');
	}
}
function W3AddTrace($iLine,$iInd=0) {
	global $wgW3Trace, $wgW3Trace_indents, $wgW3Trace_indent;
	global $wgW3_doTrace;
	global $wgW3_TraceCount;
 
	if ($wgW3_doTrace) {
		if ($wgW3_TraceCount < 1000) {
			$wgW3Trace[] = $iLine;
			$wgW3Trace_indents[] = $wgW3Trace_indent;
			$wgW3Trace_indent += $iInd;
		}
		$wgW3_TraceCount++;
	}
/**/
}
function W3Status_RawOk() {
	global $wgTitle;
	global $wgW3_func;
	global $wgW3TPLSettings;
 
	if ($wgW3TPLSettings['raw-ok']) {
		$isProt = TRUE;
	} else {
		$isProt = $wgTitle->isProtected ('edit');
	}
	if (!$isProt) {
		if (is_object($wgW3_func)) {
			$isProt = $wgW3_func->isOkRaw;
		}
	}
	W3AddTrace('IS RAW ok in ['.$wgTitle->getFullText().']: '.TrueFalse($isProt));
	return $isProt;
}
function W3Status_SQLOk() {
	global $wgTitle;
	global $wgW3_func;
 
	$isProt = $wgTitle->isProtected ('edit');
	if (!$isProt) {
		if (is_object($wgW3_func)) {
			$isProt = $wgW3_func->isOkSQL;
		}
	}
	W3AddTrace('IS SQL allowed in ['.$wgTitle->getFullText().']: '.TrueFalse($isProt));
	return $isProt;
}
function efW3Hide( $input, $args, $parser ) {
	$parser->recursiveTagParse( $input );
	return NULL;
}
function W3Let_scalar( $iVar, $iArgs, $input, $parser ) {
	global $wgRequest;
	global $wgW3_func;
 
	$strRepl = $iArgs->GetVal('repl');
	$strWith = $iArgs->GetVal('with');
	$doRepl = !is_null($strRepl) || !is_null($strWith);
	$doAppend = $iArgs->Exists('append');
 
// TRACING:
	$strTrace = ' - LET scalar:';
	if (!is_null($strRepl)) {
		$strTrace .= ' repl=&ldquo;'.$strRepl.'&rdquo;';
	}
	if (!is_null($strWith)) {
		$strTrace .= ' repl=&ldquo;'.$strWith.'&rdquo;';
	}
	if ($doAppend) {
		$strTrace .= ' APPEND';
	}
	W3AddTrace($strTrace);
 
	if ($iArgs->Exists('val')) {
		$iVar->Value = $iArgs->GetExpr('val');
		$strTrace = ' - LET VAL: expr=['.ShowHTML($iVar->Value).']';
		W3AddTrace($strTrace);
	} elseif ($iArgs->Exists('arg')) {
		$strCopy = $iArgs->vArgs['arg'];	// don't do any indirection from user input (possible security hole)
		$parser->disableCache();
		$iVar->Value = $wgRequest->getVal($strCopy); // , $strDefault) -- maybe add feature later
	} elseif ($iArgs->Exists('farg')) {
		if (is_null($wgW3_func)) {
			W3AddTrace(' - ERROR: no function active to provide arg ['.$strName.']');
		} else {
			$strName = strtolower($iArgs->GetExpr('farg'));
			if ($wgW3_func->HasArg($strName)) {
				$iVar->Value = $wgW3_func->ArgVal($strName);
				W3AddTrace(' - ARG['.$strName.'] => &ldquo;'.$iVar->Value.'&rdquo;');
			} else {
				W3AddTrace(' - ERROR: function ['.$wgW3_func->Name.'] has no argument named ['.$strName.'].');
			}
		}
	} elseif ($iArgs->Exists('chr')) {
		$iVar->Value = chr($iArgs->GetExpr('chr'));
	}
 
// AT THIS POINT, $this->Value is loaded with the value we want to operate on.
 
// later, we may want inc/dec to imply self-operation if there is no other input...
//	but this needs to be thought through carefully. For now, require "self" to increment self.
 
// do processing on current value:
	if ($iArgs->Exists('inc')) {
		$iVar->Value++;
	}
	if ($iArgs->Exists('dec')) {
		$iVar->Value--;
	}
 
	if ($iArgs->Exists('parse') || $iArgs->Exists('pre')) {		// restoring "pre" for backwards compatibility
		$iVar->Value = $parser->recursiveTagParse($iVar->Value);
	}
	if ($iArgs->Exists('ucase')) {
		$iVar->Value = strtoupper($iVar->Value);
	}
	if ($iArgs->Exists('lcase')) {
		$iVar->Value = strtolower($iVar->Value);
	}
	if ($iArgs->Exists('ucfirst')) {
		$iVar->Value = ucfirst($iVar->Value);
	}
	if ($iArgs->Exists('lcfirst')) {
		$iVar->Value = lcfirst($iVar->Value);
	}
	if ($iArgs->Exists('trim')) {
		$iVar->Value = trim($iVar->Value);
	}
	if ($iArgs->Exists('len')) {
		$strLen = $iArgs->GetExpr('len');
		if (is_numeric($strLen)) {
			$iVar->Value = substr($iVar->Value,0,$strLen);
		}
	}
	if ($doRepl) {
		if (is_null($strRepl)) {
			$strRepl = $input;
		} elseif (is_null($strWith)) {
			$strWith = $input;
		}
		$strRes = str_replace($strRepl,$strWith,$iVar->Value);
		W3AddTrace('LET REPLACE ['.$strRepl.'] WITH ['.$strWith.'] => ['.$strRes.'] in &ldquo;'.$iVar->Value.'&rdquo;');
		$iVar->Value = $strRes;
	}
	W3AddTrace('LET ['.$iVar->Name.'] <= &ldquo;'.$iVar->Value.'&rdquo;');
 
// AT THIS POINT, we have the semi-final value to be stored
// -- if it's being appended, then get the old value and prepend it to the new:
 
	if ($doAppend) {
		$valNew = $iVar->Value;		// save the newly-calculated value off to one side
 
		W3AddTrace(' - APPEND &ldquo;'.$valNew.'&rdquo;');
		$iVar->Fetch();			// restore the prior value
		$iVar->Value .= $valNew;	// append the new value to the prior value
	}
}
function W3Let_array ( $iVar, $iArgs, $input, $parser ) {
	$doSort = $iArgs->Exists('sort');
 
	if ($doSort) {
		$iVar->DoSort($iArgs->Exists('rev'),$iArgs->Exists('val'));
	}
}
function efW3Let( $input, $args, $parser ) {
        global $wgRequest;
	global $wgW3Vars,$wgW3_func;
 
	$strCopy = NULL;
	$objArgs = new W3HookArgs($args);
	$strNameRaw = $objArgs->GetVal('name');
 
// trim whitespace and normalize name:
	$strName = strtolower(trim($strNameRaw));
	$objVar = new clsW3VarName();
	$objVar->ParseName($strName);	// resolve any indirection (e.g. $var)
 
	if (isset($args['index'])) {
		$strIdx = W3GetExpr($args['index']);
		$objVar->SetIndex($strIdx);
	}
	$strName = $objVar->Name;
	W3AddTrace('LET name=['.$strNameRaw.'] parsed to ['.$strName.']');
 
	if (isset($args['null'])) {
	// if "null" option, then nothing else matters
		$objVar->Value = NULL;
	} else {
		// "copy" option works for any data type:
		if ($objVar->CheckCopy($objArgs)) {
		// do nothing; work already done
		} else {
			if (is_null($input) or isset($args['self'])) {
				$objVar->Fetch();
				W3AddTrace(' - from self: ['.$objVar->Value.']');
			} else {
				$objVar->Value = $input;
				W3AddTrace(' - from input: ['.$objVar->Value.']');
			}
		}
		if ($objVar->IsArray()) {
			W3Let_array ( $objVar, $objArgs, $input, $parser );
		} else {
			W3Let_scalar ( $objVar, $objArgs, $input, $parser );
		}
	}
 
	$objVar->Store();
 
// (optional) print the results:
	if (isset($args['echo'])) {
		return $objVar->Value;
	} else {
		return NULL;
	}
/**/
}
function efW3Get( $input, $args, $parser ) {
        global $wgRequest;
 
	$objArgs = new W3HookArgs($args);
	W3AddTrace('GET:');
 
	if (isset($args['default'])) {
		$strDefault = $args['default'];
	} else {
		$strDefault = NULL;
	}
	if (isset($args['val'])) {
		$strVal = $args['val'];
		$strVal = W3GetExpr($strVal);	// check for redirections
	} elseif (isset($args['arg'])) {
		$parser->disableCache();
		$strVal = $wgRequest->getVal($strName, $strDefault);
	} else {
		$strName = strtolower($args['name']);
		W3AddTrace(' - name=['.$strName.']');
 
		$strIdx = $objArgs->GetExpr('index',TRUE);
		$strVal = W3GetVal($strName,$strIdx);
	}
	if (isset($args['codes'])) {
		$strVal = ShowHTML($strVal);
	} else {
		$doRaw = FALSE;
		if (isset($args['raw'])) {
			if (W3Status_RawOk()) {
				$doRaw = TRUE;
			}
		}
		if (!$doRaw) {
			$strVal = $parser->recursiveTagParse($strVal);
		}
	}
	if (isset($args['len'])) {
		$strVal = substr($strVal,0,$args['len']);
	}
	if (isset($args['ucase'])) {
		$strVal = strtoupper($strVal);
	}
	if (isset($args['lcase'])) {
		$strVal = strtolower($strVal);
	}
	return $strVal;
}
function efW3Echo( $input, $args, $parser ) {
	global $wgW3_echoDepth;
 
	W3AddTrace('{ECHO('.$wgW3_echoDepth.'):');
	if (!isset($wgW3_echoDepth)) { $wgW3_echoDepth = 0; }
 
	if (isset($args['chr'])) {
		$valIn = chr($args['chr']);
	} else {
		$valIn = $input;
	}
 
	if (isset($args['strip'])) {
		$out = ShowHTML($valIn);
	}
	$doRaw = FALSE;
	if (isset($args['raw'])) {
		if (W3Status_RawOk()) {
			$doRaw = TRUE;
		}
	}
	$doNow = isset($args['now']);
	W3AddTrace(' ('.$wgW3_echoDepth.') input:['.ShowHTML($valIn).']');
	if ($doRaw) {
		$out = $valIn;
	} else {
		$wgW3_echoDepth++;
		$out = $parser->recursiveTagParse($valIn);
		W3AddTrace(' ('.$wgW3_echoDepth.') PARSING returned ['.ShowHTML($out).']');
		$wgW3_echoDepth--;
//		W3AddEcho($tmp);
	}
 
	W3AddTrace(' ('.$wgW3_echoDepth.') output: ['.ShowHTML($out).'] ECHO}');
	if ($doNow) {
		return $out;
	} else {
		W3AddEcho($out);
	}
}
function efW3Dump( $input, $args, $parser ) {
	global $wgW3Vars, $wgW3_funcs;
	global $wgW3_doTrace, $wgW3_doTrace_vars;	// tracing options
	global $wgW3Trace, $wgW3Trace_indents;	// tracing data
	global $wgW3_TraceCount;
 
	$out = '<ul>';
	$doOnly = isset($args['only']);			// default to NOT showing everything
	$doTrace = !$doOnly || isset($args['trace']);	// show trace log
	$doVars = !$doOnly || isset($args['vars']);	// show all variables
	$doFuncs = !$doOnly || isset($args['funcs']);	// show function definitions
	$doMem = !$doOnly || isset($args['mem']);	// show memory usage
 
	$wgW3_doTrace = $doTrace;
	$wgW3_doTrace_vars = $doTrace && $doVars;
 
	if ($doMem) {
		$out .= '<li> <b>Memory usage before</b>: '.memory_get_usage(TRUE).' bytes';
	}
 
	if ($input != '') {
		$out = $parser->recursiveTagParse($input);
	}
	if ($doMem) {
		$out .= '<li> <b>Memory usage after</b>: '.memory_get_usage(TRUE).' bytes';
	}
	if ($doVars) {
		if (is_array($wgW3Vars)) {
			$out .= '<li><b>Variables</b>:<ul>';
			foreach ($wgW3Vars as $name => $value) {
				if (is_array($value)) {
					$out .= '<li> ['.$name.']: array';
					$out .= '<ul>';
					foreach ($value as $akey => $aval) {
						$out .= '<li> '.$name.'['.$akey.'] = ['.ShowHTML($aval).']';
					}
					$out .= '</ul>';
				} else {
					$out .= '<li> ['.$name.'] = ['.$value.']';
				}
			}
			$out .= '</ul>';
		} else {
			$out .= '<li><i>No variables set</i>';
		}
	}
	if ($doFuncs) {
		if (is_array($wgW3_funcs)) {
			$out .= '<li><b>Functions</b>:<ul>';
			foreach ($wgW3_funcs as $name => $obj) {
				$out .= '<li>'.$obj->dump();
			}
			$out .= '</ul>';
		} else {
			$out .= '<li><i>No functions defined</i>';
		}
	}
	if ($doTrace) {
		if (is_array($wgW3Trace)) {
			$out .= '<li><b>Trace</b> ('.$wgW3_TraceCount.' events):<ul>';
			$indCur = 0;
			foreach ($wgW3Trace as $idx => $line) {
 
				$indLine = $wgW3Trace_indents[$idx];
				if ($indLine > $indCur) {
					$out .= '-<b>'.$indLine.'</b>-<ul>';
				} elseif ($indLine < $indCur) {
					$out .= '</ul>';
				}
				$indCur = $indLine;
/**/
				$out .= '<li><b>'.$idx.'</b> '.$line;
			}
			$out .= '</ul>';
		} else {
			$out .= '<li><i>'.$wgW3_TraceCount.' trace events</i>';
		}
	}
	$out .= '</ul>';
	return $out;
}
/*
function efW3Trace( $input, $args, $parser ) {
	global $wgW3_doTrace_vars;
 
	$wgW3_doTrace_vars = isset($args['assigns']);
}
*/
function efW3If( $input, $args, $parser ) {
	global $wgW3_ifFlag,$wgW3_ifDepth;
 
	$doHide = isset($args['hide']);	// only output <echo> sections
 
	$ifFlag = false;
	if (!$wgW3_ifDepth) {
		$wgW3_ifDepth = 0;
	}
	if (isset($args['flag'])) {
		$strName = $args['flag'];
		$strVal = W3GetVal($strName);
		if (is_null($strVal) || ($strVal == '')) {
			$ifFlag = FALSE;
			$dbgType = 'blank';
		} else if (is_numeric($strVal)) {
			$ifFlag = ($strVal != 0);
			$dbgType = 'numeric';
		} else {
			$ifFlag = TRUE;
			$dbgType = '';
		}
		W3AddTrace('IF(@'.$wgW3_ifDepth.'): ['.$strVal.']  != 0: ['.TrueFalse($ifFlag).']:'.$dbgType);
	} elseif (isset($args['comp'])) {
// We need to be able to pass either constants or variables via these parameters, so use GetExpr not GetVal
		$strName = $args['comp'];
		$strVal1 = W3GetExpr($strName);
		$strTrace = '('.$strName.'="'.$strVal1.'")';
		$strName = $args['with'];
		$strVal2 = W3GetExpr($strName);
		$strTrace .= '('.$strName.'="'.$strVal2.'")';
		if (isset($args['pre'])) {
			$wgW3_ifDepth++;
			$strVal1 = $parser->recursiveTagParse($strVal1);
			$strVal2 = $parser->recursiveTagParse($strVal2);
			$wgW3_ifDepth--;
//			$strVal1 = $parser->replaceVariables($strVal1);
//			$strVal2 = $parser->replaceVariables($strVal2);
 
		}
		$ifFlag = ($strVal1 == $strVal2);
		W3AddTrace('IF(@'.$wgW3_ifDepth.')'.$strTrace.':'.TrueFalse($ifFlag));
	}
	if (isset($args['not'])) {
		$ifFlag = !$ifFlag;	// invert the flag
	}
	$wgW3_ifFlag[$wgW3_ifDepth] = $ifFlag;
	if ($ifFlag) {
		$wgW3_ifDepth++;
		$out = $parser->recursiveTagParse($input);
		$wgW3_ifDepth--;
	} else {
		$out = NULL;
	}
 
	if ($doHide) {
		$out = W3GetEcho(