1
+ − 1
<?php
+ − 2
+ − 3
/**
+ − 4
*
+ − 5
* Parses for bulleted and numbered lists.
+ − 6
*
+ − 7
* @category Text
+ − 8
*
+ − 9
* @package Text_Wiki
+ − 10
*
+ − 11
* @author Paul M. Jones <pmjones@php.net>
+ − 12
*
+ − 13
* @license LGPL
+ − 14
*
+ − 15
* @version $Id: List.php,v 1.7 2005/11/06 20:44:09 toggg Exp $
+ − 16
*
+ − 17
*/
+ − 18
+ − 19
/**
+ − 20
*
+ − 21
* Parses for bulleted and numbered lists.
+ − 22
*
+ − 23
* This class implements a Text_Wiki_Parse to find source text marked as
+ − 24
* a bulleted or numbered list. In short, if a line starts with '* ' then
+ − 25
* it is a bullet list item; if a line starts with '# ' then it is a
+ − 26
* number list item. Spaces in front of the * or # indicate an indented
+ − 27
* sub-list. The list items must be on sequential lines, and may be
+ − 28
* separated by blank lines to improve readability. Using a non-* non-#
+ − 29
* non-whitespace character at the beginning of a line ends the list.
+ − 30
*
+ − 31
* @category Text
+ − 32
*
+ − 33
* @package Text_Wiki
+ − 34
*
+ − 35
* @author Paul M. Jones <pmjones@php.net>
+ − 36
*
+ − 37
*/
+ − 38
+ − 39
class Text_Wiki_Parse_List extends Text_Wiki_Parse {
+ − 40
+ − 41
+ − 42
/**
+ − 43
*
+ − 44
* The regular expression used to parse the source text and find
+ − 45
* matches conforming to this rule. Used by the parse() method.
+ − 46
*
+ − 47
* @access public
+ − 48
*
+ − 49
* @var string
+ − 50
*
+ − 51
* @see parse()
+ − 52
*
+ − 53
*/
+ − 54
+ − 55
var $regex = '/^((\*|#) .*\n)(?!\2 |(?: {1,}((?:\*|#) |\n)))/Usm';
+ − 56
+ − 57
+ − 58
/**
+ − 59
*
+ − 60
* Generates a replacement for the matched text. Token options are:
+ − 61
*
+ − 62
* 'type' =>
+ − 63
* 'bullet_start' : the start of a bullet list
+ − 64
* 'bullet_end' : the end of a bullet list
+ − 65
* 'number_start' : the start of a number list
+ − 66
* 'number_end' : the end of a number list
+ − 67
* 'item_start' : the start of item text (bullet or number)
+ − 68
* 'item_end' : the end of item text (bullet or number)
+ − 69
* 'unknown' : unknown type of list or item
+ − 70
*
+ − 71
* 'level' => the indent level (0 for the first level, 1 for the
+ − 72
* second, etc)
+ − 73
*
+ − 74
* 'count' => the list item number at this level. not needed for
+ − 75
* xhtml, but very useful for PDF and RTF.
+ − 76
*
+ − 77
* @access public
+ − 78
*
+ − 79
* @param array &$matches The array of matches from parse().
+ − 80
*
+ − 81
* @return A series of text and delimited tokens marking the different
+ − 82
* list text and list elements.
+ − 83
*
+ − 84
*/
+ − 85
+ − 86
function process(&$matches)
+ − 87
{
+ − 88
// the replacement text we will return
+ − 89
$return = '';
+ − 90
+ − 91
// the list of post-processing matches
+ − 92
$list = array();
+ − 93
+ − 94
// a stack of list-start and list-end types; we keep this
+ − 95
// so that we know what kind of list we're working with
+ − 96
// (bullet or number) and what indent level we're at.
+ − 97
$stack = array();
+ − 98
+ − 99
// the item count is the number of list items for any
+ − 100
// given list-type on the stack
+ − 101
$itemcount = array();
+ − 102
+ − 103
// have we processed the very first list item?
+ − 104
$pastFirst = false;
+ − 105
+ − 106
// populate $list with this set of matches. $matches[1] is the
+ − 107
// text matched as a list set by parse().
+ − 108
preg_match_all(
+ − 109
'=^( {0,})(\*|#) (.*)$=Ums',
+ − 110
$matches[1],
+ − 111
$list,
+ − 112
PREG_SET_ORDER
+ − 113
);
+ − 114
+ − 115
// loop through each list-item element.
+ − 116
foreach ($list as $key => $val) {
+ − 117
+ − 118
// $val[0] is the full matched list-item line
+ − 119
// $val[1] is the number of initial spaces (indent level)
+ − 120
// $val[2] is the list item type (* or #)
+ − 121
// $val[3] is the list item text
+ − 122
+ − 123
// how many levels are we indented? (1 means the "root"
+ − 124
// list level, no indenting.)
+ − 125
$level = strlen($val[1]) + 1;
+ − 126
+ − 127
// get the list item type
+ − 128
if ($val[2] == '*') {
+ − 129
$type = 'bullet';
+ − 130
} elseif ($val[2] == '#') {
+ − 131
$type = 'number';
+ − 132
} else {
+ − 133
$type = 'unknown';
+ − 134
}
+ − 135
+ − 136
// get the text of the list item
+ − 137
$text = $val[3];
+ − 138
+ − 139
// add a level to the list?
+ − 140
if ($level > count($stack)) {
+ − 141
+ − 142
// the current indent level is greater than the
+ − 143
// number of stack elements, so we must be starting
+ − 144
// a new list. push the new list type onto the
+ − 145
// stack...
+ − 146
array_push($stack, $type);
+ − 147
+ − 148
// ...and add a list-start token to the return.
+ − 149
$return .= $this->wiki->addToken(
+ − 150
$this->rule,
+ − 151
array(
+ − 152
'type' => $type . '_list_start',
+ − 153
'level' => $level - 1
+ − 154
)
+ − 155
);
+ − 156
}
+ − 157
+ − 158
// remove a level from the list?
+ − 159
while (count($stack) > $level) {
+ − 160
+ − 161
// so we don't keep counting the stack, we set up a temp
+ − 162
// var for the count. -1 becuase we're going to pop the
+ − 163
// stack in the next command. $tmp will then equal the
+ − 164
// current level of indent.
+ − 165
$tmp = count($stack) - 1;
+ − 166
+ − 167
// as long as the stack count is greater than the
+ − 168
// current indent level, we need to end list types.
+ − 169
// continue adding end-list tokens until the stack count
+ − 170
// and the indent level are the same.
+ − 171
$return .= $this->wiki->addToken(
+ − 172
$this->rule,
+ − 173
array (
+ − 174
'type' => array_pop($stack) . '_list_end',
+ − 175
'level' => $tmp
+ − 176
)
+ − 177
);
+ − 178
+ − 179
// reset to the current (previous) list type so that
+ − 180
// the new list item matches the proper list type.
+ − 181
$type = $stack[$tmp - 1];
+ − 182
+ − 183
// reset the item count for the popped indent level
+ − 184
unset($itemcount[$tmp + 1]);
+ − 185
}
+ − 186
+ − 187
// add to the item count for this list (taking into account
+ − 188
// which level we are at).
+ − 189
if (! isset($itemcount[$level])) {
+ − 190
// first count
+ − 191
$itemcount[$level] = 0;
+ − 192
} else {
+ − 193
// increment count
+ − 194
$itemcount[$level]++;
+ − 195
}
+ − 196
+ − 197
// is this the very first item in the list?
+ − 198
if (! $pastFirst) {
+ − 199
$first = true;
+ − 200
$pastFirst = true;
+ − 201
} else {
+ − 202
$first = false;
+ − 203
}
+ − 204
+ − 205
// create a list-item starting token.
+ − 206
$start = $this->wiki->addToken(
+ − 207
$this->rule,
+ − 208
array(
+ − 209
'type' => $type . '_item_start',
+ − 210
'level' => $level,
+ − 211
'count' => $itemcount[$level],
+ − 212
'first' => $first
+ − 213
)
+ − 214
);
+ − 215
+ − 216
// create a list-item ending token.
+ − 217
$end = $this->wiki->addToken(
+ − 218
$this->rule,
+ − 219
array(
+ − 220
'type' => $type . '_item_end',
+ − 221
'level' => $level,
+ − 222
'count' => $itemcount[$level]
+ − 223
)
+ − 224
);
+ − 225
+ − 226
// add the starting token, list-item text, and ending token
+ − 227
// to the return.
+ − 228
$return .= $start . $val[3] . $end;
+ − 229
}
+ − 230
+ − 231
// the last list-item may have been indented. go through the
+ − 232
// list-type stack and create end-list tokens until the stack
+ − 233
// is empty.
+ − 234
while (count($stack) > 0) {
+ − 235
$return .= $this->wiki->addToken(
+ − 236
$this->rule,
+ − 237
array (
+ − 238
'type' => array_pop($stack) . '_list_end',
+ − 239
'level' => count($stack)
+ − 240
)
+ − 241
);
+ − 242
}
+ − 243
+ − 244
// we're done! send back the replacement text.
+ − 245
return "\n\n" . $return . "\n\n";
+ − 246
}
+ − 247
}
+ − 248
?>