File Coverage

blib/lib/CSS/DOM/Style.pm
Criterion Covered Total %
statement 158 163 96.9
branch 80 88 90.9
condition 27 36 75.0
subroutine 23 25 92.0
pod 13 14 92.8
total 301 326 92.3


line stmt bran cond sub pod time code
1             package CSS::DOM::Style;
2              
3             $VERSION = '0.15';
4              
5 22     22   37355 use warnings; no warnings qw' utf8';
  22     22   47  
  22         985  
  22         119  
  22         48  
  22         827  
6 22     22   112 use strict;
  22         52  
  22         802  
7              
8 22     22   4734 use CSS::DOM::Exception 'SYNTAX_ERR';
  22         49  
  22         1475  
9 22     22   29222 use CSS::DOM::Util qw 'escape_ident unescape';
  22         75  
  22         1559  
10 22     22   149 use Scalar::Util 'weaken';
  22         41  
  22         54898  
11              
12             # ~~~ use overload fallback => 1, '@{}' =>
13              
14             # Internal object structure
15             #
16             # Each style object is a hash ref:
17             # {
18             # owner => $owner_rule,
19             # parser => $property_parser,
20             # mod_handler => sub { ... }, # undef initially
21             # names => [...],
22             # props => {...},
23             # pri => {...}, # property priorities
24             # }
25             #
26             # The value of an element in the props hash can be one of three things
27             # 1) a CSSValue object
28             # 2) an array ref that is a blueprint for a CSSValue object:
29             # [ $css_code, $class, @constructor_args]
30             # 3) a string of css code
31             # Item (3) is only used when there is no property parser.
32              
33             sub parse {
34 393     393 1 302114 require CSS::DOM::Parser;
35 393         2007 goto &CSS::DOM::Parser::parse_style_declaration;
36             }
37              
38             sub new {
39 532     532 1 2781 my($class) = shift;
40              
41 532         2132 my $self = bless {}, $class;
42 532 100       1611 if(@_ == 1) {
43 128         432 $self->{owner} = shift;
44             }
45             else {
46 404         989 my %args = @_;
47 404         1846 $self->{owner} = delete $args{owner};
48 404         1107 $self->{parser}
49             = delete $args{property_parser};
50             }
51             {
52 532   100     1036 $self->{parser} ||= (
  532   100     5722  
53             ($self->{owner} || next)->parentStyleSheet || next
54             )->property_parser;
55             }
56 532         2687 weaken $self->{owner};
57 532         3230 return $self
58             }
59              
60             sub cssText {
61 94     94 1 986 my $self = shift;
62 94         141 my $out;
63 94 100       264 if (defined wantarray) {
64 88         302 $out = join "; ", map {
65 89         248 my $pri = $self->getPropertyPriority($_);
66 88         359 "$_: ".$self->getPropertyValue($_)." !"x!!$pri
67             . escape_ident($pri)
68 89         131 } @{$$self{names}};
69             }
70 94 100       1016 if(@_) {
71 6         14 my $css = shift;
72 6 100 33     43 !defined $css || !length $css and
73             @$self{'props','names'} = (), return $out;
74              
75 5         949 require CSS::DOM::Parser;
76 5         33 my $new =CSS::DOM::Parser::parse_style_declaration(
77             $css, property_parser => $$self{parser}
78             );
79              
80 5         26 @$self{'props','names'} = @$new{'props','names'};
81 5         271 _m($self);
82             }
83 93         436 return $out;
84             }
85              
86             sub getPropertyValue { # ~~~ Later I plan to make this return lists of
87             # scalars in list context (for list properties).
88 2901     2901 1 7352 my $self = shift;
89 2901   50     8791 my $props = $self->{props} || return '';
90 2901         6462 my $name = lc$_[0];
91              
92 2901 100       9290 if(my $spec = $self->{parser}) { serialise: {
93 1919 100       2370 if(my $p = $spec->get_property($name)) {
  1919         6216  
94 1918 100 66     7927 if(exists $p->{serialise} and my $s = $p->{serialise}) {
95 115         563 my @p = $spec->subproperty_names($name);
96 115         260 my %p;
97 115         397 for(@p) {
98 438         1181 my $v = $self->getPropertyValue($_) ;
99 438 100       1358 length $v or last serialise;
100 405 100       1179 $p{$_}
101             = $spec->get_property($_)->{default} eq $v ?'':$v;
102             }
103 82         391 return $s->(\%p);
104             }
105             }
106             }}
107              
108 2819 100       9989 exists $props->{$name}
109             or return return '';
110 2620         5737 my $val = $props->{$name};
111 2620 100       15151 return ref $val eq 'ARRAY' ? $$val[0]
    100          
112             : !ref $val ? $val
113             : $val->cssText;
114             }
115              
116             sub getPropertyCSSValue {
117 86     86 1 331 my $self = shift;
118 86 100       646 $self->{parser} or return;
119             exists +(my $props = $self->{props} || return)->{
120 85 100 50     582 my $name = lc$_[0]
121             } or return return;
122 81         184 my $valref = \$props->{$name};
123             return ref $$valref eq 'ARRAY'
124             ? scalar (
125             $$$valref[1]->can('new')
126 81 100 66     1339 || do {
127             (my $pack = $$$valref[1]) =~ s e::e/egg;
128             require "$pack.pm";
129             },
130             $$valref =
131             $$$valref[1]->new(
132             owner => $self, property => $name,
133             @$$valref[2..$#$$valref],
134             )
135             ) : $$valref;
136             }
137              
138             sub removeProperty {
139 102     102 1 1009 my $self = shift;
140 102         226 my $name = lc shift;
141              
142             # Get the value so we can return it
143 102         147 my $val;
144 102 100       317 $val = $self->getPropertyValue($name)
145             if defined wantarray;
146              
147             # Get names of subprops if we are dealing with a shorthand prop
148 102         138 my @to_delete;
149 102 100       335 if(my $spec = $self->{parser}) {
150 96         325 @to_delete = $spec->subproperty_names($name);
151             }
152 102 100       919 @to_delete or @to_delete = $name;
153              
154             # Delete the properties
155 102         245 for my $name(@to_delete) {
156 175   50     841 delete +($self->{props} || return $val)->{$name};
157 175 50       1945 @{$$self{names}} = grep $_ ne $name,
  175         1351  
158 175         284 @{$$self{names} || return $val};
159             }
160              
161 102         394 $val;
162             }
163              
164             sub getPropertyPriority {
165 92   100 92 1 150 return ${shift->{pri}||return ''}{lc shift} || ''
166             }
167              
168             sub setProperty {
169 1085     1085 1 27123 my ($self, $name, $value, $priority) = @_;
170              
171             # short-circuit for the common case
172 1085 100       3374 length $value or $self->removeProperty($name),return;
173              
174 1054         13601 require CSS'DOM'Parser;
175 1054 100       1955 my @tokens = eval { CSS'DOM'Parser'tokenise_value($value); }
  1054         15931  
176             or die CSS::DOM'Exception->new( SYNTAX_ERR, $@);
177              
178             # check for whitespace/comment assignment
179 1053 100       3854 $tokens[0] =~ /^s+\z/ and $self->removeProperty($name),return;
180              
181 1051   100     3680 my $props = $$self{props} ||= {};
182 1051   100     4027 my $pri = $$self{pri} ||= {};
183              
184 1051         1630 my $val;
185 1051 100       4344 if(my $spec = $self->{parser}) {
186 902 100       4368 my(@args) = $spec->match($name, @tokens)
187             or return;
188 875 100       3383 if(@args == 1) { # shorthand
189 117         219 while(my($k,$v) = each %{ $args[0] }) {
  735         3035  
190 618 100       1912 $self->removeProperty($k), next
191             if $v eq "";
192 82         227 exists $$props{$k=lc$k}
193 553 100       2097 or push @{$$self{names}}, $k;
194 553         1293 $$props{$k} = $v;
195 553         2062 $$pri{$k} = $priority;
196             }
197 117         762 return;
198             }
199             else {
200 758         1812 $val = \@args;
201             }
202             }
203              
204 907 100       3478 exists $$props{$name=lc$name} or push @{$$self{names}}, $name;
  135         571  
205 907   66     2967 $$props{$name} = $val || join "", @{ $tokens[1] };
206 907         2793 $$pri{$name} = $priority;
207              
208 907         2394 _m($self);
209             return
210 907         3412 }
211              
212             sub item {
213 4     4 1 12 my $ret = shift->{names}[shift];
214 4 100       26 return defined $ret ? $ret : ''
215             }
216              
217             sub parentRule {
218 3     3 1 524 shift->{owner}
219             }
220              
221             sub _set_property_tokens { # private
222 631     631   1310 my ($self,$name,$types,$tokens) = @_;
223              
224             # Parse out the priority first
225 631         828 my $priority;
226 631 100 66     2239 if($types =~ /(s?(ds?))i\z/ and $tokens->[$-[2]] eq '!') {
227 3         13 $types =~ s///;
228 3         23 $priority = unescape pop @$tokens;
229 3         17 pop @$tokens for 1..length $1;
230             } else {
231 628         1039 $priority = '';
232             }
233              
234             # Get the prop & priority hashes
235 631   100     3334 my $props = $$self{props} ||= {};
236 631   100     3160 my $pri = $$self{pri} ||={};
237              
238             # See if we need to parse the value
239 631         934 my $val;
240 631 100       1693 if(my $spec = $self->{parser}) {
241 13 100       64 my(@args) = $spec->match($name,$types,$tokens)
242             or return;
243 9 100       46 if(@args == 1) {
244 1         3 while(my($k,$v) = each %{ $args[0] }) {
  4         21  
245 3 100       25 $self->removeProperty($k), next
246             if $v eq "";
247 2         5 exists $$props{$k=lc$k}
248 2 50       10 or push @{$$self{names}}, $k;
249 2         6 $$props{$k} = $v;
250 2         6 $$pri{$k} = $priority;
251             }
252 1         6 return;
253             }
254             else {
255 8         25 $val = \@args;
256             }
257             }
258 618         1607 else { $val = join "", @$tokens }
259              
260             # Assign the value & priority
261 626 50   1   4276 exists $$props{$name=lc$name} or push @{$$self{names}}, $name;
  626         47520  
  1         17  
  1         3  
  1         23  
262 626         2450 $$props{$name} = $val;
263 626         3188 $$pri{$name} = $priority;
264             }
265              
266              
267             { my $prop_re = qr/[a-z]+(?:[A-Z][a-z]+)*/;
268             sub can {
269 0         0 SUPER::can { shift } @_ or
  0         0  
270 0 0   0 0 0 $_[0] =~ /^$prop_re\z/o ? \&{+shift} : undef;
    0          
271             }
272             sub AUTOLOAD {
273 2187     2187   598183 my $self = shift;
274 2187 50       17067 if(our $AUTOLOAD =~ /(?<=:)($prop_re)\z/o) {
275 2187         14912 (my $prop = $1) =~ s/([A-Z])/-\l$1/g;
276 2187         3160 my $val;
277             defined wantarray
278 2187 100       7674 and $val = $self->getPropertyValue($prop);
279 2187 100       9319 @_ and $self->setProperty($prop, shift);
280 2187         10612 return $val;
281             } else {
282 0         0 die "Undefined subroutine $AUTOLOAD called at ",
283             join(" line ", (caller)[1,2]), ".\n";
284             }
285             }
286 0     0   0 sub DESTROY{}
287             }
288             *cssFloat = \&float;
289              
290             sub modification_handler {
291 110     110 1 334 my $old = (my $self = shift)->{mod_handler};
292 110 100       304 $self->{mod_handler} = shift if @_;
293 110         583 $old;
294             }
295              
296             sub _m#odified
297             {
298 912 100   912   1792 &{$_[0]->{mod_handler} or return}($_[0]);
  912         3337  
299             }
300              
301 97     97 1 554 sub property_parser { shift->{parser} }
302              
303             sub length { # We put this one last to avoid having to say CORE::length
304             # elsewhere.
305 5 100   5 1 17 scalar @{shift->{names}||return 0}
  5         36  
306             }
307              
308              
309              
310             !()__END__()!
311              
312             =head1 NAME
313              
314             CSS::DOM::Style - CSS style declaration class for CSS::DOM
315              
316             =head1 VERSION
317              
318             Version 0.15
319              
320             =head1 SYNOPSIS
321              
322             use CSS::DOM::Style;
323            
324             $style = CSS::DOM::Style::parse(' text-decoration: none ');
325            
326             $style->cssText; # returns 'text-decoration: none'
327             $style->cssText('color: blue'); # replace contents
328            
329             $style->getPropertyValue('color'); # 'blue'
330             $style->color; # same
331             $style->setProperty(color=>'green'); # change it
332             $style->color('green'); # same
333              
334             =head1 DESCRIPTION
335              
336             This module provides the CSS style declaration class for L<CSS::DOM>. (A
337             style declaration is what comes between the braces in C<p { margin: 0 }>.)
338             It
339             implements
340             the CSSStyleDeclaration DOM interface.
341              
342             =head1 CONSTRUCTORS
343              
344             =over 4
345              
346             =item CSS::DOM::Style::parse( $string )
347              
348             =item CSS::DOM::Style::parse( $string, property_parser => $parser )
349              
350             This parses the C<$string> and returns a new style declaration
351             object. This is useful if you have text from an HTML C<style> attribute,
352             for instance.
353              
354             For details on C<$property_parser>, see L<CSS::DOM::PropertyParser>.
355              
356             =item new CSS::DOM::Style $owner_rule
357              
358             =item new CSS::DOM::Style owner => $owner_rule, property_parser => $p
359              
360             You don't normally need to call this, but, in case you do, here it is.
361             C<$owner_rule>, which is optional, is expected to be a L<CSS::DOM::Rule>
362             object, or a subclass like L<CSS::DOM::Rule::Style>.
363              
364             =back
365              
366             =head1 METHODS
367              
368             =over 4
369              
370             =item cssText ( $new_value )
371              
372             Returns the body of this style declaration (without the braces). If you
373             pass an argument, it will parsed and replace the existing CSS data.
374              
375             =item getPropertyValue ( $name )
376              
377             Returns the value of the named CSS property as a string.
378              
379             =item getPropertyCSSValue ( $name )
380              
381             Returns an object representing the property's value.
382             (See L<CSS::DOM::Value>.)
383              
384             =item removeProperty ( $name )
385              
386             Removes the named property, returning its value.
387              
388             =item getPropertyPriority
389              
390             Returns the property's priority. This is usually the empty string or the
391             word 'important'.
392              
393             =item setProperty ( $name, $value, $priority )
394              
395             Sets the CSS property named C<$name>, giving it a value of C<$value> and
396             setting the priority to C<$priority>.
397              
398             =item length
399              
400             Returns the number of properties
401              
402             =item item ( $index )
403              
404             Returns the name of the property at the given index.
405              
406             =item parentRule
407              
408             Returns the rule to which this declaration belongs.
409              
410             =item modification_handler ( $coderef )
411              
412             This method, not part of the DOM, allows you to attach a call-back routine
413             that is run whenever a change occurs to the style object (with the style
414             object as its only argument). If you call it
415             without an argument it returns the current handler. With an argument, it
416             returns the old value after setting it.
417              
418             =item property_parser
419              
420             This returns the parser that was passed to the constructor.
421              
422             =back
423              
424             This module also has methods for accessing each CSS property directly.
425             Simply capitalise each letter in a CSS property name that follows a hyphen,
426             then remove the hyphens, and you'll have the method name. E.g., call the
427             C<borderBottomWidth> method to get/set the border-bottom-width property.
428             One exception to this is that C<cssFloat> is the method used to access the
429             'float' property. (But you can also use the C<float> method, though it's
430             not part of the DOM standard.)
431              
432             =head1 SEE ALSO
433              
434             L<CSS::DOM>
435              
436             L<CSS::DOM::Rule::Style>
437              
438             L<CSS::DOM::PropertyParser>
439              
440             L<HTML::DOM::Element>