File Coverage

blib/lib/Text/TWikiFormat/SAX.pm
Criterion Covered Total %
statement 271 294 92.1
branch 117 126 92.8
condition 83 89 93.2
subroutine 27 34 79.4
pod 0 1 0.0
total 498 544 91.5


line stmt bran cond sub pod time code
1             package Text::TWikiFormat::SAX;
2 1     1   5474 use base 'XML::SAX::Base';
  1         4  
  1         125  
3              
4             $VERSION = '0.03';
5              
6 1     1   5 use strict;
  1         3  
  1         42  
7 1     1   874 use XML::SAX::DocumentLocator;
  1         610  
  1         346  
8              
9             sub new {
10 2     2 0 2405 my ($class, %params) = @_;
11 2         16 my $self = $class->SUPER::new(%params);
12              
13 2         99 $self->{_onlink} = $params{onlink};
14 2         16 return $self;
15             }
16              
17             sub _parse_bytestream {
18 0     0   0 my ($self, $fh) = @_;
19 0         0 my $parser = TWiki::SAX::Parser->new($self->{_onlink});
20 0         0 $parser->set_parent($self);
21 0         0 local $/;
22 0         0 my $text = <$fh>;
23 0         0 $parser->parse($text);
24             }
25              
26             sub _parse_characterstream {
27 0     0   0 my ($self, $fh) = @_;
28 0         0 die "parse_characterstream not supported";
29             }
30              
31             sub _parse_string {
32 112     112   106433 my ($self, $str) = @_;
33 112         457 my $parser = TWiki::SAX::Parser->new($self->{_onlink});
34 112         321 $parser->set_parent($self);
35 112         247 $parser->parse($str);
36             }
37              
38             sub _parse_systemid {
39 0     0   0 my ($self, $sysid) = @_;
40 0         0 my $parser = TWiki::SAX::Parser->new($self->{_onlink});
41 0         0 $parser->set_parent($self);
42 0 0       0 open(FILE, $sysid) || die "Can't open $sysid: $!";
43 0         0 local $/;
44 0         0 my $text = ;
45 0         0 $parser->parse($text);
46             }
47              
48              
49             package TWiki::SAX::Parser;
50 1     1   7 use XML::SAX::Writer;
  1         2  
  1         36  
51 1     1   3063 use HTML::Parser;
  1         20408  
  1         71  
52 1     1   12 use strict;
  1         2  
  1         45  
53 1     1   5 use vars qw(@ENDING_WITH_EOL @AUTO_CLOSED $p $s $e $f $LAST_HTML_TAG);
  1         2  
  1         6740  
54              
55             @ENDING_WITH_EOL = qw(h1 h2 h3 h4 h5 h6 li);
56             @AUTO_CLOSED = qw(nop br hr);
57              
58             $b = qr/.*?(?:\n|\A)/s; # beginning of line
59              
60             $p = qr/.*?[ \(]|\A/s; # prefix, wikitags start with,
61             $f = qr/[\s\,\.\;\:\!\?\)]|\Z/; # finalizer, wikitags end with
62              
63             $s = qr/[#\[\%\<\&\?A-Za-z0-9]/; # start, words start with
64             $e = qr/.*?[A-Za-z0-9\:\]\%\>\;\?]/s; # end, words end with
65              
66             sub new {
67 112     112   211 my ($class, $onlink) = @_;
68 112         872 my $self = bless { _onlink => $onlink }, $class;
69 112         886 $self->{html_parser} = HTML::Parser->new(
70             api_version => 3,
71             start_h => [\&_html_tag, "self, tagname, attr, text"],
72             end_h => [\&_html_tag, "self, tagname, text"],
73             marked_sections => 1,
74             );
75 112         7545 return $self;
76             }
77              
78             sub _html_tag {
79 21     21   29 my $parser = shift;
80 21         55 $LAST_HTML_TAG = [@_];
81 21         162 $parser->eof();
82             }
83              
84             sub set_parent {
85 112     112   148 my $self = shift;
86 112         253 $self->{parent} = shift;
87             }
88              
89             sub parent {
90 1909     1909   3332 my $self = shift;
91 1909         8930 return $self->{parent};
92             }
93              
94             sub parse {
95 112     112   139 my $self = shift;
96              
97 112         245 my $sysid = $self->parent->{ParserOptions}->{Source}{SystemId};
98             $self->parent->set_document_locator(
99             XML::SAX::DocumentLocator->new(
100 0     0   0 sub { "" },
101 0     0   0 sub { $sysid },
102 0     0   0 sub { $self->{line_number} },
103 0     0   0 sub { 0 },
104 112         264 ),
105             );
106 112         5104 $self->parent->start_document({});
107 112         35430 $self->parent->start_element(_element('wiki'));
108              
109 112         7689 $self->parse_wiki(shift);
110              
111 112         258 $self->parent->end_element(_element('wiki', 1));
112 112         7373 $self->parent->end_document({});
113             }
114              
115             sub _open_element {
116 372     372   563 my($self, $element) = @_;
117 372 100       789 $self->parent->start_element(UNIVERSAL::isa($element, 'HASH') ? $element : _element($element));
118 372 100       32213 push @{ $self->{stack} }, UNIVERSAL::isa($element, 'HASH') ? $element->{Name} : $element;
  372         3841  
119             }
120              
121             sub _close_element {
122 427     427   702 my($self, $element) = @_;
123              
124 427 100       908 if (!$element) {
125 94         101 my $exists;
126 94         200 foreach my $ewe (@ENDING_WITH_EOL) { $exists += grep { $_ eq $ewe } @{ $self->{stack} } }
  658         672  
  861         2484  
  658         1227  
127 94 100       408 return unless $exists;
128             }
129              
130 383         419 while(@{ $self->{stack} }) {
  417         1208  
131 299         426 my $s_element = pop @{ $self->{stack} };
  299         877  
132 299         703 $self->parent->end_element(_element($s_element), 1);
133              
134 299 100       29777 if ($element) {
  350 50       668  
135 249 100       1001 return 1 if ($s_element eq $element);
136             } elsif (grep {$s_element eq $_} @ENDING_WITH_EOL) {
137 50         188 return 1;
138             }
139             }
140             }
141              
142             sub _open_list {
143 76     76   126 my($self, $ident, $type) = @_;
144 76         170 my $element = _get_list_element($type);
145              
146 76   100     341 my $prev_ident = $self->{list}->[-1]->[0] || 0;
147 76   100     289 my $prev_element = $self->{list}->[-1]->[1] || '';
148              
149 76 100       209 if ($ident == $prev_ident) {
    100          
    50          
150 24 100       74 if ($element ne $prev_element) {
151 7 50       19 if ($prev_element) {
152 7         18 $self->_close_element($prev_element);
153 7         8 pop @{ $self->{list} };
  7         16  
154             }
155 7         20 $self->_open_element($element);
156 7         10 push @{ $self->{list} }, [$ident, $element];
  7         26  
157             }
158             }
159             # opening new <*l>
160             elsif ($ident > $prev_ident) {
161 47         105 $self->_open_element($element);
162 47         78 push @{ $self->{list} }, [$ident, $element];
  47         180  
163             }
164             #
165             elsif ($ident < $prev_ident) {
166 5         15 while ($ident < $prev_ident) {
167 5         13 $self->_close_element($prev_element);
168 5         6 pop @{ $self->{list} };
  5         11  
169 5         15 $self->_open_list($ident, _get_list_type($element));
170 5   50     17 $prev_ident = $self->{list}->[-1]->[0] || 0;
171 5   50     23 $prev_element = $self->{list}->[-1]->[1] || '';
172             } ;
173             }
174             }
175              
176             sub _close_list {
177 31     31   43 my $self = shift;
178              
179             # getting first occurence of 'ul', 'ol'
180 31         41 my $pos = 0;
181 31         40 foreach (0..@{$self->{stack}}) {
  31         90  
182 32 100 66     281 if ($self->{stack}->[$_] && $self->{stack}->[$_] =~ /^[ou]l$/) {
183 31         39 $pos = $_;
184 31         93 last ;
185             }
186             }
187              
188 31         46 my $result;
189 31         39 while(@{ $self->{stack} } > $pos) {
  104         300  
190 73         88 my $s_element = pop @{ $self->{stack} };
  73         137  
191 73 100       290 pop @{ $self->{list} } if ($s_element eq $self->{list}->[-1]->[1]);
  42         80  
192 73         188 $self->parent->end_element(_element($s_element), 1);
193 73         6018 $result++;
194             }
195 31         48 return $result;
196             }
197              
198             sub _get_list_element {
199 76     76   106 my ($type) = @_;
200 76 100       234 return ('ul') if $type eq '*';
201 33 50       225 return ('ol') if $type =~ /^\w+$/;
202 0         0 die sprintf "unknow list element : \'%s\'", $type;
203             }
204              
205             sub _get_list_type {
206 5     5   8 my ($element) = @_;
207 5 100       14 return ('*') if $element eq 'ul';
208 4 50       25 return ('1') if $element eq 'ol';
209 0         0 die sprintf "unknow list element : \'%s\'", $element;
210             }
211              
212             sub _handle_found {
213 159     159   528 my ($self, $pre, $post, $element, $type) = @_;
214              
215 159         347 $self->format_text($pre);
216 159 100       1161 my @elements = (UNIVERSAL::isa($element, 'ARRAY')) ? @$element : ($element);
217 159         313 foreach (@elements) {
218 169 100       350 if ($type eq 'open') {
219 97         212 $self->_open_element($_);
220             } else {
221 72         196 $self->_close_element($_);
222             }
223             }
224 159         385 $self->format_text($post);
225             }
226              
227             sub parse_wiki {
228 112     112   159 my $self = shift;
229              
230 112         225 $self->{stack} = [];
231 112         300 $self->{list} = [[]];
232 112         210 $self->{in_table} = 0;
233 112         178 $self->{'in_tr'} = 0;
234 112         361 $self->{in_td} = 0;
235 112         187 $self->{parse_wiki} = 1;
236 112         174 $self->{parse_html} = 1;
237              
238 112         193 my ($text) = @_;
239 112         208 $text =~ s/\r//g; # Remove \r
240 112         269 $text =~ s/\\\n//g; # Join lines ending in "\"
241              
242 112         435 $self->format_text($text);
243 112         339 $self->_close_element('__default');
244             }
245              
246             sub format_text {
247 1388     1388   2416 my($self, $text) = @_;
248              
249 1388 100       8600 if ($text) {
250             #
251 779 100 100     74155 if ($text =~ s/(.*?)//s) {
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100          
    100          
    100          
    50          
252 14         45 $self->format_text($1);
253 14         39 $self->_open_element('pre');
254 14         26 $self->{parse_wiki} = 0;
255 14         28 $self->{parse_html} = 0;
256 14         29 $self->format_text($text);
257             }
258             #
 
259             elsif ($self->{parse_html} && $text =~ s/(.*?)
//s) { 
260 5         15 $self->format_text($1);
261 5         18 $self->_open_element('pre');
262 5         9 $self->{parse_wiki} = 0;
263 5         18 $self->format_text($text);
264             }
265             # horizontal line
266             elsif ($self->{parse_wiki} && $text =~ s/($b)-{3,}(\s)/$2/s) {
267 3         9 $self->format_text($1);
268 3         10 $self->parent->start_element(_element('hr'));
269 3         166 $self->parent->end_element(_element('hr'), 1);
270 3         149 $self->format_text($text);
271             }
272              
273             # openening tags
274             #
  • 275             elsif ($self->{parse_wiki} && $text =~ s/($b)(\t+| {3,})(\*|\w)[\.\) ]+([^\n]+)//s) {
    276 71         395 my($f1, $f2, $f3, $f4, $f5) = ($1,$2,$3,$4,$5);
    277 71         178 $self->format_text($f1);
    278 71         280 $self->_open_list(length($f2), $f3);
    279 71         172 $self->_open_element('li');
    280 71         164 $self->format_text($f4);
    281              
    282 71 100       318 if ($text !~ /^\n(\t+| {3,})(\*|\w+)[\.\) ]/) {
    283 31         96 $self->_close_list();
    284 31         84 $text =~ s/^\n//;
    285             }
    286              
    287 71         162 $self->format_text($text);
    288             }
    289             # table handling
    290             elsif ($self->{parse_wiki} && $text =~ s/($b)\|([^\n\|]+)(\|+)/\|/s) {
    291 61         192 my($cell, $finalizer) = ($2,$3);
    292 61         146 $self->format_text($1);
    293              
    294 61 100       157 unless ($self->{in_table}) {
    295 19         44 my $el = _element('table');
    296 19         53 $self->_open_element($el);
    297 19         68 $self->{in_table} = 1;
    298             }
    299              
    300 61 100       166 unless ($self->{'in_tr'}) {
    301 26         70 $self->_open_element('tr');
    302 26         57 $self->{'in_tr'} = 1;
    303             }
    304              
    305 61         103 my $el = _element('td');
    306 61 100       174 _add_attrib($el, 'colspan', length($finalizer)) if (length($finalizer) > 1);
    307              
    308             # aligning text inside cell
    309 61         289 $cell =~ /^(\s*).*?(\s*)$/;
    310 61   100     252 my $l1 = length( $1 || '' );
    311 61   100     221 my $l2 = length( $2 || '' );
    312 61 100       121 if( $l1 >= 2 ) {
    313 5 100       15 if( $l2 <= 1 ) {
    314 1         3 _add_attrib($el, 'align', 'right');
    315             } else {
    316 4         11 _add_attrib($el, 'align', 'center');
    317             }
    318             }
    319 61         141 $self->_open_element($el);
    320 61         300 $self->format_text($cell);
    321 61         150 $self->_close_element('td');
    322 61 100 66     377 if ($self->{'in_tr'} && $text =~ s/^\|\n//) {
    323 14         31 $self->{'in_tr'} = 0;
    324 14         28 $self->_close_element('tr');
    325 14 100 66     105 if ($self->{'in_table'} && $text !~ /^\|/) {
    326 7         15 $self->{'in_table'} = 0;
    327 7         17 $self->_close_element('table');
    328             }
    329             }
    330 61         144 $self->format_text($text);
    331             }
    332             # openening tags
    333             #

    ..

    334             # handles pre, post
    335             elsif ($self->{parse_wiki} && $text =~ s/($b)---(\+{1,6})\s*//s) {
    336 14         78 $self->_handle_found($1, $text, 'h'.length($2), 'open');
    337             }
    338             #
    339             elsif ($self->{parse_wiki} && $text =~ s/($p)\*($s)/$2/s) {
    340 42         243 $self->_handle_found($1, $text, 'strong', 'open');
    341             }
    342             #
    343             elsif ($self->{parse_wiki} && $text =~ s/($p)\_($s)/$2/s) {
    344 26         81 $self->_handle_found($1, $text, 'em', 'open');
    345             }
    346             #
    347             elsif ($self->{parse_wiki} && $text =~ s/($p)\_\_($s)/$2/s) {
    348 4         21 $self->_handle_found($1, $text, ['strong', 'em'], 'open');
    349             }
    350             #
    351             elsif ($self->{parse_wiki} && $text =~ s/($p)\=($s)/$2/s) {
    352 5         16 $self->_handle_found($1, $text, 'code', 'open');
    353             }
    354             #
    355             elsif ($self->{parse_wiki} && $text =~ s/($p)\=\=($s)/$2/s) {
    356 1         6 $self->_handle_found($1, $text, ['strong', 'code'], 'open');
    357             }
    358             #
    359             elsif ($self->{parse_wiki} && $text =~ s/(.*)\[\[([^\]]+)\](?:\[([\w\t \-]+)\])?\]//s) {
    360 12         49 my ($link, $label) = ($2,$3);
    361 12         36 $self->format_text($1);
    362 12   66     42 $label ||= $link;
    363 12         39 $label =~ s/([^\/])\/[^\/].*$/$1/;
    364 12 100       45 ($link, $label) = $self->{_onlink}->($link, $label) if $self->{_onlink};
    365 12         885 my $el = _element('a');
    366 12         45 _add_attrib($el, 'href', $link);
    367 12         44 $self->_open_element($el);
    368 12         36 $self->parent->characters({Data => $label});
    369 12         165 $self->_close_element('a');
    370 12         33 $self->format_text($text);
    371             }
    372             elsif ($self->{parse_html} && $text =~ s/^([^<]*)(<[^\/])/$2/) {
    373 13         40 $self->format_text($1);
    374 13         190 $self->{html_parser}->parse($text);
    375              
    376 13         34 my $tag = $LAST_HTML_TAG->[0];
    377 13         26 my $el = _element($tag);
    378 13         22 foreach my $attrib (keys %{ $LAST_HTML_TAG->[1] }) {
      13         45  
    379 9         34 _add_attrib($el, $attrib, $LAST_HTML_TAG->[1]->{$attrib});
    380             }
    381 13         71 $self->_open_element($el);
    382 13 100       200 $self->_close_element($tag) if (grep $_ eq $tag, @AUTO_CLOSED);
    383              
    384 13         34 my $tag_text = quotemeta($LAST_HTML_TAG->[2]);
    385 13         283 $text =~ s/^.*?$tag_text\n*//;
    386 13         41 $self->format_text($text);
    387             }
    388              
    389             # closing tags
    390             #
    391             elsif ($text =~ s/(.*?)<\/verbatim>//s) {
    392 14         43 $self->format_text($1);
    393 14         39 $self->_close_element('pre');
    394 14         28 $self->{parse_wiki} = 1;
    395 14         22 $self->{parse_html} = 1;
    396 14         35 $self->format_text($text);
    397             }
    398             #
    399             elsif ($self->{parse_html} && $text =~ s/(.*?)<\/pre>//s) {
    400 5         14 $self->format_text($1);
    401 5         14 $self->_close_element('pre');
    402 5         11 $self->{parse_wiki} = 1;
    403 5         14 $self->format_text($text);
    404             }
    405             # table
    406             elsif ($self->{parse_wiki} && $text =~ s/\|(\n|\Z)//s) {
    407 13 50       42 if ($self->{in_td}) {
    408 0         0 $self->_close_element('td');
    409 0         0 $self->{in_td} = 0;
    410             }
    411 13 100       224 if ($self->{'in_tr'}) {
    412 12         28 $self->_close_element('tr');
    413 12         23 $self->{'in_tr'} = 0;
    414             }
    415 13         26 $self->format_text($text);
    416             }
    417             #
    418             elsif ($self->{parse_wiki} && $text =~ s/($e)\*($f)/$2/s) {
    419 41         150 $self->_handle_found($1, $text, 'strong', 'close');
    420             }
    421             #
    422             elsif ($self->{parse_wiki} && $text =~ s/($e)\_($f)/$2/s) {
    423 16         50 $self->_handle_found($1, $text, 'em', 'close');
    424             }
    425             #
    426             elsif ($self->{parse_wiki} && $text =~ s/($e)\_\_($f)/$2/s) {
    427 4         18 $self->_handle_found($1, $text, ['em', 'strong'], 'close');
    428             }
    429             #
    430             elsif ($self->{parse_wiki} && $text =~ s/($e)\=($f)/$2/s) {
    431 5         15 $self->_handle_found($1, $text, 'code', 'close');
    432             }
    433             #
    434             elsif ($self->{parse_wiki} && $text =~ s/($e)\=\=([\s\,\.\;\:\!\?\)]|\Z)/$2/s) {
    435 1         5 $self->_handle_found($1, $text, ['code', 'strong'], 'close');
    436             }
    437             # other html
    438             elsif ($self->{parse_html} && $text =~ s/^([^<]*)(<\/)/$2/) {
    439 8         33 $self->format_text($1);
    440 8         58 $self->{html_parser}->parse($text);
    441 8         14 my ($tag, $tag_text) = @{ $LAST_HTML_TAG };
      8         19  
    442 8         21 $self->_close_element($tag);
    443 8         158 $text =~ s/^.*?$tag_text\n*//;
    444 8         26 $self->format_text($text);
    445             }
    446              
    447             # default text handling
    448             elsif ($text =~ s/^([^\n]+)//) {
    449 307 100       1140 my $t = $self->{parse_html} ? $self->deescape($1) : $1;
    450 307         728 $self->parent->characters({Data => $t});
    451 307         4540 $self->format_text($text);
    452             }
    453             elsif ($text =~ s/^\n//) {
    454 94   100     276 my $closed += $self->_close_element() || 0;
    455 94 100 100     509 if ($self->{parse_wiki} && !$closed) {
        100          
    456 29         72 $self->parent->start_element(_element('br'));
    457 29         2755 $self->parent->end_element(_element('br'), 1);
    458             }
    459             elsif (!$self->{parse_wiki}) {
    460 15         37 $self->parent->characters({Data => "\n"});
    461             }
    462 94         2026 $self->format_text($text);
    463             }
    464             }
    465             }
    466              
    467             sub setDeEscaperRegex {
    468 95     95   149 my $self = shift;
    469 95         195 my $writer = $self->parent->{Handler}->{Handler};
    470 95         316 my %escape = reverse %{ $writer->{Escape} };
      95         1477  
    471              
    472 475         14720 $self->{DeEscaperRegex} = eval 'qr/' .
    473 95         347 join( '|', map { $_ = "\Q$_\E" } keys %escape) .
    474             '/;';
    475 95         676 $self->{DeEscape} = \%escape;
    476 95         196 return $self;
    477             }
    478              
    479             sub deescape {
    480 293     293   374 my $self = shift;
    481 293         578 my $str = shift;
    482 293 100       1798 $self->setDeEscaperRegex unless defined $self->{DeEscaperRegex};
    483              
    484 293         547 $str =~ s/($self->{DeEscaperRegex})/$self->{DeEscape}->{$1}/oge;
      7         37  
    485 293         908 return $str;
    486             }
    487              
    488             sub _element {
    489 1032     1032   1703 my ($name, $end) = @_;
    490             return {
    491 1032 100       8247 Name => $name,
    492             LocalName => $name,
    493             $end ? () : (Attributes => {}),
    494             NamespaceURI => '',
    495             Prefix => '',
    496             };
    497             }
    498              
    499             sub _add_attrib {
    500 29     29   63 my ($el, $name, $value) = @_;
    501              
    502 29         295 $el->{Attributes}{"{}$name"} =
    503             {
    504             Name => $name,
    505             LocalName => $name,
    506             Prefix => "",
    507             NamespaceURI => "",
    508             Value => $value,
    509             };
    510             }
    511              
    512             1;
    513             __END__