File Coverage

blib/lib/Text/Trac/InlineNode.pm
Criterion Covered Total %
statement 136 138 98.5
branch 35 44 79.5
condition 7 14 50.0
subroutine 28 28 100.0
pod 0 3 0.0
total 206 227 90.7


line stmt bran cond sub pod time code
1             package Text::Trac::InlineNode;
2              
3 9     9   51 use strict;
  9         14  
  9         211  
4 9     9   36 use warnings;
  9         15  
  9         168  
5 9     9   3680 use Tie::IxHash;
  9         32414  
  9         76  
6 9     9   3176 use Text::Trac::Macro;
  9         20  
  9         48  
7 9     9   268 use UNIVERSAL::require;
  9         16  
  9         38  
8 9     9   3135 use Text::Trac::LinkResolver;
  9         25  
  9         98  
9 9     9   4109 use HTML::Entities qw();
  9         42960  
  9         15293  
10              
11             our $VERSION = '0.24';
12              
13             tie my %token_table, 'Tie::IxHash';
14              
15             #my $handler = $token_table{'!?\\[\\d+\\]|(?:\\b|!)r\\d+\\b(?!:\\d)'};
16             #$handler->format_link('test');
17              
18             my $link_scheme = '[\w.+-]+';
19             my $quoted_string = q{'[^']+'|"[^"]+"};
20             my $shref_target_first = '[\w/?!#@]';
21             my $shref_target_middle = '(?:\|(?=[^|\s])|[^|<>\s])';
22             my $shref_target_last = '[a-zA-Z0-9/=]';
23             my $shref = "!?$link_scheme:
24             (?:
25             $quoted_string
26             |$shref_target_first(?:$shref_target_middle*$shref_target_last)?
27             )
28             ";
29              
30             my $macro = '\[\[[\w/+-]+(?:\(.*\))?\]\]';
31              
32             my $lhref_relative_target = '[/.][^\s[\]]*';
33             my $lhref = "!?\\[
34             (?:
35             $link_scheme:
36             (?:$quoted_string|[^\\[\\]\\s]*)
37             |(?:$lhref_relative_target|[^\\[\\]\\s])
38             )
39             (?:
40             \\s+
41             $quoted_string
42             |[^\\]]+
43             )?
44             \\]
45             ";
46              
47             my $rules = join '|', ( map {"($_)"} ( keys %token_table ) );
48             $rules = qr/$rules/x;
49              
50             s/^\!\?// for values %token_table;
51             s/^\\// for values %token_table;
52              
53             sub new {
54 1607     1607 0 8030 my ( $class, $c ) = @_;
55              
56             # external link resolvers
57 1607         2176 my %external_handler;
58 1607         2956 for (@Text::Trac::LinkResolver::handlers) {
59 14463         27302 my $class = 'Text::Trac::LinkResolver::' . ucfirst($_);
60 14463         32602 $class->require;
61 14463         288090 my $handler = $class->new($c);
62 14463 100       42414 $token_table{ $handler->{pattern} } = $handler if defined $handler->{pattern};
63 14463         103288 $external_handler{$_} = $handler;
64             }
65              
66             %token_table = (
67 1607         4490 q{'''''} => 'bolditalic',
68             q{'''} => 'bold',
69             q{''} => 'italic',
70             '!?__' => 'underline',
71             '!?~~' => 'strike',
72             '!?,,' => 'subscript',
73             '!?\^' => 'superscript',
74             '`|\{\{\{|\}\}\}' => 'inline',
75             $macro => 'macro',
76             %token_table,
77             $lhref => 'lhref',
78             $shref => 'shref',
79             );
80              
81 1607         1293705 my $rules = join '|', ( map {"($_)"} ( keys %token_table ) );
  27319         133594  
82 1607         13469 $rules = qr/$rules/x;
83              
84 1607         3817 s/^\!\?// for values %token_table;
85 1607         198367 s/^\\// for values %token_table;
86              
87 1607         197549 my $self = {
88             context => $c,
89             open_tags => [],
90             rules => $rules,
91             external_handler => \%external_handler,
92             };
93 1607         2940 bless $self, $class;
94 1607         35819 return $self;
95             }
96              
97             sub parse {
98 210     210 0 1131 my ( $self, $rest ) = @_;
99 210         311 my $html = '';
100 210         3148 while ( $rest =~ /$self->{rules}/xms ) {
101 123         380 $html .= $self->escape($`) . $self->_replace( $&, $`, $' );
102 123         1023 $rest = $';
103             }
104 210         493 return $html . $self->escape($rest);
105             }
106              
107             sub escape {
108 337     337 0 713 my ( $self, $s ) = @_;
109 337         963 return HTML::Entities::encode( $s, '<>&"' );
110             }
111              
112             sub _replace {
113 123     123   5622 my ( $self, $match, $pre_match, $post_match ) = @_;
114 123 100       394 if ( $match =~ s/^!// ) {
115 21         57 return $match;
116             }
117             else {
118             TOKEN:
119 102         298 for my $token ( keys %token_table ) {
120 1071 100       25507 if ( $match =~ /$token/x ) {
121 110         514 my $formatter = $token_table{$token};
122 110 100       882 if ( ref $formatter ) {
123 25         66 for (qw/ log source attachment http /) {
124 84 100       793 next TOKEN if $match =~ /^\[?$_/;
125             }
126 17         68 return $formatter->format_link($match);
127             }
128             else {
129 85         189 my $method = "_${formatter}_formatter";
130 85         304 return $self->$method( $match, $pre_match, $post_match );
131             }
132             }
133             }
134             }
135             }
136              
137             sub _simple_tag_handler {
138 40     40   60 my ( $self, $open_tag, $close_tag ) = @_;
139              
140 40 100       65 if ( $self->_is_open($open_tag) ) {
141 20         47 $self->_close_tag($open_tag);
142 20         75 return $close_tag;
143             }
144             else {
145 20         46 $self->_open_tag($open_tag);
146 20         85 return $open_tag;
147             }
148             }
149              
150             sub _is_open {
151 44     44   59 my ( $self, $tag ) = @_;
152 44         48 return grep { $tag eq $_ } @{ $self->{open_tags} };
  24         66  
  44         93  
153             }
154              
155             sub _open_tag {
156 22     22   31 my ( $self, $tag ) = @_;
157 22         26 push @{ $self->{open_tags} }, $tag;
  22         45  
158             }
159              
160             sub _close_tag {
161 22     22   33 my ( $self, $tag ) = @_;
162              
163 22         29 my $index = 0;
164 22         25 for ( @{ $self->{open_tags} } ) {
  22         38  
165 24 100       58 last if $tag eq $_;
166 2         4 $index++;
167             }
168 22         30 splice @{ $self->{open_tags} }, $index;
  22         40  
169             }
170              
171             sub _bolditalic_formatter {
172 4     4   8 my $self = shift;
173              
174 4         6 my $is_open = $self->_is_open('');
175              
176 4         4 my $tmp;
177 4 100       10 if ($is_open) {
178 2         4 $tmp .= '';
179 2         4 $self->_close_tag('');
180             }
181              
182 4         7 $tmp .= $self->_bold_formatter;
183              
184 4 100       9 unless ($is_open) {
185 2         3 $tmp .= '';
186 2         3 $self->_open_tag('');
187             }
188              
189 4         16 return $tmp;
190             }
191              
192             sub _bold_formatter {
193 16     16   20 my $self = shift;
194 16         29 return $self->_simple_tag_handler( '', '' );
195             }
196              
197             sub _italic_formatter {
198 4     4   6 my $self = shift;
199 4         7 return $self->_simple_tag_handler( '', '' );
200             }
201              
202             sub _underline_formatter {
203 4     4   8 my ( $self, $match, $pre_match, $post_match ) = @_;
204 4 50       9 my $class_underline = $self->{context}->{class} ? q{class="underline"} : '';
205 4         12 return $self->_simple_tag_handler( qq{}, '' );
206             }
207              
208             sub _strike_formatter {
209 4     4   7 my ( $self, $match, $pre_match, $post_match ) = @_;
210 4         8 return $self->_simple_tag_handler( '', '' );
211             }
212              
213             sub _superscript_formatter {
214 4     4   9 my ( $self, $match, $pre_match, $post_match ) = @_;
215 4         8 return $self->_simple_tag_handler( '', '' );
216             }
217              
218             sub _subscript_formatter {
219 4     4   9 my ( $self, $match, $pre_match, $post_match ) = @_;
220 4         8 return $self->_simple_tag_handler( '', '' );
221             }
222              
223             sub _inline_formatter {
224 4     4   8 my ( $self, $match, $pre_match, $post_match ) = @_;
225 4         9 return $self->_simple_tag_handler( '', '' );
226             }
227              
228             sub _shref_formatter {
229 20     20   53 my ( $self, $match ) = @_;
230              
231 20         676 my ( $ns, $target ) = (
232             $match =~ m/($link_scheme):
233             (
234             $quoted_string
235             |$shref_target_first
236             (?:
237             $shref_target_middle*
238             $shref_target_last
239             )?
240             )
241             /x
242             );
243 20         95 return $self->_make_link( $ns, $target, $match, $match );
244             }
245              
246             sub _lhref_formatter {
247 20     20   54 my ( $self, $match ) = @_;
248              
249 20         451 my ( $ns, $target, $label ) = (
250             $match =~ m/\[
251             ($link_scheme):
252             (
253             (?:$quoted_string|[^\]\s]*)
254             |(?:$lhref_relative_target|[^\]\s])
255             )
256             (?:
257             \s+
258             ($quoted_string|[^\]]+)
259             )?
260             \]
261             /x
262             );
263 20 100       65 if ( !$label ) { # e.g. `[http://target]` or `[wiki:target]`
264 8 50       17 if ($target) {
265 8 50       19 if ( $target =~ m!^//! ) {
266 0         0 $label = $ns . ':' . $target;
267             }
268             else {
269 8         18 $label = $target;
270             }
271             }
272             else { # e.g. `[search:]`
273 0         0 $label = $ns;
274             }
275             }
276 20         65 return $self->_make_link( $ns, $target, $match, $label );
277             }
278              
279             sub _make_link {
280 40     40   111 my ( $self, $ns, $target, $match, $label ) = @_;
281 40 100 66     280 if ( defined $target && ( $target =~ m!^//! or $target eq 'mailto' ) ) {
      66        
282 5         21 return $self->_make_ext_link( $ns . ':' . $target, $label );
283             }
284             else {
285 35         59 my $handler;
286 35 50       100 if ( defined $ns ) {
287 35         77 $handler = $self->{external_handler}->{$ns};
288             }
289 35 100       157 return $handler ? $handler->format_link( $match, $target, $label ) : $match;
290             }
291             }
292              
293             sub _make_ext_link {
294 5     5   12 my ( $self, $url, $text, $title ) = @_;
295              
296 5 50       12 my $title_attr = $title ? qq{title="$title"} : '';
297              
298 5   33     24 $title ||= $text;
299              
300 5   50     18 my $local = $self->{context}->{local} || '';
301 5 50       12 my $class_link = $self->{context}->{class} ? q{class="ext-link"} : '';
302 5 50       13 my $class_icon = $self->{context}->{class} ? q{class="icon"} : '';
303 5 50       15 my $span = $self->{context}{span} ? qq{} : '';
304 5 50 33     40 if ( $url !~ /^$local/ or !$local ) {
305 5         38 return qq{$span$text};
306             }
307             }
308              
309             sub _macro_formatter {
310 5     5   15 my ( $self, $match ) = @_;
311              
312 5         28 my ( $name, $args ) = ( $match =~ m!\[\[ ([\w/+-]+) (?:\( (.*) \))? \]\]!x );
313              
314 5 100       20 if ( $name =~ /br/i ) {
315 1         6 return '
';
316             }
317             else {
318 4         34 return Text::Trac::Macro->new->parse( $name, $args, $match );
319             }
320             }
321              
322             package Text::Trac::InlineNode::Initializer;
323              
324             1;