localstyle: New plugin, allows overrding the toplevel local.css with one that is...
[ikiwiki] / 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 3.00;
8 use HTML::Parser;
9
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 }
15
16 sub getsetup () {
17         return
18                 plugin => {
19                         safe => 1,
20                         rebuild => undef,
21                 },
22 }
23
24 my %tocpages;
25
26 sub preprocess (@) {
27         my %params=@_;
28
29         if ($params{page} eq $params{destpage}) {
30                 $params{levels}=1 unless exists $params{levels};
31
32                 # It's too early to generate the toc here, so just record the
33                 # info.
34                 $tocpages{$params{destpage}}=\%params;
35
36                 return "\n<div class=\"toc\"></div>\n";
37         }
38         else {
39                 # Don't generate toc in an inlined page, doesn't work
40                 # right.
41                 return "";
42         }
43 }
44
45 sub format (@) {
46         my %params=@_;
47         my $content=$params{content};
48         
49         return $content unless exists $tocpages{$params{page}};
50         %params=%{$tocpages{$params{page}}};
51
52         my $p=HTML::Parser->new(api_version => 3);
53         my $page="";
54         my $index="";
55         my %anchors;
56         my $curlevel;
57         my $startlevel=0;
58         my $liststarted=0;
59         my $indent=sub { "\t" x $curlevel };
60         $p->handler(start => sub {
61                 my $tagname=shift;
62                 my $text=shift;
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         
68                         # Take the first header level seen as the topmost level,
69                         # even if there are higher levels seen later on.
70                         if (! $startlevel) {
71                                 $startlevel=$level;
72                                 $curlevel=$startlevel-1;
73                         }
74                         elsif ($level < $startlevel) {
75                                 $level=$startlevel;
76                         }
77                         
78                         return if $level - $startlevel >= $params{levels};
79         
80                         if ($level > $curlevel) {
81                                 while ($level > $curlevel + 1) {
82                                         $index.=&$indent."<ol>\n";
83                                         $curlevel++;
84                                         $index.=&$indent."<li class=\"L$curlevel\">\n";
85                                 }
86                                 $index.=&$indent."<ol>\n";
87                                 $curlevel=$level;
88                                 $liststarted=1;
89                         }
90                         elsif ($level < $curlevel) {
91                                 while ($level < $curlevel) {
92                                         $index.=&$indent."</li>\n" if $curlevel;
93                                         $curlevel--;
94                                         $index.=&$indent."</ol>\n";
95                                 }
96                                 $liststarted=0;
97                         }
98                                 
99                         $index.=&$indent."</li>\n" unless $liststarted;
100                         $liststarted=0;
101                         $index.=&$indent."<li class=\"L$curlevel\">".
102                                 "<a href=\"#$anchor\">";
103         
104                         $p->handler(text => sub {
105                                 $page.=join("", @_);
106                                 $index.=join("", @_);
107                         }, "dtext");
108                         $p->handler(end => sub {
109                                 my $tagname=shift;
110                                 if ($tagname =~ /^h(\d+)$/i) {
111                                         $p->handler(text => undef);
112                                         $p->handler(end => undef);
113                                         $index.="</a>\n";
114                                 }
115                                 $page.=join("", @_);
116                         }, "tagname, text");
117                 }
118                 else {
119                         $page.=$text;
120                 }
121         }, "tagname, text");
122         $p->handler(default => sub { $page.=join("", @_) }, "text");
123         $p->parse($content);
124         $p->eof;
125
126         while ($startlevel && $curlevel >= $startlevel) {
127                 $index.=&$indent."</li>\n" if $curlevel;
128                 $curlevel--;
129                 $index.=&$indent."</ol>\n";
130         }
131
132         $page=~s/(<div class=\"toc\">)/$1\n$index/;
133         return $page;
134 }
135
136 1