File Coverage

blib/lib/CSS/Tiny/Style.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package CSS::Tiny::Style;
2              
3 1     1   35954 use version; $VERSION = qv('0.0.3');
  1         3327  
  1         8  
4              
5 1     1   96 use warnings;
  1         2  
  1         141  
6 1     1   6 use strict;
  1         6  
  1         30  
7 1     1   4 use Carp;
  1         2  
  1         112  
8              
9 1     1   659 use CSS::Tiny;
  0            
  0            
10             use HTML::Element;
11              
12             package CSS::Tiny;
13              
14             sub selectors {
15             my $self = shift;
16             return keys %{ $self };
17             }
18              
19             sub styles {
20             my $self = shift;
21             my @styles = $self->_sorted_styles;
22              
23             return @styles;
24             }
25              
26             sub _sorted_styles {
27             return sort { $a->specificity <=> $b->specificity } shift->_all_styles
28             }
29              
30             sub _all_styles {
31             my $self = shift;
32             my @res;
33             while (my ($selector, $properties) = each %{ $self }) {
34             push @res, CSS::Tiny::Style->new($selector, $properties);
35             }
36             return @res;
37             }
38              
39             our $AUTOLOAD;
40              
41             sub AUTOLOAD {
42             my $self = shift;
43              
44             my $attr = $AUTOLOAD;
45             $attr =~ s/.*:://;
46              
47             return unless exists $self->{$attr};
48             return CSS::Tiny::Style->new($attr, $self->{$attr});
49             }
50              
51              
52             sub style {
53             my $self = shift;
54             my $style = shift;
55              
56             return unless exists $self->{$style};
57             return CSS::Tiny::Style->new($style, $self->{$style});
58             }
59              
60             package CSS::Tiny::Style;
61              
62             use warnings;
63             use strict;
64             no warnings qw/uninitialized/;
65              
66             use Carp;
67             use overload
68             '""' => \&stringify;
69              
70             sub new {
71             my $class = shift;
72             $class = ref $class || $class;
73              
74             my $selector = shift || croak "Need a selector";
75             my $properties = shift || {};
76              
77             my $self = $properties;
78             $self->{_selector} = $selector;
79             $self->{_selarr} = undef; # set later to [];
80              
81             bless $self, $class;
82             }
83              
84             sub stringify {
85             my $self = shift;
86             my @fields = grep { !/^_/ } keys %{ $self };
87             for (@fields) { $_ = join ":", $_, $self->{$_} };
88             return join ';', @fields;
89             }
90              
91             ##################################################################################################################
92             # from css spec at http://www.w3.org/TR/REC-CSS2/selector.html#q1
93             ##################################################################################################################
94             #
95             # * Matches any element. Universal selector
96             #
97             # E Matches any E element (i.e., an element of type E).
98             #
99             # E F Matches any F element that is a descendant of an E element.
100             #
101             # E > F Matches any F element that is a child of an element E.
102             #
103             # E:first-child Matches element E when E is the first child of its parent.
104             #
105             # E + F Matches any F element immediately preceded by a sibling element E.
106             #
107             # E[foo] Matches any E element with the "foo" attribute set (whatever the value).
108             #
109             # E[foo="warning"] Matches any E element whose "foo" attribute value is exactly equal to "warning".
110             #
111             # E[foo~="warning"] Matches any E element whose "foo" attribute value is a list of space-separated values,
112             # one of which is exactly equal to "warning".
113             #
114             # E[lang|="en"] Matches any E element whose "lang" attribute has a hyphen-separated list of values
115             # beginning (from the left) with "en".
116             #
117             # DIV.warning Language specific. (In HTML, the same as DIV[class~="warning"].)
118             #
119             # E#myid Matches any E element with ID equal to "myid". ID selectors
120             #
121             ##################################################################################################################
122              
123             sub element_match {
124             my $self = shift;
125             my $el = shift || croak "No element to match";
126              
127             return if lc $el->tag eq 'html';
128             return if lc $el->tag eq 'head';
129             return if $el->look_up("_tag", "head");
130              
131             my @sel = @_;
132              
133             @sel = $self->selector_array unless @sel;
134              
135              
136             no strict 'refs';
137              
138             my $sel = shift @sel;
139             for (qw/tag id class/) {
140             my $sub = "_$_";
141              
142             next unless (my $val = &$sub($sel)); # skip test if no value in selector
143              
144             my $att = /tag/ ? '_tag' : $_; # HTML::Element behaviour
145             $val = $val eq '*' ? $el->tag : $val; # always matches
146              
147              
148             return unless ($val && ((lc $val) eq (lc $el->attr($att))));
149             }
150             use strict 'refs';
151             return 1;
152             }
153              
154             sub match {
155             my $self = shift;
156              
157             # the next argument is an element or a listref of elements
158             my @el = shift; if (ref $el[0] eq 'ARRAY') { @el = @{$el[0]} };
159              
160             my ($sel, $rel, @sel);
161             if (@_) {
162             (
163             $sel, # the first selector
164             $rel, # the relationship, i.e.: '>' or '+' or ' '
165             @sel # the remaining selector
166             ) = @_;
167             } else {
168             ($sel, $rel, @sel) = $self->selector_array;
169             }
170              
171              
172             #+++++++++++++++++++++++++++++++++++++++++++++++++++++
173             # 1) loop through elements
174             # 2) check if one matches
175             # 3) if it matches, loop through his relatives
176             # 4) return true if one matches
177             #+++++++++++++++++++++++++++++++++++++++++++++++++++++
178              
179             my $match = 0;
180             for (@el) {
181             if ($self->element_match($_, $sel)) {
182             # if element matches, check his relatives
183             if ($rel) {
184             my $rellist = $_->$rel;
185             $match = $self->match($rellist, @sel)
186             } else {
187             $match = 1;
188             }
189             }
190             last if $match;
191             };
192             return $match;
193             }
194              
195             sub selector {
196             my $self = shift;
197             $self->{'_selector'};
198             }
199              
200              
201             *selarr = *selector_array;
202             sub selector_array {
203             my $self = shift;
204             unless (defined $self->{_selarr}) {
205             my $selector = $self->selector;
206             my @sel = _sel_arr($selector);
207             $self->{_selarr} = [@sel];
208             }
209             return @{ $self->{_selarr} };
210             }
211              
212             sub add_to {
213             my $self = shift;
214             my $element = shift;
215              
216             my $style = $element->attr('style');
217             $style = $style eq "" ? $self->stringify : (join ";", ($style, $self->stringify));
218             #$style =~ s/^;//; # why why why?
219             $element->attr('style', $style)
220             }
221              
222             ###########################################################################################################
223             # from CSS spec at http://www.w3.org/TR/CSS21/cascade.html#specificity
224             ###########################################################################################################
225             # A selector's specificity is calculated as follows:
226             #
227             # * count the number of ID attributes in the selector (= a)
228             # * count the number of other attributes and pseudo-classes in the selector (= b)
229             # * count the number of element names in the selector (= c)
230             # * ignore pseudo-elements.
231             #
232             # Concatenating the three numbers a-b-c (in a number system with a large base) gives the specificity.
233             #
234             # Example(s):
235             #
236             # Some examples:
237             #
238             # * {} /* a=0 b=0 c=0 -> specificity = 0 */
239             # LI {} /* a=0 b=0 c=1 -> specificity = 1 */
240             # UL LI {} /* a=0 b=0 c=2 -> specificity = 2 */
241             # UL OL+LI {} /* a=0 b=0 c=3 -> specificity = 3 */
242             # H1 + *[REL=up]{} /* a=0 b=1 c=1 -> specificity = 11 */
243             # UL OL LI.red {} /* a=0 b=1 c=3 -> specificity = 13 */
244             # LI.red.level {} /* a=0 b=2 c=1 -> specificity = 21 */
245             # #x34y {} /* a=1 b=0 c=0 -> specificity = 100 */
246             ###########################################################################################################
247              
248             sub specificity {
249             my $self = shift;
250             return 0 if $self->selector eq '*';
251             return (
252             $self->count_ids * 100 +
253             $self->count_attributes * 10 +
254             $self->count_tags
255             );
256             }
257              
258             sub tag { _tag(shift->selector) }
259              
260             sub id { _id(shift->selector) }
261              
262             sub class { _class(shift->selector) }
263              
264             sub count_ids {
265             my @sel = shift->selarr;
266             return scalar grep { /\#/ } @sel
267             }
268              
269             sub count_attributes {
270             my @sel = shift->selarr;
271             return scalar grep { /\./ || /\[/ } @sel
272             }
273              
274             sub count_tags {
275             my @sel = shift->selarr;
276             return int (((scalar grep { !/\*/ } @sel) / 2) + 1)
277             }
278              
279             sub _sel_arr {
280             local $_ = shift;
281             my @d;
282              
283             while ($_) {
284             my ($tag, $op);
285              
286              
287             s/([a-zA-Z0-9.\#\*]+)\s*$//; $tag = $1;
288             $op = $1 if (s/(\s*[+>]*\s*)$//);
289              
290              
291             push @d, $tag if $tag;
292              
293             for ($op) {
294             /\+/ && do { push @d, 'left'; last; };
295             /\>/ && do { push @d, 'parent'; last; };
296             /^\s+$/ && do { push @d, 'lineage'; last; };
297             }
298             }
299             return @d;
300             }
301              
302             sub _tag {
303             local $_ = shift;
304              
305             return '*' if (/^\./ || /^\#/);
306             /^(\w+)[\#\.]*/;
307             return $1;
308             }
309              
310             sub _id {
311             local $_ = shift;
312             /\#(\w+)\W*/;
313             return $1;
314             }
315              
316             sub _class {
317             local $_ = shift;
318             /\.(\w+)\W*/;
319             return $1;
320             }
321              
322              
323             1;
324             __END__