File Coverage

blib/lib/CSS/Object/Parser/Default.pm
Criterion Covered Total %
statement 65 67 97.0
branch 6 12 50.0
condition 6 12 50.0
subroutine 11 11 100.0
pod 2 2 100.0
total 90 104 86.5


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   3134 use strict;
  4         12  
  4         142  
14 4     4   24 use warnings;
  4         9  
  4         124  
15 4     4   24 use Module::Generic;
  4         9  
  4         30  
16 4     4   1182 use parent qw( CSS::Object::Parser );
  4         9  
  4         26  
17 4     4   210 use CSS::Object::Rule;
  4         10  
  4         32  
18 4     4   892 use CSS::Object::Selector;
  4         10  
  4         32  
19 4     4   935 use CSS::Object::Property;
  4         9  
  4         33  
20 4     4   837 use Devel::Confess;
  4         9  
  4         20  
21 4     4   4220 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 67 my $self = shift( @_ );
43 18         75 my $style = shift( @_ );
44 18         75 my $contents = shift( @_ );
45 18   50     119 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     589 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         868 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     788 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       166 $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         791 $contents =~ s{\/\*[[:blank:]\h]*(.*?)[[:blank:]\h]*\*\/}
71 2         8 {
72 2         12 my $cmt = $1;
73 2         10 $cmt =~ s/\;/__SEMI_COLON__/gs;
74             $cmt =~ s/\:/__COLON__/gs;
75 2         11 # $self->message( 3, "Found comment, now modified to '$cmt'" );
76             "/* $cmt */";
77             }sex;
78 18         121 # $self->message( 3, "Rule content is (after comment processing): '$contents'" );
  36         197  
79             foreach( grep{ /\S/ } split( /\;/, $contents ) )
80             {
81             # $self->message( 3, "Processing rule property '$_'" );
82 29         809 ## Found one or more comments before the property
83             while( s/^[[:blank:]\h]*\/\*[[:blank:]\h]*(.*?)[[:blank:]\h]*\*\///s )
84 2         11 {
85 2         18 my $txt = $1;
86 2         13 $txt =~ s/__SEMI_COLON__/\;/gs;
87 2         22 $txt =~ s/__COLON__/\:/gs;
88 2   50     81 $self->message( 3, "Adding comment element '$txt'." );
89 2 50       179 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       468
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         382 ## Put back the colon we temporarily substituted to avoid confusion in the parser
98             $+{value} =~ s/__COLON__/\:/gs;
99 29         435 # 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     853 # format => $rule->format,
106 29 50       373 }) || return( $self->error( "Unable to create a new CSS::Object::Property object: ", CSS::Object::Property->error ) );
107 29 50       189 $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         1146 # push( @{$self->{parent}->{styles}}, $rule );
111             return( $rule );
112             }
113              
114             sub parse_string
115 5     5 1 13 {
116 5         14 my $self = shift( @_ );
117 5         39 my $string = shift( @_ );
118             $self->message( 3, "Parsing string '$string'." );
119 5         189  
120             $string =~ s/\r\n|\r|\n/ /g;
121 5         50
122             my $rules = Module::Generic::Array->new;
123 5         160 ## Split into styles
  23         81  
124             foreach( grep{ /\S/ } split( /(?<=\})/, $string ) )
125 18 50       341 {
126             unless( /^[[:blank:]\h]*([^{]+?)[[:blank:]\h]*\{(.*)\}[[:blank:]\h]*$/ )
127 0         0 {
128             return( $self->error( "Invalid or unexpected style data '$_'" ) );
129             }
130 18   50     106 # $self->message( 3, "Adding rule object for \$1 = '$1' and \$2 = '$2'." );
131 18         1085 my $rule = $self->add_rule( $1, $2 ) || return( $self->pass_error );
132             $rules->push( $rule );
133 5         87 }
134             return( $rules );
135             }
136              
137             1;
138              
139             __END__
140              
141             =encoding utf-8
142              
143             =head1 NAME
144              
145             CSS::Object::Parser::Default - CSS Object Oriented Default Parser
146              
147             =head1 SYNOPSIS
148              
149             use CSS::Object;
150             my $css = CSS::Object->new(
151             parser => 'CSS::Object::Parser::Default',
152             format => $format_object,
153             debug => 3,
154             ) || die( CSS::Object->error );
155             $css->read( '/my/file.css' ) || die( $css->error );
156              
157             =head1 VERSION
158              
159             v0.1.0
160              
161             =head1 DESCRIPTION
162              
163             L<CSS::Object::Parser::Default> is a simple lightweight css parser.
164              
165             =head1 CONSTRUCTOR
166              
167             =head2 new
168              
169             To instantiate a new L<CSS::Object::Parser::Default> object, pass an hash reference of following parameters:
170              
171             =over 4
172              
173             =item I<debug>
174              
175             This is an integer. The bigger it is and the more verbose is the output.
176              
177             =back
178              
179             =head1 METHODS
180              
181             =head2 add_rule
182              
183             It takes 2 parameters: string of selectors and the rule content, i.e. inside the curly braces.
184              
185             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.
186              
187             It returns the rule object created.
188              
189             =head2 parse_string
190              
191             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.
192              
193             It does this by calling L</add_rule> on each rule found in the css data provided.
194              
195             Each L<CSS::Object::Rule> object containing one more more L<CSS::Object::Selector> objects and one or more L<CSS::Object::Property> objects.
196              
197             =head1 AUTHOR
198              
199             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
200              
201             =head1 SEE ALSO
202              
203             L<CSS::Object>
204              
205             =head1 COPYRIGHT & LICENSE
206              
207             Copyright (c) 2020 DEGUEST Pte. Ltd.
208              
209             You can use, copy, modify and redistribute this package and associated
210             files under the same terms as Perl itself.
211              
212             =cut