50f2686277afa2de60ef86bd88a870938f1c96e4
[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                         pos=$epos+1;
76                 }
77                 else {
78                         # Replace the smiley with its expanded value.
79                         substr($_, $spos, length($smiley))=
80                                 htmllink($params{page}, $params{destpage},
81                                          $smileys{$smiley}, linktext => $smiley);
82                         pos=$epos+1;
83                 }
84
85                 # Breaks out at end, otherwise it will scan through again,
86                 # replacing de-escaped ones.
87                 #last unless defined pos;
88         }
89
90         return $_;
91 } # }}}
92
93 1