File Coverage

blib/lib/HTML/ListScraper/Book.pm
Criterion Covered Total %
statement 81 89 91.0
branch 17 24 70.8
condition 3 3 100.0
subroutine 16 18 88.8
pod 0 13 0.0
total 117 147 79.5


line stmt bran cond sub pod time code
1             package HTML::ListScraper::Book;
2              
3 5     5   22067 use warnings;
  5         8  
  5         176  
4 5     5   27 use strict;
  5         8  
  5         123  
5              
6 5     5   1295 use Class::Generate qw(class);
  5         32461  
  5         2380  
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 6     6 0 22 my $class = shift;
18 6         45 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 6         24 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 162         396 $self->{unclosed_tags}->{$_} = 1;
27             }
28              
29 6         14 bless $self, $class;
30              
31 6         37 return $self;
32             }
33              
34             sub shapeless {
35 54     54 0 106 my $self = shift;
36              
37 54 50       191 if (@_) {
38 0         0 $self->{shapeless} = !!$_[0];
39             }
40              
41 54         258 return $self->{shapeless};
42             }
43              
44             sub is_unclosed_tag {
45 968     968 0 2455 my ($self, $name) = @_;
46              
47 968         4206 return exists($self->{unclosed_tags}->{$name});
48             }
49              
50             sub push_item {
51 2337     2337 0 4895 my ($self, $name) = @_;
52              
53 2337         4254 my $index = ($self->{index})++;
54 2337         54506 $self->_push(HTML::ListScraper::Tag->new(name => $name, index => $index));
55             }
56              
57             sub push_link {
58 289     289 0 715 my ($self, $name, $link) = @_;
59              
60 289         532 my $index = ($self->{index})++;
61 289         6923 $self->_push(HTML::ListScraper::Tag->new(
62             name => $name, index => $index, link => $link));
63             }
64              
65             sub get_internal_name {
66 0     0 0 0 my ($self, $name) = @_;
67              
68 0 0       0 return exists($self->{p2t}->{$name}) ? $self->{p2t}->{$name} : undef;
69             }
70              
71             sub intern_name {
72 2689     2689 0 5725 my ($self, $name) = @_;
73              
74 2689 100       9054 if (!exists($self->{p2t}->{$name})) {
75 5     5   4813 use bytes;
  5         54  
  5         29  
76              
77 190         360 my $c = ($self->{next})++;
78 190 50       494 if ($self->{next} > 255) {
79             # 18Apr2007: HTML::ListScraper::get_known_sequence
80             # depends on 1-byte internal names
81 0         0 die "can't handle so many tags";
82             # could probably switch to 2-byte numbers, but is that
83             # useful?
84             }
85              
86 190         572 $self->{p2t}->{$name} = bytes::chr($c);
87             }
88              
89 2689         90471 return $self->{p2t}->{$name};
90             }
91              
92             sub _push {
93 2626     2626   71631 my ($self, $td) = @_;
94              
95 2626         59950 my $name = $td->name;
96 2626         21772 my $iname = $self->intern_name($name);
97 2626         4220 push @{$self->{dseq}}, $td;
  2626         5996  
98 2626         3975 push @{$self->{tseq}}, $iname;
  2626         17228  
99             }
100              
101             sub append_text {
102 1767     1767 0 3478 my ($self, $text) = @_;
103              
104 1767         2769 my $count = scalar(@{$self->{dseq}});
  1767         3900  
105              
106             # ignore text before the first tag
107 1767 100       4401 if (!$count) {
108 1         11 return; # if we had a verbose mode, we would warn here
109             }
110              
111 1766         3461 my $td = $self->{dseq}->[$count - 1];
112 1766         40940 $td->append_text($text);
113             }
114              
115             sub get_internal_sequence {
116 74     74 0 146 my $self = shift;
117              
118 74 100       361 return wantarray ? @{$self->{tseq}} : $self->{tseq};
  3         474  
119             }
120              
121             sub is_presentable {
122 1972     1972 0 3165 my ($self, $start, $len) = @_;
123              
124 1972 50       4748 if ($self->{shapeless}) {
125 0         0 return 1;
126             }
127              
128 1972         2626 my $i = 0;
129 1972         2666 my @stack;
130 1972         4719 while ($i < $len) {
131 9774         227142 my $name = $self->{dseq}->[$start + $i]->name;
132 9774         73705 my $tag = $name;
133 9774         23794 $tag =~ s~^\/~~;
134              
135 9774 100       25895 if ($name eq $tag) {
136 5165         11751 push @stack, $tag;
137             } else {
138 4609   100     24146 while (scalar(@stack) &&
139             ($stack[scalar(@stack) - 1] ne $tag)) {
140 411 100       1081 if ($self->is_unclosed_tag($stack[scalar(@stack) - 1])) {
141 92         605 pop @stack;
142             } else {
143 319         1663 return 0;
144             }
145             }
146              
147 4290 100       10605 if (!scalar(@stack)) {
148 1268         5872 return 0;
149             }
150              
151 3022         5077 pop @stack;
152             }
153              
154 8187         24655 ++$i;
155             }
156              
157 385         1020 while (scalar(@stack)) {
158 483         1053 my $top = pop @stack;
159 483 100       1286 if (!$self->is_unclosed_tag($top)) {
160 332         1702 return 0;
161             }
162             }
163              
164 53         241 return 1;
165             }
166              
167             sub get_all_tags {
168 0     0 0 0 my $self = shift;
169              
170 0 0       0 return wantarray ? @{$self->{dseq}} : $self->{dseq};
  0         0  
171             }
172              
173             sub get_tags {
174 97     97 0 175 my ($self, $start, $len) = @_;
175              
176 97         158 my $last = $start + $len - 1;
177 97         242 return @{$self->{dseq}}[$start .. $last];
  97         749  
178             }
179              
180             sub get_tag {
181 1172     1172 0 1892 my ($self, $pos) = @_;
182              
183 1172         3514 return $self->{dseq}->[$pos];
184             }
185              
186             1;