]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blob - IkiWiki/Plugin/toc.pm
use heading identifiers in TOC links
[git.ikiwiki.info.git] / IkiWiki / Plugin / toc.pm
1 #!/usr/bin/perl
2 # Table Of Contents generator
3 package IkiWiki::Plugin::toc;
5 use warnings;
6 use strict;
7 use IkiWiki 3.00;
8 use HTML::Parser;
10 sub import {
11         hook(type => "getsetup", id => "toc", call => \&getsetup);
12         hook(type => "preprocess", id => "toc", call => \&preprocess);
13         hook(type => "format", id => "toc", call => \&format);
14 }
16 sub getsetup () {
17         return
18                 plugin => {
19                         safe => 1,
20                         rebuild => undef,
21                         section => "widget",
22                 },
23 }
25 my %tocpages;
27 sub preprocess (@) {
28         my %params=@_;
30         if ($params{page} eq $params{destpage}) {
31                 $params{levels}=1 unless exists $params{levels};
33                 # It's too early to generate the toc here, so just record the
34                 # info.
35                 $tocpages{$params{destpage}}=\%params;
37                 return "\n<div class=\"toc\"></div>\n";
38         }
39         else {
40                 # Don't generate toc in an inlined page, doesn't work
41                 # right.
42                 return "";
43         }
44 }
46 sub format (@) {
47         my %params=@_;
48         my $content=$params{content};
49         
50         return $content unless exists $tocpages{$params{page}};
51         %params=%{$tocpages{$params{page}}};
53         my $p=HTML::Parser->new(api_version => 3);
54         my $page="";
55         my $index="";
56         my %anchors;
57         my $startlevel=($params{startlevel} ? $params{startlevel} : 0);
58         my $curlevel=$startlevel-1;
59         my $liststarted=0;
60         my $indent=sub { "\t" x $curlevel };
61         $p->handler(start => sub {
62                 my ($tagname, $text, $attr) = @_;
63                 if ($tagname =~ /^h(\d+)$/i) {
64                         my $level=$1;
65                         my $anchor="index".++$anchors{$level}."h$level";
66                         $page.="$text<a name=\"$anchor\"></a>";
67                         # if the heading already has a unique ID, use that instead in TOC
68                         if ($attr->{id}) {
69                                 $anchor = $attr->{id};
70                         }
72                         # Unless we're given startlevel as a parameter,
73                         # take the first header level seen as the topmost level,
74                         # even if there are higher levels seen later on.
75                         if (! $startlevel) {
76                                 $startlevel=$level;
77                                 $curlevel=$startlevel-1;
78                         }
79                         elsif (defined $params{startlevel} &&
80                                $level < $params{startlevel}) {
81                             return;
82                         }
83                         elsif ($level < $startlevel) {
84                                 $level=$startlevel;
85                         }
86                         
87                         return if $level - $startlevel >= $params{levels};
88         
89                         if ($level > $curlevel) {
90                                 while ($level > $curlevel + 1) {
91                                         $index.=&$indent."<ol>\n";
92                                         $curlevel++;
93                                         $index.=&$indent."<li class=\"L$curlevel\">\n";
94                                 }
95                                 $index.=&$indent."<ol>\n";
96                                 $curlevel=$level;
97                                 $liststarted=1;
98                         }
99                         elsif ($level < $curlevel) {
100                                 while ($level < $curlevel) {
101                                         $index.=&$indent."</li>\n" if $curlevel;
102                                         $curlevel--;
103                                         $index.=&$indent."</ol>\n";
104                                 }
105                                 $liststarted=0;
106                         }
107                                 
108                         $index.=&$indent."</li>\n" unless $liststarted;
109                         $liststarted=0;
110                         $index.=&$indent."<li class=\"L$curlevel\">".
111                                 "<a href=\"#$anchor\">";
112         
113                         $p->handler(text => sub {
114                                 $page.=join("", @_);
115                                 $index.=join("", @_);
116                         }, "dtext");
117                         $p->handler(end => sub {
118                                 my $tagname=shift;
119                                 if ($tagname =~ /^h(\d+)$/i) {
120                                         $p->handler(text => undef);
121                                         $p->handler(end => undef);
122                                         $index.="</a>\n";
123                                 }
124                                 $page.=join("", @_);
125                         }, "tagname, text");
126                 }
127                 else {
128                         $page.=$text;
129                 }
130         }, "tagname, text, attr");
131         $p->handler(default => sub { $page.=join("", @_) }, "text");
132         $p->parse($content);
133         $p->eof;
135         while ($startlevel && $curlevel >= $startlevel) {
136                 $index.=&$indent."</li>\n" if $curlevel;
137                 $curlevel--;
138                 $index.=&$indent."</ol>\n";
139         }
141         $page=~s/(<div class=\"toc\">)/$1\n$index/;
142         return $page;