File Coverage

blib/lib/HTML/ListScraper.pm
Criterion Covered Total %
statement 139 174 79.8
branch 21 44 47.7
condition 4 6 66.6
subroutine 21 26 80.7
pod 12 12 100.0
total 197 262 75.1


line stmt bran cond sub pod time code
1             package HTML::ListScraper;
2              
3 4     4   89256 use warnings;
  4         10  
  4         131  
4 4     4   20 use strict;
  4         7  
  4         90  
5              
6 4     4   3786 use HTML::Parser;
  4         27210  
  4         163  
7 4     4   5167 use Class::Generate qw(class);
  4         126139  
  4         433  
8              
9 4     4   2601 use HTML::ListScraper::Vat;
  4         11  
  4         125  
10 4     4   2320 use HTML::ListScraper::Book;
  4         13  
  4         130  
11 4     4   2370 use HTML::ListScraper::Sweep;
  4         12  
  4         141  
12              
13 4     4   30 use vars qw(@ISA);
  4         6  
  4         8869  
14              
15             class 'HTML::ListScraper::Sequence' => {
16             len => { type => '$', required => 1, readonly => 1 },
17             instances => { type => '@', required => 1 }
18             };
19              
20             class 'HTML::ListScraper::Instance' => {
21             start => { type => '$', required => 1, readonly => 1 },
22             match => { type => '$', required => 1, readonly => 1 },
23             score => { type => '$', required => 0, readonly => 1 },
24             tags => { type => '@', required => 1 }
25             };
26              
27             @ISA = qw(HTML::Parser);
28              
29             our $VERSION = '0.08';
30              
31             sub new {
32 5     5 1 9095 my $class = shift;
33              
34 5         49 my $self = $class->SUPER::new(@_);
35 5         286 bless $self, $class;
36              
37 5         46 $self->{book} = HTML::ListScraper::Book->new();
38 5         15 $self->{min_count} = 2;
39              
40 5         63 $self->handler('start' => 'on_start', 'self, tagname, attr');
41 5         27 $self->handler('text' => 'on_text', 'self, dtext');
42 5         22 $self->handler('end' => 'on_end', 'self, tagname');
43              
44 5         17 return $self;
45             }
46              
47             sub min_count {
48 0     0 1 0 my $self = shift;
49              
50 0 0       0 if (@_) {
51 0 0       0 if ($_[0] < 2) {
52 0         0 die "minimal sequence count must be at least 2";
53             }
54              
55 0         0 $self->{min_count} = $_[0];
56             }
57              
58 0         0 return $self->{min_count};
59             }
60              
61             sub shapeless {
62 0     0 1 0 my $self = shift;
63              
64 0 0       0 if (@_) {
65 0         0 $self->{book}->shapeless($_[0]);
66             }
67              
68 0         0 return $self->{book}->shapeless;
69             }
70              
71             sub is_unclosed_tag {
72 0     0 1 0 my ($self, $name) = @_;
73              
74 0         0 return $self->{book}->is_unclosed_tag($name);
75             }
76              
77             sub get_all_tags {
78 0     0 1 0 my $self = shift;
79              
80 0         0 return $self->{book}->get_all_tags();
81             }
82              
83             sub get_sequences {
84 5     5 1 85 my $self = shift;
85              
86 5         12 my @sequences;
87 5         59 my $vat = HTML::ListScraper::Vat->new($self->{book}, $self->{min_count});
88 5         24 my $foam = $vat->create_sequence;
89 5 50       21 if ($foam) {
90 5         23 foreach my $handle ($foam->get_sequences) {
91 5         22 my $occ = $foam->get_occurence($handle);
92 5         29 push @sequences, $self->_make_sequence($occ);
93             }
94             }
95              
96             return sort {
97 5         247 $a->len <=> $b->len;
  0         0  
98             } @sequences;
99             }
100              
101             sub find_sequences {
102 3     3 1 6400 my $self = shift;
103              
104 3         7 my @sequences;
105 3         18 my $vat = HTML::ListScraper::Vat->new($self->{book}, $self->{min_count});
106 3         11 my $foam = $vat->create_sequence;
107 3 50       14 if ($foam) {
108 3         13 foreach my $sign ($foam->get_sequences) {
109 3         13 my $occ = $foam->get_occurence($sign);
110 3         17 push @sequences, $self->_make_approx_seq($sign, $occ);
111             }
112             }
113              
114             return sort {
115 3         151 $a->len <=> $b->len;
  0         0  
116             } @sequences;
117             }
118              
119             sub _get_known_occ {
120 3     3   7 my ($self, $needle) = @_;
121              
122 3         8 my $len = length($needle);
123              
124 3         14 my $haystack = join '', $self->{book}->get_internal_sequence;
125              
126 3         110 my $occ = undef;
127 3         15 my $pos = index($haystack, $needle);
128 3         15 while ($pos >= 0) {
129 17 100       42 if (!defined($occ)) {
130 2         16 $occ = HTML::ListScraper::Occurence->new($len, $pos);
131             } else {
132 15         46 $occ->append_pos($pos);
133             }
134              
135 17         56 $pos = index($haystack, $needle, $pos + $len);
136             }
137              
138 3         10 return $occ;
139             }
140              
141             sub get_known_sequence {
142 0     0 1 0 my $self = shift;
143              
144 0         0 my $len = scalar(@_);
145 0 0       0 if ($len < 2) {
146 0         0 die "need at least 2 items to look for";
147             }
148              
149 0         0 my $needle = '';
150 0         0 foreach (@_) {
151 0         0 my $internal = $self->{book}->get_internal_name($_);
152 0 0       0 if (!defined($internal)) { # sequence not found if item not found
153 0         0 return undef;
154             }
155              
156 0         0 $needle .= $internal;
157             }
158              
159 0         0 my $occ = $self->_get_known_occ($needle);
160 0 0       0 return !defined($occ) ? undef : $self->_make_sequence($occ);
161             }
162              
163             sub find_known_sequence {
164 3     3 1 6732 my $self = shift;
165              
166 3         9 my $len = scalar(@_);
167              
168 3 50       13 if ($len < 2) {
169 0         0 die "need at least 2 items to look for";
170             }
171              
172 3         31 my @tags = @_;
173              
174 3         8 my $sign = '';
175 3         10 foreach (@tags) {
176 63         190 my $iname = $self->{book}->intern_name($_);
177 63         124 $sign .= $iname;
178             }
179              
180 3         14 my $occ = $self->_get_known_occ($sign);
181 3 100       10 if ($occ) {
182 2         12 return $self->_make_approx_seq($sign, $occ);
183             } else {
184 1         5 return $self->_make_whole_seq($sign);
185             }
186             }
187              
188             sub _make_approx_seq {
189 5     5   11 my ($self, $sign, $occ) = @_;
190              
191 5         20 my $len = $occ->len;
192 5         11 my $edge;
193             my @instances;
194 5         21 foreach my $pos ($occ->positions) {
195 43         69 my $gap;
196 43 100       160 if (!defined($edge)) {
    50          
197 5         8 $gap = 0;
198             } elsif ($pos >= $edge + $len) {
199 38         61 $gap = $edge + $len;
200             }
201              
202 43 50       115 if (defined($gap)) {
203 43         118 push @instances,
204             $self->_make_approx_inst($sign, $gap, $pos);
205              
206 43         154 my @tags = $self->{book}->get_tags($pos, $len);
207 43         1140 push @instances,
208             HTML::ListScraper::Instance->new(start => $pos,
209             match => 'exact', tags => \@tags);
210 43         1242 $edge = $pos;
211             }
212             }
213              
214 5 50       24 if (!defined($edge)) {
215 0         0 die "no occurence";
216             }
217              
218 5         22 my $iseq = $self->{book}->get_internal_sequence;
219 5         9 my $end = scalar(@$iseq);
220 5         20 push @instances,
221             $self->_make_approx_inst($sign, $edge + $len, $end);
222              
223 5         159 return HTML::ListScraper::Sequence->new(len => $len,
224             instances => \@instances);
225             }
226              
227             sub _make_whole_seq {
228 1     1   3 my ($self, $sign) = @_;
229              
230 1         4 my $iseq = $self->{book}->get_internal_sequence;
231 1         3 my $end = scalar(@$iseq);
232 1         4 my @instances = $self->_make_approx_inst($sign, 0, $end);
233              
234 1         4 my $seq = undef;
235 1 50       5 if (scalar(@instances)) {
236 1         30 $seq = HTML::ListScraper::Sequence->new(len => length($sign),
237             instances => \@instances);
238             }
239              
240 1         41 return $seq;
241             }
242              
243             sub _make_approx_inst {
244 49     49   99 my ($self, $sign, $begin, $end) = @_;
245              
246 49         87 my $size = $end - $begin;
247 49 100       129 if ($size < 2) {
248 33         72 return ();
249             }
250              
251             my $sweep = HTML::ListScraper::Sweep->new(
252 16         101 book => $self->{book}, sign => $sign,
253             begin => $begin, end => $end);
254 16         64 my $dust = $sweep->create_dust;
255 16         30 my @instances;
256 16         76 foreach my $align ($dust->get_alignments) {
257 29         840 my @tags;
258 29         739 foreach my $pos ($align->positions) {
259 495         1684 my $t = $self->{book}->get_tag($pos);
260 495         1061 push @tags, $t;
261             }
262              
263 29         737 my $start = $tags[0]->index;
264 29         827 push @instances,
265             HTML::ListScraper::Instance->new(start => $start,
266             match => 'approx', score => $align->score,
267             tags => \@tags);
268             }
269              
270 16         1544 return @instances;
271             }
272              
273             sub _make_sequence {
274 5     5   11 my ($self, $occ) = @_;
275              
276 5         19 my $len = $occ->len;
277 5         13 my $edge;
278             my @instances;
279 5         18 foreach my $pos ($occ->positions) {
280 54 50 66     285 if (!defined($edge) || ($pos >= $edge + $len)) {
281 54         195 my @tags = $self->{book}->get_tags($pos, $len);
282 54         1489 push @instances,
283             HTML::ListScraper::Instance->new(start => $pos,
284             match => 'exact', tags => \@tags);
285 54         1667 $edge = $pos;
286             }
287             }
288              
289 5         147 return HTML::ListScraper::Sequence->new(len => $len,
290             instances => \@instances);
291             }
292              
293             sub _is_tag {
294 2623     2623   5338 my ($self, $tag) = @_;
295              
296 2623         12977 return $tag =~ m/^[a-z0-9-:]+$/i;
297             }
298              
299             sub on_start {
300 1412     1412 1 13017 my ($self, $rtag, $attr) = @_;
301              
302 1412         2617 my $tag = $rtag;
303 1412         2614 $tag =~ s/\s*\/$//;
304              
305 1412 50       3177 if (!$self->_is_tag($tag)) {
306 0         0 $self->{book}->append_text($tag);
307 0         0 return;
308             }
309              
310 1412 100 66     5402 if (exists($attr->{href}) && $attr->{href}) {
311 289         976 $self->{book}->push_link($tag, $attr->{href});
312             } else {
313 1123         3729 $self->{book}->push_item($tag);
314             }
315              
316 1412 50       13983 if ($tag ne $rtag) { # empty tag - close it
317 0         0 $self->on_end($tag);
318             }
319             }
320              
321             sub on_text {
322 1767     1767 1 8221 my ($self, $text) = @_;
323              
324 1767         5882 $self->{book}->append_text($text);
325             }
326              
327             sub on_end {
328 1211     1211 1 8933 my ($self, $tag) = @_;
329              
330 1211 50       2729 if (!$self->_is_tag($tag)) {
331 0         0 $self->{book}->append_text($tag);
332 0         0 return;
333             }
334              
335 1211         5930 $self->{book}->push_item("/$tag");
336             }
337              
338             1;
339              
340             __END__