File Coverage

blib/lib/HTML/ListScraper.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


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