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