File Coverage

blib/lib/CSS/Object.pm
Criterion Covered Total %
statement 162 191 84.8
branch 29 68 42.6
condition 10 24 41.6
subroutine 38 43 88.3
pod 21 23 91.3
total 260 349 74.5


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## CSS Object Oriented - ~/lib/CSS/Object.pm
3             ## Version v0.1.2
4             ## Copyright(c) 2020 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <@sitael.tokyo.deguest.jp>
6             ## Created 2020/06/24
7             ## Modified 2020/08/10
8             ##
9             ##----------------------------------------------------------------------------
10             package CSS::Object;
11             BEGIN
12             {
13 6     6   301706 use strict;
  6         13  
  6         161  
14 6     6   27 use warnings;
  6         10  
  6         143  
15 6     6   26 use warnings::register;
  6         12  
  6         1762  
16 6     6   2617 use parent qw( Module::Generic );
  6         1961  
  6         27  
17 6     6   90693102 use CSS::Object::Builder;
  6         18  
  6         72  
18 6     6   3547 use CSS::Object::Comment;
  6         14  
  6         49  
19 6     6   1184 use CSS::Object::Format;
  6         9  
  6         29  
20 6     6   3361 use CSS::Object::Property;
  6         23  
  6         128  
21 6     6   1276 use CSS::Object::Rule;
  6         11  
  6         47  
22 6     6   3289 use CSS::Object::Rule::At;
  6         15  
  6         55  
23 6     6   3634 use CSS::Object::Rule::Keyframes;
  6         30  
  6         63  
24 6     6   3196 use CSS::Object::Selector;
  6         12  
  6         62  
25 6     6   1154 use CSS::Object::Value;
  6         12  
  6         25  
26 6     6   1088 use Want ();
  6         9  
  6         95  
27 6     6   24 use Devel::Confess;
  6         10  
  6         22  
28 6     6   9321 our $VERSION = 'v0.1.2';
29             };
30              
31             sub init
32             {
33 6     6 1 4148 my $self = shift( @_ );
34 6         40 $self->{parser} = 'CSS::Object::Parser::Default';
35 6         24 $self->{format} = '';
36 6         12 $self->{_init_strict_use_sub} = 1;
37 6         41 $self->SUPER::init( @_ );
38             # $self->message( 3, "Formatter class set: '", ref( $self->format ), "'." );
39 6 100       325 unless( $self->_is_a( $self->{format}, 'CSS::Object::Format' ) )
40             {
41 3         35 my $format = CSS::Object::Format->new(
42             debug => $self->debug
43             );
44 3         9 $self->format( $format );
45             }
46 6         74 $self->{rules} = Module::Generic::Array->new;
47 6         48 return( $self );
48             }
49              
50             ## Add comment at the top level. To add comment inside a rule, see add_element in CSS::Object::Rule
51             sub add_element
52             {
53 1     1 1 3 my $self = shift( @_ );
54 1   50     3 my $elem = shift( @_ ) || return( $self->error( "No element object was provided to add to this rule." ) );
55 1 50       36 return( $self->error( "Element object provided ($elem) is not a CSS::Object::Element object." ) ) if( !$self->_is_a( $elem, 'CSS::Object::Element' ) );
56             # $self->message( 3, "Adding element object '$elem'." );
57             # $elem->format( $self->format );
58 1         14 $elem->debug( $self->debug );
59             # $self->properties->push( $prop );
60 1         30 $self->elements->push( $elem );
61 1         21 return( $self );
62             }
63              
64             sub add_rule
65             {
66 9     9 1 21 my $self = shift( @_ );
67 9         16 my $rule = shift( @_ );
68             # $self->message( 3, "CSS rule provided to add to our stack of elements: '", overload::StrVal( $rule ), "'." );
69 9 50       31 return( $self->error( "No rule object was provided to add." ) ) if( !defined( $rule ) );
70 9 50       27 return( $self->error( "Object provided is not a CSS::Object::Rule object." ) ) if( !$self->_is_a( $rule, 'CSS::Object::Rule' ) );
71             # $self->rules->push( $rule );
72 9         137 $self->elements->push( $rule );
73             ## $self->message( 3, "Returning rule objected added: '", overload::StrVal( $rule ), "'." );
74 9         204 return( $rule );
75             }
76              
77             sub as_string
78             {
79 2     2 1 534 my $self = shift( @_ );
80 2         11 $self->messagef( 3, "There are %d elements in our stack.", $self->elements->length );
81 2 50       77876 if( @_ )
82             {
83 0         0 my $format = shift( @_ );
84 0 0 0     0 return( $self->error( "Provided parameter to as_string was not an CSS::Object::Format object." ) ) if( $format !~ /^CSS\::Object\::Format/ && !$self->_is_a( $format, 'CSS::Object::Format' ) );
85             $self->elements->foreach(sub
86             {
87 0     0   0 shift->format( $format );
88 0         0 });
89             }
90              
91 2         14 my $output = Module::Generic::Array->new;
92             # $self->rules->foreach(sub
93             $self->elements->foreach(sub
94             {
95 4     4   226 $output->push( shift->as_string );
96 2         24 });
97 2         99 my $nl = $self->format->new_line;
98 2         70 return( $output->join( "$nl$nl" )->scalar );
99             }
100              
101             sub builder
102             {
103 1     1 1 323 my $self = shift( @_ );
104 1 50       4 return( $self->{_builder} ) if( $self->_is_object( $self->{_builder} ) );
105             # $self->message( 3, "Creating builder object with debug set to '", $self->debug, "' and formatter set to '", $self->format->class, "'." );
106 1   50     8 my $b = CSS::Object::Builder->new( $self, debug => $self->debug ) ||
107             return( $self->error( "Could not initialise the CSS builder: ", CSS::Object::Builder->error ) );
108 1         2 $self->{_builder} = $b;
109 1         2 return( $b );
110             }
111              
112 1     1 1 22 sub charset { return( shift->_set_get_scalar_as_object( 'charset', @_ ) ); }
113              
114             ## Array of CSS::Object::Element objects or their sub classes
115 27     27 1 140 sub elements { return( shift->_set_get_array_as_object( 'elements', @_ ) ); }
116              
117             sub format
118             {
119 131     131 1 1279 my $self = shift( @_ );
120 131 100       288 if( @_ )
121             {
122 6         10 my $val = shift( @_ );
123 6         9 my $format;
124 6 100 33     32 if( ref( $val ) )
    50          
125             {
126 3   50     19 $format = $self->_set_get_object( 'format', 'CSS::Object::Format', $val ) || return;
127             }
128             ## Formatter as a class name
129             elsif( !ref( $val ) && CORE::index( $val, '::' ) != -1 )
130             {
131 3 50       16 $self->_load_class( $val ) || return;
132 3   50     377 $format = $val->new( debug => $self->debug ) || return( $self->pass_error( $val->error ) );
133 3         19 $self->_set_get_object( 'format', 'CSS::Object::Format', $format );
134             }
135             else
136             {
137 0         0 return( $self->error( "Unknown format \"$val\". I do not know what to do with it." ) );
138             }
139             # $self->message( 3, "New formatter set: '$format'." );
140             $self->elements->foreach(sub
141             {
142 0 0   0   0 shift->format( $format ) || return;
143 6         231 });
144 6         513 return( $format );
145             }
146 125         286 return( $self->_set_get_object( 'format', 'CSS::Object::Format' ) );
147             }
148              
149             sub get_rule_by_selector
150             {
151 1     1 1 1066 my( $self, $name ) = @_;
152 1 50       4 return( $self->error( "No selector was provided to find its equivalent rule object." ) ) if( !$name );
153             # $self->messagef( 3, "%d elements found.", $self->elements->length );
154 1         6 my $found = Module::Generic::Array->new;
155 1         10 foreach my $rule ( @{$self->elements} )
  1         3  
156             {
157 3 50       77 next if( !$rule->isa( 'CSS::Object::Rule' ) );
158             # $self->messagef( 3, "This rule has %d selectors", $rule->selectors->length );
159 3         5 foreach my $sel ( @{$rule->selectors} )
  3         19  
160             {
161             # $self->message( 3, "Does '", $sel->name, "' match our target selector '$name' ?" );
162 5 100       129 if( $sel->name eq $name )
163             {
164             # return( $rule );
165 1         37 $found->push( $rule );
166             }
167             }
168             }
169             ## The user is calling this in a chain context, we make sure this is possible using the Module::Generic::Null class if needed
170 1 50       44 if( Want::want( 'OBJECT' ) )
    50          
171             {
172 0 0       0 rreturn( $found->length > 0 ? $found->first : Module::Generic::Null->new );
173             }
174             elsif( Want::want( 'LIST' ) )
175             {
176 0         0 rreturn( @$found );
177             }
178             else
179             {
180 1         93 return( $found->first );
181             }
182             }
183              
184             sub load_parser
185             {
186 5     5 1 11 my $self = shift( @_ );
187 5         16 my $parser_class = $self->parser;
188 5 50       305 $self->_load_class( "$parser_class" ) || return( $self->error( "Unable to load parser class \"$parser_class\": ", $self->error ) );
189 5   50     491 my $parser = $parser_class->scalar->new( $self ) || return( $self->error( "Unable to instantiate parser \"$parser_class\" object: ", $parser_class->scalar->error ) );
190 5         21 $parser->debug( $self->debug );
191             # $self->message( 3, "Parser \"$parser_class\" initiated with object '$parser'." );
192 5         171 return( $parser );
193             }
194              
195             sub new_at_rule
196             {
197 0     0 0 0 my $self = shift( @_ );
198 0         0 my $o = CSS::Object::Rule::At->new( @_,
199             format => $self->format,
200             debug => $self->debug,
201             css => $self,
202             );
203 0 0       0 return( $self->error( "Cannot create a new at rule object: ", CSS::Object::Rule::At->error ) ) if( !defined( $o ) );
204 0         0 return( $o );
205             }
206              
207             sub new_keyframes_rule
208             {
209 1     1 0 18 my $self = shift( @_ );
210 1         4 my $o = CSS::Object::Rule::Keyframes->new( @_,
211             format => $self->format,
212             debug => $self->debug,
213             css => $self,
214             );
215 1 50       3 return( $self->error( "Cannot create a new keyframes rule object: ", CSS::Object::Rule::Keyframes->error ) ) if( !defined( $o ) );
216 1         4 return( $o );
217             }
218              
219             sub new_comment
220             {
221 5     5 1 37 my $self = shift( @_ );
222 5         18 my $o = CSS::Object::Comment->new( @_, format => $self->format, debug => $self->debug );
223 5 50       50 return( $self->error( "Cannot create a new comment object: ", CSS::Object::Comment->error ) ) if( !defined( $o ) );
224 5         20 return( $o );
225             }
226              
227             sub new_property
228             {
229 15     15 1 394 my $self = shift( @_ );
230 15         54 my $o = CSS::Object::Property->new( @_, format => $self->format, debug => $self->debug );
231 15 50       50 return( $self->error( "Cannot create a new property object: ", CSS::Object::Property->error ) ) if( !defined( $o ) );
232 15         54 return( $o );
233             }
234              
235             sub new_rule
236             {
237 26     26 1 776 my $self = shift( @_ );
238 26         116 $self->message( 3, "Creating new rule with formatter '", $self->format->class, "'." );
239 26         525 my $o = CSS::Object::Rule->new( @_, format => $self->format, debug => $self->debug );
240 26 50       89 return( $self->error( "Cannot create a new rule object: ", CSS::Object::Rule->error ) ) if( !defined( $o ) );
241             # $self->message( 3, "Returning \"", overload::StrVal( $o ), "\"." );
242 26         119 return( $o );
243             }
244              
245             sub new_selector
246             {
247 47     47 1 575 my $self = shift( @_ );
248 47         145 my $o = CSS::Object::Selector->new( @_, format => $self->format, debug => $self->debug );
249 47 50       112 return( $self->error( "Cannot create a new selector object: ", CSS::Object::Selector->error ) ) if( !defined( $o ) );
250 47         152 return( $o );
251             }
252              
253             sub new_value
254             {
255 0     0 1 0 my $self = shift( @_ );
256 0         0 my $o = CSS::Object::Value->new( @_, format => $self->format, debug => $self->debug );
257 0 0       0 return( $self->error( "Cannot create a new value object: ", CSS::Object::Value->error ) ) if( !defined( $o ) );
258 0         0 return( $o );
259             }
260              
261             sub parse_string
262             {
263 5     5 1 11 my $self = shift( @_ );
264 5         13 my $string = shift( @_ );
265 5         28 $self->message( 3, "Parsing string '$string'" );
266              
267             # remove comments
268             # $string =~ s!/\*.*?\*\/!!g;
269 5         100 $string =~ s|<!--||g;
270 5         20 $string =~ s|-->||g;
271            
272 5   50     16 my $parser = $self->load_parser || return;
273 5   50     20 my $elems = $parser->parse_string( $string ) || return( $self->pass_error( $parser->error ) );
274 5         21 $self->messagef( 3, "Parser returned %d elements.", $elems->length );
275 5         195129 $self->messagef( 3, "First element is of class \"", ref( $elems->first ), "\"." );
276             # $self->messagef( 3, "First rule has %d properties.", $rules->first->properties->length );
277 5         448 return( $elems );
278             }
279              
280 6     6 1 159 sub parser { return( shift->_set_get_scalar_as_object( 'parser', @_ ) ); }
281              
282 2     2 1 39345 sub purge { return( shift->elements->reset ); }
283              
284             sub read_file
285             {
286 5     5 1 39817 my $self = shift( @_ );
287 5         13 my $path = shift( @_ );
288              
289 5 50       22 if( ref( $path ) )
    50          
290             {
291 0 0       0 if( ref( $path ) eq 'ARRAY' )
292             {
293 0         0 $self->read_file( $_ ) for( @$path );
294 0         0 return( $self );
295             }
296             }
297             elsif( $path )
298             {
299 5         39 $self->message( 3, "Reading file \"$path\"." );
300 5   50     312 my $io = IO::File->new( "<$path" ) || return( $self->error( "Could not open file \"$path\": $!" ) );
301 5         17850 $io->binmode( ':utf8' );
302 5         211 my $source = join( '', $io->getlines );
303 5         338 $io->close;
304 5         123 $self->messagef( 3, "%d bytes of data read.", CORE::length( $source ) );
305 5 50       146 if( $source )
306             {
307 5   50     21 my $elems = $self->parse_string( $source ) || return;
308 5         25 $self->messagef( 3, "%d elements found from parsing.", $elems->length );
309             # $self->rules->push( @$rules );
310 5         214330 $self->elements->push( @$elems );
311             }
312 5         220 return( $self );
313             }
314 0         0 return( $self->error( "Only scalars and arrays accepted: $!" ) );
315             }
316              
317             sub read_string
318             {
319 0     0 1 0 my $self = shift( @_ );
320 0         0 my $data = shift( @_ );
321              
322 0 0       0 if( ref( $data ) )
    0          
323             {
324 0 0       0 if( ref( $data ) eq 'ARRAY' )
325             {
326 0         0 $self->read_string( $_ ) for( @$data );
327 0         0 return( $self );
328             }
329             }
330             elsif( length( $data ) )
331             {
332 0         0 my $elems = $self->parse_string( $data );
333             ## $self->rules->push( @$rules );
334 0         0 $self->elements->push( @$elems );
335             }
336 0         0 return( $self );
337             }
338              
339             # sub rules { return( shift->_set_get_array_as_object( 'rules', @_ ) ); }
340 27 50   27 1 250 sub rules { return( shift->elements->map(sub{ $_->isa( 'CSS::Object::Rule' ) ? $_ : () }) ); }
  6     6   108306  
341              
342             1;
343              
344             __END__
345              
346             =encoding utf-8
347              
348             =head1 NAME
349              
350             CSS::Object - CSS Object Oriented
351              
352             =head1 SYNOPSIS
353              
354             use CSS::Object;
355              
356             =head1 VERSION
357              
358             v0.1.2
359              
360             =head1 DESCRIPTION
361              
362             L<CSS::Object> is a object oriented CSS parser and manipulation interface.
363              
364             =head1 CONSTRUCTOR
365              
366             =head2 new
367              
368             To instantiate a new L<CSS::Object> object, pass an hash reference of following parameters:
369              
370             =over 4
371              
372             =item I<debug>
373              
374             This is an integer. The bigger it is and the more verbose is the output.
375              
376             =item I<format>
377              
378             This is a L<CSS::Object::Format> object or one of its child modules.
379              
380             =item I<parser>
381              
382             This is a L<CSS::Object::Parser> object or one of its child modules.
383              
384             =back
385              
386             =head1 EXCEPTION HANDLING
387              
388             Whenever an error has occurred, L<CSS::Object> will set a L<Module::Generic::Exception> object containing the detail of the error and return undef.
389              
390             The error object can be retrieved with the inherited L<Module::Generic/error> method. For example:
391              
392             my $css = CSS::Object->new( debug => 3 ) || die( CSS::Object->error );
393              
394             =head1 METHODS
395              
396             =head2 add_element
397              
398             Provided with a L<CSS::Object::Element> object and this adds it to the list of css elements.
399              
400             It uses an array object L</elements> which is an L<Module::Generic::Array> object.
401              
402             =head2 add_rule
403              
404             Provided with a L<CSS::Object::Rule> object and this adds it to our list of rules. It returns the rule object that was added.
405              
406             =head2 as_string
407              
408             This will return the css data structure, currently registered, as a string.
409              
410             It takes an optional L<CSS::Object::Format> object as a parameter, to control the output. If none are provided, it will use the default one calling L</format>
411              
412             =head2 builder
413              
414             This returns a new L<CSS::Object::Builder> object.
415              
416             =head2 charset
417              
418             This sets or gets the css charset. It stores the value in a L<Module::Generic::Scalar> object.
419              
420             =head2 elements
421              
422             Sets or gets the array of CSS elements. This is a L<Module::Generic::Array> object that accepts only L<CSS::Object::Element> objects or its child classes, such as L<CSS::Object::Rule>, L<CSS::Object::Comment>, etc
423              
424             =head2 format
425              
426             Sets or gets a L<CSS::Object::Format> object. See L</as_string> below for more detail about their use.
427              
428             L<CSS::Object::Format> objects control the stringification of the css structure. By default, it will return the data in a string identical or at least very similar to the one parsed if it was parsed.
429              
430             =head2 get_rule_by_selector
431              
432             Provided with a selector and this returns a L<CSS::Object::Rule> object or an empty string.
433              
434             =head2 load_parser
435              
436             This will instantiate a new object based on the parser name specified with L</parser> or during css object instantiation.
437              
438             It returns a new L<CSS::Object::Parser> object, or one of its child module matching the L</parser> specified.
439              
440             =head2 new_comment
441              
442             This returns a new L<CSS::Object::Comment> object and pass its instantiation method the provided arguments.
443              
444             return( $css->new_comment( $array_ref_of_comment_ilnes ) );
445              
446             =head2 new_property
447              
448             This takes a property name, and an optional value o array of values and return a new L<CSS::Object::Property> object
449              
450             =head2 new_rule
451              
452             This returns a new L<CSS::Object::Rule> object.
453              
454             =head2 new_selector
455              
456             This takes a selector name and returns a new L<CSS::Object::Selector> object.
457              
458             =head2 new_value
459              
460             This takes a property value and returns a new L<CSS::Object::Value> object.
461              
462             =head2 parse_string
463              
464             Provided with some css data and this will instantiate the L</parser>, call L<CSS::Object::Parser/parse_string> and returns an array of L<CSS::Object::Rule> objects. The array is an array object from L<Module::Generic::Array> and can be used as a regular array or as an object.
465              
466             =head2 parser
467              
468             Sets or gets the L<CSS::Object::Parser> object to be used by L</parse_string> to parse css data.
469              
470             A valid parser object can be from L<CSS::Object::Parser> or any of its sub modules.
471              
472             It returns the current parser object.
473              
474             =head2 purge
475              
476             This empties the array containing all the L<CSS::Object::Rule> objects.
477              
478             =head2 read_file
479              
480             Provided with a css file, and this will load it into memory and parse it using the parser name registered with L</parser>.
481              
482             It can also take an array reference of css files who will be each fed to L</read_file>
483              
484             It returns the L<CSS::Object> used to call this method.
485              
486             =head2 read_string
487              
488             Provided with some css data, and this will call L</parse_string>. It also accepts an array reference of data.
489              
490             It returns the css object used to call this method.
491              
492             =head2 rules
493              
494             This sets or gets the L<Module::Generic::Array> object used to store all the L<CSS::Object::Rule> objects.
495              
496             =head1 AUTHOR
497              
498             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
499              
500             =head1 SEE ALSO
501              
502             L<CSS::Object>
503              
504             =head1 COPYRIGHT & LICENSE
505              
506             Copyright (c) 2020 DEGUEST Pte. Ltd.
507              
508             You can use, copy, modify and redistribute this package and associated
509             files under the same terms as Perl itself.
510              
511             =cut