51b32b28fa1a1a18823ef3fed6206b53c8faffb3
[ikiwiki.git] / IkiWiki / Plugin / smiley.pm
1 #!/usr/bin/perl
2 package IkiWiki::Plugin::smiley;
3
4 use warnings;
5 use strict;
6 use IkiWiki 2.00;
7
8 my %smileys;
9 my $smiley_regexp;
10
11 sub import { #{{{
12         add_underlay("smiley");
13         hook(type => "sanitize", id => "smiley", call => \&sanitize);
14 } # }}}
15
16 sub build_regexp () { #{{{
17         my $list=readfile(srcfile("smileys.mdwn"));
18         while ($list =~ m/^\s*\*\s+\\([^\s]+)\s+\[\[([^]]+)\]\]/mg) {
19                 my $smiley=$1;
20                 my $file=$2;
21
22                 $smileys{$smiley}=$file;
23
24                 # Add a version with < and > escaped, since they probably
25                 # will be (by markdown) by the time the sanitize hook runs.
26                 $smiley=~s/</&lt;/g;
27                 $smiley=~s/>/&gt;/g;
28                 $smileys{$smiley}=$file;
29         }
30         
31         if (! %smileys) {
32                 debug(gettext("failed to parse any smileys"));
33                 $smiley_regexp='';
34                 return;
35         }
36         
37         # sort and reverse so that substrings come after longer strings
38         # that contain them, in most cases.
39         $smiley_regexp='('.join('|', map { quotemeta }
40                 reverse sort keys %smileys).')';
41         #debug($smiley_regexp);
42 } #}}}
43
44 sub sanitize (@) { #{{{
45         my %params=@_;
46
47         build_regexp() unless defined $smiley_regexp;
48         
49         $_=$params{content};
50         return $_ unless length $smiley_regexp;
51         
52 MATCH:  while (m{(?:^|(?<=\s|>))(\\?)$smiley_regexp(?:(?=\s|<)|$)}g) {
53                 my $escape=$1;
54                 my $smiley=$2;
55                 my $epos=$-[1];
56                 my $spos=$-[2];
57                 
58                 # Smilies are not allowed inside <pre> or <code>.
59                 # For each tag in turn, match forward to find the next <tag>
60                 # or </tag> after the smiley.
61                 my $pos=pos;
62                 foreach my $tag ("pre", "code") {
63                         if (m/<(\/)?\s*$tag\s*>/isg && defined $1) {
64                                 # </tag> found first, so the smiley is
65                                 # inside the tag, so do not expand it.
66                                 next MATCH;
67                         }
68                         # Reset pos back to where it was before this test.
69                         pos=$pos;
70                 }
71
72                 if ($escape) {
73                         # Remove escape.
74                         substr($_, $epos, 1)="";
75                 }
76                 else {
77                         # Replace the smiley with its expanded value.
78                         substr($_, $spos, length($smiley))=
79                                 htmllink($params{page}, $params{destpage},
80                                          $smileys{$smiley}, linktext => $smiley);
81                 }
82         }
83
84         return $_;
85 } # }}}
86
87 1