blib/lib/Labyrinth/Plugin/Wiki/Text.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 12 | 12 | 100.0 |
branch | n/a | ||
condition | n/a | ||
subroutine | 4 | 4 | 100.0 |
pod | n/a | ||
total | 16 | 16 | 100.0 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Labyrinth::Plugin::Wiki::Text; | ||||||
2 | |||||||
3 | 2 | 2 | 6406 | use warnings; | |||
2 | 4 | ||||||
2 | 67 | ||||||
4 | 2 | 2 | 8 | use strict; | |||
2 | 2 | ||||||
2 | 60 | ||||||
5 | |||||||
6 | 2 | 2 | 6 | use vars qw($VERSION); | |||
2 | 2 | ||||||
2 | 105 | ||||||
7 | $VERSION = '1.06'; | ||||||
8 | |||||||
9 | =head1 NAME | ||||||
10 | |||||||
11 | Labyrinth::Plugin::Wiki::Text - Wiki text handler for Labyrinth framework. | ||||||
12 | |||||||
13 | =head1 DESCRIPTION | ||||||
14 | |||||||
15 | Contains all the Wiki text rendering code for Labyrinth. | ||||||
16 | |||||||
17 | =cut | ||||||
18 | |||||||
19 | # ------------------------------------- | ||||||
20 | # Library Modules | ||||||
21 | |||||||
22 | 2 | 2 | 14 | use base qw(Labyrinth::Plugin::Base); | |||
2 | 3 | ||||||
2 | 727 | ||||||
23 | |||||||
24 | use Labyrinth::Audit; | ||||||
25 | use Labyrinth::DBUtils; | ||||||
26 | use Labyrinth::MLUtils; | ||||||
27 | use Labyrinth::Support; | ||||||
28 | use Labyrinth::Users; | ||||||
29 | use Labyrinth::Variables; | ||||||
30 | |||||||
31 | # ------------------------------------- | ||||||
32 | # Variables | ||||||
33 | |||||||
34 | # preset with restricted pages | ||||||
35 | my %wiki_links = map {$_ => 1} qw(People Login Search RecentChanges); | ||||||
36 | |||||||
37 | my ($LinkPattern,$SitePattern,$UrlPattern,$UriPattern,$MailPattern,$SendPattern); | ||||||
38 | |||||||
39 | # HTML tag lists | ||||||
40 | # Single tags (that do not require a closing /tag) | ||||||
41 | my @HtmlSingle = qw(br hr); | ||||||
42 | # Tags that must be in |
||||||
43 | my @HtmlPairs = ( qw(b i p u h1 h2 h3 h4 h5 h6 code em strike strong blockquote ol ul li dt dd tr td th), | ||||||
44 | @HtmlSingle); # All singles can also be pairs | ||||||
45 | |||||||
46 | # ------------------------------------- | ||||||
47 | # Public Methods | ||||||
48 | |||||||
49 | =head1 PUBLIC INTERFACE METHODS | ||||||
50 | |||||||
51 | =over 4 | ||||||
52 | |||||||
53 | =item Render | ||||||
54 | |||||||
55 | Controls the process of rendering a given page. | ||||||
56 | |||||||
57 | =item InitLinkPatterns | ||||||
58 | |||||||
59 | Prepares patterns used to translate wiki links into HTML links. | ||||||
60 | |||||||
61 | =item Wiki2HTML | ||||||
62 | |||||||
63 | Translate WikiFormat into XHTML. | ||||||
64 | |||||||
65 | =item CommonMarkup | ||||||
66 | |||||||
67 | Looks for and translates common WikiFormat markup into XHTML. | ||||||
68 | |||||||
69 | =item WikiLink | ||||||
70 | |||||||
71 | Looks for and translates WikiFormat links into XHTML. | ||||||
72 | |||||||
73 | =item WikiHeading | ||||||
74 | |||||||
75 | Translate WikiFormat heading into XHTML. | ||||||
76 | |||||||
77 | =cut | ||||||
78 | |||||||
79 | sub Render { | ||||||
80 | my $self = shift; | ||||||
81 | my $hash = shift; | ||||||
82 | my $title = $cgiparams{pagename}; | ||||||
83 | my $content = $hash->{content}; | ||||||
84 | |||||||
85 | InitLinkPatterns() unless($LinkPattern); | ||||||
86 | |||||||
87 | $content = Wiki2HTML($content); | ||||||
88 | |||||||
89 | # reposition top level heading | ||||||
90 | if($content =~ s!^(.*?)!!) { |
||||||
91 | $title = $1; | ||||||
92 | } | ||||||
93 | |||||||
94 | return $title,$content; | ||||||
95 | } | ||||||
96 | |||||||
97 | sub InitLinkPatterns { | ||||||
98 | my $UpperLetter = '[A-Z\xc0-\xde]'; | ||||||
99 | my $LowerLetter = '[a-z\xdf-\xff]'; | ||||||
100 | my $AnyLetter = '[A-Za-z\xc0-\xff_0-9\$]'; | ||||||
101 | my $AnyString = '[A-Za-z\xc0-\xff_0-9 \-\&\'~.,\?\(\)\"!\$:\/]'; | ||||||
102 | |||||||
103 | # Main link pattern: lowercase between uppercase, then anything | ||||||
104 | my $LpA = $UpperLetter . $AnyLetter . '*'; | ||||||
105 | my $LpB = $AnyLetter . $AnyString . '*'; | ||||||
106 | my $LpC = $AnyLetter . '*:' . $AnyString . '*'; | ||||||
107 | |||||||
108 | $LinkPattern = qr!\[\[($LpA|$LpC)\]\]!; | ||||||
109 | $SitePattern = qr!\[\[($LpA|$LpC)\|($LpB)\]\]!; | ||||||
110 | |||||||
111 | $UrlPattern = qr!\[($settings{urlregex})\]!; | ||||||
112 | $UriPattern = qr!\[($settings{urlregex})[ \|]($LpB)\]!; | ||||||
113 | |||||||
114 | $MailPattern = qr!\[(?:mailto:)?($settings{emailregex})\]!; | ||||||
115 | $SendPattern = qr!\[(?:mailto:)?($settings{emailregex})[ |]($LpB)\]!; | ||||||
116 | } | ||||||
117 | |||||||
118 | sub Wiki2HTML { | ||||||
119 | my ($text) = @_; | ||||||
120 | my (@stack, $code, $oldcode, $parse); | ||||||
121 | my $depth = 0; | ||||||
122 | my $html = ''; | ||||||
123 | |||||||
124 | $code = 'p'; # we assume a paragraph starts | ||||||
125 | $text =~ s/\r\n?/\n/g; | ||||||
126 | for (split(/\n/, $text)) { # Process lines one-at-a-time | ||||||
127 | $_ .= "\n"; | ||||||
128 | $parse = 2; | ||||||
129 | if (s/^(\*+)/ |
||||||
130 | $code = "ul"; | ||||||
131 | $depth = length $1; | ||||||
132 | } elsif (s/^(\#+)/ |
||||||
133 | $code = "ol"; | ||||||
134 | $depth = length $1; | ||||||
135 | } elsif (s/^![ \t]//) { | ||||||
136 | $code = "pre"; | ||||||
137 | $depth = 1; | ||||||
138 | $parse = 0; | ||||||
139 | } elsif (s/^([ \t]{2})//) { | ||||||
140 | $code = "pre"; | ||||||
141 | $depth = 1; | ||||||
142 | $parse = 1; | ||||||
143 | } elsif (s/^(\" )//) { | ||||||
144 | $code = "blockquote"; | ||||||
145 | $depth = 1; | ||||||
146 | } else { | ||||||
147 | $code = "p"; | ||||||
148 | $depth = 0; | ||||||
149 | } | ||||||
150 | while (@stack > $depth) { # Close tags as needed | ||||||
151 | $html .= '' . pop(@stack) . ">\n"; | ||||||
152 | } | ||||||
153 | if ($depth > 0) { | ||||||
154 | # $depth = $IndentLimit if ($depth > $IndentLimit); | ||||||
155 | if (@stack) { # Non-empty stack | ||||||
156 | $oldcode = pop(@stack); | ||||||
157 | if ($oldcode ne $code) { | ||||||
158 | $html .= "$oldcode><$code>\n"; | ||||||
159 | } | ||||||
160 | push(@stack, $code); | ||||||
161 | } | ||||||
162 | while (@stack < $depth) { | ||||||
163 | push(@stack, $code); | ||||||
164 | $html .= "<$code>\n"; | ||||||
165 | } | ||||||
166 | } | ||||||
167 | |||||||
168 | if($code eq 'pre') { | ||||||
169 | s!^\s*$! \n!; # Blank lines become new lines |
||||||
170 | } else { | ||||||
171 | s!^\s*$! !; # Blank lines become new paragraphs |
||||||
172 | } | ||||||
173 | $html .= CommonMarkup($_, $parse); | ||||||
174 | } | ||||||
175 | while (@stack > 0) { # Clear stack | ||||||
176 | $html .= '' . pop(@stack) . ">\n"; | ||||||
177 | } | ||||||
178 | |||||||
179 | $html = process_html($html,0,1); | ||||||
180 | |||||||
181 | # $html =~ s! (.*?)\s*<(ul|ol|h[1-6]|pre|p)>! $1 \n<$2>!gs; # close's. |
||||||
182 | # $html =~ s! (.*?)\s*$! $1 !gs; # close final. |
||||||
183 | # $html =~ s!\s*\s*!!gs; # remove extra close paragraphs | ||||||
184 | # $html =~ s! \s* !!gs; # remove black paragraphs |
||||||
185 | # $html =~ s/(\s| )* \s*/\n /gs; # multiple blank lines fold into one. |
||||||
186 | # $html =~ s/\s* \s*<(ul|ol|h[1-6]|pre)/\n<$1/gs; # remove unnecessary 's. |
||||||
187 | # $html =~ s!([^>\s]+)\s* !$1 \n!gs; # close paragraphs. |
||||||
188 | # $html =~ s!\s*\s* \n
| ||||||
189 | |||||||
190 | LogDebug("html=[$html]"); | ||||||
191 | return $html; | ||||||
192 | } | ||||||
193 | |||||||
194 | # 2 = Full parser | ||||||
195 | # 1 = Link only parsing | ||||||
196 | # 0 = no parsing | ||||||
197 | |||||||
198 | sub CommonMarkup { | ||||||
199 | my ($text, $parse) = @_; | ||||||
200 | local $_ = $text; | ||||||
201 | |||||||
202 | if ($parse > 1) { | ||||||
203 | s!\<pre\>((.|\n)*?)\<\/pre\>!$1!ig; |
||||||
204 | s!\<code\>((.|\n)*?)\<\/code\>!$1 !ig; |
||||||
205 | |||||||
206 | my $t; | ||||||
207 | for $t (@HtmlPairs) { | ||||||
208 | s!\<$t(\s[^<>]+?)?\>(.*?)\<\/$t\>!<$t$1>$2<\/$t>!gis; | ||||||
209 | } | ||||||
210 | for $t (@HtmlSingle) { | ||||||
211 | s!\<$t(\s[^<>]+?)?\>!<$t$1>!gi; | ||||||
212 | } | ||||||
213 | |||||||
214 | # The quote markup patterns avoid overlapping tags (with 5 quotes) | ||||||
215 | # by matching the inner quotes for the strong pattern. | ||||||
216 | s/!!(.*?)!!/$1<\/code>/g; #' |
||||||
217 | s/('*)'''(.*?)'''/$1$2<\/strong>/g; #' | ||||||
218 | s/''(.*?)''/$1<\/em>/g; | ||||||
219 | s/(^|\n)\s*(\=+)\s+([^\n]+)\s+\=+/WikiHeading($1, $2, $3)/geo; | ||||||
220 | |||||||
221 | s!\<br\>! !g; |
||||||
222 | s!----+! !g; |
||||||
223 | s!====+! !g; |
||||||
224 | } | ||||||
225 | |||||||
226 | if($parse > 0) { | ||||||
227 | s!$SitePattern!WikiLink($1,$2)!eg; | ||||||
228 | s!$LinkPattern!WikiLink($1,$1)!eg; | ||||||
229 | |||||||
230 | s!$UriPattern!$2!g; | ||||||
231 | s!$UrlPattern!$1!g; | ||||||
232 | |||||||
233 | s!$SendPattern!$2!g; | ||||||
234 | s!$MailPattern!$1!g; | ||||||
235 | } | ||||||
236 | |||||||
237 | return $_; | ||||||
238 | } | ||||||
239 | |||||||
240 | sub WikiLink { | ||||||
241 | my ($page,$name) = @_; | ||||||
242 | |||||||
243 | if($page =~ /cpan:~(.*)/) { | ||||||
244 | $page =~ s!cpan:~!!; | ||||||
245 | $name =~ s!cpan:~!!; | ||||||
246 | return qq!$name!; | ||||||
247 | } elsif($page =~ /cpan:(.*)/) { | ||||||
248 | $page =~ s!cpan:!!; | ||||||
249 | $name =~ s!cpan:!!; | ||||||
250 | return qq!$name!; | ||||||
251 | } elsif($page =~ /perldoc:(.*)/) { | ||||||
252 | $page =~ s!perldoc:!!; | ||||||
253 | $name =~ s!perldoc:!!; | ||||||
254 | return qq!$name!; | ||||||
255 | } elsif($page =~ /user:(\d+|[\w ]+)/) { | ||||||
256 | $name = undef if($page eq $name); | ||||||
257 | return _mapuser($1,$name); | ||||||
258 | } elsif($page =~ /image:(\d+)/) { | ||||||
259 | return _mapimage(id => $1); | ||||||
260 | } elsif($page =~ /image:(.*)/) { | ||||||
261 | return _mapimage(name => $1); | ||||||
262 | } elsif($page =~ /media:(\d+)/) { | ||||||
263 | return _mapmedia(id => $1); | ||||||
264 | } elsif($page =~ /media:(.*)/) { | ||||||
265 | return _mapmedia(name => $1); | ||||||
266 | } | ||||||
267 | |||||||
268 | $wiki_links{$page} ||= do { | ||||||
269 | my @rows = $dbi->GetQuery('hash','CheckWikiPage',$page); | ||||||
270 | @rows ? 1 : 0; | ||||||
271 | }; | ||||||
272 | |||||||
273 | if($wiki_links{$page}) { | ||||||
274 | return qq!$name! | ||||||
275 | } | ||||||
276 | |||||||
277 | return qq!$name?! | ||||||
278 | } | ||||||
279 | |||||||
280 | sub WikiHeading { | ||||||
281 | my ($pre, $depth, $text) = @_; | ||||||
282 | |||||||
283 | $depth = length($depth) - 1; | ||||||
284 | $depth = 6 if ($depth > 6); | ||||||
285 | return $pre . " |
||||||
286 | } | ||||||
287 | |||||||
288 | # ------------------------------------- | ||||||
289 | # Private Methods | ||||||
290 | |||||||
291 | sub _mapuser { | ||||||
292 | my $id = shift; | ||||||
293 | my $nm = $id; | ||||||
294 | |||||||
295 | if($id =~ /^\d+$/) { | ||||||
296 | $nm = UserName($id); | ||||||
297 | } else { | ||||||
298 | $id = UserID($id); | ||||||
299 | } | ||||||
300 | |||||||
301 | return qq!$nm!; | ||||||
302 | } | ||||||
303 | |||||||
304 | sub _mapimage { | ||||||
305 | my %hash = @_; | ||||||
306 | my @rows; | ||||||
307 | |||||||
308 | if($hash{id}) { | ||||||
309 | @rows = $dbi->GetQuery('hash','GetImageByID',$hash{id}); | ||||||
310 | } else { | ||||||
311 | @rows = $dbi->GetQuery('hash','GetImageByName',$hash{id}); | ||||||
312 | } | ||||||
313 | |||||||
314 | return unless(@rows); | ||||||
315 | return qq!!; | ||||||
316 | } | ||||||
317 | |||||||
318 | sub _mapmedia { | ||||||
319 | my %hash = @_; | ||||||
320 | my @rows; | ||||||
321 | |||||||
322 | if($hash{id}) { | ||||||
323 | @rows = $dbi->GetQuery('hash','GetImageByID',$hash{id}); | ||||||
324 | } else { | ||||||
325 | @rows = $dbi->GetQuery('hash','GetImageByName',$hash{id}); | ||||||
326 | } | ||||||
327 | |||||||
328 | return unless(@rows); | ||||||
329 | return qq!$rows[0]->{tag}!; | ||||||
330 | } | ||||||
331 | |||||||
332 | 1; | ||||||
333 | |||||||
334 | __END__ |