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