File Coverage

blib/lib/pQuery/DOM.pm
Criterion Covered Total %
statement 177 186 95.1
branch 80 96 83.3
condition 18 26 69.2
subroutine 36 39 92.3
pod 26 29 89.6
total 337 376 89.6


line stmt bran cond sub pod time code
1 12     12   66 use strict; use warnings;
  12     12   23  
  12         410  
  12         65  
  12         75  
  12         403  
2             package pQuery::DOM;
3              
4 12     12   65 use Carp;
  12         23  
  12         1261  
5              
6 12     12   248 use base 'HTML::TreeBuilder';
  12         29  
  12         3753  
7 12     12   163947 use base 'HTML::Element';
  12         27  
  12         6958  
8              
9             # This is a copy of HTML::TreeBuilder::new. Sadly. TreeBuilder should be
10             # easier to subclass. The only change is s/HTML::Element/pQuery::DOM/g.
11             sub _builder { # constructor!
12 36     36   80 my $class = shift;
13 36   33     217 $class = ref($class) || $class;
14              
15 36         173 my $self = pQuery::DOM->new('html'); # Initialize HTML::Element part
16             {
17             # A hack for certain strange versions of Parser:
18 36         72 my $other_self = HTML::Parser->new();
  36         747  
19 36         2935 %$self = (%$self, %$other_self); # copy fields
20             # Yes, multiple inheritance is messy. Kids, don't try this at home.
21 36         328 bless $other_self, "HTML::TreeBuilder::_hideyhole";
22             # whack it out of the HTML::Parser class, to avoid the destructor
23             }
24              
25             # The root of the tree is special, as it has these funny attributes,
26             # and gets reblessed into this class.
27              
28             # Initialize parser settings
29 36         195 $self->{'_implicit_tags'} = 1;
30 36         84 $self->{'_implicit_body_p_tag'} = 0;
31             # If true, trying to insert text, or any of %isPhraseMarkup right
32             # under 'body' will implicate a 'p'. If false, will just go there.
33              
34 36         70 $self->{'_tighten'} = 1;
35             # whether ignorable WS in this tree should be deleted
36              
37 36         185 $self->{'_implicit'} = 1; # to delete, once we find a real open-"html" tag
38              
39 36         286 $self->{'_element_class'} = 'pQuery::DOM';
40 36         77 $self->{'_ignore_unknown'} = 1;
41 36         71 $self->{'_ignore_text'} = 0;
42 36         78 $self->{'_warn'} = 0;
43 36         73 $self->{'_no_space_compacting'}= 1;
44 36         66 $self->{'_store_comments'} = 0;
45 36         77 $self->{'_store_declarations'} = 0;
46 36         170 $self->{'_store_pis'} = 0;
47 36         126 $self->{'_p_strict'} = 0;
48              
49             # Parse attributes passed in as arguments
50 36 50       120 if(@_) {
51 0         0 my %attr = @_;
52 0         0 for (keys %attr) {
53 0         0 $self->{"_$_"} = $attr{$_};
54             }
55             }
56              
57             # rebless to our class
58 36         85 bless $self, $class;
59              
60 36         344 $self->{'_element_count'} = 1;
61             # undocumented, informal, and maybe not exactly correct
62              
63 36         295 $self->{'_head'} = $self->insert_element('head',1);
64 36         1582 $self->{'_pos'} = undef; # pull it back up
65 36         117 $self->{'_body'} = $self->insert_element('body',1);
66 36         968 $self->{'_pos'} = undef; # pull it back up again
67 36         278 $self->ignore_ignorable_whitespace(0);
68 36         539 $self->store_comments(1);
69 36         395 $self->no_space_compacting(1);
70              
71 36         458 return $self;
72             }
73              
74             sub new {
75 1358     1358 1 450645 my $class = shift;
76 1358   33     5702 $class = ref($class) || $class;
77              
78 1358         2086 my $tag = shift;
79 1358 50 33     6481 Carp::croak("No tagname") unless defined $tag and length $tag;
80 1358 50       3222 Carp::croak "\"$tag\" isn't a good tag name!"
81             if $tag =~ m/[<>\/\x00-\x20]/; # minimal sanity, certainly!
82 1358         4451 my $self = bless { _tag => scalar($class->_fold_case($tag)) }, $class;
83 1358         11040 my($attr, $val);
84 1358         4737 while (($attr, $val) = splice(@_, 0, 2)) {
85 637 50       1609 $val = $attr unless defined $val;
86 637         1945 $self->{$class->_fold_case($attr)} = $val;
87             }
88 1358 100       13270 if ($tag eq 'html') {
89 52         302 $self->{'_pos'} = undef;
90             }
91 1358         3689 return $self;
92             }
93              
94             #------------------------------------------------------------------------------#
95             # pQuery::DOM Class Methods
96             #------------------------------------------------------------------------------#
97             sub fromHTML {
98 36     36 1 660 my ($class, $html) = @_;
99 36         57 my $dom;
100 36 100       480 if ($html =~ /^\s*.*<\/html>\s*\z/is) {
101 16         533 $dom = $class->_builder->parse_content($html);
102 16         28212 return $dom;
103             }
104 20         196 $dom = $class->_builder->parse_content('' . $html . '');
105             my @dom = map {
106 33 100       96 if (ref($_)) {
  20 100       102  
107 22         60 delete $_->{_parent};
108             }
109 33         79 $_;
110 20         7262 } @{$dom->{_body}{_content} || [$dom->{_content}[-1]]};
111 20 100       389 return wantarray ? @dom : $dom[0];
112             }
113              
114             sub createElement {
115 1     1 1 3 my ($class, $tag) = @_;
116 1 50       7 return unless $tag =~ /^\w+$/;
117 1         5 return $class->fromHTML('<' . $tag . '>');
118             }
119              
120             sub createComment {
121 1     1 1 5 my ($class, $comment) = @_;
122 1         5 return $class->fromHTML('');
123             }
124              
125             #------------------------------------------------------------------------------#
126             # DOM Object Methods
127             #------------------------------------------------------------------------------#
128             sub toHTML {
129 9     9 1 27 my $self = shift;
130              
131 9         17 my $html = '';
132              
133 9         34 _to_html($self, \$html);
134              
135 9         49 return $html;
136             }
137              
138             sub innerHTML {
139 15     15 1 531 my $self = shift;
140              
141 15 100       113 return if $self->{_tag} eq '~comment';
142              
143 14 100       51 if (@_) {
144 1         7 $self->{_content} = [pQuery::DOM->fromHTML($_[0])];
145 1         4 return $_[0];
146             }
147              
148 13         32 my $html = '';
149              
150 13 50       20 my @list = @{$self->{_content} || []};
  13         63  
151 13         34 for (@list) {
152 27         71 _to_html($_, \$html);
153             }
154              
155 13         70 return $html;
156             }
157              
158             sub getElementsByTagName {
159 155     155 1 246 my ($self, $tag) = @_;
160 155         207 $tag = lc $tag;
161 155         211 my $found = [];
162 155 100   5234   674 _find($self, $found, sub { $_->{_tag} eq $tag or $tag eq "*" });
  5234         23860  
163 155 100 100     1110 shift @$found if @$found and $found->[0] == $self;
164 155         1073 return $found;
165             }
166              
167             sub getElementById {
168 7     7 1 492 my ($self, $id) = @_;
169 7         18 my $found = [];
170 7 100   332   44 _find($self, $found, sub { $_->{id} and $_->{id} eq $id});
  332         1138  
171 7 50       57 return wantarray ? @$found : $found->[0];
172             }
173              
174             sub nodeType {
175 199 100   199 1 1250 return $_[0]->{_tag} eq '~comment' ? 8 : 1;
176             }
177              
178             sub nodeName {
179 260 100   260 1 629 return '#comment' if $_[0]->{_tag} eq '~comment';
180 259         1718 return uc($_[0]->{_tag});
181             }
182              
183             sub tagName {
184 5 100   5 1 881 return '' if $_[0]->{_tag} eq '~comment';
185 4         15 return $_[0]->nodeName;
186             }
187              
188             sub nodeValue {
189 2     2 1 5 my $self = shift;
190 2 100       13 return $self->{text} if $self->{_tag} eq '~comment';
191 1         5 return;
192             }
193              
194             sub getAttribute {
195 93     93 1 737 return $_[0]->{$_[1]};
196             }
197              
198             sub setAttribute {
199 2     2 1 9 $_[0]->{lc($_[1])} = $_[2];
200 2         6 return;
201             }
202              
203             sub removeAttribute {
204 1     1 1 5 delete $_[0]->{lc($_[1])};
205             }
206              
207             sub hasAttributes {
208 3     3 1 8 my $self = shift;
209 3 100       21 return 0 if $self->{_tag} eq '~comment';
210 2 100       18 return scalar(grep /^[a-z0-9]/, keys %$self) ? 1 : 0;
211             }
212              
213             sub className {
214 91 100   91 1 282 if ($_[1]) {
215 1         6 return $_[0]->setAttribute(class => $_[1]);
216             }
217 90         162 my $className = $_[0]->getAttribute("class");
218 90 100       329 return defined $className
219             ? $className
220             : '';
221             }
222              
223             sub parentNode {
224 240     240 1 954 return $_[0]->{_parent};
225             }
226              
227             sub childNodes {
228 2 50   2 1 9 return @{$_[0]->{_content} || []};
  2         13  
229             }
230              
231             sub firstChild {
232 5 100   5 1 495 return unless $_[0]->{_content};
233 2         17 return $_[0]->{_content}[0];
234             }
235              
236             sub lastChild {
237 1 50   1 1 5 return unless $_[0]->{_content};
238 1         7 return $_[0]->{_content}[-1];
239             }
240              
241             sub firstChildRef {
242 13 50   13 1 37 my $content = $_[0]->{_content} or return;
243 13         30 for (my $i = 0; $i < @$content; $i++) {
244 26 100       99 return $content->[$i] if ref $content->[$i];
245             }
246 0         0 return;
247             }
248              
249             sub lastChildRef {
250 46 50   46 1 100 my $content = $_[0]->{_content} or return;
251 46         97 for (my $i = @$content - 1; $i >= 0; $i--) {
252 92 100       307 return $content->[$i] if ref $content->[$i];
253             }
254 0         0 return;
255             }
256              
257             sub appendChild {
258 2     2 1 10 my ($self, $elem) = @_;
259 2 50       5 return unless defined $elem;
260 2   100     9 my $content = $self->{_content} ||= [];
261 2         5 push @$content, $elem;
262 2         5 return $elem;
263             }
264              
265             sub previousSibling {
266 0     0 0 0 die "pQuery::DOM does not support the previousSibling method";
267             }
268              
269             sub nextSiblingRef {
270 96 50   96 1 186 my $content = $_[0]->parentNode->{_content} or return;
271 96         104 my $found = 0;
272 96         195 for (my $i = 0; $i < @$content; $i++) {
273 857 100 100     2393 return $content->[$i] if $found and ref $content->[$i];
274 776 100 100     3109 $found = 1 if ref($content->[$i]) and $content->[$i] == $_[0];
275             }
276 15         73 return;
277             }
278              
279             sub previousSiblingRef {
280 1 50   1 1 3 my $content = $_[0]->parentNode->{_content} or return;
281 1         2 my $found = 0;
282 1         6 for (my $i = @$content - 1; $i >= 0; $i--) {
283 3 50 66     12 return $content->[$i] if $found and ref $content->[$i];
284 3 100 66     18 $found = 1 if ref($content->[$i]) and $content->[$i] == $_[0];
285             }
286 1         4 return;
287             }
288              
289             sub nextSibling {
290 0     0 0 0 die "pQuery::DOM does not support the nextSibling method";
291             }
292              
293             sub attributes {
294 0     0 0 0 die "pQuery::DOM::attributes not yet implemented";
295             }
296              
297             #------------------------------------------------------------------------------#
298             # Common pQuery method mistakes
299             #------------------------------------------------------------------------------#
300             # sub text {
301             # confess "Invalid method 'text' called on pQuery::DOM object";
302             # }
303              
304             # self closing tags
305             my %selfclose = (
306             "br" => 1,
307             "hr" => 1,
308             "input" => 1
309             );
310              
311             #------------------------------------------------------------------------------#
312             # Helper Functions
313             #------------------------------------------------------------------------------#
314             sub _to_html {
315 125     125   186 my ($elem, $html) = @_;
316 125 100       263 if (not ref $elem) {
317 80         120 $$html .= $elem;
318 80         347 return;
319             }
320 45 100       116 if ($elem->{_tag} eq '~comment') {
321 2         7 $$html .= '';
322 2         6 return;
323             }
324 43         83 $$html .= '<' . $elem->{_tag};
325 43 100       109 $$html .= qq{ id="$elem->{id}"}
326             if $elem->{id};
327 43 100       617 $$html .= qq{ class="$elem->{class}"}
328             if $elem->{class};
329 43         286 for (sort keys %$elem) {
330 155 100       587 next if /^(_|id$|class$)/i;
331 14         41 $$html .= qq{ $_="$elem->{$_}"};
332             }
333              
334 43 50       145 if (exists $selfclose{$elem->{_tag}})
335             {
336 0         0 $$html .= '/>';
337             }
338             else
339             {
340 43         59 $$html .= '>';
341 43 100       69 for my $child (@{$elem->{_content} || []}) {
  43         148  
342 89         234 _to_html($child, $html);
343             }
344 43         154 $$html .= '{_tag} . '>';
345             }
346             }
347             # XXX "work around vim hilight bug
348              
349             sub _find {
350 5566     5566   18762 my ($elem, $found, $test) = @_;
351 5566         5715 $_ = $elem;
352 5566 100       7648 if (&$test()) {
353 2322         3418 push @$found, $_;
354             }
355              
356 5566 100       22084 map _find($_, $found, $test), grep ref($_), @{$elem->{_content} || []};
  5566         24159  
357             }
358              
359             1;