File Coverage

blib/lib/HTML/DOM/Attr.pm
Criterion Covered Total %
statement 120 123 97.5
branch 54 60 90.0
condition 22 28 78.5
subroutine 33 33 100.0
pod 12 15 80.0
total 241 259 93.0


line stmt bran cond sub pod time code
1             package HTML::DOM::Attr;
2              
3 25     25   144 use warnings;
  25         44  
  25         1256  
4              
5             # attribute constants (array elems)
6             BEGIN{
7 25     25   61 my $x;
8 25         686 %constants
9             = map +($_=>$x++), qw[
10             _doc _elem _name _val _list _styl
11             ]
12             }
13 25     25   120 use constant 1.03 \%constants;
  25         477  
  25         2362  
14             # after compilation:
15             delete @{__PACKAGE__."::"}{ keys %constants, 'constants' };
16              
17 25     25   129 use strict;
  25         40  
  25         1121  
18              
19             # The internal fields are:
20             # _doc # owner document
21             # _elem # owner element
22             # _name
23             # _val # actually contains an array with one element, so
24             # _list # node list # that nodelists can work efficiently
25             # _styl # style obj
26              
27              
28             use overload fallback => 1,
29 77     77   398 '""' => sub { shift->value },
30 25     25   111 'bool' => sub{1};
  25     1184   39  
  25         205  
  1184         2497  
31              
32 25         1160 use HTML::DOM::Exception qw'NOT_FOUND_ERR NO_MODIFICATION_ALLOWED_ERR
33 25     25   1803 HIERARCHY_REQUEST_ERR ';
  25         39  
34 25     25   110 use HTML::DOM::Node 'ATTRIBUTE_NODE';
  25         36  
  25         821  
35 25     25   113 use Scalar::Util qw'weaken blessed refaddr';
  25         34  
  25         29079  
36              
37             require HTML::DOM::EventTarget;
38             require HTML::DOM::NodeList;
39              
40             our @ISA = 'HTML::DOM::EventTarget';
41              
42             our $VERSION = '0.058';
43              
44             # -------- NON-DOM AND PRIVATE METHODS -------- #
45              
46             sub new { # @_[1..2] contains the nayme & vallew
47             # ~~~ INVALID_CHARACTER_ERR is meant to be raised if the specified name contains an invalid character.
48 348     348 0 445 my @self;
49 348 100       1193 @self[_name,_val] = ($_[1],[defined$_[2]?$_[2]:'']);
50             # value should be an empty
51 348         1066 bless \@self, shift; # string, not undef
52             }
53              
54              
55              
56             sub _set_ownerDocument {
57 369     369   1313 weaken ($_[0][_doc] = $_[1]);
58             }
59              
60             sub _element { # This is like ownerElement, except that it lets you set it.
61 394 100   394   689 if(@_ > 1) {
62 369         512 my $old = $_[0][_elem];
63 369         825 weaken ($_[0][_elem] = $_[1]);
64 369         600 return $old
65             }
66 25         103 $_[0][_elem];
67             }
68              
69             sub DOES {
70 1 50   1 0 292 return !0 if $_[1] eq 'HTML::DOM::Node';
71 0 0       0 eval { shift->SUPER::DOES(@_) } || !1
  0         0  
72             }
73              
74             sub _value { # returns the value as it is, whether it is a node or scalar
75 24     24   72 $_[0][_val][0];
76             }
77              
78             sub _val_as_node { # turns the attribute's value into a text node if it is
79             # not one already and returns it
80 26     26   949 my $val = $_[0][_val][0];
81             defined blessed $val && $val->isa('HTML::DOM::Text')
82             ? $val
83 26 100 66     196 : do {
84 13 100       44 my $val = $_[0][_val][0] =
85             $_[0]->ownerDocument->createTextNode(
86             $_[0][_styl] ? $_[0][_styl]->cssText : $val
87             );
88 13         45 weaken($val->{_parent}=($_[0]));
89 13         91 $val
90             }
91             }
92              
93             # ~~~ Should I make this public? This actually allows a style object to be
94             # attached to any attr node, not just a style attr. Is this useful?
95             # (Actually, it would be problematic for event attributes, unless some-
96             # one really wants to run css code :-)
97             sub style {
98 39     39 0 55 my $self = shift;
99 39   66     127 $self->[_styl] ||= do{
100             require CSS::DOM::Style,
101 27         530 my $ret = CSS::DOM::Style::parse(my $val = $self->value);
102             $ret->modification_handler(my $cref = sub {
103 13 100   13   1776 if(ref(my $text = $self->_value)) {
104             # We can’t use ->data here because it will
105             # trigger chardatamodified (see sub new),
106             # which sets cssText, which calls this.
107 1         4 $text->attr('text', shift->cssText)
108             }
109 13         27 $self->_modified;
110 27         20000 });
111 27         174 weaken $self;
112 27         58 my $css_code = $ret->cssText;
113 27 100       571 if($val ne $css_code) { &$cref($ret) }
  5         11  
114 27         134 $ret;
115             };
116             }
117              
118             sub _modified {
119 276     276   361 my $self = shift;
120 276         426 my ($old_val,$new) = @_;
121 276   100     565 my $element = $self->[_elem] || return;
122 264 100       450 defined $new or $new = value $self;
123 264 100 100     1132 if ($self->[_name] =~ /^on(.*)/is
124             and my $listener_maker = $self->ownerDocument
125             ->event_attr_handler
126             ) {
127 2         13 my $eavesdropper = &$listener_maker(
128             $element, my $evt_name = lc $1, $new
129             );
130 2 50       25 defined $eavesdropper
131             and $element->event_handler(
132             $evt_name, $eavesdropper
133             );
134             }
135              
136             $element->trigger_event(
137 264 100       801 DOMAttrModified =>
138             attr_name => $self->[_name],
139             attr_change_type => 1,
140             prev_value => defined $old_val?$old_val:$new,
141             new_value => $new,
142             rel_node => $self,
143             )
144             }
145              
146             sub _text_node_modified {
147 6     6   9 my $self = shift;
148 6 100       17 if($$self[_styl]) {
149 1         3 $$self[_styl]->cssText(shift->newValue)
150             }
151             else {
152 5         17 $self->_modified($_[0]->prevValue,$_[0]->newValue);
153             }
154             }
155              
156              
157             # ----------- ATTR-ONLY METHODS ---------- #
158              
159             sub name {
160 568     568 1 1848 $_[0][_name];
161             }
162              
163             sub value {
164 944 100   944 1 8096 if(my $style = $_[0][_styl]) {
165 22         27 shift;
166 22         47 return $style->cssText(@_);
167             }
168 922 100       1724 if(@_ > 1){
169 255         343 my $old = $_[0][_val][0];
170 255 100       702 if(ref $old) {
    100          
171 1         4 $old = $old->data;
172 1         4 $_[0][_val][0]->data($_[1]);
173             # ~~~ Can we combine these two statements by using data’s retval?
174             }
175             elsif((my $new_val = $_[0][_val][0] = "$_[1]") ne $old) {
176 252 100       578 if($_[0]->get_event_listeners(
177             'DOMCharacterDataModified'
178             )) {
179 1         6 $_[0]->firstChild->trigger_event(
180             'DOMCharacterDataModified',
181             prev_value => $old,
182             new_value => $new_val
183             )
184             }
185             else {
186 251         497 $_[0]->_modified($old,$new_val);
187             }
188             }
189 255         1266 return $old;
190             }
191 667         956 my $val = $_[0][_val][0];
192 667 100       2258 ref $val ? $val->data : $val;
193             }
194              
195             sub specified {
196 24     24 1 57 my $attr=shift;
197 24   100     95 ($$attr[_elem]||return 1)->_attr_specified($$attr[_name]);
198             }
199              
200             sub ownerElement { # ~~~ If the attr is detached, is _element currently
201             # erased as it should be?
202             shift->_element || ()
203 6 100   6 1 23 }
204              
205             # ------------------ NODE METHODS ------------ #
206              
207             *nodeName = \&name;
208             *nodeValue = \&value;
209             *nodeType =\&ATTRIBUTE_NODE;
210              
211             # These all return null
212             *previousSibling = *nextSibling = *attributes = *parentNode = *prefix =
213             *namespaceURI = *localName = *normalize
214       41     = sub {};
215              
216             sub childNodes {
217 12 100 66 12 1 788 wantarray ? $_[0]->_val_as_node :(
218             $_[0]->_val_as_node,
219             $_[0][_list] ||= HTML::DOM::NodeList->new($_[0][_val])
220             );
221             }
222              
223             *firstChild = *lastChild = \&_val_as_node;
224              
225 34     34 1 1326 sub ownerDocument { $_[0][_doc] }
226              
227             sub insertBefore {
228 3     3 1 530 die HTML::DOM::Exception->new(NO_MODIFICATION_ALLOWED_ERR,
229             'The list of child nodes of an attribute cannot be modified');
230             }
231              
232             sub replaceChild {
233 11     11 1 41 my($self,$new_node,$old_node) = @_;
234 11         24 my $val = $self->_value;
235 11 100 66     51 die HTML::DOM::Exception->new(NOT_FOUND_ERR,
236             'The node passed to replaceChild is not a child of this attribute')
237             if !ref $val || $old_node != $val;
238 10 100 66     84 if(defined blessed $new_node and
239             isa $new_node 'HTML::DOM::DocumentFragment') {
240 3 100       8 (($new_node) = $new_node->childNodes) != 1 and
241             die HTML::DOM::Exception->new(HIERARCHY_REQUEST_ERR,
242             'The document fragment passed to replaceChild ' .
243             'does not have exactly one child node');
244             }
245 9 100 66     65 die HTML::DOM::Exception->new(HIERARCHY_REQUEST_ERR,
246             'The node passed to replaceChild is not a text node')
247             if !defined blessed $new_node ||
248             !$new_node->isa('HTML::DOM::Text');
249              
250 7         25 $old_node->trigger_event('DOMNodeRemoved',
251             rel_node => $self);
252 7   100     46 my $in_doc = $self->[_elem] && $self->[_elem]->is_inside(
253             $self->[_doc]
254             );
255 7 100       16 if($in_doc) {
256 1         3 $old_node->trigger_event('DOMNodeRemovedFromDocument')
257             }
258 7         28 my $old_parent = $new_node->parent;
259 7 100       21 $old_parent and $new_node->trigger_event('DOMNodeRemoved',
260             rel_node => $old_parent);
261 7 50       49 if($new_node->is_inside($self->[_doc])){
262 0         0 $new_node->trigger_event('DOMNodeRemovedFromDocument')
263             }
264             else {
265             # Even if it’s already the same document, it’s actually
266             # quicker just to set it than to check first.
267 7         21 $new_node->_set_ownerDocument( $self->[_doc] );
268             }
269              
270 7         35 ($_[0][_val][0] = $new_node)->detach;
271 7         21 weaken($new_node->{_parent}=($self));
272 7         16 $old_node->parent(undef);
273              
274 7         21 $new_node->trigger_event('DOMNodeInserted', rel_node => $self);
275 7 100       34 if($in_doc) {
276 1         3 $new_node->trigger_event('DOMNodeInsertedIntoDocument')
277             }
278             $_->trigger_event('DOMSubtreeModified')
279 7         36 for grep defined, $old_parent, $self;
280 7         25 $self->_modified($old_node->data, $new_node->data);
281              
282 7         37 $old_node;
283             }
284              
285              
286             *removeChild = *appendChild = \&insertBefore;
287              
288 1     1 1 240 sub hasChildNodes { 1 }
289              
290             sub cloneNode {
291             # ~~~ The spec. is not clear as to what should be done with an
292             # Attr’s child node when it is cloned shallowly. I’m here fol-
293             # lowing the behaviour of Safari and Firefox, which both ignore
294             # the ‘deep’ option.
295 4     4 1 10 my($self,$deep) = @_;
296 4         14 my $clone = bless [@$self], ref $self;
297 4         13 weaken $$clone[_doc];
298 4         14 delete $$clone[$_] for _elem, _list;
299 4         10 $$clone[_val] = ["$$clone[_val][0]"]; # copy the single-elem array
300             # that ->[_val] contains,
301             # flattening it in order effec-
302             # tively to clone it.
303 4         13 $clone;
304             }
305              
306 1     1 1 419 sub hasAttributes { !1 }
307              
308             sub isSupported {
309 2     2 1 156 my $self = shift;
310 2 50       8 return !1 if $_[0] =~ /events\z/i;
311 2         8 $HTML::DOM::Implementation::it->hasFeature(@_)
312             }
313              
314              
315             1
316              
317             __END__