- function replace_callback ($text, $callbacks) {
- wfProfileIn( __METHOD__ );
- $openingBraceStack = array(); # this array will hold a stack of parentheses which are not closed yet
- $lastOpeningBrace = -1; # last not closed parentheses
-
- $validOpeningBraces = implode( '', array_keys( $callbacks ) );
-
- $i = 0;
- while ( $i < strlen( $text ) ) {
- # Find next opening brace, closing brace or pipe
- if ( $lastOpeningBrace == -1 ) {
- $currentClosing = '';
- $search = $validOpeningBraces;
- } else {
- $currentClosing = $openingBraceStack[$lastOpeningBrace]['braceEnd'];
- $search = $validOpeningBraces . '|' . $currentClosing;
- }
- $rule = null;
- $i += strcspn( $text, $search, $i );
- if ( $i < strlen( $text ) ) {
- if ( $text[$i] == '|' ) {
- $found = 'pipe';
- } elseif ( $text[$i] == $currentClosing ) {
- $found = 'close';
- } elseif ( isset( $callbacks[$text[$i]] ) ) {
- $found = 'open';
- $rule = $callbacks[$text[$i]];
- } else {
- # Some versions of PHP have a strcspn which stops on null characters
- # Ignore and continue
- ++$i;
- continue;
- }
- } else {
- # All done
- break;
- }
-
- if ( $found == 'open' ) {
- # found opening brace, let's add it to parentheses stack
- $piece = array('brace' => $text[$i],
- 'braceEnd' => $rule['end'],
- 'title' => '',
- 'parts' => null);
-
- # count opening brace characters
- $piece['count'] = strspn( $text, $piece['brace'], $i );
- $piece['startAt'] = $piece['partStart'] = $i + $piece['count'];
- $i += $piece['count'];
-
- # we need to add to stack only if opening brace count is enough for one of the rules
- if ( $piece['count'] >= $rule['min'] ) {
- $lastOpeningBrace ++;
- $openingBraceStack[$lastOpeningBrace] = $piece;
- }
- } elseif ( $found == 'close' ) {
- # lets check if it is enough characters for closing brace
- $maxCount = $openingBraceStack[$lastOpeningBrace]['count'];
- $count = strspn( $text, $text[$i], $i, $maxCount );
-
- # check for maximum matching characters (if there are 5 closing
- # characters, we will probably need only 3 - depending on the rules)
- $matchingCount = 0;
- $matchingCallback = null;
- $cbType = $callbacks[$openingBraceStack[$lastOpeningBrace]['brace']];
- if ( $count > $cbType['max'] ) {
- # The specified maximum exists in the callback array, unless the caller
- # has made an error
- $matchingCount = $cbType['max'];
- } else {
- # Count is less than the maximum
- # Skip any gaps in the callback array to find the true largest match
- # Need to use array_key_exists not isset because the callback can be null
- $matchingCount = $count;
- while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $cbType['cb'] ) ) {
- --$matchingCount;
- }
- }
-
- if ($matchingCount <= 0) {
- $i += $count;
- continue;
- }
- $matchingCallback = $cbType['cb'][$matchingCount];
-
- # let's set a title or last part (if '|' was found)
- if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
- $openingBraceStack[$lastOpeningBrace]['title'] =
- substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
- $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
- } else {
- $openingBraceStack[$lastOpeningBrace]['parts'][] =
- substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
- $i - $openingBraceStack[$lastOpeningBrace]['partStart']);
- }
-
- $pieceStart = $openingBraceStack[$lastOpeningBrace]['startAt'] - $matchingCount;
- $pieceEnd = $i + $matchingCount;
-
- if( is_callable( $matchingCallback ) ) {
- $cbArgs = array (
- 'text' => substr($text, $pieceStart, $pieceEnd - $pieceStart),
- 'title' => trim($openingBraceStack[$lastOpeningBrace]['title']),
- 'parts' => $openingBraceStack[$lastOpeningBrace]['parts'],
- 'lineStart' => (($pieceStart > 0) && ($text[$pieceStart-1] == "\n")),
- );
- # finally we can call a user callback and replace piece of text
- $replaceWith = call_user_func( $matchingCallback, $cbArgs );
- $text = substr($text, 0, $pieceStart) . $replaceWith . substr($text, $pieceEnd);
- $i = $pieceStart + strlen($replaceWith);
- } else {
- # null value for callback means that parentheses should be parsed, but not replaced
- $i += $matchingCount;
- }