includes/wikiengine/Parse/Default/List.php
changeset 1027 98c052fc3337
parent 1026 f0431eb8161e
child 1028 dde4416dea00
equal deleted inserted replaced
1026:f0431eb8161e 1027:98c052fc3337
     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 ?>