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   2680 use strict;
  4         8  
  4         124  
14 4     4   18 use warnings;
  4         7  
  4         92  
15 4     4   17 use Module::Generic;
  4         7  
  4         21  
16 4     4   993 use parent qw( CSS::Object::Parser );
  4         7  
  4         19  
17 4     4   168 use CSS::Object::Rule;
  4         7  
  4         24  
18 4     4   767 use CSS::Object::Selector;
  4         7  
  4         24  
19 4     4   718 use CSS::Object::Property;
  4         9  
  4         22  
20 4     4   728 use Devel::Confess;
  4         6  
  4         16  
21 4     4   3418 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 33 my $self = shift( @_ );
43 18         56 my $style = shift( @_ );
44 18         51 my $contents = shift( @_ );
45 18   50     92 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     378 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         591 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     607 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       120 $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         609 $contents =~ s{\/\*[[:blank:]\h]*(.*?)[[:blank:]\h]*\*\/}
71 2         6 {
72 2         10 my $cmt = $1;
73 2         8 $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         90 # $self->message( 3, "Rule content is (after comment processing): '$contents'" );
  36         138  
79             foreach( grep{ /\S/ } split( /\;/, $contents ) )
80             {
81             # $self->message( 3, "Processing rule property '$_'" );
82 29         647 ## Found one or more comments before the property
83             while( s/^[[:blank:]\h]*\/\*[[:blank:]\h]*(.*?)[[:blank:]\h]*\*\///s )
84 2         7 {
85 2         14 my $txt = $1;
86 2         11 $txt =~ s/__SEMI_COLON__/\;/gs;
87 2         17 $txt =~ s/__COLON__/\:/gs;
88 2   50     73 $self->message( 3, "Adding comment element '$txt'." );
89 2 50       170 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       382
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         314 ## Put back the colon we temporarily substituted to avoid confusion in the parser
98             $+{value} =~ s/__COLON__/\:/gs;
99 29         347 # 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     739 # format => $rule->format,
106 29 50       151 }) || return( $self->error( "Unable to create a new CSS::Object::Property object: ", CSS::Object::Property->error ) );
107 29 50       142 $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         856 # push( @{$self->{parent}->{styles}}, $rule );
111             return( $rule );
112             }
113              
114             sub parse_string
115 5     5 1 9 {
116 5         10 my $self = shift( @_ );
117 5         33 my $string = shift( @_ );
118             $self->message( 3, "Parsing string '$string'." );
119 5         387  
120             $string =~ s/\r\n|\r|\n/ /g;
121 5         45
122             my $rules = Module::Generic::Array->new;
123 5         129 ## Split into styles
  23         59  
124             foreach( grep{ /\S/ } split( /(?<=\})/, $string ) )
125 18 50       249 {
126             unless( /^[[:blank:]\h]*([^{]+?)[[:blank:]\h]*\{(.*)\}[[:blank:]\h]*$/ )
127 0         0 {
128             return( $self->error( "Invalid or unexpected style data '$_'" ) );
129             }
130 18         80 # $self->message( 3, "Adding rule object for \$1 = '$1' and \$2 = '$2'." );
131 18 50       85 my $rule = $self->add_rule( $1, $2 );
132 18         65 return if( !defined( $rule ) );
133             $rules->push( $rule );
134 5         63 }
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