* pagespec_match() has changed to take named parameters, to better allow
[ikiwiki.git] / IkiWiki / Plugin / toc.pm
1 #!/usr/bin/perl
2 # Table Of Contents generator
3 package IkiWiki::Plugin::toc;
4
5 use warnings;
6 use strict;
7 use IkiWiki 2.00;
8 use HTML::Parser;
9
10 sub import { #{{{
11         hook(type => "preprocess", id => "toc", call => \&preprocess);
12         hook(type => "format", id => "toc", call => \&format);
13 } # }}}
14
15 my %tocpages;
16
17 sub preprocess (@) { #{{{
18         my %params=@_;
19
20         $params{levels}=1 unless exists $params{levels};
21
22         # It's too early to generate the toc here, so just record the
23         # info.
24         $tocpages{$params{destpage}}=\%params;
25
26         return "\n<div class=\"toc\"></div>\n";
27 } # }}}
28
29 sub format (@) { #{{{
30         my %params=@_;
31         my $content=$params{content};
32         
33         return $content unless exists $tocpages{$params{page}};
34         %params=%{$tocpages{$params{page}}};
35
36         my $p=HTML::Parser->new(api_version => 3);
37         my $page="";
38         my $index="";
39         my %anchors;
40         my $curlevel;
41         my $startlevel=0;
42         my $liststarted=0;
43         my $indent=sub { "\t" x $curlevel };
44         $p->handler(start => sub {
45                 my $tagname=shift;
46                 my $text=shift;
47                 if ($tagname =~ /^h(\d+)$/i) {
48                         my $level=$1;
49                         my $anchor="index".++$anchors{$level}."h$level";
50                         $page.="$text<a name=\"$anchor\" />";
51         
52                         # Take the first header level seen as the topmost level,
53                         # even if there are higher levels seen later on.
54                         if (! $startlevel) {
55                                 $startlevel=$level;
56                                 $curlevel=$startlevel-1;
57                         }
58                         elsif ($level < $startlevel) {
59                                 $level=$startlevel;
60                         }
61                         
62                         return if $level - $startlevel >= $params{levels};
63         
64                         if ($level > $curlevel) {
65                                 while ($level > $curlevel + 1) {
66                                         $index.=&$indent."<ol>\n";
67                                         $curlevel++;
68                                         $index.=&$indent."<li class=\"L$curlevel\">\n";
69                                 }
70                                 $index.=&$indent."<ol>\n";
71                                 $curlevel=$level;
72                                 $liststarted=1;
73                         }
74                         elsif ($level < $curlevel) {
75                                 while ($level < $curlevel) {
76                                         $index.=&$indent."</li>\n" if $curlevel;
77                                         $curlevel--;
78                                         $index.=&$indent."</ol>\n";
79                                 }
80                                 $liststarted=0;
81                         }
82         
83                         $p->handler(text => sub {
84                                 $page.=join("", @_);
85                                 $index.=&$indent."</li>\n" unless $liststarted;
86                                 $liststarted=0;
87                                 $index.=&$indent."<li class=\"L$curlevel\">".
88                                         "<a href=\"#$anchor\">".
89                                         join("", @_).
90                                         "</a>\n";
91                                 $p->handler(text => undef);
92                         }, "dtext");
93                 }
94                 else {
95                         $page.=$text;
96                 }
97         }, "tagname, text");
98         $p->handler(default => sub { $page.=join("", @_) }, "text");
99         $p->parse($content);
100         $p->eof;
101
102         while ($startlevel && $curlevel >= $startlevel) {
103                 $index.=&$indent."</li>\n" if $curlevel;
104                 $curlevel--;
105                 $index.=&$indent."</ol>\n";
106         }
107
108         $page=~s/(<div class=\"toc\">)/$1\n$index/;
109         return $page;
110 }
111
112 1