File Coverage

blib/lib/HTML/Blitz/Matcher.pm
Criterion Covered Total %
statement 102 103 99.0
branch 29 44 65.9
condition 2 3 66.6
subroutine 10 10 100.0
pod 0 5 0.0
total 143 165 86.6


line stmt bran cond sub pod time code
1             # This code can be redistributed and modified under the terms of the GNU
2             # General Public License as published by the Free Software Foundation, either
3             # version 3 of the License, or (at your option) any later version.
4             # See the "COPYING" file for details.
5             package HTML::Blitz::Matcher;
6 11     11   77 use HTML::Blitz::pragma;
  11         22  
  11         74  
7 11         691 use HTML::Blitz::SelectorType qw(
8             LT_DESCENDANT
9             LT_CHILD
10             LT_SIBLING
11             LT_ADJACENT_SIBLING
12 11     11   9167 );
  11         31  
13 11     11   77 use Scalar::Util ();
  11         24  
  11         456  
14              
15             use constant {
16 11         1870 INTBITS => length(sprintf '%b', ~0),
17 11     11   64 };
  11         20  
18              
19             our $VERSION = '0.08';
20              
21 274 50   274 0 657 method new($class: $rules) {
  274 50       525  
  274         423  
  274         489  
  274         347  
22 274         2195 bless {
23             slices => [
24             map [ $_, { cur => 0, stack => [{ extra_bits => 0 }] } ], @$rules
25             ],
26             doc_state => [
27             {
28             nth_child => 0,
29             nth_child_of_type => {},
30             on_leave => [],
31             },
32             ],
33             }, $class
34             }
35              
36 1539     1539   2617 fun _guniq(@values) {
  1539         1914  
37 1539         2119 my ($seen_undef, %seen_ref, %seen_str);
38             grep
39             !(
40             ref($_) ? $seen_ref{Scalar::Util::refaddr $_} :
41 1539 0       8934 defined($_) ? $seen_str{$_} :
    50          
42             $seen_undef
43             )++,
44             @values
45             }
46              
47 1539 50   1539 0 3134 method enter($tag, $attributes) {
  1539 50       2646  
  1539         2149  
  1539         2846  
  1539         1866  
48 1539         2370 my $doc_state = $self->{doc_state};
49 1539         2335 my $dsp = $doc_state->[-1];
50 1539         2410 my $nth_child = ++$dsp->{nth_child};
51 1539         3461 my $nth_child_of_type = ++$dsp->{nth_child_of_type}{$tag};
52 1539         4652 push @$doc_state, {
53             nth_child => 0,
54             nth_child_of_type => {},
55             on_leave => [],
56             };
57              
58 1539         2609 my @ret;
59              
60 1539         2087 for my $slice (@{$self->{slices}}) {
  1539         3238  
61 1762         3147 my ($glass, $goop) = @$slice;
62 1762         2801 my $cur = $goop->{cur};
63 1762         2598 my $stack = $goop->{stack};
64 1762         2490 my $sp = $stack->[-1];
65 1762         2500 my $extra_volatile = $sp->{extra_volatile};
66 1762         2771 $sp->{extra_volatile} = [];
67              
68 1762         3894 push @$stack, my $sp_next = {
69             extra_bits => 0,
70             };
71 1762         2496 my $cur_next;
72              
73 1762         2313 for my $i ($cur, @{$sp->{extra}}, @$extra_volatile) {
  1762         3583  
74 1797         2613 my $sss = $glass->[$i];
75 1797 100       4730 $sss->matches($tag, $attributes, $nth_child, $nth_child_of_type)
76             or next;
77              
78 518         1247 my $link = $sss->link_type;
79 518         829 my $k = $i + 1;
80 518         759 my $bit_shift = $k - $cur - 1;
81 518 50       1019 $bit_shift < INTBITS
82             or die "Internal error: Too many combinators in a single selector (" . ($bit_shift + 1) . " exceeds limit of " . INTBITS . ")";
83 518         755 my $bit = 1 << $bit_shift;
84              
85 518 100       995 if (!defined $link) {
    100          
    100          
    100          
    50          
86 436         921 push @ret, $glass->[$k];
87             } elsif ($link eq LT_DESCENDANT) {
88 33         58 $cur_next = $k;
89             } elsif ($link eq LT_CHILD) {
90 44 50       91 if (!($sp_next->{extra_bits} & $bit)) {
91 44         61 $sp_next->{extra_bits} |= $bit;
92 44         53 push @{$sp_next->{extra}}, $k;
  44         112  
93             }
94             } elsif ($link eq LT_SIBLING) {
95 1 50       10 if (!($sp->{extra_bits} & $bit)) {
96 1         2 $sp->{extra_bits} |= $bit;
97 1         2 push @{$sp->{extra}}, $k;
  1         5  
98             }
99             } elsif ($link eq LT_ADJACENT_SIBLING) {
100 4         6 push @{$sp->{extra_volatile}}, $k;
  4         10  
101             } else {
102 0         0 die "Internal error: unexpected selector combinator '$link'";
103             }
104             }
105              
106 1762 100       4632 if (defined $cur_next) {
107 33         76 $stack->[-1] = {
108             cur => $cur,
109             extra_bits => 0,
110             };
111 33         81 $goop->{cur} = $cur_next;
112             }
113             }
114              
115             _guniq @ret
116 1539         3078 }
117              
118 1501 50   1501 0 3031 method leave(@args) {
  1501         2106  
  1501         2810  
  1501         1899  
119 1501         1839 my $dsp = pop @{$self->{doc_state}};
  1501         2696  
120 1501 100       3321 if (defined(my $marker = $dsp->{marker})) {
121 3         7 splice @{$self->{slices}}, $marker;
  3         15  
122             }
123              
124 1501         2130 for my $slice (@{$self->{slices}}) {
  1501         2883  
125 1727         2535 my $goop = $slice->[1];
126 1727         2495 my $stack = $goop->{stack};
127 1727         2400 my $sp_prev = pop @$stack;
128 1727 100       4770 if (defined(my $cur = $sp_prev->{cur})) {
129 33         76 $goop->{cur} = $cur;
130             }
131             }
132              
133 1501         2145 for my $cb (reverse @{$dsp->{on_leave}}) {
  1501         4587  
134 8         28 $cb->(@args);
135             }
136             }
137              
138 8 50   8 0 23 method on_leave($callback) {
  8 50       41  
  8         17  
  8         52  
  8         16  
139 8         16 push @{$self->{doc_state}[-1]{on_leave}}, $callback;
  8         39  
140             }
141              
142 6 50   6 0 17 method add_temp_rule(@temp_rules) {
  6         10  
  6         25  
  6         10  
143 6         11 my $slices = $self->{slices};
144 6   66     27 $self->{doc_state}[-1]{marker} //= @$slices;
145 6         44 push @$slices, map [ $_, { cur => 0, stack => [{ extra_bits => 0 }] } ], @temp_rules;
146             }
147              
148             1