File Coverage

blib/lib/Combine/HTMLExtractor.pm
Criterion Covered Total %
statement 27 140 19.2
branch 0 82 0.0
condition 0 17 0.0
subroutine 9 16 56.2
pod 0 5 0.0
total 36 260 13.8


line stmt bran cond sub pod time code
1             package Combine::HTMLExtractor;
2              
3 1     1   8 use strict;
  1         2  
  1         38  
4              
5 1     1   1057 use HTML::TokeParser 2; # use HTML::TokeParser::Simple 2;
  1         5122  
  1         34  
6 1     1   9 use URI 1;
  1         19  
  1         35  
7 1     1   7 use Carp qw( croak );
  1         2  
  1         70  
8              
9 1     1   5 use vars qw( $VERSION );
  1         2  
  1         54  
10             $VERSION = '0.121';
11              
12             ## The html tags which might have URLs
13             # the master list of tagolas and required attributes (to constitute a link)
14 1     1   6 use vars qw( %TAGS );
  1         3  
  1         189  
15             %TAGS = (
16             a => [qw( href )],
17             applet => [qw( archive code codebase src )],
18             area => [qw( href )],
19             base => [qw( href )],
20             bgsound => [qw( src )],
21             blockquote => [qw( cite )],
22             body => [qw( background )],
23             del => [qw( cite )],
24             div => [qw( src )], # IE likes it, but don't know where it's documented
25             embed => [qw( pluginspage pluginurl src )],
26             form => [qw( action )],
27             frame => [qw( src longdesc )],
28             iframe => [qw( src )],
29             ilayer => [qw( background src )],
30             img => [qw( dynsrc longdesc lowsrc src usemap )],
31             input => [qw( dynsrc lowsrc src )],
32             ins => [qw( cite )],
33             isindex => [qw( action )], # real oddball
34             layer => [qw( src )],
35             link => [qw( src href )],
36             object => [qw( archive classid code codebase data usemap )],
37             q => [qw( cite )],
38             script => [qw( src )], # HTML::Tagset has 'for' ~ it's WRONG!
39             sound => [qw( src )],
40             table => [qw( background )],
41             td => [qw( background )],
42             th => [qw( background )],
43             tr => [qw( background )],
44             ## the exotic cases
45             meta => undef,
46             '!doctype' => [qw( url )], # is really a process instruction
47             );
48              
49             ## tags which contain <.*?> STUFF TO GET
50 1     1   6 use vars qw( @TAGS_IN_NEED );
  1         3  
  1         64  
51             @TAGS_IN_NEED = qw(
52             a
53             blockquote
54             del
55             ins
56             q
57             );
58              
59 1     1   6 use vars qw( @VALID_URL_ATTRIBUTES );
  1         2  
  1         58  
60             @VALID_URL_ATTRIBUTES = qw(
61             action
62             archive
63             background
64             cite
65             classid
66             code
67             codebase
68             data
69             dynsrc
70             href
71             longdesc
72             lowsrc
73             pluginspage
74             pluginurl
75             src
76             usemap
77             );
78              
79 1     1   5 use vars qw( %SECTIONTAGS );
  1         3  
  1         1811  
80             %SECTIONTAGS = (
81             'div' => 1,
82             'p' => 1,
83             'table' => 1,
84             'ol' => 1,
85             'ul' => 1,
86             'dir' => 1,
87             'menu' => 1,
88             'h1' => 1,
89             'h2' => 1,
90             'h3' => 1,
91             'h4' => 1,
92             'h5' => 1,
93             'h6' => 1
94             );
95              
96             sub new {
97 0     0 0   my($class, $cb, $base, $strip) = @_;
98 0           my $self = bless {}, $class;
99              
100              
101 0 0         $self->{_cb} = $cb if defined $cb;
102 0 0         $self->{_base} = URI->new($base) if defined $base;
103 0   0       $self->strip( $strip || 0 );
104              
105 0           return $self;
106             }
107              
108             sub strip {
109 0     0 0   my( $self, $on ) = @_;
110 0 0         return $self->{_strip} unless defined $on;
111 0 0         return $self->{_strip} = $on ? 1 : 0;
112             }
113              
114             ## $p= HTML::TokeParser::Simple->new($filename || FILEHANDLE ||\$filecontents);
115              
116             sub parse {
117 0     0 0   my( $this, $hmmm ) = @_;
118 0           my $tp = new HTML::TokeParser( $hmmm );# my $tp = new HTML::TokeParser::Simple( $hmmm );
119              
120 0 0         unless($tp) {
121 0           croak qq[ Couldn't create a HTML::TokeParser object: $!];# croak qq[ Couldn't create a HTML::TokeParser::Simple object: $!];
122             }
123              
124 0           $this->{_tp} = $tp;
125              
126 0           $this->_parsola();
127 0           return();
128             }
129              
130             sub _parsola {
131 0     0     my $self = shift;
132              
133             ## a stack of links for keeping track of TEXT
134             ## which is all of "text"
135 0           my @TEXT = ();
136 0           $self->{_LINKS} = [];
137 0           my $tottext=''; #All visible text
138 0           my $inHeading=0; my $headtext=''; #All headings
  0            
139             # ["S", $tag, $attr, $attrseq, $text]
140             # ["E", $tag, $text]
141             # ["T", $text, $is_data]
142             # ["C", $text]
143             # ["D", $text]
144             # ["PI", $token0, $text]
145              
146 0           while (my $T = $self->{_tp}->get_token() ) {
147 0           my $NL; #NewLink
148 0           my $Tag = $T->[1]; # my $Tag = $T->return_tag;
149 0           my $got_TAGS_IN_NEED=0;
150             # Adump($T); #Debug
151              
152             ## Start tag?
153 0 0         if( $T->[0] eq 'S' ) { # if($T->is_start_tag) {
    0          
    0          
    0          
154 0 0         if ( $Tag =~ /^h\d$/ ) { $inHeading=1; }
  0            
155 0 0         if (exists $SECTIONTAGS{$Tag}) { $tottext .= "\n\n";}
  0            
156 0 0         next unless exists $TAGS{$Tag};
157              
158             ## Do we have a tag for which we want to capture text?
159 0           $got_TAGS_IN_NEED = 0;
160 0           $got_TAGS_IN_NEED = grep { /^\Q$Tag\E$/i } @TAGS_IN_NEED;
  0            
161              
162             ## then check to see if we got things besides META :)
163 0 0         if(defined $TAGS{ $Tag }) {
    0          
164              
165 0           for my $Btag(@{$TAGS{$Tag}}) {
  0            
166             ## and we check if they do have one with a value
167 0 0         if(exists $T->[2]->{ $Btag }) { # if(exists $T->return_attr()->{ $Btag }) {
168              
169 0           $NL = $T->[2]; #Save all attributes incl ALT in IMG # $NL = $T->return_attr();
170             ## TAGS_IN_NEED are tags in deed (start capturing the STUFF)
171 0 0         if($got_TAGS_IN_NEED) {
172 0           push @TEXT, $NL;
173 0           $NL->{_TEXT} = "";
174             }
175             }
176             }
177 0 0         if ($Tag eq 'img') {
178             #extract ALT-text
179 0 0         if (exists $T->[2]->{alt}) {
180 0           $tottext .= '[' . $T->[2]->{alt} . '] ';
181             } ##else { $tottext .= '[IMG]'; }
182             }
183             }elsif($Tag eq 'meta') {
184 0           $NL = $T->[2]; # $NL = $T->return_attr();
185              
186 0 0 0       if(defined $$NL{content} and length $$NL{content} and (
      0        
      0        
187             defined $$NL{'http-equiv'} && $$NL{'http-equiv'} =~ /refresh/i
188             or
189             defined $$NL{'name'} && $$NL{'name'} =~ /refresh/i
190             ) ) {
191              
192 0           my( $timeout, $url ) = split m{;\s*?URL=}, $$NL{content},2;
193 0           my $base = $self->{_base};
194 0 0         $$NL{url} = URI->new_abs( $url, $base ) if $base;
195 0 0         $$NL{url} = $url unless exists $$NL{url};
196 0 0         $$NL{timeout} = $timeout if $timeout;
197             }
198             }
199              
200             ## In case we got nested tags
201 0 0         if(@TEXT) {
202 0           $TEXT[-1]->{_TEXT} .= $T->[-1]; # $TEXT[-1]->{_TEXT} .= $T->as_is;
203             # my $t=$T->[-1]; print " Nested: $t\n"; #debug
204             }
205              
206             ## Text?
207             }elsif($T->[0] eq 'T') { # }elsif($T->is_text) {
208 0 0         $TEXT[-1]->{_TEXT} .= $T->[-2] if @TEXT; # $TEXT[-1]->{_TEXT} .= $T->as_is if @TEXT;
209 0           $tottext .= $T->[-2] . ' '; # $tottext .= $T->as_is;
210 0 0         if ( $inHeading ) { $headtext .= $T->[-2]; } # if ( $h ne '' ) { $headtext .= $T->as_is . '; '; }
  0            
211             ## Declaration?
212             }elsif($T->[0] eq 'D') { # }elsif($T->is_declaration) {
213             ## We look at declarations, to get anly custom .dtd's (tis linky)
214 0           my $text = $T->[-1]; # my $text = $T->as_is;
215 0 0         if( $text =~ m{ SYSTEM \s \" ( [^\"]* ) \" > $ }ix ) {
216 0           $NL = { raw => $text, url => $1, tag => '!doctype' };
217             }
218             ## End tag?
219             }elsif($T->[0] eq 'E'){ # }elsif($T->is_end_tag){
220 0 0         if ( $Tag =~ /^h\d$/ ) { $inHeading=0; $headtext .= '; '; }
  0            
  0            
221 0 0         if (exists $SECTIONTAGS{$Tag}) { $tottext .= "\n\n";}
  0            
222             ## these be ignored (maybe not in between tags
223             ## unless we're stacking (bug #5723)
224 0 0 0       if(@TEXT and exists $TAGS{$Tag}) {
225 0           $TEXT[-1]->{_TEXT} .= $T->[-1]; # $TEXT[-1]->{_TEXT} .= $T->as_is;
226 0           my $pop = pop @TEXT;
227 0 0         $TEXT[-1]->{_TEXT} .= $pop->{_TEXT} if @TEXT;
228 0 0         $pop->{_TEXT} = _stripHTML( \$pop->{_TEXT} ) if $self->strip;
229             # my $t = $pop->{_TEXT}; print " I endtag stripHTML: $t\n";
230 0 0         $self->{_cb}->($self, $pop) if exists $self->{_cb};
231             }
232             }
233              
234 0 0         if(defined $NL) {
235 0           $$NL{tag} = $Tag;
236              
237 0           my $base = $self->{_base};
238              
239 0           for my $at( @VALID_URL_ATTRIBUTES ) {
240 0 0         if( exists $$NL{$at} ) {
241 0 0         $$NL{$at} = URI->new_abs( $$NL{$at}, $base) if $base;
242             }
243             }
244              
245 0 0         if(exists $self->{_cb}) {
246 0 0 0       $self->{_cb}->($self, $NL ) if not $got_TAGS_IN_NEED or not @TEXT; #bug#5470
247             } else {
248 0           push @{$self->{_LINKS}}, $NL;
  0            
249             # my $t=$$NL{_TEXT}.';'.$$NL{tag}; print " PushNL: $t\n";
250             # foreach $t (keys(%{$NL})) { print " K=$t; V=$$NL{$t}\n"; }
251             }
252             }
253             }## endof while (my $token = $p->get_token)
254              
255 0           undef $self->{_tp};
256             # $headtext =~ s/; $//;
257 0           my $NL = { tag=>'headings', _TEXT => $headtext };
258 0           push @{$self->{_LINKS}}, $NL;
  0            
259 0           $tottext=~ s/\s*\n\s*\n[\s\n]+/\n\n/g;
260 0           $tottext=~ s/[\x20\t]+/ /g;
261 0           $NL = { tag=>'text', _TEXT => $tottext };
262 0           push @{$self->{_LINKS}}, $NL;
  0            
263 0           return();
264             }
265              
266             sub links {
267 0     0 0   my $self = shift;
268             ## just like HTML::LinkExtor's
269 0           return $self->{_LINKS};
270             }
271              
272              
273             sub _stripHTML {
274 0     0     my $HtmlRef = shift;
275 0           my $tp = new HTML::TokeParser( $HtmlRef ); # my $tp = new HTML::TokeParser::Simple( $HtmlRef );
276 0           my $t = $tp->get_token(); # MUST BE A START TAG (@TAGS_IN_NEED)
277             # otherwise it ain't come from LinkExtractor
278 0 0         if($t->[0] eq 'S') { # if($t->is_start_tag) {
279 0           return $tp->get_trimmed_text( '/'.$t->[1] ); # return $tp->get_trimmed_text( '/'.$t->return_tag );
280             } else {
281 0           require Data::Dumper;
282 0           local $Data::Dumper::Indent=1;
283 0           die " IMPOSSIBLE!!!! ",
284             Data::Dumper::Dumper(
285             '$HtmlRef',$HtmlRef,
286             '$t', $t,
287             );
288             }
289             }
290              
291             sub Adump {
292 0     0 0   my ($a) = @_;
293 0           my @aref = @{$a};
  0            
294 0           print 'Dump: ', $aref[0], ',', $aref[1];
295 0 0         if ($aref[0] eq 'S') { print ',', $aref[4]; }
  0 0          
    0          
296 0           elsif ($aref[0] eq 'E') { print ',', $aref[2]; }
297             # elsif (($aref[0] eq 'T') && $aref[2]) { print ',TRUE'; }
298             # elsif (($aref[0] eq 'T') && !$aref[2]) { print ',FALSE'; }
299             # elsif ($aref[0] eq 'C') { }
300             # elsif ($aref[0] eq 'D') { }
301 0           elsif ($aref[0] eq 'PI') { print ',', $aref[2]; }
302 0           print "\n";
303 0           return;
304             }
305              
306             1;
307              
308             __END__