ikiwiki/IkiWiki/Plugin/toc.pm

146 lines
3.3 KiB
Perl

#!/usr/bin/perl
# Table Of Contents generator
package IkiWiki::Plugin::toc;
use warnings;
use strict;
use IkiWiki 3.00;
use HTML::Parser;
sub import {
hook(type => "getsetup", id => "toc", call => \&getsetup);
hook(type => "preprocess", id => "toc", call => \&preprocess);
hook(type => "format", id => "toc", call => \&format);
}
sub getsetup () {
return
plugin => {
safe => 1,
rebuild => undef,
section => "widget",
},
}
my %tocpages;
sub preprocess (@) {
my %params=@_;
if ($params{page} eq $params{destpage}) {
$params{levels}=1 unless exists $params{levels};
# It's too early to generate the toc here, so just record the
# info.
$tocpages{$params{destpage}}=\%params;
return "\n<div class=\"toc\"></div>\n";
}
else {
# Don't generate toc in an inlined page, doesn't work
# right.
return "";
}
}
sub format (@) {
my %params=@_;
my $content=$params{content};
return $content unless exists $tocpages{$params{page}};
%params=%{$tocpages{$params{page}}};
my $p=HTML::Parser->new(api_version => 3);
my $page="";
my $index="";
my %anchors;
my $startlevel=($params{startlevel} ? $params{startlevel} : 0);
my $curlevel=$startlevel-1;
my $liststarted=0;
my $indent=sub { "\t" x $curlevel };
$p->handler(start => sub {
my ($tagname, $text, $attr) = @_;
if ($tagname =~ /^h(\d+)$/i) {
my $level=$1;
my $anchor="index".++$anchors{$level}."h$level";
$page.="$text<a name=\"$anchor\"></a>";
# if the heading already has a unique ID, use that instead in TOC
if ($attr->{id}) {
$anchor = $attr->{id};
}
# Unless we're given startlevel as a parameter,
# take the first header level seen as the topmost level,
# even if there are higher levels seen later on.
if (! $startlevel) {
$startlevel=$level;
$curlevel=$startlevel-1;
}
elsif (defined $params{startlevel} &&
$level < $params{startlevel}) {
return;
}
elsif ($level < $startlevel) {
$level=$startlevel;
}
return if $level - $startlevel >= $params{levels};
if ($level > $curlevel) {
while ($level > $curlevel + 1) {
$index.=&$indent."<ol>\n";
$curlevel++;
$index.=&$indent."<li class=\"L$curlevel\">\n";
}
$index.=&$indent."<ol>\n";
$curlevel=$level;
$liststarted=1;
}
elsif ($level < $curlevel) {
while ($level < $curlevel) {
$index.=&$indent."</li>\n" if $curlevel;
$curlevel--;
$index.=&$indent."</ol>\n";
}
$liststarted=0;
}
$index.=&$indent."</li>\n" unless $liststarted;
$liststarted=0;
$index.=&$indent."<li class=\"L$curlevel\">".
"<a href=\"#$anchor\">";
$p->handler(text => sub {
$page.=join("", @_);
$index.=join("", @_);
}, "dtext");
$p->handler(end => sub {
my $tagname=shift;
if ($tagname =~ /^h(\d+)$/i) {
$p->handler(text => undef);
$p->handler(end => undef);
$index.="</a>\n";
}
$page.=join("", @_);
}, "tagname, text");
}
else {
$page.=$text;
}
}, "tagname, text, attr");
$p->handler(default => sub { $page.=join("", @_) }, "text");
$p->parse($content);
$p->eof;
while ($startlevel && $curlevel >= $startlevel) {
$index.=&$indent."</li>\n" if $curlevel;
$curlevel--;
$index.=&$indent."</ol>\n";
}
$page=~s/(<div class=\"toc\">)/$1\n$index/;
return $page;
}
1