3
class Parser_OldPP extends Parser {
5
* parse any parentheses in format ((title|part|part))
6
* and call callbacks to get a replacement text for any found piece
8
* @param string $text The text to parse
9
* @param array $callbacks rules in form:
10
* '{' => array( # opening parentheses
11
* 'end' => '}', # closing parentheses
12
* 'cb' => array(2 => callback, # replacement callback to call if {{..}} is found
13
* 3 => callback # replacement callback to call if {{{..}}} is found
16
* 'min' => 2, # Minimum parenthesis count in cb
17
* 'max' => 3, # Maximum parenthesis count in cb
20
function replace_callback ($text, $callbacks) {
21
wfProfileIn( __METHOD__ );
22
$openingBraceStack = array(); # this array will hold a stack of parentheses which are not closed yet
23
$lastOpeningBrace = -1; # last not closed parentheses
25
$validOpeningBraces = implode( '', array_keys( $callbacks ) );
28
while ( $i < strlen( $text ) ) {
29
# Find next opening brace, closing brace or pipe
30
if ( $lastOpeningBrace == -1 ) {
32
$search = $validOpeningBraces;
34
$currentClosing = $openingBraceStack[$lastOpeningBrace]['braceEnd'];
35
$search = $validOpeningBraces . '|' . $currentClosing;
38
$i += strcspn( $text, $search, $i );
39
if ( $i < strlen( $text ) ) {
40
if ( $text[$i] == '|' ) {
42
} elseif ( $text[$i] == $currentClosing ) {
44
} elseif ( isset( $callbacks[$text[$i]] ) ) {
46
$rule = $callbacks[$text[$i]];
48
# Some versions of PHP have a strcspn which stops on null characters
58
if ( $found == 'open' ) {
59
# found opening brace, let's add it to parentheses stack
60
$piece = array('brace' => $text[$i],
61
'braceEnd' => $rule['end'],
65
# count opening brace characters
66
$piece['count'] = strspn( $text, $piece['brace'], $i );
67
$piece['startAt'] = $piece['partStart'] = $i + $piece['count'];
68
$i += $piece['count'];
70
# we need to add to stack only if opening brace count is enough for one of the rules
71
if ( $piece['count'] >= $rule['min'] ) {
73
$openingBraceStack[$lastOpeningBrace] = $piece;
75
} elseif ( $found == 'close' ) {
76
# lets check if it is enough characters for closing brace
77
$maxCount = $openingBraceStack[$lastOpeningBrace]['count'];
78
$count = strspn( $text, $text[$i], $i, $maxCount );
80
# check for maximum matching characters (if there are 5 closing
81
# characters, we will probably need only 3 - depending on the rules)
83
$matchingCallback = null;
84
$cbType = $callbacks[$openingBraceStack[$lastOpeningBrace]['brace']];
85
if ( $count > $cbType['max'] ) {
86
# The specified maximum exists in the callback array, unless the caller
88
$matchingCount = $cbType['max'];
90
# Count is less than the maximum
91
# Skip any gaps in the callback array to find the true largest match
92
# Need to use array_key_exists not isset because the callback can be null
93
$matchingCount = $count;
94
while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $cbType['cb'] ) ) {
99
if ($matchingCount <= 0) {
103
$matchingCallback = $cbType['cb'][$matchingCount];
105
# let's set a title or last part (if '|' was found)
106
if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
107
$openingBraceStack[$lastOpeningBrace]['title'] =
108
substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
109
$i - $openingBraceStack[$lastOpeningBrace]['partStart']);
111
$openingBraceStack[$lastOpeningBrace]['parts'][] =
112
substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
113
$i - $openingBraceStack[$lastOpeningBrace]['partStart']);
116
$pieceStart = $openingBraceStack[$lastOpeningBrace]['startAt'] - $matchingCount;
117
$pieceEnd = $i + $matchingCount;
119
if( is_callable( $matchingCallback ) ) {
121
'text' => substr($text, $pieceStart, $pieceEnd - $pieceStart),
122
'title' => trim($openingBraceStack[$lastOpeningBrace]['title']),
123
'parts' => $openingBraceStack[$lastOpeningBrace]['parts'],
124
'lineStart' => (($pieceStart > 0) && ($text[$pieceStart-1] == "\n")),
126
# finally we can call a user callback and replace piece of text
127
$replaceWith = call_user_func( $matchingCallback, $cbArgs );
128
$text = substr($text, 0, $pieceStart) . $replaceWith . substr($text, $pieceEnd);
129
$i = $pieceStart + strlen($replaceWith);
131
# null value for callback means that parentheses should be parsed, but not replaced
132
$i += $matchingCount;
135
# reset last opening parentheses, but keep it in case there are unused characters
136
$piece = array('brace' => $openingBraceStack[$lastOpeningBrace]['brace'],
137
'braceEnd' => $openingBraceStack[$lastOpeningBrace]['braceEnd'],
138
'count' => $openingBraceStack[$lastOpeningBrace]['count'],
141
'startAt' => $openingBraceStack[$lastOpeningBrace]['startAt']);
142
$openingBraceStack[$lastOpeningBrace--] = null;
144
if ($matchingCount < $piece['count']) {
145
$piece['count'] -= $matchingCount;
146
$piece['startAt'] -= $matchingCount;
147
$piece['partStart'] = $piece['startAt'];
148
# do we still qualify for any callback with remaining count?
149
$currentCbList = $callbacks[$piece['brace']]['cb'];
150
while ( $piece['count'] ) {
151
if ( array_key_exists( $piece['count'], $currentCbList ) ) {
153
$openingBraceStack[$lastOpeningBrace] = $piece;
159
} elseif ( $found == 'pipe' ) {
160
# lets set a title if it is a first separator, or next part otherwise
161
if (null === $openingBraceStack[$lastOpeningBrace]['parts']) {
162
$openingBraceStack[$lastOpeningBrace]['title'] =
163
substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
164
$i - $openingBraceStack[$lastOpeningBrace]['partStart']);
165
$openingBraceStack[$lastOpeningBrace]['parts'] = array();
167
$openingBraceStack[$lastOpeningBrace]['parts'][] =
168
substr($text, $openingBraceStack[$lastOpeningBrace]['partStart'],
169
$i - $openingBraceStack[$lastOpeningBrace]['partStart']);
171
$openingBraceStack[$lastOpeningBrace]['partStart'] = ++$i;
175
wfProfileOut( __METHOD__ );