| // +----------------------------------------------------------------------+ // // $Id: toc.php,v 1.2 2004/01/31 17:09:46 pmjones Exp $ /** * * This class implements a Text_Wiki_Rule to find all heading tokens and * build a table of contents. The %%TOC%% tag gets replaced with a list * of all the level-2 through level-6 headings. * * @author Paul M. Jones * * @package Text_Wiki * */ class Text_Wiki_Rule_toc extends Text_Wiki_Rule { /** * * The regular expression used to parse the source text and find * matches conforming to this rule. Used by the parse() method. * * @access public * * @var string * * @see parse() * */ var $regex = "/%%TOC%%/m"; /** * * The collection of headings (text and levels). * * @access public * * @var array * * @see _getEntries() * */ var $entry = array(); /** * * Custom parsing (have to process heading entries first). * * @access public * * @see Text_Wiki::parse() * */ function parse() { $this->_getEntries(); parent::parse(); } /** * * Generates a replacement for the matched text. Token options are: * * 'type' => ['list_start'|'list_end'|'item_end'|'item_end'|'target'] * * 'level' => The heading level (1-6). * * 'count' => Which heading this is in the list. * * @access public * * @param array &$matches The array of matches from parse(). * * @return string A token indicating the TOC collection point. * */ function process(&$matches) { $output = $this->addToken(array('type' => 'list_start')); foreach ($this->entry as $key => $val) { $options = array( 'type' => 'item_start', 'count' => $val['count'], 'level' => $val['level'] ); $output .= $this->addToken($options); $output .= $val['text']; $output .= $this->addToken(array('type' => 'item_end')); } $output .= $this->addToken(array('type' => 'list_end')); return $output; } /** * * Finds all headings in the text and saves them in $this->entry. * * @access private * * @return void * */ function _getEntries() { // the wiki delimiter $delim = $this->_wiki->delim; // list of all TOC entries (h2, h3, etc) $this->entry = array(); // the new source text with TOC entry tokens $newsrc = ''; // when passing through the parsed source text, keep track of when // we are in a delimited section $in_delim = false; // when in a delimited section, capture the token key number $key = ''; // TOC entry count $count = 0; // pass through the parsed source text character by character $k = strlen($this->_wiki->_source); for ($i = 0; $i < $k; $i++) { // the current character $char = $this->_wiki->_source{$i}; // are alredy in a delimited section? if ($in_delim) { // yes; are we ending the section? if ($char == $delim) { // yes, get the replacement text for the delimited // token number and unset the flag. $key = (int)$key; $rule = $this->_wiki->_tokens[$key][0]; $opts = $this->_wiki->_tokens[$key][1]; $in_delim = false; // is the key a start heading token // of level 2 or deeper? if ($rule == 'heading' && $opts['type'] == 'start' && $opts['level'] > 1) { // yes, add a TOC target link to the // tokens array... $token = $this->addToken( array( 'type' => 'target', 'count' => $count, 'level' => $opts['level'] ) ); // ... and to the new source, before the // heading-start token. $newsrc .= $token . $delim . $key . $delim; // retain the toc item $this->entry[] = array ( 'count' => $count, 'level' => $opts['level'], 'text' => $opts['text'] ); // increase the count for the next entry $count++; } else { // not a heading-start of 2 or deeper. // re-add the delimited token number // as it was in the original source. $newsrc .= $delim . $key . $delim; } } else { // no, add to the delimited token key number $key .= $char; } } else { // not currently in a delimited section. // are we starting into a delimited section? if ($char == $delim) { // yes, reset the previous key and // set the flag. $key = ''; $in_delim = true; } else { // no, add to the output as-is $newsrc .= $char; } } } // replace with changed source text $this->_wiki->_source = $newsrc; /* // PRIOR VERSION // has problems mistaking marked-up numbers for delimited tokens // creates target tokens, retrieves heading level and text $this->entry = array(); $count = 0; // loop through all tokens and get headings foreach ($this->_wiki->_tokens as $key => $val) { // only get heading starts of level 2 or deeper if ($val[0] == 'heading' && $val[1]['type'] == 'start' && $val[1]['level'] > 1) { // the level of this header $level = $val[1]['level']; // the text of this header $text = $val[1]['text']; // add a toc-target link to the tokens array $token = $this->addToken( array( 'type' => 'target', 'count' => $count, 'level' => $level ) ); // put the toc target token in front of the // heading-start token $start = $delim . $key . $delim; $this->_wiki->_source = str_replace($start, $token.$start, $this->_wiki->_source); // retain the toc item $this->entry[] = array ( 'count' => $count, 'level' => $level, 'text' => $text ); // increase the count for the next item $count++; } } */ } /** * * Renders a token into text matching the requested format. * * @access public * * @param array $options The "options" portion of the token (second * element). * * @return string The text rendered from the token options. * */ function renderXhtml($options) { // type, count, level extract($options); // the prefix used for anchor names $prefix = 'toc'; if ($type == 'target') { // ... generate an anchor. return ""; } if ($type == 'list_start') { return "

\n"; } if ($type == 'list_end') { return "

\n"; } if ($type == 'item_start') { // build some indenting spaces for the text $indent = ($level - 2) * 4; $pad = str_pad('', $indent); $pad = str_replace(' ', ' ', $pad); // add an extra linebreak in front of heading-2 // entries (makes for nice section separations) if ($level <= 2 && $count > 0) { $pad = "
\n$pad"; } // create the entry line as a link to the toc anchor return "$pad"; } if ($type == 'item_end') { return "
\n"; } } } ?>