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