File Coverage

blib/lib/CSS/Tiny.pm
Criterion Covered Total %
statement 71 76 93.4
branch 17 26 65.3
condition 2 2 100.0
subroutine 12 14 85.7
pod 9 9 100.0
total 111 127 87.4


line stmt bran cond sub pod time code
1             package CSS::Tiny;
2              
3             # See POD at end for docs
4              
5 5     5   62369 use strict;
  5         10  
  5         264  
6             BEGIN {
7 5     5   115 require 5.005;
8 5         18 $CSS::Tiny::VERSION = '1.20';
9 5         5835 $CSS::Tiny::errstr  = '';
10             }
11              
12             # Create an empty object
13 4     4 1 1799 sub new { bless {}, shift }
14              
15             # Create an object from a file
16             sub read {
17 3     3 1 2968 my $class = shift;
18              
19             # Check the file
20 3 50       13 my $file = shift or return $class->_error( 'You did not specify a file name' );
21 3 50       43 return $class->_error( "The file '$file' does not exist" ) unless -e $file;
22 3 50       9 return $class->_error( "'$file' is a directory, not a file" ) unless -f _;
23 3 50       9 return $class->_error( "Insufficient permissions to read '$file'" ) unless -r _;
24              
25             # Read the file
26 3         11 local $/ = undef;
27 3 50       54 open( CSS, $file ) or return $class->_error( "Failed to open file '$file': $!" );
28 3         32 my $contents = <CSS>;
29 3         17 close( CSS );
30              
31 3         9 $class->read_string( $contents )
32             }
33              
34             # Create an object from a string
35             sub read_string {
36 8 100   8 1 4732 my $self = ref $_[0] ? shift : bless {}, shift;
37              
38             # Flatten whitespace and remove /* comment */ style comments
39 8         14 my $string = shift;
40 8         18 $string =~ tr/\n\t/ /;
41 8         16 $string =~ s!/\*.*?\*\/!!g;
42              
43             # Split into styles
44 8         88 foreach ( grep { /\S/ } split /(?<=\})/, $string ) {
  29         61  
45 21 50       88 unless ( /^\s*([^{]+?)\s*\{(.*)\}\s*$/ ) {
46 0         0 return $self->_error( "Invalid or unexpected style data '$_'" );
47             }
48              
49             # Split in such a way as to support grouped styles
50 21         30 my $style = $1;
51 21         28 my $properties = $2;
52 21         28 $style =~ s/\s{2,}/ /g;
53 21         36 my @styles = grep { s/\s+/ /g; 1; } grep { /\S/ } split /\s*,\s*/, $style;
  23         34  
  23         37  
  23         46  
54 21   100     27 foreach ( @styles ) { $self->{$_} ||= {} }
  23         107  
55              
56             # Split into properties
57 21         35 foreach ( grep { /\S/ } split /\;/, $properties ) {
  43         76  
58 30 50       105 unless ( /^\s*(\*?[\w._-]+)\s*:\s*(.*?)\s*$/ ) {
59 0         0 return $self->_error( "Invalid or unexpected property '$_' in style '$style'" );
60             }
61 30         32 foreach ( @styles ) { $self->{$_}->{lc $1} = $2 }
  32         101  
62             }
63             }
64              
65             $self
66 8         24 }
67              
68             # Copy an object, using Clone.pm if available
69 5 100   5 1 13 BEGIN { local $@; eval "use Clone 'clone';"; eval <<'END_PERL' if $@; }
  5     5   360  
  5     1   1633  
  5         2245  
  5         9821  
  5         250  
  1         421  
  1         5  
  1         25  
  5         6  
  5         5  
  5         10  
  6         14  
  1         3  
70             sub clone {
71             my $self = shift;
72             my $copy = ref($self)->new;
73             foreach my $key ( keys %$self ) {
74             my $section = $self->{$key};
75             $copy->{$key} = {};
76             foreach ( keys %$section ) {
77             $copy->{$key}->{$_} = $section->{$_};
78             }
79             }
80             $copy;
81             }
82             END_PERL
83              
84             # Save an object to a file
85             sub write {
86 1     1 1 623 my $self = shift;
87 1 50       4 my $file = shift or return $self->_error( 'No file name provided' );
88              
89             # Write the file
90 1 50       77 open( CSS, '>'. $file ) or return $self->_error( "Failed to open file '$file' for writing: $!" );
91 1         3 print CSS $self->write_string;
92 1         49 close( CSS );
93             }
94              
95             # Save an object to a string
96             sub write_string {
97 6     6 1 745 my $self = shift;
98              
99             # Iterate over the styles
100             # Note: We use 'reverse' in the sort to avoid a special case related
101             # to A:hover even though the file ends up backwards and looks funny.
102             # See http://www.w3.org/TR/CSS2/selector.html#dynamic-pseudo-classes
103 6         7 my $contents = '';
104 6         19 foreach my $style ( reverse sort keys %$self ) {
105 8         10 $contents .= "$style {\n";
106 8         5 foreach ( sort keys %{ $self->{$style} } ) {
  8         19  
107 12         27 $contents .= "\t" . lc($_) . ": $self->{$style}->{$_};\n";
108             }
109 8         11 $contents .= "}\n";
110             }
111              
112 6         25 return $contents;
113             }
114              
115             # Generate a HTML fragment for the CSS
116             sub html {
117 2 100   2 1 284 my $css = $_[0]->write_string or return '';
118 1         5 return "<style type=\"text/css\">\n<!--\n${css}-->\n</style>";
119             }
120              
121             # Generate an xhtml fragment for the CSS
122             sub xhtml {
123 2 100   2 1 288 my $css = $_[0]->write_string or return '';
124 1         7 return "<style type=\"text/css\">\n/* <![CDATA[ */\n${css}/* ]]> */\n</style>";
125             }
126              
127             # Error handling
128 0     0 1 0 sub errstr { $CSS::Tiny::errstr }
129 0     0   0 sub _error { $CSS::Tiny::errstr = $_[1]; undef }
  0         0  
130              
131             1;
132              
133             __END__
134            
135             =pod
136            
137             =head1 NAME
138            
139             CSS::Tiny - Read/Write .css files with as little code as possible
140            
141             =head1 SYNOPSIS
142            
143             # In your .css file
144             H1 { color: blue }
145             H2 { color: red; font-family: Arial }
146             .this, .that { color: yellow }
147            
148             # In your program
149             use CSS::Tiny;
150            
151             # Create a CSS stylesheet
152             my $CSS = CSS::Tiny->new();
153            
154             # Open a CSS stylesheet
155             $CSS = CSS::Tiny->read( 'style.css' );
156            
157             # Reading properties
158             my $header_color = $CSS->{H1}->{color};
159             my $header2_hashref = $CSS->{H2};
160             my $this_color = $CSS->{'.this'}->{color};
161             my $that_color = $CSS->{'.that'}->{color};
162            
163             # Changing styles and properties
164             $CSS->{'.newstyle'} = { color => '#FFFFFF' }; # Add a style
165             $CSS->{H1}->{color} = 'black'; # Change a property
166             delete $CSS->{H2}; # Delete a style
167            
168             # Save a CSS stylesheet
169             $CSS->write( 'style.css' );
170            
171             # Get the CSS as a <style>...</style> tag
172             $CSS->html;
173            
174             =head1 DESCRIPTION
175            
176             C<CSS::Tiny> is a perl class to read and write .css stylesheets with as
177             little code as possible, reducing load time and memory overhead. CSS.pm
178             requires about 2.6 meg or ram to load, which is a large amount of
179             overhead if you only want to do trivial things.
180             Memory usage is normally scoffed at in Perl, but in my opinion should be
181             at least kept in mind.
182            
183             This module is primarily for reading and writing simple files, and anything
184             we write shouldn't need to have documentation/comments. If you need
185             something with more power, move up to CSS.pm. With the increasing complexity
186             of CSS, this is becoming more common, but many situations can still live
187             with simple CSS files.
188            
189             =head2 CSS Feature Support
190            
191             C<CSS::Tiny> supports grouped styles of the form
192             C<this, that { color: blue }> correctly when reading, ungrouping them into
193             the hash structure. However, it will not restore the grouping should you
194             write the file back out. In this case, an entry in the original file of
195             the form
196            
197             H1, H2 { color: blue }
198            
199             would become
200            
201             H1 { color: blue }
202             H2 { color: blue }
203            
204             C<CSS::Tiny> handles nested styles of the form C<P EM { color: red }>
205             in reads and writes correctly, making the property available in the
206             form
207            
208             $CSS->{'P EM'}->{color}
209            
210             C<CSS::Tiny> ignores comments of the form C</* comment */> on read
211             correctly, however these comments will not be written back out to the
212             file.
213            
214             =head1 CSS FILE SYNTAX
215            
216             Files are written in a relatively human-orientated form, as follows:
217            
218             H1 {
219             color: blue;
220             }
221             .this {
222             color: red;
223             font-size: 10px;
224             }
225             P EM {
226             color: yellow;
227             }
228            
229             When reading and writing, all property descriptors, for example C<color>
230             and C<font-size> in the example above, are converted to lower case. As an
231             example, take the following CSS.
232            
233             P {
234             Font-Family: Verdana;
235             }
236            
237             To get the value C<'Verdana'> from the object C<$CSS>, you should
238             reference the key C<$CSS-E<gt>{P}-E<gt>{font-family}>.
239            
240             =head1 METHODS
241            
242             =head2 new
243            
244             The constructor C<new> creates and returns an empty C<CSS::Tiny> object.
245            
246             =head2 read $filename
247            
248             The C<read> constructor reads a CSS stylesheet, and returns a new
249             C<CSS::Tiny> object containing the properties in the file.
250            
251             Returns the object on success, or C<undef> on error.
252            
253             =head2 read_string $string
254            
255             The C<read_string> constructor reads a CSS stylesheet from a string.
256            
257             Returns the object on success, or C<undef> on error.
258            
259             =head2 clone
260            
261             The C<clone> method creates an identical copy of an existing C<CSS::Tiny>
262             object.
263            
264             =head2 write_string
265            
266             Generates the stylesheet for the object and returns it as a string.
267            
268             =head2 write
269            
270             The C<write $filename> generates the stylesheet for the properties, and
271             writes it to disk. Returns true on success. Returns C<undef> on error.
272            
273             =head2 html
274            
275             The C<html> method generates the CSS, but wrapped in a C<style> HTML tag,
276             so that it can be dropped directly onto a HTML page.
277            
278             =head2 xhtml
279            
280             The C<html> method generates the CSS, but wrapped in a C<style> XHTML tag,
281             so that it can be dropped directly onto an XHTML page.
282            
283             =head2 errstr
284            
285             When an error occurs, you can retrieve the error message either from the
286             C<$CSS::Tiny::errstr> variable, or using the C<errstr> method.
287            
288             =head1 CAVEATS
289            
290             =head2 CSS Rule Order
291            
292             While the order of rules in CSS is important, this is one of the features
293             that is sacrificed to keep things small and dependency-free. If you need
294             to preserve order yourself, we recommend that you upgrade to the more
295             powerful L<CSS> module.
296            
297             If this is not possible in your case, alternatively it can be done with the
298             help of another module such as L<Tie::IxHash>:
299            
300             my $css = CSS::Tiny->new;
301             tie %$css, 'Tie::IxHash';
302             $css->read('style.css');
303            
304             Note: You will also need to remember to add the additional dependency to
305             your code or module in this case.
306            
307             =head1 SUPPORT
308            
309             Bugs should be reported via the CPAN bug tracker at
310            
311             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CSS-Tiny>
312            
313             For other issues, or commercial enhancement or support, contact the author.
314            
315             =head1 AUTHOR
316            
317             Adam Kennedy E<lt>adamk@cpan.orgE<gt>
318            
319             =head1 SEE ALSO
320            
321             L<CSS>, L<http://www.w3.org/TR/REC-CSS1>, L<Config::Tiny>, L<http://ali.as/>
322            
323             =head1 COPYRIGHT
324            
325             Copyright 2002 - 2010 Adam Kennedy.
326            
327             This program is free software; you can redistribute
328             it and/or modify it under the same terms as Perl itself.
329            
330             The full text of the license can be found in the
331             LICENSE file included with this module.
332            
333             =cut
334