File Coverage

blib/lib/HTML/ListScraper/Book.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package HTML::ListScraper::Book;
2              
3 1     1   29025 use warnings;
  1         2  
  1         30  
4 1     1   5 use strict;
  1         3  
  1         33  
5              
6 1     1   1286 use Class::Generate qw(class);
  0            
  0            
7              
8             class 'HTML::ListScraper::Tag' => {
9             name => { type => '$', required => 1, readonly => 1 },
10             index => { type => '$', required => 1, readonly => 1 },
11             link => { type => '$', readonly => 1 },
12             text => '$',
13             '&append_text' => q{ $text .= $_[0]; }
14             };
15              
16             sub new {
17             my $class = shift;
18             my $self = { shapeless => 0, index => 0,
19             dseq => [ ], next => 0, tseq => [ ], p2t => { } };
20              
21             # the list is from HTML 4.01 Transitional DTD; head and body is
22             # included not because we seriously expect them to be unpaired,
23             # but just to simplify documentation - they aren't going to get
24             # into repeated sequences anyway...
25             foreach (qw(area base basefont body br col colgroup dd dt frame head hr img input isindex li link meta option p param tbody td tfoot th thead tr)) {
26             $self->{unclosed_tags}->{$_} = 1;
27             }
28              
29             bless $self, $class;
30              
31             return $self;
32             }
33              
34             sub shapeless {
35             my $self = shift;
36              
37             if (@_) {
38             $self->{shapeless} = !!$_[0];
39             }
40              
41             return $self->{shapeless};
42             }
43              
44             sub is_unclosed_tag {
45             my ($self, $name) = @_;
46              
47             return exists($self->{unclosed_tags}->{$name});
48             }
49              
50             sub push_item {
51             my ($self, $name) = @_;
52              
53             my $index = ($self->{index})++;
54             $self->_push(HTML::ListScraper::Tag->new(name => $name, index => $index));
55             }
56              
57             sub push_link {
58             my ($self, $name, $link) = @_;
59              
60             my $index = ($self->{index})++;
61             $self->_push(HTML::ListScraper::Tag->new(
62             name => $name, index => $index, link => $link));
63             }
64              
65             sub get_internal_name {
66             my ($self, $name) = @_;
67              
68             return exists($self->{p2t}->{$name}) ? $self->{p2t}->{$name} : undef;
69             }
70              
71             sub intern_name {
72             my ($self, $name) = @_;
73              
74             if (!exists($self->{p2t}->{$name})) {
75             use bytes;
76              
77             my $c = ($self->{next})++;
78             if ($self->{next} > 255) {
79             # 18Apr2007: HTML::ListScraper::get_known_sequence
80             # depends on 1-byte internal names
81             die "can't handle so many tags";
82             # could probably switch to 2-byte numbers, but is that
83             # useful?
84             }
85              
86             $self->{p2t}->{$name} = bytes::chr($c);
87             }
88              
89             return $self->{p2t}->{$name};
90             }
91              
92             sub _push {
93             my ($self, $td) = @_;
94              
95             my $name = $td->name;
96             my $iname = $self->intern_name($name);
97             push @{$self->{dseq}}, $td;
98             push @{$self->{tseq}}, $iname;
99             }
100              
101             sub append_text {
102             my ($self, $text) = @_;
103              
104             my $count = scalar(@{$self->{dseq}});
105              
106             # ignore text before the first tag
107             if (!$count) {
108             return; # if we had a verbose mode, we would warn here
109             }
110              
111             my $td = $self->{dseq}->[$count - 1];
112             $td->append_text($text);
113             }
114              
115             sub get_internal_sequence {
116             my $self = shift;
117              
118             return wantarray ? @{$self->{tseq}} : $self->{tseq};
119             }
120              
121             sub is_presentable {
122             my ($self, $start, $len) = @_;
123              
124             if ($self->{shapeless}) {
125             return 1;
126             }
127              
128             my $i = 0;
129             my @stack;
130             while ($i < $len) {
131             my $name = $self->{dseq}->[$start + $i]->name;
132             my $tag = $name;
133             $tag =~ s~^\/~~;
134              
135             if ($name eq $tag) {
136             push @stack, $tag;
137             } else {
138             while (scalar(@stack) &&
139             ($stack[scalar(@stack) - 1] ne $tag)) {
140             if ($self->is_unclosed_tag($stack[scalar(@stack) - 1])) {
141             pop @stack;
142             } else {
143             return 0;
144             }
145             }
146              
147             if (!scalar(@stack)) {
148             return 0;
149             }
150              
151             pop @stack;
152             }
153              
154             ++$i;
155             }
156              
157             while (scalar(@stack)) {
158             my $top = pop @stack;
159             if (!$self->is_unclosed_tag($top)) {
160             return 0;
161             }
162             }
163              
164             return 1;
165             }
166              
167             sub get_all_tags {
168             my $self = shift;
169              
170             return wantarray ? @{$self->{dseq}} : $self->{dseq};
171             }
172              
173             sub get_tags {
174             my ($self, $start, $len) = @_;
175              
176             my $last = $start + $len - 1;
177             return @{$self->{dseq}}[$start .. $last];
178             }
179              
180             sub get_tag {
181             my ($self, $pos) = @_;
182              
183             return $self->{dseq}->[$pos];
184             }
185              
186             1;