File Coverage

blib/lib/CSS/Object/Parser/Default.pm
Criterion Covered Total %
statement 66 68 97.0
branch 7 14 50.0
condition 5 10 50.0
subroutine 11 11 100.0
pod 2 2 100.0
total 91 105 86.6


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## CSS Object Oriented - ~/lib/CSS/Object/Parser/Default.pm
3             ## Version v0.1.0
4             ## Copyright(c) 2020 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <@sitael.tokyo.deguest.jp>
6             ## Created 2020/08/09
7             ## Modified 2020/08/09
8             ##
9             ##----------------------------------------------------------------------------
10             package CSS::Object::Parser::Default;
11             BEGIN
12             {
13 4     4   3305 use strict;
  4         10  
  4         146  
14 4     4   21 use warnings;
  4         8  
  4         106  
15 4     4   31 use Module::Generic;
  4         11  
  4         30  
16 4     4   1266 use parent qw( CSS::Object::Parser );
  4         10  
  4         26  
17 4     4   206 use CSS::Object::Rule;
  4         13  
  4         33  
18 4     4   923 use CSS::Object::Selector;
  4         9  
  4         29  
19 4     4   842 use CSS::Object::Property;
  4         9  
  4         30  
20 4     4   881 use Devel::Confess;
  4         10  
  4         18  
21 4     4   3960 our $VERSION = 'v0.1.0';
22             };
23              
24             ## add a style to the style list
25             # From css spec at http://www.w3.org/TR/REC-CSS2/selector.html#q1
26             # * Matches any element. Universal selector
27             # E Matches any E element (i.e., an element of type E).
28             # E F Matches any F element that is a descendant of an E element.
29             # E > F Matches any F element that is a child of an element E.
30             # E:first-child Matches element E when E is the first child of its parent.
31             # E + F Matches any F element immediately preceded by a sibling element E.
32             # E[foo] Matches any E element with the "foo" attribute set (whatever the value).
33             # E[foo="warning"] Matches any E element whose "foo" attribute value is exactly equal to "warning".
34             # E[foo~="warning"] Matches any E element whose "foo" attribute value is a list of space-separated values,
35             # one of which is exactly equal to "warning".
36             # E[lang|="en"] Matches any E element whose "lang" attribute has a hyphen-separated list of values
37             # beginning (from the left) with "en".
38             # DIV.warning Language specific. (In HTML, the same as DIV[class~="warning"].)
39             # E#myid Matches any E element with ID equal to "myid". ID selectors
40             sub add_rule
41             {
42 18     18 1 41 my $self = shift( @_ );
43 18         54 my $style = shift( @_ );
44 18         74 my $contents = shift( @_ );
45 18   50     81 my $css = $self->css || return( $self->error( "Our css object is gone!" ) );
46            
47             # $self->message( 3, "Passing the format object (", $self->format, ") to new CSS::Object::Rule." );
48             # my $rule = CSS::Object::Rule->new(
49 18   50     403 my $rule = $css->new_rule(
50             # format => $self->format,
51             debug => $self->debug,
52             ) || return( $self->pass_error( CSS::Object::Rule->error ) );
53              
54             ## parse the selectors
55 18         706 for my $name ( split( /[[:blank:]\h]*,[[:blank:]\h]*/, $style ) )
56             {
57             # $self->message( 3, "Adding new selector with name '$name'." );
58             ## my $sel = CSS::Object::Selector->new({
59 33   50     524 my $sel = $css->new_selector(
60             name => $name,
61             # format => $self->format,
62             debug => $self->debug,
63             ) || return( $self->error( "Unable to create a new CSS::Object::Selector objet: ", CSS::Object::Selector->error ) );
64 33 50       114 $rule->add_selector( $sel ) || return( $self->error( "Unable to add selector name '$name' to rule: ", $rule->error ) );
65             }
66              
67             ## parse the properties
68             # $self->message( 3, "Rule content is (before comment processing): '$contents'" );
69             ## Check possible comments and replace any ';' inside so they do not mess up this parsing here
70 18         627 $contents =~ s{\/\*[[:blank:]\h]*(.*?)[[:blank:]\h]*\*\/}
71 2         7 {
72 2         10 my $cmt = $1;
73 2         9 $cmt =~ s/\;/__SEMI_COLON__/gs;
74             $cmt =~ s/\:/__COLON__/gs;
75 2         8 # $self->message( 3, "Found comment, now modified to '$cmt'" );
76             "/* $cmt */";
77             }sex;
78 18         97 # $self->message( 3, "Rule content is (after comment processing): '$contents'" );
  36         157  
79             foreach( grep{ /\S/ } split( /\;/, $contents ) )
80             {
81             # $self->message( 3, "Processing rule property '$_'" );
82 29         749 ## Found one or more comments before the property
83             while( s/^[[:blank:]\h]*\/\*[[:blank:]\h]*(.*?)[[:blank:]\h]*\*\///s )
84 2         10 {
85 2         17 my $txt = $1;
86 2         16 $txt =~ s/__SEMI_COLON__/\;/gs;
87 2         23 $txt =~ s/__COLON__/\:/gs;
88 2   50     81 $self->message( 3, "Adding comment element '$txt'." );
89 2 50       103 my $cmt = $css->new_comment( [split( /\r?\n/, $txt )] ) || return( $self->error( "Unable to create a new CSS::Object::Comment object: ", CSS::Object::Comment->error ) );
90             $rule->add_element( $cmt ) || return( $self->error( "Unable to add comment element to our rule: ", $rule->error ) );
91             }
92 29 50       398
93             unless( /^[[:blank:]\h]*(?<name>[\w\.\_\-]+)[[:blank:]\h]*:[[:blank:]\h]*(?<value>.*?)[[:blank:]\h]*$/ )
94 0         0 {
95             return( $self->error( "Invalid or unexpected property '$_' in style '$style'" ) );
96             }
97 29         371 ## Put back the colon we temporarily substituted to avoid confusion in the parser
98             $+{value} =~ s/__COLON__/\:/gs;
99 29         405 # my( $prop_name, $prop_val ) = @+{qw(name value)};
100             $self->message( 3, "Adding new property with name '$+{name}', and value '$+{value}'." );
101             my $prop = CSS::Object::Property->new({
102             debug => $self->debug,
103             name => $+{name},
104             value => $+{value},
105 29   50     761 # format => $rule->format,
106 29 50       156 }) || return( $self->error( "Unable to create a new CSS::Object::Property object: ", CSS::Object::Property->error ) );
107 29 50       145 $self->message( 3, "An error occurred while trying to instantiate a CSS::Object::Property object: ", CSS::Object::Property->error ) if( !defined( $prop ) );
108             $rule->add_property( $prop ) || return( $self->error( "Unable to add property name '$+{name}' to rule: ", $rule->error ) );
109             }
110 18         931 # push( @{$self->{parent}->{styles}}, $rule );
111             return( $rule );
112             }
113              
114             sub parse_string
115 5     5 1 11 {
116 5         11 my $self = shift( @_ );
117 5         35 my $string = shift( @_ );
118             $self->message( 3, "Parsing string '$string'." );
119 5         187  
120             $string =~ s/\r\n|\r|\n/ /g;
121 5         37
122             my $rules = Module::Generic::Array->new;
123 5         137 ## Split into styles
  23         62  
124             foreach( grep{ /\S/ } split( /(?<=\})/, $string ) )
125 18 50       246 {
126             unless( /^[[:blank:]\h]*([^{]+?)[[:blank:]\h]*\{(.*)\}[[:blank:]\h]*$/ )
127 0         0 {
128             return( $self->error( "Invalid or unexpected style data '$_'" ) );
129             }
130 18         85 # $self->message( 3, "Adding rule object for \$1 = '$1' and \$2 = '$2'." );
131 18 50       82 my $rule = $self->add_rule( $1, $2 );
132 18         65 return if( !defined( $rule ) );
133             $rules->push( $rule );
134 5         68 }
135             return( $rules );
136             }
137              
138             1;
139              
140             __END__
141              
142             =encoding utf-8
143              
144             =head1 NAME
145              
146             CSS::Object::Parser::Default - CSS Object Oriented Default Parser
147              
148             =head1 SYNOPSIS
149              
150             use CSS::Object;
151             my $css = CSS::Object->new(
152             parser => 'CSS::Object::Parser::Default',
153             format => $format_object,
154             debug => 3,
155             ) || die( CSS::Object->error );
156             $css->read( '/my/file.css' ) || die( $css->error );
157              
158             =head1 VERSION
159              
160             v0.1.0
161              
162             =head1 DESCRIPTION
163              
164             L<CSS::Object::Parser::Default> is a simple lightweight css parser.
165              
166             =head1 CONSTRUCTOR
167              
168             =head2 new
169              
170             To instantiate a new L<CSS::Object::Parser::Default> object, pass an hash reference of following parameters:
171              
172             =over 4
173              
174             =item I<debug>
175              
176             This is an integer. The bigger it is and the more verbose is the output.
177              
178             =back
179              
180             =head1 METHODS
181              
182             =head2 add_rule
183              
184             It takes 2 parameters: string of selectors and the rule content, i.e. inside the curly braces.
185              
186             It creates a new L<CSS::Object::Rule> object, adds to it a new L<CSS::Object::Selector> object for each selector found and also add a new L<CSS::Object::Property> object for each property found.
187              
188             It returns the rule object created.
189              
190             =head2 parse_string
191              
192             Provided with some css text data and this will parse it and return an array object of L<CSS::Object::Rule> objects. The array returned is an L<Module::Generic::Array> object.
193              
194             It does this by calling L</add_rule> on each rule found in the css data provided.
195              
196             Each L<CSS::Object::Rule> object containing one more more L<CSS::Object::Selector> objects and one or more L<CSS::Object::Property> objects.
197              
198             =head1 AUTHOR
199              
200             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
201              
202             =head1 SEE ALSO
203              
204             L<CSS::Object>
205              
206             =head1 COPYRIGHT & LICENSE
207              
208             Copyright (c) 2020 DEGUEST Pte. Ltd.
209              
210             You can use, copy, modify and redistribute this package and associated
211             files under the same terms as Perl itself.
212              
213             =cut