import of Diogenes 0.9.18
[diogenes.git] / include / Text / Wiki / Parse / Default / List.php
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.3 2005/02/23 17:38:29 pmjones 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((\*|#) .*\n)(?! {0,}(\* |# |\n))/Us';
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" . $return . "\n\n";
246 }
247 }
248 ?>