File Coverage

blib/lib/HTML/Linear.pm
Criterion Covered Total %
statement 76 76 100.0
branch 14 14 100.0
condition 6 6 100.0
subroutine 18 18 100.0
pod 9 9 100.0
total 123 123 100.0


line stmt bran cond sub pod time code
1             package HTML::Linear;
2             # ABSTRACT: represent HTML::Tree as a flat list
3 64     64   2332 use strict;
  64         126  
  64         3359  
4 64     64   374 use utf8;
  64         125  
  64         947  
5 64     64   2040 use warnings qw(all);
  64         128  
  64         4586  
6              
7 64     64   96552 use Digest::SHA qw(sha256);
  64         326810  
  64         7599  
8              
9 64     64   96483 use Moo;
  64         1342078  
  64         488  
10 64     64   270434 use MooX::Types::MooseLike::Base qw(:all);
  64         637302  
  64         34756  
11             extends 'HTML::TreeBuilder';
12              
13 64     64   56923 use HTML::Linear::Element;
  64         227  
  64         3138  
14 64     64   599 use HTML::Linear::Path;
  64         131  
  64         112600  
15              
16             ## no critic (ProtectPrivateSubs, RequireFinalReturn)
17              
18             our $VERSION = '0.019'; # VERSION
19              
20              
21             has _list => (
22             is => 'ro',
23             isa => ArrayRef[InstanceOf('HTML::Linear::Element')],
24             default => sub { [] },
25             );
26              
27 6296     6296   8736 sub _add_element { push @{shift->_list}, shift }
  6296         42763  
28 140     140 1 14771 sub as_list { @{shift->_list} }
  140         3693  
29 2     2 1 798 sub count_elements { 0 + @{shift->_list} }
  2         15  
30 16     16 1 207 sub get_element { shift->_list->[shift] }
31              
32              
33             has _shrink => (
34             is => 'rwp',
35             isa => Bool,
36             default => sub { 0 },
37             );
38              
39 51     51 1 3187 sub set_shrink { shift->_set__shrink(1) }
40 6     6 1 1327 sub unset_shrink { shift->_set__shrink(0) }
41              
42              
43             has _strict => (
44             is => 'rwp',
45             isa => Bool,
46             default => sub { 0 },
47             );
48              
49 43     43 1 3876 sub set_strict { shift->_set__strict(1) }
50 6     6 1 1796 sub unset_strict { shift->_set__strict(0) }
51              
52              
53             has _uniq => (is => 'ro', isa => HashRef[Str], default => sub { {} });
54              
55              
56             has _path_count => (is => 'ro', isa => HashRef[Str], default => sub { {} });
57              
58              
59             after eof => sub {
60             my ($self) = @_;
61              
62             $self->deparse($self, []);
63              
64             if ($self->_shrink) {
65             my %short;
66             for my $elem ($self->as_list) {
67             my @rpath = reverse $elem->as_xpath;
68             my $i = 0;
69             unless ($self->_strict) {
70             for (; $i <= $#rpath; $i++) {
71             last if $elem->path->[-1 - $i]->is_groupable;
72             }
73             }
74             for my $j ($i .. $#rpath) {
75             my $key = sha256(join '' => @rpath[0 .. $j]);
76             $short{$key}{offset} = $#rpath - $j;
77             push @{$short{$key}{elem}}, $elem;
78             ++$short{$key}{accumulator}{$elem->as_xpath};
79             }
80             }
81              
82             for my $key (sort { $short{$b}{offset} <=> $short{$a}{offset} } keys %short) {
83             next if 1 < keys %{$short{$key}{accumulator}};
84             for my $elem (@{$short{$key}{elem}}) {
85             next if $elem->trim_at;
86             $elem->trim_at($short{$key}{offset});
87             }
88             }
89             }
90             };
91              
92              
93             sub add_element {
94 6296     6296 1 42562 my ($self, $elem) = @_;
95              
96 6296         195952 $elem->index($self->_path_count->{join ',', $elem->path}++);
97 6296         520500 $elem->index_map($self->_uniq);
98              
99 6296         1354051 $self->_add_element($elem);
100             }
101              
102              
103             sub deparse {
104 5046     5046 1 9629 my ($self, $node, $path) = @_;
105              
106 22657 100       680690 my $level = HTML::Linear::Path->new({
107             address => $node->address,
108             attributes => {
109             map {
110 5046         19241 m{^[_/]}x
111             ? ()
112             : (lc, $node->attr($_))
113             } $node->all_attr_names
114             },
115             strict => $self->_strict,
116             tag => $node->tag,
117             });
118              
119 5046         724931 my $flag = 0;
120 5046 100       18250 $flag = 1 if not $node->content_list;
121 5046 100 100     48959 $flag = 1 if $node->content_list and (ref(($node->content_list)[0]) ne '');
122 2667         111360 $self->add_element(
123             HTML::Linear::Element->new({
124             depth => $node->depth,
125 5046 100       82947 path => [ @{$path}, $level ],
126             strict => $self->_strict,
127             })
128             ) if $flag;
129              
130 5046         11288 my (%uniq, %uniq_strict, %is_groupable);
131 5046         15439 for my $child ($node->content_list) {
132 8583 100       141989 if (ref $child) {
133 4954         7787 my $l = $self->deparse($child, [ @{$path}, $level ]);
  4954         36443  
134 4954         17672 push @{$uniq{$l->as_xpath}}, $l->address;
  4954         23709  
135 4954         82443 push @{$uniq_strict{$l->as_xpath(1)}}, $l->address;
  4954         15603  
136 4954         174596 $is_groupable{$l->address} = $l->is_groupable;
137             } else {
138 3629         193363 $self->add_element(
139             HTML::Linear::Element->new({
140             content => $child,
141             depth => $node->depth,
142 3629         13090 path => [ @{$path}, $level ],
143             strict => $self->_strict,
144             })
145             );
146             }
147             }
148              
149 5046         102565 my %count;
150 5046         20205 while (my ($xpath, $address) = each %uniq_strict) {
151 3094         4637 my $i = 1;
152 3094         3710 for my $addr (@{$address}) {
  3094         6651  
153 4954         9998 $count{$addr} = $i;
154             } continue {
155 4954         26026 ++$i;
156             }
157             }
158              
159 5046         16024 while (my ($xpath, $address) = each %uniq) {
160 3227 100 100     3948 if (
161 4954         39511 grep { $count{$_} > 1 } @{$address}
  3227         5589  
162             #or ($self->_strict and $self->_shrink) # less verbose; unstable
163             or $self->_shrink # verbose; stable
164             ) {
165 2027         3214 my $i = 1;
166 2027         2497 for my $addr (@{$address}) {
  2027         4012  
167 3754 100       9866 $self->_uniq->{$addr} =
168             HTML::Linear::Path::_wrap(array => '[')
169             . HTML::Linear::Path::_wrap(number => $is_groupable{$addr} ? $i : $count{$addr})
170             . HTML::Linear::Path::_wrap(array => ']');
171             } continue {
172 3754         14438 ++$i;
173             }
174             }
175             }
176              
177 5046         22386 return $level;
178             }
179              
180             1;
181              
182             __END__