1<?php
2// vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
3/**
4 * BBCode: Parses for code blocks.
5 *
6 * This class implements a Text_Wiki_Rule to find source text marked as
7 * bulleted or numbered lists as defined by text surrounded by [list] [*] ... [/list]
8 * Numebering is obtained thru [list=1] or [list=a] defining the first item "number"
9 * On parsing, the text itself is left in place, but the starting, element and ending
10 * tags are replaced with tokens. (nested lists enabled)
11 *
12 * PHP versions 4 and 5
13 *
14 * @category   Text
15 * @package    Text_Wiki
16 * @author     Bertrand Gugger <bertrand@toggg.com>
17 * @copyright  2005 bertrand Gugger
18 * @license    http://www.gnu.org/copyleft/lesser.html  LGPL License 2.1
19 * @version    CVS: $Id$
20 * @link       http://pear.php.net/package/Text_Wiki
21 */
22
23/**
24 * List rule parser class for BBCode.
25 *
26 * @category   Text
27 * @package    Text_Wiki
28 * @author     Bertrand Gugger <bertrand@toggg.com>
29 * @copyright  2005 bertrand Gugger
30 * @license    http://www.gnu.org/copyleft/lesser.html  LGPL License 2.1
31 * @version    Release: @package_version@
32 * @link       http://pear.php.net/package/Text_Wiki
33 * @see        Text_Wiki_Parse::Text_Wiki_Parse()
34 */
35class Text_Wiki_Parse_List extends Text_Wiki_Parse {
36
37    /**
38     * The regular expression used to parse the source text and find
39     * matches conforming to this rule.  Used by the parse() method.
40     *
41     * @access public
42     * @var string
43     * @see parse()
44     */
45    var $regex =  "#\[list(?:=(.+?))?]\n?((?:((?R))|.)*?)\[/list]\n?#msi";
46
47    /**
48     * The regular expression used in second stage to find list's elements
49     * used by process() to call back processElement()
50     *
51     * @access public
52     * @var string
53     * @see process()
54     * @see processElement()
55     */
56    var $regexElement =  '#\[\*](.*?)(?=\[\*]|$)\n?#msi';
57
58    /**
59     * The current list nesting depth, starts by zero
60     *
61     * @access private
62     * @var int
63     */
64    var $_level = 0;
65
66    /**
67     * The count of items for this level
68     *
69     * @access private
70     * @var int
71     */
72    var $_count = array();
73
74    /**
75     * The type of list for this level ('bullet' or 'number')
76     *
77     * @access private
78     * @var int
79     */
80    var $_type = array();
81
82    /**
83     * Generates a replacement for the matched text. Returned token options are:
84     * 'type' =>
85     *     'bullet_list_start' : the start of a bullet list
86     *     'bullet_list_end'   : the end of a bullet list
87     *     'number_list_start' : the start of a number list
88     *     'number_list_end'   : the end of a number list
89     *     'item_start'   : the start of item text (bullet or number)
90     *     'item_end'     : the end of item text (bullet or number)
91     *     'unknown'      : unknown type of list or item
92     *
93     * 'level' => the indent level (0 for the first level, 1 for the
94     * second, etc)
95     *
96     * 'count' => the list item number at this level. not needed for
97     * xhtml, but very useful for PDF and RTF.
98     *
99     * 'format' => the optional enumerating type : A, a, I, i, or 1 (default)
100     *             as HTML <ol> tag's type attribute (only for number_... type)
101     *
102     * 'key' => the optional starting number/letter (not for items)
103     *
104     * @param array &$matches The array of matches from parse().
105     * @return A delimited token to be used as a placeholder in
106     * the source text and containing the original block of text
107     * @access public
108     */
109    function process(&$matches)
110    {
111        if (!empty($matches[3])) {
112            $this->_level++;
113            $expsub = preg_replace_callback(
114                $this->regex,
115                array(&$this, 'process'),
116                $matches[2]
117            );
118            $this->_level--;
119        } else {
120            $expsub = $matches[2];
121        }
122        if ($matches[1]) {
123            $this->_type[$this->_level] = 'number';
124            if (is_numeric($matches[1])) {
125                $format = '1';
126                $key = $matches[1] + 0;
127            } elseif (($matches[1] == 'i') || ($matches[1] == 'I')) {
128                $format = $matches[1];
129            } else {
130                $format =
131                    ($matches[1] >= 'a') && ($matches[1] <='z') ? 'a' : 'A';
132                $key = $matches[1];
133            }
134        } else {
135            $this->_type[$this->_level] = 'bullet';
136        }
137        $this->_count[$this->_level] = -1;
138        $sub = preg_replace_callback(
139            $this->regexElement,
140            array(&$this, 'processElement'),
141            $expsub
142        );
143        $param = array(
144                'level' => $this->_level,
145                'count' => $this->_count[$this->_level] );
146        $param['type'] = $this->_type[$this->_level].'_list_start';
147        if (isset($format)) {
148            $param['format'] = $format;
149        }
150        if (isset($key)) {
151            $param['key'] = $key;
152        }
153        $ret = $this->wiki->addToken($this->rule, $param );
154        $param['type'] = $this->_type[$this->_level].'_list_end';
155        return $ret . $sub . $this->wiki->addToken($this->rule, $param );
156    }
157
158    /**
159     * Generates a replacement for the matched list elements. Token options are:
160     * 'type' =>
161     *     '[listType]_item_start'   : the start of item text (bullet or number)
162     *     '[listType]_item_end'     : the end of item text (bullet or number)
163     *  where [listType] is bullet or number
164     *
165     * 'level' => the indent level (0 for the first level, 1 for the
166     * second, etc)
167     *
168     * 'count' => the item ordeer at this level.
169     *
170     * @param array &$matches The array of matches from parse().
171     * @return A delimited token to be used as a placeholder in
172     * the source text and containing the original block of text
173     * @access public
174     */
175    function processElement(&$matches)
176    {
177        return $this->wiki->addToken($this->rule, array(
178                    'type' => $this->_type[$this->_level] . '_item_start',
179                    'level' => $this->_level,
180                    'count' =>  ++$this->_count[$this->_level]) ) .
181               rtrim($matches[1]) .
182               $this->wiki->addToken($this->rule, array(
183                    'type' => $this->_type[$this->_level] . '_item_end',
184                    'level' => $this->_level,
185                    'count' =>  $this->_count[$this->_level]) );
186    }
187}
188