File: | blib/lib/CSS.pm |
Coverage: | 92.0% |
line | stmt | bran | cond | sub | time | code |
---|---|---|---|---|---|---|
1 | package CSS; | |||||
2 | ||||||
3 | 5 5 5 | 22 8 42 | use strict; | |||
4 | 5 5 5 | 29 11 28 | use warnings; | |||
5 | ||||||
6 | our $VERSION = 2.01_07; | |||||
7 | ||||||
8 | sub new { | |||||
9 | 9 | 26 | my $class = shift; | |||
10 | 9 | 36 | my $self = bless {}, $class; | |||
11 | ||||||
12 | 9 1 | 55 4 | my %opts = (ref $_[0]) ? ((ref $_[0] eq 'HASH') ? %{$_[0]} : () ) : @_; | |||
13 | ||||||
14 | 9 | 35 | $self->{atrules} = []; | |||
15 | 9 | 27 | $self->{rulesets} = []; | |||
16 | 9 | 29 | $self->{items} = []; | |||
17 | 9 | 26 | $self->{sheets} = []; | |||
18 | ||||||
19 | 9 | 32 | $self->{styles} = $self->{rulesets}; | |||
20 | ||||||
21 | 9 | 62 | $self->{grammar} = $opts{grammar} || 'CSS::Grammar::Core'; | |||
22 | 9 | 69 | $self->{adaptor} = $opts{adaptor} || 'CSS::Adaptor'; | |||
23 | ||||||
24 | 9 | 42 | return $self; | |||
25 | } | |||||
26 | ||||||
27 | sub read_file { | |||||
28 | 13 | 45 | my ($self, $path) = @_; | |||
29 | ||||||
30 | 13 | 41 | if (ref $path){ | |||
31 | 2 | 10 | if (ref $path eq 'ARRAY'){ | |||
32 | 1 1 | 2 8 | $self->read_file($_) for @$path; | |||
33 | 1 | 4 | return; | |||
34 | } | |||||
35 | } else { | |||||
36 | 11 | 39 | if ($path){ | |||
37 | 10 | 25 | local *IN; | |||
38 | 10 | 282 | open(IN, $path) or die "Couldn't open file: $!"; | |||
39 | 9 | 240 | my $source = join '',<IN>; | |||
40 | 9 | 62 | close(IN); | |||
41 | 9 | 52 | $self->parse_string($source) if $source; | |||
42 | 9 | 45 | return; | |||
43 | } | |||||
44 | } | |||||
45 | 2 | 2 | die "Only scalars and arrays accepted: $!"; | |||
46 | } | |||||
47 | ||||||
48 | sub read_string { | |||||
49 | 6 | 19 | my ($self, $data) = @_; | |||
50 | ||||||
51 | 6 | 19 | if (ref $data){ | |||
52 | 2 | 10 | if (ref $data eq 'ARRAY'){ | |||
53 | 1 1 | 2 6 | $self->read_string($_) for @$data; | |||
54 | 1 | 3 | return; | |||
55 | } | |||||
56 | } else { | |||||
57 | 4 | 21 | return $self->parse_string($data) if length $data; | |||
58 | } | |||||
59 | } | |||||
60 | ||||||
61 | sub parse_string { | |||||
62 | 12 | 39 | my ($self, $string) = @_; | |||
63 | ||||||
64 | 12 | 37 | my $grammar_class = $self->{grammar}; | |||
65 | 12 | 37 | return 0 unless $grammar_class; | |||
66 | ||||||
67 | 12 | 41 | $self->load_module($grammar_class); | |||
68 | 12 | 254 | my $grammar = eval "new $grammar_class"; | |||
69 | 12 | 96 | return 0 unless $grammar; | |||
70 | ||||||
71 | # | |||||
72 | # toke, lex, reduce & walk into a sheet | |||||
73 | # | |||||
74 | ||||||
75 | 12 | 48 | my $sheet = $grammar->parse($string); | |||
76 | ||||||
77 | 12 | 41 | unless (defined $sheet){ | |||
78 | ||||||
79 | 0 | 0 | die "Can't walk the match tree"; | |||
80 | 0 | 0 | return 0; | |||
81 | } | |||||
82 | ||||||
83 | ||||||
84 | # | |||||
85 | # merge the resultant sheet with the CSS object | |||||
86 | # | |||||
87 | ||||||
88 | 12 | 49 | $self->merge_sheet($sheet); | |||
89 | ||||||
90 | 12 | 36 | return 1; | |||
91 | } | |||||
92 | ||||||
93 | sub purge { | |||||
94 | 4 | 12 | my ($self) = @_; | |||
95 | ||||||
96 | 4 | 15 | $self->{atrules} = []; | |||
97 | 4 | 14 | $self->{rulesets} = []; | |||
98 | 4 | 12 | $self->{items} = []; | |||
99 | 4 | 17 | $self->{sheets} = []; | |||
100 | ||||||
101 | 4 | 41 | $self->{styles} = $self->{rulesets}; | |||
102 | } | |||||
103 | ||||||
104 | sub set_adaptor { | |||||
105 | 1 | 4 | my $self = shift; | |||
106 | 1 | 2 | my $adaptor = shift; | |||
107 | ||||||
108 | 1 | 4 | $self->{adaptor} = $adaptor; | |||
109 | } | |||||
110 | ||||||
111 | sub output { | |||||
112 | 5 | 12 | my $self = shift; | |||
113 | 5 | 28 | my $adaptor_class = shift || $self->{adaptor}; | |||
114 | ||||||
115 | 5 | 12 | unless ($adaptor_class){ | |||
116 | 0 | 0 | die "no adaptor class"; | |||
117 | } | |||||
118 | ||||||
119 | 5 | 16 | unless ($self->load_module($adaptor_class)){ | |||
120 | 0 | 0 | die "unable to load adaptor module"; | |||
121 | } | |||||
122 | ||||||
123 | 5 | 73 | my $adaptor = eval "$adaptor_class->new();"; | |||
124 | ||||||
125 | 5 | 18 | unless (defined $adaptor){ | |||
126 | 0 | 0 | die "can't create adaptor ($adaptor_class)"; | |||
127 | } | |||||
128 | ||||||
129 | 5 | 35 | unless ($adaptor->can('format_stylesheet')){ | |||
130 | 1 | 2 | die "adaptor can't format stylesheets"; | |||
131 | } | |||||
132 | ||||||
133 | 4 | 15 | return $adaptor->format_stylesheet($self); | |||
134 | } | |||||
135 | ||||||
136 | sub get_style_by_selector { | |||||
137 | ||||||
138 | 1 | 5 | return get_ruleset_by_selector(@_); | |||
139 | } | |||||
140 | ||||||
141 | sub get_ruleset_by_selector { | |||||
142 | 3 | 11 | my ($self, $sel_name) = @_; | |||
143 | ||||||
144 | 3 3 | 5 12 | for my $ruleset (@{$self->{rulesets}}){ | |||
145 | ||||||
146 | 7 | 21 | return $ruleset if $ruleset->match_selector($sel_name); | |||
147 | } | |||||
148 | ||||||
149 | 1 | 4 | return undef; | |||
150 | } | |||||
151 | ||||||
152 | sub merge_sheet { | |||||
153 | 12 | 39 | my ($self, $stylesheet) = @_; | |||
154 | ||||||
155 | 12 12 | 21 48 | for my $item (@{$stylesheet->{items}}){ | |||
156 | ||||||
157 | 41 41 | 64 124 | push @{$self->{items}}, $item; | |||
158 | 41 3 | 141 9 | push @{$self->{atrules}}, $item if ref $item eq 'CSS::AtRule'; | |||
159 | 41 38 | 148 149 | push @{$self->{rulesets}}, $item if ref $item eq 'CSS::Ruleset'; | |||
160 | } | |||||
161 | ||||||
162 | 12 12 | 25 46 | push @{$self->{sheets}}, $stylesheet; | |||
163 | } | |||||
164 | ||||||
165 | sub load_module { | |||||
166 | 17 | 53 | my ($self, $module) = @_; | |||
167 | ||||||
168 | 17 | 51 | my $file = "$module" . '.pm'; | |||
169 | 17 | 69 | $file =~ s{::}{/}g; | |||
170 | ||||||
171 | 17 11 | 75 29 | return eval { 1 } if $INC{$file}; | |||
172 | 6 | 189 | my $ret = eval "require \$file"; | |||
173 | 6 | 85 | return 0 unless $ret; | |||
174 | ||||||
175 | 6 | 99 | eval "\$module->import();"; | |||
176 | 6 | 30 | return 1; | |||
177 | } | |||||
178 | ||||||
179 | 1; | |||||
180 |