File Coverage

blib/lib/HTML/DOM/Attr.pm
Criterion Covered Total %
statement 120 123 97.5
branch 54 60 90.0
condition 21 28 75.0
subroutine 33 33 100.0
pod 12 15 80.0
total 240 259 92.6


line stmt bran cond sub pod time code
1             package HTML::DOM::Attr;
2              
3 25     25   93 use warnings;
  25         28  
  25         1296  
4              
5             # attribute constants (array elems)
6             BEGIN{
7 25     25   34 my $x;
8 25         648 %constants
9             = map +($_=>$x++), qw[
10             _doc _elem _name _val _list _styl
11             ]
12             }
13 25     25   97 use constant 1.03 \%constants;
  25         508  
  25         2609  
14             # after compilation:
15             delete @{__PACKAGE__."::"}{ keys %constants, 'constants' };
16              
17 25     25   93 use strict;
  25         33  
  25         1069  
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   259 '""' => sub { shift->value },
30 25     25   85 'bool' => sub{1};
  25     1164   29  
  25         219  
  1164         2195  
31              
32 25         1246 use HTML::DOM::Exception qw'NOT_FOUND_ERR NO_MODIFICATION_ALLOWED_ERR
33 25     25   1603 HIERARCHY_REQUEST_ERR ';
  25         26  
34 25     25   84 use HTML::DOM::Node 'ATTRIBUTE_NODE';
  25         26  
  25         756  
35 25     25   82 use Scalar::Util qw'weaken blessed refaddr';
  25         26  
  25         28289  
36              
37             require HTML::DOM::EventTarget;
38             require HTML::DOM::NodeList;
39              
40             our @ISA = 'HTML::DOM::EventTarget';
41              
42             our $VERSION = '0.057';
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 290 my @self;
49 348 100       1236 @self[_name,_val] = ($_[1],[defined$_[2]?$_[2]:'']);
50             # value should be an empty
51 348         932 bless \@self, shift; # string, not undef
52             }
53              
54              
55              
56             sub _set_ownerDocument {
57 369     369   1394 weaken ($_[0][_doc] = $_[1]);
58             }
59              
60             sub _element { # This is like ownerElement, except that it lets you set it.
61 394 100   394   696 if(@_ > 1) {
62 369         356 my $old = $_[0][_elem];
63 369         628 weaken ($_[0][_elem] = $_[1]);
64 369         487 return $old
65             }
66 25         106 $_[0][_elem];
67             }
68              
69             sub DOES {
70 1 50   1 0 237 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   54 $_[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   854 my $val = $_[0][_val][0];
81             defined blessed $val && $val->isa('HTML::DOM::Text')
82             ? $val
83 26 100 66     203 : do {
84 13 100       34 my $val = $_[0][_val][0] =
85             $_[0]->ownerDocument->createTextNode(
86             $_[0][_styl] ? $_[0][_styl]->cssText : $val
87             );
88 13         38 weaken($val->{_parent}=($_[0]));
89 13         77 $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 43 my $self = shift;
99 39   66     124 $self->[_styl] ||= do{
100             require CSS::DOM::Style,
101 27         618 my $ret = CSS::DOM::Style::parse(my $val = $self->value);
102             $ret->modification_handler(my $cref = sub {
103 13 100   13   1358 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         3 $text->attr('text', shift->cssText)
108             }
109 13         30 $self->_modified;
110 27         16539 });
111 27         135 weaken $self;
112 27         53 my $css_code = $ret->cssText;
113 27 100       443 if($val ne $css_code) { &$cref($ret) }
  5         12  
114 27         145 $ret;
115             };
116             }
117              
118             sub _modified {
119 276     276   257 my $self = shift;
120 276         275 my ($old_val,$new) = @_;
121 276   100     522 my $element = $self->[_elem] || return;
122 264 100       437 defined $new or $new = value $self;
123 264 100 100     1069 if ($self->[_name] =~ /^on(.*)/is
124             and my $listener_maker = $self->ownerDocument
125             ->event_attr_handler
126             ) {
127 2         7 my $eavesdropper = &$listener_maker(
128             $element, my $evt_name = lc $1, $new
129             );
130 2 50       19 defined $eavesdropper
131             and $element->event_handler(
132             $evt_name, $eavesdropper
133             );
134             }
135              
136             $element->trigger_event(
137 264 100       915 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   12 my $self = shift;
148 6 100       19 if($$self[_styl]) {
149 1         6 $$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 1907 $_[0][_name];
161             }
162              
163             sub value {
164 944 100   944 1 7847 if(my $style = $_[0][_styl]) {
165 22         15 shift;
166 22         43 return $style->cssText(@_);
167             }
168 922 100       1439 if(@_ > 1){
169 255         539 my $old = $_[0][_val][0];
170 255 100       729 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       601 if($_[0]->get_event_listeners(
177             'DOMCharacterDataModified'
178             )) {
179 1         5 $_[0]->firstChild->trigger_event(
180             'DOMCharacterDataModified',
181             prev_value => $old,
182             new_value => $new_val
183             )
184             }
185             else {
186 251         398 $_[0]->_modified($old,$new_val);
187             }
188             }
189 255         1263 return $old;
190             }
191 667         662 my $val = $_[0][_val][0];
192 667 100       2281 ref $val ? $val->data : $val;
193             }
194              
195             sub specified {
196 24     24 1 51 my $attr=shift;
197 24   100     108 ($$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 15 }
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 823 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 1254 sub ownerDocument { $_[0][_doc] }
226              
227             sub insertBefore {
228 3     3 1 682 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 67 my($self,$new_node,$old_node) = @_;
234 11         34 my $val = $self->_value;
235 11 100 66     63 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     81 if(defined blessed $new_node and
239             isa $new_node 'HTML::DOM::DocumentFragment') {
240 3 100       9 (($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 33     55 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         19 $old_node->trigger_event('DOMNodeRemoved',
251             rel_node => $self);
252 7   100     51 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         22 my $old_parent = $new_node->parent;
259 7 100       23 $old_parent and $new_node->trigger_event('DOMNodeRemoved',
260             rel_node => $old_parent);
261 7 50       27 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         23 $new_node->_set_ownerDocument( $self->[_doc] );
268             }
269              
270 7         25 ($_[0][_val][0] = $new_node)->detach;
271 7         16 weaken($new_node->{_parent}=($self));
272 7         16 $old_node->parent(undef);
273              
274 7         16 $new_node->trigger_event('DOMNodeInserted', rel_node => $self);
275 7 100       25 if($in_doc) {
276 1         4 $new_node->trigger_event('DOMNodeInsertedIntoDocument')
277             }
278             $_->trigger_event('DOMSubtreeModified')
279 7         37 for grep defined, $old_parent, $self;
280 7         28 $self->_modified($old_node->data, $new_node->data);
281              
282 7         38 $old_node;
283             }
284              
285              
286             *removeChild = *appendChild = \&insertBefore;
287              
288 1     1 1 306 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 7 my($self,$deep) = @_;
296 4         12 my $clone = bless [@$self], ref $self;
297 4         11 weaken $$clone[_doc];
298 4         15 delete $$clone[$_] for _elem, _list;
299 4         12 $$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         9 $clone;
304             }
305              
306 1     1 1 382 sub hasAttributes { !1 }
307              
308             sub isSupported {
309 2     2 1 316 my $self = shift;
310 2 50       5 return !1 if $_[0] =~ /events\z/i;
311 2         10 $HTML::DOM::Implementation::it->hasFeature(@_)
312             }
313              
314              
315             1
316              
317             __END__