tableofcontents.module in Table of Contents 5
Same filename and directory in other branches
This is a module to generate a table of contents section based on <h[2-3]> tags. It currently depends on the headinganchors.module for hotlinking to work properly. I need to learn how to properly create a module dependancy or redesign to put both filters in the same module.
For an example, see http://www.csaonline.ca/clublist.
File
tableofcontents.moduleView source
<?php
/**
* @file
* This is a module to generate a table of contents section based on <h[2-3]>
* tags. It currently depends on the headinganchors.module for hotlinking to work properly.
* I need to learn how to properly create a module dependancy or redesign to put both filters
* in the same module.
*
* For an example, see http://www.csaonline.ca/clublist.
*/
/**
* Implementation of hook_help().
*/
function tableofcontents_help($section) {
switch ($section) {
case 'admin/modules#description':
// This description is shown in the listing at admin/modules.
return t('A module to create a table of contents based on HTML header tags.');
}
}
/**
* Implementation of hook_filter_tips().
*/
function tableofcontents_filter_tips($format, $long = FALSE) {
if ($long) {
return t('Every instance of "<!--tableofcontents-->" in the input text will be replaced with a table of contents.');
}
else {
return t('Insert <!--tableofcontents--> to insert a dynamic TOC.');
}
}
/**
* Implementation of hook_filter().
*
* This is where we do the building of the TOC and replace the toc tags as needed.
*/
function tableofcontents_filter($op, $delta = 0, $format = -1, $text = '') {
if ($op == 'list') {
return array(
0 => t('Table of Contents'),
);
}
// This is where we store TOC options set by the user
global $toc_options;
switch ($op) {
case 'description':
return t('Inserts a table of contents in the place of <!--tableofcontents--> tags.');
// Ensure that the data is regenerated on every preview
case 'no cache':
return TRUE;
// We'll use the bytes 0xFE and 0xFF to replace < and > here. These bytes
// are not valid in UTF-8 data and thus unlikely to cause problems.
case 'prepare':
$options_regex = "(( [A-z]+: [A-z0-9 ]+;)+)?";
preg_match_all('!<\\!--tableofcontents' . $options_regex . '-->!', $text, $options_str, PREG_PATTERN_ORDER);
preg_match_all('/([A-z]+): ([A-z0-9 ]+);/', $options_str[1][0], $options, PREG_PATTERN_ORDER);
$toc_options = array();
for ($i = 0; $i < sizeof($options[1]); $i++) {
$toc_options[$options[1][$i]] = $options[2][$i];
}
/**
* Allowed options
*/
$allowed_options = array(
"title",
"list",
"minlevel",
"maxlevel",
);
// Check for invalid options
foreach ($toc_options as $key => $value) {
if (!in_array($key, $allowed_options)) {
form_set_error("Table of Contents", t("%key is an invalid option.", array(
"%key" => $key,
)));
}
}
// Process default options
if (!array_key_exists("title", $toc_options)) {
$toc_options["title"] = "Table of Contents";
}
// Translate ToC
$toc_options["title"] = t($toc_options["title"]);
if (!array_key_exists("list", $toc_options)) {
$toc_options["list"] = "ol";
}
if (!array_key_exists("minlevel", $toc_options)) {
$toc_options["minlevel"] = "2";
}
if (!array_key_exists("maxlevel", $toc_options)) {
$toc_options["maxlevel"] = "3";
}
// Process allowed options
if ($toc_options["list"] != "ol" && $toc_options["list"] != "ul") {
form_set_error("Table of Contents", t("%key is an invalid list option. Please choose 'ol' or 'ul'.", array(
"%key" => $toc_options["list"],
)));
}
if (!($toc_options["minlevel"] >= 1 && $toc_options["minlevel"] <= 5)) {
form_set_error("Table of Contents", t("%key is an invalid minimum level option. Please choose a number between 1 and 5.", array(
"%key" => $toc_options["minlevel"],
)));
}
if (!($toc_options["maxlevel"] >= $toc_options["minlevel"] && $toc_options["maxlevel"] <= 5)) {
form_set_error("Table of Contents", t("%key is an invalid maximum depth option. Please choose a number between %min and 5.", array(
"%min" => $toc_options["minlevel"],
"%key" => $toc_options["maxlevel"],
)));
}
return preg_replace('!<\\!--tableofcontents' . $options_regex . '-->!', '\\xFE!--tableofcontents--\\xFF', $text);
case 'process':
// We will build an array which looks like array(array('level' => 1, 'heading' => $text), ...);
$toc = array();
$matches = array();
/*
* $i is the index of the heading found
* $matches[0][$i] -> Whole string matched
* $matches[1][$i] -> First heading level
* $matches[2][$i] -> Whole string of attributes
* $matches[3][$i] -> id attibute, used for anchor
* $matches[4][$i] -> Text of id attribute
* $matches[5][$i] -> Text inside of h tag
* $matches[6][$i] -> Close heading level, should be equal to open level
*/
preg_match_all('/<h([' . $toc_options["minlevel"] . '-' . $toc_options["maxlevel"] . '])( .*(id="([^"]+)" ?.*))?>(.*)<\\/h([' . $toc_options["minlevel"] . '-' . $toc_options["maxlevel"] . '])>/i', $text, $matches, PREG_PATTERN_ORDER);
$anchors = array();
for ($i = 0; $i < sizeof($matches[0]); $i++) {
// Strip HTML and non alphanumerics
$level = $matches[1][$i];
$heading = strip_tags($matches[5][$i]);
$anchor = $matches[4][$i];
array_push($toc, array(
'level' => $level,
'heading' => $heading,
'anchor' => $anchor,
));
}
// Build HTML
// We probably need to find a way to allow styling of this based on the theme.
// The obvious example is to put a shaded background behind the TOC.
// In the future it would also be nice to have collapseable headings using javascript
// for those really long documents.
$toc_html = "<div class=\"toc\">\n<h" . $toc_options["minlevel"] . ">" . $toc_options["title"] . "</h" . $toc_options["minlevel"] . ">\n<" . $toc_options["list"] . ">\n";
$depth = $toc_options["minlevel"];
foreach ($toc as $title) {
// We need to allow for nested lists
// It should be trivial to make the heading levels shown in the TOC user customizable
$curdepth = $title['level'];
if ($curdepth <= $toc_options["maxlevel"]) {
if ($curdepth > $depth) {
$toc_html .= "<" . $toc_options["list"] . ">\n";
}
else {
if ($curdepth < $depth) {
$toc_html .= "</" . $toc_options["list"] . ">\n";
}
}
$depth = $curdepth;
// Insert the list element.
$toc_html .= "<li><a href=\"#" . $title['anchor'] . "\">" . $title['heading'] . "</a></li>\n";
}
}
// Did we recurse back out to h2 tags? If not, close open lists.
while ($depth > $toc_options["minlevel"]) {
$toc_html .= "</" . $toc_options["list"] . ">\n";
$depth = $depth - 1;
}
$toc_html .= "</div>\n";
// Find our previously changed string and replace with our generated TOC.
return str_replace('\\xFE!--tableofcontents--\\xFF', $toc_html, $text);
}
}
Functions
Name | Description |
---|---|
tableofcontents_filter | Implementation of hook_filter(). |
tableofcontents_filter_tips | Implementation of hook_filter_tips(). |
tableofcontents_help | Implementation of hook_help(). |