includes/wikiengine/Parse/Mediawiki/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 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 ?>