| 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__ |