From 183851b06bd6c52f3cae5375f433da720d410447 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Wed, 11 Oct 2006 18:12:39 +0000 Subject: MediaWiki 1.7.1 wiederhergestellt --- includes/cbt/CBTProcessor.php | 540 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 540 insertions(+) create mode 100644 includes/cbt/CBTProcessor.php (limited to 'includes/cbt/CBTProcessor.php') diff --git a/includes/cbt/CBTProcessor.php b/includes/cbt/CBTProcessor.php new file mode 100644 index 00000000..0c34204e --- /dev/null +++ b/includes/cbt/CBTProcessor.php @@ -0,0 +1,540 @@ + '{[}', '}' => '{]}' ) ); +} + +/** + * Create a CBTValue + */ +function cbt_value( $text = '', $deps = array(), $isTemplate = false ) { + global $cbtExecutingGenerated; + if ( $cbtExecutingGenerated ) { + return $text; + } else { + return new CBTValue( $text, $deps, $isTemplate ); + } +} + +/** + * A dependency-tracking value class + * Callback functions should return one of these, unless they have + * no dependencies in which case they can return a string. + */ +class CBTValue { + var $mText, $mDeps, $mIsTemplate; + + /** + * Create a new value + * @param $text String: , default ''. + * @param $deps Array: what this value depends on + * @param $isTemplate Bool: whether the result needs compilation/execution, default 'false'. + */ + function CBTValue( $text = '', $deps = array(), $isTemplate = false ) { + $this->mText = $text; + if ( !is_array( $deps ) ) { + $this->mDeps = array( $deps ) ; + } else { + $this->mDeps = $deps; + } + $this->mIsTemplate = $isTemplate; + } + + /** Concatenate two values, merging their dependencies */ + function cat( $val ) { + if ( is_object( $val ) ) { + $this->addDeps( $val ); + $this->mText .= $val->mText; + } else { + $this->mText .= $val; + } + } + + /** Add the dependencies of another value to this one */ + function addDeps( $values ) { + if ( !is_array( $values ) ) { + $this->mDeps = array_merge( $this->mDeps, $values->mDeps ); + } else { + foreach ( $values as $val ) { + if ( !is_object( $val ) ) { + var_dump( debug_backtrace() ); + exit; + } + $this->mDeps = array_merge( $this->mDeps, $val->mDeps ); + } + } + } + + /** Remove a list of dependencies */ + function removeDeps( $deps ) { + $this->mDeps = array_diff( $this->mDeps, $deps ); + } + + function setText( $text ) { + $this->mText = $text; + } + + function getText() { + return $this->mText; + } + + function getDeps() { + return $this->mDeps; + } + + /** If the value is a template, execute it */ + function execute( &$processor ) { + if ( $this->mIsTemplate ) { + $myProcessor = new CBTProcessor( $this->mText, $processor->mFunctionObj, $processor->mIgnorableDeps ); + $myProcessor->mCompiling = $processor->mCompiling; + $val = $myProcessor->doText( 0, strlen( $this->mText ) ); + if ( $myProcessor->getLastError() ) { + $processor->error( $myProcessor->getLastError() ); + $this->mText = ''; + } else { + $this->mText = $val->mText; + $this->addDeps( $val ); + } + if ( !$processor->mCompiling ) { + $this->mIsTemplate = false; + } + } + } + + /** If the value is plain text, escape it for inclusion in a template */ + function templateEscape() { + if ( !$this->mIsTemplate ) { + $this->mText = cbt_escape( $this->mText ); + } + } + + /** Return true if the value has no dependencies */ + function isStatic() { + return count( $this->mDeps ) == 0; + } +} + +/** + * Template processor, for compilation and execution + */ +class CBTProcessor { + var $mText, # The text being processed + $mFunctionObj, # The object containing callback functions + $mCompiling = false, # True if compiling to a template, false if executing to text + $mIgnorableDeps = array(), # Dependency names which should be treated as static + $mFunctionCache = array(), # A cache of function results keyed by argument hash + $mLastError = false, # Last error message or false for no error + $mErrorPos = 0, # Last error position + + /** Built-in functions */ + $mBuiltins = array( + 'if' => 'bi_if', + 'true' => 'bi_true', + '[' => 'bi_lbrace', + 'lbrace' => 'bi_lbrace', + ']' => 'bi_rbrace', + 'rbrace' => 'bi_rbrace', + 'escape' => 'bi_escape', + '~' => 'bi_escape', + ); + + /** + * Create a template processor for a given text, callback object and static dependency list + */ + function CBTProcessor( $text, $functionObj, $ignorableDeps = array() ) { + $this->mText = $text; + $this->mFunctionObj = $functionObj; + $this->mIgnorableDeps = $ignorableDeps; + } + + /** + * Execute the template. + * If $compile is true, produces an optimised template where functions with static + * dependencies have been replaced by their return values. + */ + function execute( $compile = false ) { + $fname = 'CBTProcessor::execute'; + wfProfileIn( $fname ); + $this->mCompiling = $compile; + $this->mLastError = false; + $val = $this->doText( 0, strlen( $this->mText ) ); + $text = $val->getText(); + if ( $this->mLastError !== false ) { + $pos = $this->mErrorPos; + + // Find the line number at which the error occurred + $startLine = 0; + $endLine = 0; + $line = 0; + do { + if ( $endLine ) { + $startLine = $endLine + 1; + } + $endLine = strpos( $this->mText, "\n", $startLine ); + ++$line; + } while ( $endLine !== false && $endLine < $pos ); + + $text = "Template error at line $line: $this->mLastError\n
\n";
+
+			$context = rtrim( str_replace( "\t", " ", substr( $this->mText, $startLine, $endLine - $startLine ) ) );
+			$text .= htmlspecialchars( $context ) . "\n" . str_repeat( ' ', $pos - $startLine ) . "^\n
\n"; + } + wfProfileOut( $fname ); + return $text; + } + + /** Shortcut for execute(true) */ + function compile() { + $fname = 'CBTProcessor::compile'; + wfProfileIn( $fname ); + $s = $this->execute( true ); + wfProfileOut( $fname ); + return $s; + } + + /** Shortcut for doOpenText( $start, $end, false */ + function doText( $start, $end ) { + return $this->doOpenText( $start, $end, false ); + } + + /** + * Escape text for a template if we are producing a template. Do nothing + * if we are producing plain text. + */ + function templateEscape( $text ) { + if ( $this->mCompiling ) { + return cbt_escape( $text ); + } else { + return $text; + } + } + + /** + * Recursive workhorse for text mode. + * + * Processes text mode starting from offset $p, until either $end is + * reached or a closing brace is found. If $needClosing is false, a + * closing brace will flag an error, if $needClosing is true, the lack + * of a closing brace will flag an error. + * + * The parameter $p is advanced to the position after the closing brace, + * or after the end. A CBTValue is returned. + * + * @private + */ + function doOpenText( &$p, $end, $needClosing = true ) { + $fname = 'CBTProcessor::doOpenText'; + wfProfileIn( $fname ); + $in =& $this->mText; + $start = $p; + $ret = new CBTValue( '', array(), $this->mCompiling ); + + $foundClosing = false; + while ( $p < $end ) { + $matchLength = strcspn( $in, CBT_BRACE, $p, $end - $p ); + $pToken = $p + $matchLength; + + if ( $pToken >= $end ) { + // No more braces, output remainder + $ret->cat( substr( $in, $p ) ); + $p = $end; + break; + } + + // Output the text before the brace + $ret->cat( substr( $in, $p, $matchLength ) ); + + // Advance the pointer + $p = $pToken + 1; + + // Check for closing brace + if ( $in[$pToken] == '}' ) { + $foundClosing = true; + break; + } + + // Handle the "{fn}" special case + if ( $pToken > 0 && $in[$pToken-1] == '"' ) { + wfProfileOut( $fname ); + $val = $this->doOpenFunction( $p, $end ); + wfProfileIn( $fname ); + if ( $p < $end && $in[$p] == '"' ) { + $val->setText( htmlspecialchars( $val->getText() ) ); + } + $ret->cat( $val ); + } else { + // Process the function mode component + wfProfileOut( $fname ); + $ret->cat( $this->doOpenFunction( $p, $end ) ); + wfProfileIn( $fname ); + } + } + if ( $foundClosing && !$needClosing ) { + $this->error( 'Errant closing brace', $p ); + } elseif ( !$foundClosing && $needClosing ) { + $this->error( 'Unclosed text section', $start ); + } + wfProfileOut( $fname ); + return $ret; + } + + /** + * Recursive workhorse for function mode. + * + * Processes function mode starting from offset $p, until either $end is + * reached or a closing brace is found. If $needClosing is false, a + * closing brace will flag an error, if $needClosing is true, the lack + * of a closing brace will flag an error. + * + * The parameter $p is advanced to the position after the closing brace, + * or after the end. A CBTValue is returned. + * + * @private + */ + function doOpenFunction( &$p, $end, $needClosing = true ) { + $in =& $this->mText; + $start = $p; + $tokens = array(); + $unexecutedTokens = array(); + + $foundClosing = false; + while ( $p < $end ) { + $char = $in[$p]; + if ( $char == '{' ) { + // Switch to text mode + ++$p; + $tokenStart = $p; + $token = $this->doOpenText( $p, $end ); + $tokens[] = $token; + $unexecutedTokens[] = '{' . substr( $in, $tokenStart, $p - $tokenStart - 1 ) . '}'; + } elseif ( $char == '}' ) { + // Block end + ++$p; + $foundClosing = true; + break; + } elseif ( false !== strpos( CBT_WHITE, $char ) ) { + // Whitespace + // Consume the rest of the whitespace + $p += strspn( $in, CBT_WHITE, $p, $end - $p ); + } else { + // Token, find the end of it + $tokenLength = strcspn( $in, CBT_DELIM, $p, $end - $p ); + $token = new CBTValue( substr( $in, $p, $tokenLength ) ); + // Execute the token as a function if it's not the function name + if ( count( $tokens ) ) { + $tokens[] = $this->doFunction( array( $token ), $p ); + } else { + $tokens[] = $token; + } + $unexecutedTokens[] = $token->getText(); + + $p += $tokenLength; + } + } + if ( !$foundClosing && $needClosing ) { + $this->error( 'Unclosed function', $start ); + return ''; + } + + $val = $this->doFunction( $tokens, $start ); + if ( $this->mCompiling && !$val->isStatic() ) { + $compiled = ''; + $first = true; + foreach( $tokens as $i => $token ) { + if ( $first ) { + $first = false; + } else { + $compiled .= ' '; + } + if ( $token->isStatic() ) { + if ( $i !== 0 ) { + $compiled .= '{' . $token->getText() . '}'; + } else { + $compiled .= $token->getText(); + } + } else { + $compiled .= $unexecutedTokens[$i]; + } + } + + // The dynamic parts of the string are still represented as functions, and + // function invocations have no dependencies. Thus the compiled result has + // no dependencies. + $val = new CBTValue( "{{$compiled}}", array(), true ); + } + return $val; + } + + /** + * Execute a function, caching and returning the result value. + * $tokens is an array of CBTValue objects. $tokens[0] is the function + * name, the others are arguments. $p is the string position, and is used + * for error messages only. + */ + function doFunction( $tokens, $p ) { + if ( count( $tokens ) == 0 ) { + return new CBTValue; + } + $fname = 'CBTProcessor::doFunction'; + wfProfileIn( $fname ); + + $ret = new CBTValue; + + // All functions implicitly depend on their arguments, and the function name + // While this is not strictly necessary for all functions, it's true almost + // all the time and so convenient to do automatically. + $ret->addDeps( $tokens ); + + $this->mCurrentPos = $p; + $func = array_shift( $tokens ); + $func = $func->getText(); + + // Extract the text component from all the tokens + // And convert any templates to plain text + $textArgs = array(); + foreach ( $tokens as $token ) { + $token->execute( $this ); + $textArgs[] = $token->getText(); + } + + // Try the local cache + $cacheKey = $func . "\n" . implode( "\n", $textArgs ); + if ( isset( $this->mFunctionCache[$cacheKey] ) ) { + $val = $this->mFunctionCache[$cacheKey]; + } elseif ( isset( $this->mBuiltins[$func] ) ) { + $func = $this->mBuiltins[$func]; + $val = call_user_func_array( array( &$this, $func ), $tokens ); + $this->mFunctionCache[$cacheKey] = $val; + } elseif ( method_exists( $this->mFunctionObj, $func ) ) { + $profName = get_class( $this->mFunctionObj ) . '::' . $func; + wfProfileIn( "$fname-callback" ); + wfProfileIn( $profName ); + $val = call_user_func_array( array( &$this->mFunctionObj, $func ), $textArgs ); + wfProfileOut( $profName ); + wfProfileOut( "$fname-callback" ); + $this->mFunctionCache[$cacheKey] = $val; + } else { + $this->error( "Call of undefined function \"$func\"", $p ); + $val = new CBTValue; + } + if ( !is_object( $val ) ) { + $val = new CBTValue((string)$val); + } + + if ( CBT_DEBUG ) { + $unexpanded = $val; + } + + // If the output was a template, execute it + $val->execute( $this ); + + if ( $this->mCompiling ) { + // Escape any braces so that the output will be a valid template + $val->templateEscape(); + } + $val->removeDeps( $this->mIgnorableDeps ); + $ret->addDeps( $val ); + $ret->setText( $val->getText() ); + + if ( CBT_DEBUG ) { + wfDebug( "doFunction $func args = " + . var_export( $tokens, true ) + . "unexpanded return = " + . var_export( $unexpanded, true ) + . "expanded return = " + . var_export( $ret, true ) + ); + } + + wfProfileOut( $fname ); + return $ret; + } + + /** + * Set a flag indicating that an error has been found. + */ + function error( $text, $pos = false ) { + $this->mLastError = $text; + if ( $pos === false ) { + $this->mErrorPos = $this->mCurrentPos; + } else { + $this->mErrorPos = $pos; + } + } + + function getLastError() { + return $this->mLastError; + } + + /** 'if' built-in function */ + function bi_if( $condition, $trueBlock, $falseBlock = null ) { + if ( is_null( $condition ) ) { + $this->error( "Missing condition in if" ); + return ''; + } + + if ( $condition->getText() != '' ) { + return new CBTValue( $trueBlock->getText(), + array_merge( $condition->getDeps(), $trueBlock->getDeps() ), + $trueBlock->mIsTemplate ); + } else { + if ( !is_null( $falseBlock ) ) { + return new CBTValue( $falseBlock->getText(), + array_merge( $condition->getDeps(), $falseBlock->getDeps() ), + $falseBlock->mIsTemplate ); + } else { + return new CBTValue( '', $condition->getDeps() ); + } + } + } + + /** 'true' built-in function */ + function bi_true() { + return "true"; + } + + /** left brace built-in */ + function bi_lbrace() { + return '{'; + } + + /** right brace built-in */ + function bi_rbrace() { + return '}'; + } + + /** + * escape built-in. + * Escape text for inclusion in an HTML attribute + */ + function bi_escape( $val ) { + return new CBTValue( htmlspecialchars( $val->getText() ), $val->getDeps() ); + } +} +?> -- cgit v1.2.3-54-g00ecf