toc.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.0 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Paul M. Jones <pmjones@ciaweb.net> |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: toc.php,v 1.2 2004/01/31 17:09:46 pmjones Exp $
  20. /**
  21. *
  22. * This class implements a Text_Wiki_Rule to find all heading tokens and
  23. * build a table of contents. The %%TOC%% tag gets replaced with a list
  24. * of all the level-2 through level-6 headings.
  25. *
  26. * @author Paul M. Jones <pmjones@ciaweb.net>
  27. *
  28. * @package Text_Wiki
  29. *
  30. */
  31. class Text_Wiki_Rule_toc extends Text_Wiki_Rule {
  32. /**
  33. *
  34. * The regular expression used to parse the source text and find
  35. * matches conforming to this rule. Used by the parse() method.
  36. *
  37. * @access public
  38. *
  39. * @var string
  40. *
  41. * @see parse()
  42. *
  43. */
  44. var $regex = "/%%TOC%%/m";
  45. /**
  46. *
  47. * The collection of headings (text and levels).
  48. *
  49. * @access public
  50. *
  51. * @var array
  52. *
  53. * @see _getEntries()
  54. *
  55. */
  56. var $entry = array();
  57. /**
  58. *
  59. * Custom parsing (have to process heading entries first).
  60. *
  61. * @access public
  62. *
  63. * @see Text_Wiki::parse()
  64. *
  65. */
  66. function parse()
  67. {
  68. $this->_getEntries();
  69. parent::parse();
  70. }
  71. /**
  72. *
  73. * Generates a replacement for the matched text. Token options are:
  74. *
  75. * 'type' => ['list_start'|'list_end'|'item_end'|'item_end'|'target']
  76. *
  77. * 'level' => The heading level (1-6).
  78. *
  79. * 'count' => Which heading this is in the list.
  80. *
  81. * @access public
  82. *
  83. * @param array &$matches The array of matches from parse().
  84. *
  85. * @return string A token indicating the TOC collection point.
  86. *
  87. */
  88. function process(&$matches)
  89. {
  90. $output = $this->addToken(array('type' => 'list_start'));
  91. foreach ($this->entry as $key => $val) {
  92. $options = array(
  93. 'type' => 'item_start',
  94. 'count' => $val['count'],
  95. 'level' => $val['level']
  96. );
  97. $output .= $this->addToken($options);
  98. $output .= $val['text'];
  99. $output .= $this->addToken(array('type' => 'item_end'));
  100. }
  101. $output .= $this->addToken(array('type' => 'list_end'));
  102. return $output;
  103. }
  104. /**
  105. *
  106. * Finds all headings in the text and saves them in $this->entry.
  107. *
  108. * @access private
  109. *
  110. * @return void
  111. *
  112. */
  113. function _getEntries()
  114. {
  115. // the wiki delimiter
  116. $delim = $this->_wiki->delim;
  117. // list of all TOC entries (h2, h3, etc)
  118. $this->entry = array();
  119. // the new source text with TOC entry tokens
  120. $newsrc = '';
  121. // when passing through the parsed source text, keep track of when
  122. // we are in a delimited section
  123. $in_delim = false;
  124. // when in a delimited section, capture the token key number
  125. $key = '';
  126. // TOC entry count
  127. $count = 0;
  128. // pass through the parsed source text character by character
  129. $k = strlen($this->_wiki->_source);
  130. for ($i = 0; $i < $k; $i++) {
  131. // the current character
  132. $char = $this->_wiki->_source{$i};
  133. // are alredy in a delimited section?
  134. if ($in_delim) {
  135. // yes; are we ending the section?
  136. if ($char == $delim) {
  137. // yes, get the replacement text for the delimited
  138. // token number and unset the flag.
  139. $key = (int)$key;
  140. $rule = $this->_wiki->_tokens[$key][0];
  141. $opts = $this->_wiki->_tokens[$key][1];
  142. $in_delim = false;
  143. // is the key a start heading token
  144. // of level 2 or deeper?
  145. if ($rule == 'heading' &&
  146. $opts['type'] == 'start' &&
  147. $opts['level'] > 1) {
  148. // yes, add a TOC target link to the
  149. // tokens array...
  150. $token = $this->addToken(
  151. array(
  152. 'type' => 'target',
  153. 'count' => $count,
  154. 'level' => $opts['level']
  155. )
  156. );
  157. // ... and to the new source, before the
  158. // heading-start token.
  159. $newsrc .= $token . $delim . $key . $delim;
  160. // retain the toc item
  161. $this->entry[] = array (
  162. 'count' => $count,
  163. 'level' => $opts['level'],
  164. 'text' => $opts['text']
  165. );
  166. // increase the count for the next entry
  167. $count++;
  168. } else {
  169. // not a heading-start of 2 or deeper.
  170. // re-add the delimited token number
  171. // as it was in the original source.
  172. $newsrc .= $delim . $key . $delim;
  173. }
  174. } else {
  175. // no, add to the delimited token key number
  176. $key .= $char;
  177. }
  178. } else {
  179. // not currently in a delimited section.
  180. // are we starting into a delimited section?
  181. if ($char == $delim) {
  182. // yes, reset the previous key and
  183. // set the flag.
  184. $key = '';
  185. $in_delim = true;
  186. } else {
  187. // no, add to the output as-is
  188. $newsrc .= $char;
  189. }
  190. }
  191. }
  192. // replace with changed source text
  193. $this->_wiki->_source = $newsrc;
  194. /*
  195. // PRIOR VERSION
  196. // has problems mistaking marked-up numbers for delimited tokens
  197. // creates target tokens, retrieves heading level and text
  198. $this->entry = array();
  199. $count = 0;
  200. // loop through all tokens and get headings
  201. foreach ($this->_wiki->_tokens as $key => $val) {
  202. // only get heading starts of level 2 or deeper
  203. if ($val[0] == 'heading' &&
  204. $val[1]['type'] == 'start' &&
  205. $val[1]['level'] > 1) {
  206. // the level of this header
  207. $level = $val[1]['level'];
  208. // the text of this header
  209. $text = $val[1]['text'];
  210. // add a toc-target link to the tokens array
  211. $token = $this->addToken(
  212. array(
  213. 'type' => 'target',
  214. 'count' => $count,
  215. 'level' => $level
  216. )
  217. );
  218. // put the toc target token in front of the
  219. // heading-start token
  220. $start = $delim . $key . $delim;
  221. $this->_wiki->_source = str_replace($start, $token.$start,
  222. $this->_wiki->_source);
  223. // retain the toc item
  224. $this->entry[] = array (
  225. 'count' => $count,
  226. 'level' => $level,
  227. 'text' => $text
  228. );
  229. // increase the count for the next item
  230. $count++;
  231. }
  232. }
  233. */
  234. }
  235. /**
  236. *
  237. * Renders a token into text matching the requested format.
  238. *
  239. * @access public
  240. *
  241. * @param array $options The "options" portion of the token (second
  242. * element).
  243. *
  244. * @return string The text rendered from the token options.
  245. *
  246. */
  247. function renderXhtml($options)
  248. {
  249. // type, count, level
  250. extract($options);
  251. // the prefix used for anchor names
  252. $prefix = 'toc';
  253. if ($type == 'target') {
  254. // ... generate an anchor.
  255. return "<a name=\"$prefix$count\"></a>";
  256. }
  257. if ($type == 'list_start') {
  258. return "<p>\n";
  259. }
  260. if ($type == 'list_end') {
  261. return "</p>\n";
  262. }
  263. if ($type == 'item_start') {
  264. // build some indenting spaces for the text
  265. $indent = ($level - 2) * 4;
  266. $pad = str_pad('', $indent);
  267. $pad = str_replace(' ', '&nbsp;', $pad);
  268. // add an extra linebreak in front of heading-2
  269. // entries (makes for nice section separations)
  270. if ($level <= 2 && $count > 0) {
  271. $pad = "<br />\n$pad";
  272. }
  273. // create the entry line as a link to the toc anchor
  274. return "$pad<a href=\"#$prefix$count\">";
  275. }
  276. if ($type == 'item_end') {
  277. return "</a><br />\n";
  278. }
  279. }
  280. }
  281. ?>