File Coverage

blib/lib/CSS/DOM/PropertyParser.pm
Criterion Covered Total %
statement 233 235 99.1
branch 169 180 93.8
condition 27 36 75.0
subroutine 29 29 100.0
pod 7 8 87.5
total 465 488 95.2


line stmt bran cond sub pod time code
1             package CSS::DOM::PropertyParser;
2              
3             $VERSION = '0.15';
4              
5 4     4   5848 use warnings; no warnings qw 'utf8 parenthesis';
  4     4   9  
  4         175  
  4         22  
  4         8  
  4         164  
6 4     4   23 use strict;
  4         6  
  4         161  
7              
8 4     4   23 use constant 1.03 (); # multiple
  4         121  
  4         96  
9 4     4   23 use CSS::DOM'Constants ':primitive', ':value';
  4         14  
  4         1470  
10 4     4   1841 use CSS'DOM'Util<unescape unescape_str unescape_url>;
  4         10  
  4         514  
11              
12 4     4   25 use constant old_perl => $] < 5.01;
  4         9  
  4         267  
13 4     4   23 { no strict 'refs'; delete ${__PACKAGE__.'::'}{old_perl} }
  4         7  
  4         244  
14              
15             # perl 5.10.0 has a bug affecting $^N (perl bug #56194; not the initial
16             # report, but regressions in 5.10; read the whole ticket for details). We
17             # have a workaround, but it requires more CPU, so we only enable it for
18             # this perl version.
19 4     4   20 use constant naughty_perl => 0+$] eq 5.01;
  4         7  
  4         189  
20 4     4   20 { no strict 'refs'; delete ${__PACKAGE__.'::'}{naughty_perl} }
  4         7  
  4         4479  
21              
22             *s2c = *CSS'DOM'Constants'SuffixToConst;
23             our %s2c;
24              
25             our %compiled; # compiled formats
26             our %subcompiled; # compiled sub-formats
27             # We use ‘our’ instead of ‘my’, because re-evals that are compiled at run
28             # time can be a bit buggy when they refer to ‘my’ variables.
29              
30             sub new {
31 8     8 0 2600 bless{}, shift
32             }
33              
34             sub add_property {
35 474     474 1 1347 $_[0]{$_[1]}=$_[2]
36             }
37              
38             sub get_property {
39 2332 100   2332 1 15608 exists $_[0]{$_[1]} ? $_[0]{$_[1]} : ()
40             }
41              
42             sub delete_property {
43 1 50   1 1 11 delete $_[0]{$_[1]} or ()
44             }
45              
46             sub property_names {
47 1     1 1 7 sort keys %{$_[0]};
  1         16  
48             }
49              
50             sub subproperty_names {
51 1414 50   1414 1 4787 exists $_[0]{$_[1]} or return;
52 1414         3187 my $p = $_[0]{$_[1]};
53 1414         5798 my @p = $p->{format} =~ /'([^']+)'/g;
54 135         642 exists $p->{properties} && $p->{properties} and
55 1414 100 66     5512 push @p, keys %{$p->{properties}};
56 1414         4263 @p;
57             }
58              
59             sub clone {
60             # exists &dclone or require Storable, "Storable"->import('dclone');
61             # return dclone($_[0]);
62 1     1 1 1588 require Clone;
63 1         8116 return Clone'clone($_[0]);
64             }
65              
66             # Declare the variables that the re-evals use. Some nasty hacker went and
67             # ‘fixed’ run-time re-evals to propagate hints, so now we have to do this
68             # as of perl 5.13.8.
69             our(
70             @match,@list,@valtypes,$prepped,$alt_types,@List,%Match,%match,@Match,
71             $tokens,$Self,$Fail
72             );
73              
74             # The interface for match is documented in a POD comment further down
75             # (search for the second occurrence of ‘=item match’).
76 1203         3005 sub match { SUB: {
77 1197     1197 1 5669 my ($self,$property) = (shift,shift);
78 1203 50       3715 return unless exists $self->{$property};
79              
80             # Prepare the value
81 1203         3431 (my $types, local our ($tokens,$prepped,$alt_types)) = _prep_val(@_);
82             # tokens is the actual tokens; $prepped is the tokens unescaped and lc'd
83             # if they are ids; $alt_types contains single-char strings indicating pos-
84             # sible datum types.
85             #use DDS; Dump $types if $property =~ /clip/;
86              
87              
88 1203         4525 my @subproperties = $self->subproperty_names($property);
89 1203         2043 my $shorthand = @subproperties;
90 1203         2617 my $spec = $self->{$property};
91              
92             # Check for special values
93 1203 100 100     4780 if(exists $spec->{special_values}
      66        
94             && $types eq 'i'
95             && exists $spec->{special_values}{$prepped->[0]}) {
96 6         33 @_ = ($self,$property,$spec->{special_values}{$prepped->[0]});
97 6         35 redo SUB;
98             }
99              
100             # Check for inherit
101 1197 100 100     5298 if($types eq 'i' and $prepped->[0] eq 'inherit') {
102 6         27 my @arg_array = (
103             $$tokens[0],'CSS::DOM::Value', type => CSS_INHERIT, css => $$tokens[0]
104             );
105 6 50       28 if($shorthand) {
106 0         0 return { map +($_ => \@arg_array), @subproperties };
107             }
108 6         58 else { return @arg_array }
109             }
110              
111             # Localise other vars used by the hairy regexps
112 1191         4271 local our (@Match,%Match,@valtypes,@List);
113 1191         7269 local our $Self = $self;
114              
115             # Compile the formats of the sub-properties, something we can’t do
116             # during the pattern match, as compilation requires regular expressions
117             # and perl’s re engine is not reëntrant. This has to come before the for-
118             # mat for this property, in case it relies on list-style-type. We use
119             # (??{...}) to pick it up, but that is too buggy in perl 5.8.x, so, for
120             # old perls, we compile it straight in. Consequently we also have to
121             # ‘un-cache’ any compiled format containing <counter>, in case it is
122             # shared with another parser object with another definition for
123             # list-style-type.
124 1191         2107 my $format = $$spec{format};
125 1191 100       5423 for(
126             @subproperties,
127             $format =~ '<counter>'
128             ? scalar(old_perl && delete $compiled{$format}, 'list-style-type')
129             : ()
130             ) {
131 670 100       2190 next unless exists $self->{$_};
132 669         1629 my $format = $self->{$_}{format};
133 669         888 old_perl and $compiled{$format} and delete $compiled{$format};
134 669   66     2974 $compiled{$format} ||= _compile_format($format)
135             }
136              
137             # Prepare this property’s format pattern
138 1191   66     6131 my $pattern = $compiled{$format} ||= _compile_format($format);
139              
140             # Do the actual pattern matching
141 1191 100       179388 $types =~ /^$pattern\z/ or return;
142              
143             #use DDS; Dump $types,$tokens,\@valtypes;
144             #use DDS; Dump \%Match if $property =~ /clip/;
145             # Get the values, convert them into CSSValue arg lists and return them
146 1113 100       5177 if($shorthand) {
147 118         602 my $retval = {%Match};
148 118 100       577 my $subprops = exists $spec->{properties} ? $spec->{properties} : undef;
149              
150             # We record which captures have been turned into arg lists already,
151             # since these are sometimes shared between properties.
152 118         266 my @arglistified;
153              
154 118         325 for(@subproperties) {
155 621 100       1747 if(exists $retval->{$_}) {
156 106         157 @{ $retval->{$_} } = _make_arg_list( @{ $retval->{$_} } );
  106         1198  
  106         367  
157             }
158             else {
159 515         665 my $set;
160 515 100 66     2481 if($subprops and exists $subprops->{$_}) {
161 393         492 for my $c( @{ $subprops->{$_} } ) { # capture nums
  393         1058  
162             # find the first one that matched something
163 446 100 66     3128 if( $Match[$c] and length $Match[$c][0] ) {
164 273 100       825 @{ $Match[$c] } = _make_arg_list( @{ $Match[$c] } )
  157         977  
  157         463  
165             unless $arglistified[$c]++;
166 273         543 ++$set;
167 273         660 $retval->{$_} = $Match[$c];
168 273         658 last;
169             }
170             }
171             }
172 515 100       1707 if(!$set) {
173             # use default value
174             # ~~~ Should we cache this? (If we do, we need to distinguish between
175             # ‘content: Times New Roman’ and ‘font-family: Times New Roman’.)
176 242         653 my $default = $self->{$_}{default};
177 4     4   24 no warnings 'uninitialized';
  4         8  
  4         2040  
178 242 100       1068 $retval->{$_} = length $default
179             ? [
180             $self->match($_, $default)
181             ]
182             : ""
183             }
184             }
185             }
186 118         2444 $retval;
187             }
188             else { # simple
189 995         1463 my $css = join "", @{ (_space_out($types,$tokens))[1] };
  995         2491  
190             #use DDS; Dump \@List if exists $$spec{list} && $$spec{list};
191 995 100 66     6523 return _make_arg_list(
192             $types, $tokens,
193             exists $$spec{list} && $$spec{list}
194             ? \@List
195             : (\@valtypes, $prepped)
196             );
197             }
198             }}
199              
200             sub _make_arg_list {
201 1424     1424   4070 my($types, $tokens) = (shift,shift);
202 1424         3019 my($stypes,$stokens) = _space_out($types, $tokens);
203 1424         3628 my $css = join "", @$stokens;
204 1424 100       3376 if(@_ == 1) { # list property
205 90         168 my $list = shift @'_;
206 90 100       262 my $sep = @$list <= 1 ? '' : do {
207 46         107 my $range_start = $$list[0][4];
208 46         307 my $range_end = $$list[1][4] - length($$list[1][4]) - 1;
209 46         286 my(undef,$stokens) = _space_out(
210             substr($types, $range_start-1, $range_end-$range_start+3),
211             [@$tokens[$range_start-1...$range_end+1]]
212             );
213 46         261 join "", @$stokens[1...$#$stokens-1];
214             };
215 166         460 return $css, "CSS::DOM::Value::List",
216             separator => $sep, css => $css,
217             values => [ map {
218 90         226 my @args = _make_arg_list(
219             @$_[0...3]
220             );
221 166         411 shift @args, shift @args;
222             \@args
223 166         6892 } @$list ];
224             }
225             else{
226 1334         2137 my($valtypes, $prepped) = @_;
227 1334         4440 my @valtypes = grep defined, @$valtypes;
228 1334 100 66     3687 if(@valtypes != 1 and
      66        
229             $valtypes[0] != CSS_COUNTER || do { # The code in this block is to
230 4     4   27 no warnings 'uninitialized'; # distinguish between counter(id,
  4         9  
  4         6439  
231             my $found; # id) (which is a CSS_COUNTER) and
232             for(@valtypes[1...$#valtypes-1]) { # counter(id) id (CSS_CUSTOM).
233             $_ == -1 and ++$found, last; # -1 is a special marker for the end of
234             } # a counter
235             $found
236             }) {
237 64         832 return $css => "CSS::DOM::Value", type => CSS_CUSTOM, value => $css;
238             }
239 1270         2431 my $type = shift @valtypes;
240             return $css, "CSS::DOM::Value::Primitive",
241             type => $type, css => $css,
242             value =>
243             $type == CSS_NUMBER || $type == CSS_PERCENTAGE || $type == CSS_EMS ||
244             $type == CSS_EXS || $type == CSS_PX || $type == CSS_CM ||
245             $type == CSS_MM || $type == CSS_IN || $type == CSS_PT ||
246             $type == CSS_PC || $type == CSS_DEG || $type == CSS_RAD ||
247             $type == CSS_GRAD || $type == CSS_MS || $type == CSS_S ||
248             $type == CSS_HZ || $type == CSS_KHZ
249             ? $css
250             : $type == CSS_STRING
251             ? unescape_str $css
252             : $type == CSS_IDENT
253             ? unescape $css
254             : $type == CSS_URI
255             ? unescape_url $css
256             : $type == CSS_COUNTER
257             ? [
258             $$prepped[$types =~ /i/, $-[0]],
259             $types =~ /'/ ? $$prepped[$-[0]] : undef,
260             $types =~ /i.*?i/ ? $$prepped[$+[0]-1] : undef,
261             ]
262             : $type == CSS_RGBCOLOR
263             ? substr $types, 0, 1, eq '#'
264             ? $$prepped[0]
265 7         49 : do{
266 222         304 my @vals;
267 222         747 while($types =~ /([%D1])/g) {
268             push @vals, [
269             type =>
270             $1 eq '%' ? CSS_PERCENTAGE
271 64 50       509 : $1 eq 'D' ? $s2c{unescape do{
    100          
272 0         0 ($$tokens[$-[1]] =~ '(\D+)')[0]
273             }}
274             : CSS_NUMBER,
275             value => $1,
276             css => $1,
277             ]
278             }
279             \@vals
280 222         2897 }
281             : $type == CSS_ATTR
282             ? $$prepped[$types =~ /i/, $-[0]]
283             : $type == CSS_RECT
284             ? [
285             map scalar(
286             $types =~ /\G.*?(d?([D1])|i)/g,
287             $1 eq 'i'
288             ? [type => CSS_IDENT, value => 'auto'] #$$prepped[$-[1]]]
289             : [
290             type =>
291             $2 eq 'D'
292 1270 100 100     51360 ? $s2c{unescape do{($$tokens[$-[2]] =~ '(\D+)')[0]}}
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
293             : CSS_NUMBER,
294             value => join "", @$tokens[$-[1]...$+[1]-1]
295             ]
296             ), 1...4
297             ]
298             : die __PACKAGE__ . " internal error: unknown type: $type"
299             }
300             }
301              
302             sub _space_out {
303 2465     2465   4480 my($types,$tokens) = @_;
304 2465 50       6748 Carp'cluck() if ref $tokens ne 'ARRAY';
305 2465         12789 $tokens = [@$tokens];
306 2465         3709 my @posses;
307 2465         21035 $types =~ s/(?<=[^(f])(?![),]|\z)/
308 577 100       1902 if($tokens->[-1+pos $types] =~ m=^[+-]\z=) {
309 86         349 ''
310             }
311             else {
312 491         818 push @posses, pos $types; 's'
  491         2329  
313             }
314             /ge;
315 2465         9167 splice @$tokens, $_, 0, ' ' for reverse @posses;
316 2465         9985 return $types, $tokens;
317             }
318              
319             # Defined further down, to keep the hairiness out of the way.
320             my($colour_names_re, $system_colour_names_re);
321              
322             sub _prep_val {
323 1203 50   1203   2948 defined &unescape or
324             require CSS::DOM::Util, 'CSS::DOM::Util'->import('unescape');
325 1203         2057 my($types,$tokens);
326 1203 100       3053 if(@_ > 1) {
327 915         2376 ($types,$tokens)= @_;
328             }
329             else {
330 288         1906 require CSS::DOM::Parser;
331 288         1083 ($types, $tokens) = CSS::DOM::Parser'tokenise($_[0]);
332             }
333              
334             # strip out all whitespace tokens
335             {
336 1203         2070 my @posses;
  1203         2091  
337 1203         3616 $tokens = [@$tokens]; # We have to copy it as it may be referenced
338 1203         2770 $types =~ s/s/push @posses,pos$types;''/gem; # elsewhere.
  343         690  
  343         990  
339 1203         4604 splice@$tokens,$_,1 for reverse @posses;
340             }
341            
342 1203         2158 my @prepped;
343             my @alt_type;
344 1203         3579 for(0..$#$tokens) {
345 1843         3976 my $type = substr $types, $_, 1;
346 1843         2317 my $thing;
347 1843 100       9050 if($type =~ /[if#]/) {
    100          
    100          
    100          
348 1119         4422 $thing = lc unescape($$tokens[$_]);
349 1119 100       3308 if($type eq 'i') {
    100          
350 1061 100       13907 if($thing =~ /^$colour_names_re\z/o) { $alt_type[$_] = 'c' }
  216 100       575  
351 31         80 elsif($thing =~ /^$system_colour_names_re\z/o) { $alt_type[$_] = 's' }
352             }
353             elsif($type eq '#') {
354 13 100       91 $thing =~ /^#(?:[0-9a-f]{3}){1,2}\z/ and $alt_type[$_] = 'c';#olour
355             # ~~~ What about escapes?
356             }
357             }
358             elsif($type eq 'D') { # dimension
359 200         1162 ($thing = $$tokens[$_]) =~ s/^[.0-9]+//;
360 200         759 $thing = lc unescape($thing);
361 200 100       3268 if($thing =~ /^(?:deg|g?rad)\z/) { $alt_type[$_] = 'a'}#ngle
  24 100       62  
362             elsif($thing =~ /^(?:e[mx]|p[xtc]|in|[cm]m)\z/) {
363 147         591 $alt_type[$_] = 'l'#ength
364             }
365             }
366             elsif($type eq '1') { # number
367 141         319 $thing = 0+$$tokens[$_]; # change 0.000 to 0, etc.
368             }
369             elsif($type eq 'd') { # delimiter
370 45 100       196 $alt_type[$_] = '+' if $$tokens[$_] =~ /^[+-]\z/;
371             }
372 1843 100       5960 defined $alt_type[$_] or $alt_type[$_] = '';
373 1843         5853 push @prepped, $thing;
374             }
375              
376 1203         7566 return ($types,$tokens,\@prepped,\@alt_type);
377             }
378              
379              
380             # Various bits and pieces for _compile_format’s use
381              
382             $Fail = qr/(?!)/; # avoid recompiling the same sub-regexp doz-
383             # ens of times
384              
385             # This optionally matches a sign
386             my $sign = '(?:d(?(?{$$alt_types[pos()-1]eq"+"})|(?!)))?';
387              
388             # These $type_ expressions save the current value type in @valtypes.
389             my $type_is_ # generic one to stick inside (?{...})
390             = '$valtypes[$#valtypes='
391             . (naughty_perl ? '$pos[-1]' : 'pos()-length$^N')
392             . ']=';
393             my $type_is_dim_or_number
394             = '(?{
395             $valtypes[
396             $#valtypes=' . (naughty_perl ? '$pos[-1]' : 'pos()-length$^N') . '
397             ]
398             = $$prepped[pos()-1] ? $s2c{ $$prepped[pos()-1] } : CSS_NUMBER
399             })';
400              
401             # Constants defined in _compile_format and only used there get deleted at
402             # run time.
403 4     4   26 { no strict 'refs'; delete @{__PACKAGE__.'::'}{cap_start=>cap_end=>} }
  4         8  
  4         920  
404              
405             sub _compile_format {
406 116     116   219 my $format = shift;
407 116         194 my $no_match_stuff = shift; # Leave out the @%match localisation stuff
408              
409             # The types of transmogrifications we need to make:
410              
411             # Whitespace is ignored.
412             #
413             # [] is simply (?:).
414             #
415             # () is itself, except we record the captures manually with (?{}).
416             #
417             # The chars ? * + | are left as is (except when | is doubled).
418             #
419             # <...> thingies are replaced with simple regexps that match the type
420             # and then check with a re-eval to see whether the token matches. Then we
421             # have another re-eval that records the type of match in @valtypes, so we
422             # can distinguish between ‘red’ matched by <ident> (counter-reset: red),
423             # ‘red’ matched by <colour> (color: red) and ‘red’ matched by <str/words>
424             # (font-family: red).
425             #
426             # Identifiers are treated similarly.
427             #
428             # '...' references are turned into complicated re-evals that look up the
429             # format for the other property and add it to the %match hash if
430             # it matches.
431             #
432             # || causes the innermost enclosing group to be transformed into a per-
433             # mutation-matching pattern. Since at least one is required, we
434             # put question marks after all sub-patterns except the first in
435             # each alternate. For example, a||b||c (where the letters rep-
436             # resent sub-patterns, not actual chars in the format)
437             # becomes a(?:bc?|cb?)?|b(?:ac?|ca?)?|c(?:ab?|ba?)?.
438              
439             # Concerning the [@%][Mm]atch variables:
440             #
441             # All captures are saved separately in an array during matching. To
442             # account for backtracking, we have to localise every assignment. Since
443             # the localisations will be undone when the re exits, we have to save them
444             # in separate variables. The lc vars are used during matching; the capita-
445             # lised variables afterwards. Since we may be parsing sub-properties
446             # (with their own sets of captures), we need a second localisation mechan-
447             # ism that restores the previous set of captured values when a sub-proper-
448             # ty’s re exits. (We can’t use Perl’s, because the rest of the outer pat-
449             # tern is called recursively from within the inner pattern.) So:
450             #
451             # @match holds arrays of captures, $match[-1] being the current array.
452             # When the re exits, @{ $match[-1] } is copied into @Match. Subpatterns
453             # push onto @match upon entry and pop it on exit.
454             # ~~~ Actually, it seems we don’t currently pop it, but all tests pass. Why
455             # is this?
456             #
457             # @list is similar to @match, but it holds all captured matches in the
458             # order they matched, skipping those that did not match. It includes mul-
459             # tiple elements for quantified captures (that is, if they matched multi-
460             # ple times). @match, on the other hand, is indexed by capture number,
461             # like @-, et al. In other words, if we match ‘'rhext' 'scled'’ against
462             # ‘(<ident>)? (<string>)+’, we have:
463             # @match: undef (elem 0 is always undef), undef, 'scled'
464             # @list: 'rhext', 'scled'
465             #
466             # %match holds named captures (sub-properties) directly (no extra locali-
467             # sation necessary), which are then copied to %Match afterwards.
468             #
469             # In perl 5.10.0 (see the definition of naughty_perl, above). We work
470             # around the unreliability of $^N by pushing the current pos onto @pos
471             # before a sub-pattern or capture, and popping it afterwards. We use
472             # $pos[-1] instead of pos()-length$^N (for the beginning of the capture).
473              
474 116 50       380 my $pattern = $no_match_stuff
475             ? '' : '(?{local @match=(@match,[]); local @list=(@list,[])})(?:';
476             # We add (?: to account for top-level alternations.
477              
478 116         304 my @group_start = length $pattern; # holds the position within $pattern of
479             # the last group start
480 116         288 my @permut_marker = []; # where a || occurs (array of arrays; each group
481             # has its own array on this stack)
482 116         201 my @capture_nums;
483 116         186 my $last_capture = 0;
484              
485             # For each piece of the format, add to the pattern.
486 116         867 while(
487             $format =~ /(\s+)|(\|\|)|<([^>]+)>|([a-z-]+)|([0-9]+)|'([^']+)'|(.)/g
488             ) {
489 1687 100       4918 next if $1; # ignore whitespace
490              
491             # cygwin hack:
492             use constant { # re-evals for before and after captures
493 4         6120 cap_start => naughty_perl ? '(?{local @pos=(@pos,pos)})' : '',
494             cap_end => naughty_perl ? '(?{local @pos=@pos; --$#pos})' : '',
495 4     4   23 };
  4         7  
496              
497 1413 100       7966 if($2) { # ||
    100          
    100          
    100          
    100          
    100          
498 32         51 push @{ $permut_marker[-1] }, length $pattern;
  32         193  
499             }
500             elsif($3) { # <...>
501             # We have to wrap most of these in (?:...) in case they get quantified.
502             # (‘ab’ has to become ‘(?:ab)’ so that ‘ab?’ becomes ‘(?:ab)?’.)
503             $pattern .=
504             $3 eq 'angle' ?
505             "(?:($sign\[D1])" . cap_start . '(?(?{
506             $$alt_types[pos()-1]eq"a"||$$prepped[pos()-1]eq 0
507             })|(?!))' . $type_is_dim_or_number . cap_end .")"
508             : $3 eq 'attr' ?
509             '(?x:' . cap_start . '(
510             f(?(?{$$prepped[pos()-1]eq"attr("})|(?!))i\)
511             )' . "(?{ $type_is_ CSS_ATTR })" . cap_end . ")"
512             : $3 =~ /^colou?r\z/ ?
513             "(?x:" . cap_start . "(?:
514             ([i#](?(?{
515             \$\$alt_types[pos()-1]eq 'c'||\$\$alt_types[pos()-1]eq 's'
516             })|(?!))) (?{ $type_is_ (
517             \$\$alt_types[pos()-1]eq 'c' ? CSS_RGBCOLOR : CSS_IDENT
518             ) })
519             |
520             (f
521             (?:
522             (?(?{\$\$prepped[pos()-1]eq 'rgb('})|(?!))
523             (?: $sign 1(?:,$sign 1){2} | $sign%(?:,$sign%){2} )
524             |
525             (?(?{\$\$prepped[pos()-1]eq 'rgba('})|(?!))
526             (?: $sign 1(?:,$sign 1){2} | $sign%(?:,$sign%){2} ),$sign 1
527             )
528             \\)) (?{ $type_is_ CSS_RGBCOLOR })
529             )" . cap_end . ")"
530              
531             # <counter> represents the following four:
532             # counter(<identifier>)
533             # counter(<identifier>,'list-style-type')
534             # counters(<identifier>,<string>)
535             # counters(<identifier>,<string>,'list-style-type')
536 138 50       3333 : $3 eq 'counter' ? do {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
537 4         13 our $Self;
538 4         10 my $list_style_type = old_perl
539             ? exists $$Self{"list-style-type"}
540             ? $compiled{$$Self{"list-style-type"}{format}}
541             ||= _compile_format($$Self{"list-style-type"}{format})
542             : '(?!)'
543             : '(??{
544             exists $$Self{"list-style-type"}
545             ? $compiled{$$Self{"list-style-type"}{format}}
546             : $Fail
547             })'
548             ;
549 4         45 q*(?x:* . cap_start . q*(f(?{$$prepped[pos()-1]})
550             (?(?{$^R eq "counter("})
551             i(?:,* . $list_style_type . q*)?
552             |
553             (?(?{$^R eq "counters("})
554             i,'(?:,* . $list_style_type . q*)?
555             |
556             (?!)
557             )
558             )
559             \))*
560             . "(?{ $type_is_ CSS_COUNTER;"
561             . ' $valtypes[$#valtypes=pos()-1] = -1})' # -1 is a special
562             . cap_end . ')' # marker for the end
563             } # of a counter
564             : $3 eq 'frequency' ?
565             '(?:' . cap_start . '((?:d(?(?{
566             $$tokens[pos()-1]eq"+"||$$tokens[-1+pos]eq"-"&&$$tokens[pos]eq 0
567             })|(?!)))?[D1](?(?{
568             my$p=$$prepped[pos()-1];$p eq"hz"||$p eq"khz"||$p eq 0
569             })|(?!)))' . $type_is_dim_or_number . cap_end . ")"
570             : $3 eq 'identifier' ?
571             "(?:" . cap_start . "(i)(?{ $type_is_ CSS_IDENT })" . cap_end . ")"
572             : $3 eq 'integer' ?
573             '(?:' . cap_start . '(1(?(?{index$$tokens[pos()-1],".",==-1})|(?!)))'
574             . "(?{ $type_is_ CSS_NUMBER })" . cap_end . ")"
575             : $3 eq 'length' ?
576             "(?:" . cap_start . "($sign\[D1])" . '(?(?{
577             $$alt_types[pos()-1]eq"l"||$$prepped[pos()-1]eq 0
578             })|(?!))' . $type_is_dim_or_number . cap_end . ")"
579             : $3 eq 'number' ?
580             "(?:" . cap_start . "(1)(?{ $type_is_ CSS_NUMBER })" . cap_end . ")"
581             : $3 eq 'percentage' ?
582             "(?:" . cap_start
583             . "($sign%)(?{ $type_is_ CSS_PERCENTAGE })"
584             . cap_end . ")"
585             : $3 eq 'shape' ?
586             q*(?x:* . cap_start . q*(f
587             (?(?{$$prepped[pos()-1] eq "rect("})
588             (?:
589             (?:
590             (?:d(?(?{$$alt_types[pos()-1]eq"+"})|(?!)))?[D1](?(?{
591             $$alt_types[pos()-1]eq"l"||$$prepped[pos()-1] eq 0
592             })|(?!))
593             |
594             i(?(?{$$prepped[pos()-1]eq"auto"})|(?!))
595             ),?
596             ){4}
597             |
598             (?!)
599             )
600             \))* . "(?{ $type_is_ CSS_RECT })" . cap_end . ")"
601             : $3 eq 'string' ?
602             "(?:" . cap_start . "(')(?{ $type_is_ CSS_STRING })" . cap_end . ")"
603             : $3 eq 'str/words' ?
604             "(?:" . cap_start
605             . "('|i+)(?{ $type_is_ CSS_STRING })"
606             . cap_end . ")"
607             : $3 eq 'time' ?
608             "(?:" . cap_start . "($sign\[D1])" . '(?(?{
609             my$p=$$prepped[pos()-1];$p eq"ms"||$p eq"s"||$p eq 0
610             })|(?!))' . $type_is_dim_or_number . cap_end . ")"
611             : $3 eq 'url' ?
612             "(?:" . cap_start . "(u)(?{ $type_is_ CSS_URI })" . cap_end . ")"
613             : die "Unrecognised data type in property format: <$3>";
614             }
615             elsif($4) { # identifier
616 477         4232 $pattern .=
617             '(?:' . cap_start
618             . '(i)(?(?{$$prepped[-1+pos]eq"' . $4 . '"})|(?!))'
619             . "(?{ $type_is_ CSS_IDENT })"
620             . cap_end . ")";
621             }
622             elsif($5) { # number
623 18         349 $pattern .=
624             '(?:' . cap_start
625             . '(1)(?(?{$$tokens[-1+pos]eq"' . $5 . '"})|(?!))'
626             . "(?{ $type_is_ CSS_NUMBER })"
627             . cap_end . ")";
628             }
629 713         6821 elsif($6) { # '...' reference
630 35         310 $pattern .=
631             '(?:' # again, we use (?: ... ) in case a question mark is added
632             . cap_start
633             . '((??{
634             exists $$Self{"' . $6 . '"}
635             ? $compiled{$$Self{"' . $6 . '"}{format}}
636             : $Fail;
637             }))'
638             . '(?{
639             # We have a do-block here because a re-eval’s lexical pad is very
640             # buggy and must not be used. (See perl bug #65150.)
641             local$match{"'.$6.'"}=do{
642             my @range
643             = ' . (naughty_perl ? '$pos[-1]' : 'pos()-length$^N') . '
644             ...-1+pos;
645             [
646             '.(
647             naughty_perl ? 'substr($_,$pos[-1],pos()-$pos[-1])' : '$^N'
648             ).',
649             [@$tokens[@range]],[@valtypes[@range]],[@$prepped[@range]]
650             ];
651             }
652             })'
653             . cap_end
654             .')'
655            
656             }
657             elsif(do{$7 =~ /^[]|[()]\z/}) { # group or alternation
658             # For non-capturing groups, we use (?: ... ).
659             # For capturing groups, since they may be quantified, and since we have
660             # to put a re-eval after them to capture the value, we use an extra non-
661             # capturing group: (?:( ... )(?{...}))
662             # Since || is stronger than |, we have to treat | a bit like ][
663              
664 659 100       984 if(do{$7 =~ /^[])|]\z/}) { # end of a group
  659         2682  
665 569         943 my $markers = pop @permut_marker;
666 569 100       1349 if(@$markers) { # Oh no!
667 6         19 unshift @$markers, $group_start[-1];
668 6         31 _make_permutations($pattern, $markers);
669             }
670 569         801 pop @group_start;
671 569 100       2262 $pattern .=
    100          
672             $7 eq '|' ? '|'
673             : $7 eq ']' ? ')'
674             : ')(?{
675             (
676             local $match[-1][' . pop(@capture_nums) . '],
677             local $list[-1]
678             ) = do {
679             my @range
680             = '.(naughty_perl ? '$pos[-1]' : 'pos()-length$^N').'...-1+pos;
681             my @a = (
682             '.(
683             naughty_perl
684             ? 'substr($_,$pos[-1],pos()-$pos[-1])'
685             : '$^N'
686             ).',
687             [@$tokens[@range]],[@valtypes[@range]],[@$prepped[@range]],
688             pos
689             );
690             \@a, [@{$list[-1]}, \@a]
691             }
692             })' . cap_end . ')';
693             # We have to intertwine these assignments in this convoluted way
694             # because of the lexical-in-re-eval bug [perl #65150].
695             }
696 659 100       964 if(do{$7 =~ /^[[(|]\z/}) { # start of a group
  659         3865  
697 569 100       1694 $pattern
698             .= '(?:'
699             . (cap_start.'(')
700             x ($7 eq '(')
701             unless $7 eq '|';
702 569         887 push @group_start, length $pattern;
703 569         910 push @permut_marker, [];
704 569 100       4046 $7 eq '(' and push @capture_nums, ++$last_capture;
705             }
706             }
707             else {
708 54         403 $pattern .= do{$7 =~ /^[?*+]\z/} ? $7
  9         104  
709 54 100       98 : do{$7 =~ /^[;{},:]\z/} ? quotemeta $7
    100          
710             : '(?:d(?(?{$$tokens[-1+pos]eq"' .quotemeta($7) .'"})|(?!)))'
711             ;
712             }
713             }
714              
715             # There may be top-level ‘||’ things, so we check for those.
716 116 100       325 if(@{$permut_marker[0]}) {
  116         421  
717 10         19 unshift @{ $permut_marker[0] }, $group_start[0];
  10         32  
718 10         49 _make_permutations($pattern, $permut_marker[0]);
719             }
720              
721             # Deal with the match vars
722 116 50       387 $pattern .= ')(?{@Match=@{$match[-1]};@List=@{$list[-1]};%Match=%match})'
723             unless $no_match_stuff;
724              
725 4     4   30 use re 'eval';
  4         9  
  4         13747  
726 116         277225 return qr/$pattern/;
727             }
728              
729             sub _make_permutations { # args: pattern, \@markers
730             # pattern is modified in-place
731 16     16   42 my $markers = pop;
732 16         50 for my $pattern($_[0]) {
733             # Split up the end of the pattern back to the beginning of the inner-
734             # most enclosing group, as specified by the markers. Put the separate
735             # pieces into @alts.
736 16         21 my @alts;
737 16         45 for(reverse @$markers) {
738 48         190 unshift @alts, substr $pattern, $_, length $pattern, '';
739             }
740            
741             # Do the permutations
742 16         76 $pattern .= _permute(@alts);
743             }
744             }
745              
746             sub _permute {
747 160 100   160   431 if(@_ == 2) { return "(?:$_[0]$_[1]?|$_[1]$_[0]?)" }
  115         4339  
748             else {
749             return
750 45         271 "(?:"
751             . join("|", map $_[$_] . _permute(@_[0..$_-1,$_+1...$#_]) . '?', 0..$#_)
752             . ")"
753             }
754             }
755              
756              
757             =begin comment
758              
759             Colour names:
760              
761             perl -MRegexp::Assemble -le 'my $ra = new Regexp::Assemble; $ra->add($_) for qw " transparent aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen"; print $ra->re '
762              
763             perl -MRegexp::Assemble -le 'my $ra = new Regexp::Assemble; $ra->add($_) for qw " activeborder activecaption appworkspace background buttonface buttonhighlight buttonshadow buttontext captiontext graytext highlight highlighttext inactiveborder inactivecaption incativecaptiontext infobackground infotext menu menutext scrollbar threeddarkshadow threedface threedhighlight threedlightshadow threedshadow window windowframe windowtext "; print $ra->re '
764              
765             =end comment
766              
767             =cut
768              
769             $colour_names_re = '(?:d(?:ark(?:s(?:late(?:gr[ae]y|blue)|(?:eagree|almo)n)|g(?:r(?:e(?:en|y)|ay)|oldenrod)|o(?:r(?:ange|chid)|livegreen)|(?:turquois|blu)e|magenta|violet|khaki|cyan|red)|eep(?:skyblue|pink)|imgr[ae]y|odgerblue)|l(?:i(?:ght(?:s(?:(?:eagree|almo)n|(?:teel|ky)blue|lategr[ae]y)|g(?:r(?:e(?:en|y)|ay)|oldenrodyellow)|c(?:oral|yan)|yellow|blue|pink)|me(?:green)?|nen)|a(?:vender(?:blush)?|wngreen)|emonchiffon)|m(?:edium(?:(?:aquamarin|turquois|purpl|blu)e|s(?:(?:pring|ea)green|lateblue)|(?:violetre|orchi)d)|i(?:(?:dnightblu|styros)e|ntcream)|a(?:genta|roon)|occasin)|s(?:(?:a(?:(?:ddle|ndy)brow|lmo)|pringgree)n|late(?:gr[ae]y|blue)|ea(?:green|shell)|(?:teel|ky)blue|i(?:enna|lver)|now)|p(?:a(?:le(?:g(?:oldenrod|reen)|turquoise|violetred)|payawhip)|(?:owderblu|urpl)e|e(?:achpuff|ru)|ink|lum)|c(?:(?:h(?:artreus|ocolat)|adetblu)e|or(?:n(?:flowerblue|silk)|al)|(?:rimso|ya)n)|b(?:l(?:a(?:nchedalmond|ck)|ue(?:violet)?)|(?:isqu|eig)e|urlywood|rown)|g(?:r(?:e(?:en(?:yellow)?|y)|ay)|ol(?:denro)?d|hostwhite|ainsboro)|o(?:l(?:ive(?:drab)?|dlace)|r(?:ange(?:red)?|chid))|a(?:(?:ntiquewhit|liceblu|zur)e|qua(?:marine)?)|t(?:(?:urquois|histl)e|ransparent|omato|eal|an)|f(?:loralwhite|orestgreen|irebrick|uchsia)|r(?:o(?:sybrown|yalblue)|ed)|i(?:ndi(?:anred|go)|vory)|wh(?:it(?:esmok)?e|eat)|ho(?:neydew|tpink)|nav(?:ajowhite|y)|yellow(?:green)?|violet|khaki)';
770              
771             $system_colour_names_re = '(?:in(?:active(?:caption|border)|fo(?:background|text)|cativecaptiontext)|b(?:utton(?:(?:highligh|tex)t|shadow|face)|ackground)|threed(?:(?:light|dark)?shadow|highlight|face)|(?:(?:caption|gray)tex|highligh(?:ttex)?)t|a(?:ctive(?:caption|border)|ppworkspace)|window(?:frame|text)?|menu(?:text)?|scrollbar)';
772              
773             =head1 NAME
774              
775             CSS::DOM::PropertyParser - Parser for CSS property values
776              
777             =head1 VERSION
778              
779             Version 0.15
780              
781             =head1 SYNOPSIS
782              
783             use CSS::DOM::PropertyParser;
784            
785             $spec = new CSS::DOM::PropertyParser; # empty
786             # OR
787             $spec = $CSS::DOM::PropertyParser::Default->clone;
788            
789             $spec->add_property(
790             overflow => {
791             format => 'visible|hidden|scroll|auto',
792             default => 'visible',
793             inherit => 0,
794             }
795             );
796            
797             $hashref = $spec->get_property('overflow');
798            
799             $hashref = $spec->delete_property('overflow');
800            
801             @names = $spec->property_names;
802              
803             =head1 DESCRIPTION
804              
805             Objects of this class provide lists of supported properties for L<CSS::DOM>
806             style sheets. They also describe the syntax and parsing of those
807             properties' values.
808              
809             Some CSS properties simply have their own values (e.g., overflow); some
810             are abbreviated forms of several other properties (e.g., font). These are
811             referred to in this documentation as 'simple' and 'shorthand' properties.
812              
813             =head1 CONSTRUCTOR
814              
815             C<$spec = new CSS::DOM::PropertyParser> returns an object that does not
816             recognise any properties, to which you
817             can add your own properties.
818              
819             There are two parser objects that come with this module. These are
820             C<$CSS::DOM::PropertyParser::CSS21>, which contains all of CSS 2.1, and
821             C<$CSS::DOM::PropertyParser::Default>, which is currently identical to the
822             former, but to which parts of CSS 3 which eventually be added.
823              
824             If one of the default specs will do, you don't need a constructor. Simply
825             pass it to the L<CSS::DOM> constructor. If you want to modify it, clone it
826             first, using the C<clone> method (as shown in the L</SYNOPSIS>). It is
827             often convenient to clone the C<$Default> spec and delete those properties
828             that are not supported.
829              
830             =head1 METHODS
831              
832             =for comment
833             =head2 Methods for Controlling Property Specifications
834              
835             =over 4
836              
837             =item clone
838              
839             Returns a deep clone of the object. (It's deep so that you can modify the
840             hashes/arrays inside it without modifying the original.)
841              
842             =item add_property ( $name, \%spec )
843              
844             Adds the specification for the named property. See
845             L</HOW INDIVIDUAL PROPERTIES ARE SPECIFIED>, below.
846              
847             =item get_property ( $name )
848              
849             Returns the hashref passed to the previous method.
850              
851             =item delete_property ( $name )
852              
853             Deletes the property and returns the hash ref.
854              
855             =item property_names
856              
857             Returns a list of the names of supported properties.
858              
859             =item subproperty_names ( $name )
860              
861             Returns a list of the names of C<$name>'s sub-properties if it is a
862             shorthand property.
863              
864             =item match
865              
866             Currently for internal use only. See the source code for documentation.
867             Use at your own risk.
868              
869             =back
870              
871             =begin comment
872              
873             Once I’ve made CSS::DOM::Parser’s tokenise routine public (after a bit of
874             polishing) (or broken it out into a separate module, CSS::Tokeniser), I’ll
875             add this to the docs. I also actually have to modify ‘match’ to use this
876             interface, of course.
877              
878             =head2 Methods Used by L<CSS::DOM::Style>
879              
880             If you are thinking of writing a subclass of PropertyParser, you need to be
881             aware of these methods.
882              
883             Instead of writing a subclass, you can create your
884             own class that does not inherit from PropertyParser use that. It will need
885             to
886             implement these methods here. The methods listed above can be omitted.
887              
888             =over
889              
890             =item match ( $property, $value )
891              
892             =item match ( $property, $token_types, \@tokens )
893              
894             This checks to see whether C<$value> is a valid value for the C<$property>,
895             parsing it if it is. C<$token_types> and C<@tokens> are the values returned
896             by C<CSS::DOM::Parser::tokenise>.
897              
898             Return values are as follows:
899              
900             If the value doesn't match: empty list.
901              
902             If the property is a simple one: (0) the CSS
903             code for the value (possibly normalised), (1) the class to which a value
904             object belongs, (2..) arguments to be passed to the constructor.
905              
906             For a shorthand property, the return value is a single hash ref, the keys
907             being sub-property names and the values array refs containing what would be
908             returned for a simple property.
909              
910             A custom class or subclass can return a L<CSS::DOM::Value> instead of the
911             class and constructor args, in which case the first return value can
912             simply be C<undef> (it should return C<(undef, $object)>).
913              
914             Examples (return value starts on the line following each method call):
915              
916             # $prim stands for "CSS::DOM::Value::Primitive"
917             # $list stands for "CSS::DOM::Value::List"
918            
919             $prop_parser->match('background-position','top left');
920             'top left', 'CSS::DOM::Value', CSS_CUSTOM, 'top left'
921            
922             $prop_parser->match('background-position','inherit');
923             'inherit', 'CSS::DOM::Value', CSS_INHERIT
924            
925             $prop_parser->match('top','1em');
926             '1em', $prim, type => CSS_EMS, value => 1
927              
928             $prop_parser->match('content','"\66oo"');
929             '"\66oo"', $prim, type => CSS_STRING, value => foo
930            
931             $prop_parser->match('clip','rect( 5px, 6px, 7px, 8px )');
932             'rect(5px, 6px, 7px, 8px)', $prim,
933             type => CSS_RECT,
934             value => [ [ type => CSS_PX, value => 5 ],
935             [ type => CSS_PX, value => 6 ],
936             [ type => CSS_PX, value => 7 ],
937             [ type => CSS_PX, value => 8 ] ]
938            
939             $prop_parser->match('color','#fff');
940             '#fff', $prim, type => CSS_RGBCOLOR, value => '#fff'
941            
942             $prop_parser->match('color','rgba(255,0,0,.5)');
943             'rgba(255, 0, 0, .5)', $prim, type => CSS_RGBCOLOR,
944             value => [ [ type => CSS_NUMBER, value => 255 ],
945             [ type => CSS_NUMBER, value => 0 ],
946             [ type => CSS_NUMBER, value => 0 ],
947             [ type => CSS_NUMBER, value => .5 ] ]
948            
949             $prop_parser->match('content','counter(foo,disc)');
950             'counter(foo, disc)', $list,
951             separator => ' ',
952             values => [
953             [
954             type => CSS_COUNTER,
955             value => [
956             [ type => CSS_IDENT, value => 'foo' ],
957             undef,
958             [ type => CSS_IDENT, value => 'disc' ],
959             ]
960             ],
961             ]
962            
963             $prop_parser->match('font-family','Lucida Grande');
964             'Lucida Grande', $list,
965             separator => ', ',
966             values => [
967             [ type => CSS_STRING, value => 'Lucida Grande' ],
968             ]
969              
970             $prop_parser->match('counter-reset','Lucida Grande');
971             'Lucida Grande', $list,
972             separator => ' ',
973             values => [
974             [ type => CSS_IDENT, value => 'Lucida' ],
975             [ type => CSS_IDENT, value => 'Grande' ],
976             ]
977            
978             $prop_parser->match('font','bold 13px Lucida Grande');
979             {
980             'font-style' => [
981             'normal', $prim, type => CSS_IDENT, value => 'normal'
982             ],
983             'font-variant' => [
984             'normal', $prim, type => CSS_IDENT, value => 'normal'
985             ],
986             'font-weight' => [
987             'bold', $prim, type => CSS_IDENT, value => 'bold'
988             ],
989             'font-size' => [ '13px', $prim, type => CSS_PX, value => 13 ],
990             'line-height' => [
991             'normal', $prim, type => CSS_IDENT, value => 'normal'
992             ],
993             'font-family' => [ 'Lucida Grande', $list,
994             separator => ', ',
995             values => [
996             [ type => CSS_STRING, value => 'Lucida Grande' ],
997             ]
998             ]
999             }
1000              
1001             =item whatever
1002              
1003             ~~~
1004             CSS::DOM::Style currently relies on the internal formatting of the hash
1005             refs. I want to allow custom property parser classes to do away with hash
1006             refs
1007             altogether, so I will need extra methods here that Style will use instead.
1008              
1009             =back
1010              
1011             =end comment
1012              
1013             =head1 HOW INDIVIDUAL PROPERTIES ARE SPECIFIED
1014              
1015             Before you read this the first time, look at the L</Example> below, and
1016             then come back and use this for reference.
1017              
1018             The specification for an individual property is a hash ref. There are
1019             several keys that each hash ref can have:
1020              
1021             =over
1022              
1023             =item format
1024              
1025             This is set to a string that describes the format of the property. The
1026             syntax used is based on the CSS 2.1 spec, but is not exactly the same.
1027             Unlike regular expressions, these formats are applied to properties on a
1028             token-by-token basis, not one character at a time. (This means that
1029             C<100|200> cannot be written as C<[1|2]00>, as that would mean
1030             S<C<1 00 | 2 00>>.)
1031              
1032             Whitespace is ignored in the format and in the CSS property except as a
1033             token separator.
1034              
1035             There are several metachars (in order of precedence):
1036              
1037             [...] grouping (like (?:...) )
1038             (...) capturing group (just like a regexp)
1039             ? optional
1040             * zero or more
1041             + one or more
1042             || alternates that can come in any order and are optional,
1043             but at least one must be specified (the order will be
1044             retained if possible)
1045             | alternates, exactly one of which is required
1046              
1047             In addition, the following datatypes can be specified in angle brackets:
1048              
1049             <angle> A number with a 'deg', 'rad' or 'grad' suffix
1050             <attr> attr(...)
1051             <colour> (You can omit the 'u' if you want to.) One of CSS's
1052             predefined colour or system colour names, or a #
1053             followed by 3 or 6 hex digits, or the 'rgb(...)'
1054             format (rgba is supported, too)
1055             <counter> counter(...)
1056             <frequency> A unit of Hz or kHz
1057             <identifier> An identifier token
1058             <integer> An integer (really?!)
1059             <length> Number followed by a length unit (em, ex, px, in, cm,
1060             mm, pt, pc)
1061             <number> A number token
1062             <percentage> Number followed by %
1063             <shape> rect(...)
1064             <string> A string token
1065             <str/words> A sequence of identifiers or a single string (e.g., a
1066             font name)
1067             <time> A unit of seconds or milliseconds
1068             <url> A URL token
1069              
1070             The format for a shorthand property can contain the name of a sub-property
1071             in single ASCII quotes.
1072              
1073             All other characters are understood verbatim.
1074              
1075             It is not necessary to include the word 'inherit' in the format, since
1076             every property supports that.
1077              
1078             C<< <counter> >> makes use of the specification for the list-style-type
1079             property. So if you modify the latter, it will affect C<< <counter> >> as
1080             well.
1081              
1082             =item default
1083              
1084             The default value. This only applies to simple properties.
1085              
1086             =item inherit
1087              
1088             Whether the property is inherited.
1089              
1090             =item special_values
1091              
1092             A hash ref of values that are replaced with other values (e.g.,
1093             S<C<< caption => '13px sans-serif' >>>.) The keys
1094             are lowercase identifier names.
1095              
1096             This feature only applies to single identifiers. In fact, it exists solely
1097             for the font property's use.
1098              
1099             =item list
1100              
1101             Set to true if the property is a list of values. The capturing parentheses
1102             in the format determine the individual values of the list.
1103              
1104             This applies to simple properties only.
1105              
1106             =item properties
1107              
1108             For a shorthand property, list the sub-properties here. The keys are the
1109             property names. The values are array refs. The elements within the arrays
1110             are numbers indicating which captures in the format are to be used for the
1111             sub-property's value. They are tried one after the other. Whichever is the
1112             first that matches (null matches not counting) is used.
1113              
1114             Sub-properties that are referenced in the C<format> need not be listed
1115             here.
1116              
1117             =item serialise
1118              
1119             For shorthand properties only. Set this to a subroutine that serialises the
1120             property. It is called with a hashref of sub-properties as its sole
1121             argument. The values of the hash are blank for properties that are set to
1122             their initial values. This sub is only called when all sub-properties are
1123             set.
1124              
1125             =back
1126              
1127             =head2 Example
1128              
1129             =cut
1130              
1131             0&&q r
1132              
1133             =for ;
1134              
1135             our $CSS21 = new CSS::DOM::PropertyParser;
1136             my %properties = (
1137             azimuth => {
1138             format => '<angle> |
1139             [ left-side | far-left | left | center-left |
1140             center | center-right | right | far-right |
1141             right-inside ] || behind
1142             | leftwards | rightwards',
1143             default => '0',
1144             inherit => 1,
1145             },
1146              
1147             'background-attachment' => {
1148             format => 'scroll | fixed',
1149             default => 'scroll',
1150             inherit => 0,
1151             },
1152              
1153             'background-color' => {
1154             format => '<colour>',
1155             default => 'transparent',
1156             inherit => 0,
1157             },
1158              
1159             'background-image' => {
1160             format => '<url> | none',
1161             default => 'none',
1162             inherit => 0,
1163             },
1164            
1165             'background-position' => {
1166             format => '[<percentage>|<length>|left|right]
1167             [<percentage>|<length>|top|center|bottom]? |
1168             [top|bottom] [left|center|right]? |
1169             center [<percentage>|<length>|left|right|top|bottom|
1170             center]?',
1171             default => '0% 0%',
1172             inherit => 0,
1173             },
1174            
1175             'background-repeat' => {
1176             format => 'repeat | repeat-x | repeat-y | no-repeat',
1177             default => 'repeat',
1178             inherit => 0,
1179             },
1180            
1181             background => {
1182             format => "'background-color' || 'background-image' ||
1183             'background-repeat' || 'background-attachment' ||
1184             'background-position'",
1185             serialise => sub {
1186             my $p = shift;
1187             my $ret = '';
1188             for(qw/ background-color background-image background-repeat
1189             background-attachment background-position /) {
1190             length $p->{$_} and $ret .= "$p->{$_} ";
1191             }
1192             chop $ret;
1193             length $ret ? $ret : 'none'
1194             },
1195             },
1196            
1197             'border-collapse' => {
1198             format => 'collapse | separate',
1199             inherit => 1,
1200             default => 'separate',
1201             },
1202            
1203             'border-color' => {
1204             format => '(<colour>)[(<colour>)[(<colour>)(<colour>)?]?]?',
1205             properties => {
1206             'border-top-color' => [1],
1207             'border-right-color' => [2,1],
1208             'border-bottom-color' => [3,1],
1209             'border-left-color' => [4,2,1],
1210             },
1211             serialise => sub {
1212             my $p = shift;
1213             my @vals = map $p->{"border-$_-color"},
1214             qw/top right bottom left/;
1215             $vals[3] eq $vals[1] and pop @vals,
1216             $vals[2] eq $vals[0] and pop @vals,
1217             $vals[1] eq $vals[0] and pop @vals;
1218             return join " ", @vals;
1219             },
1220             },
1221            
1222             'border-spacing' => {
1223             format => '<length> <length>?',
1224             default => '0',
1225             inherit => 1,
1226             },
1227            
1228             'border-style' => {
1229             format => "(none|hidden|dotted|dashed|solid|double|groove|ridge|
1230             inset|outset)
1231             [ (none|hidden|dotted|dashed|solid|double|groove|
1232             ridge|inset|outset)
1233             [ (none|hidden|dotted|dashed|solid|double|groove|
1234             ridge|inset|outset)
1235             (none|hidden|dotted|dashed|solid|double|groove|
1236             ridge|inset|outset)?
1237             ]?
1238             ]?",
1239             properties => {
1240             'border-top-style' => [1],
1241             'border-right-style' => [2,1],
1242             'border-bottom-style' => [3,1],
1243             'border-left-style' => [4,2,1],
1244             },
1245             serialise => sub {
1246             my $p = shift;
1247             my @vals = map $p->{"border-$_-style"},
1248             qw/top right bottom left/;
1249             $vals[3] eq $vals[1] and pop @vals,
1250             $vals[2] eq $vals[0] and pop @vals,
1251             $vals[1] eq $vals[0] and pop @vals;
1252             return join " ", map $_||'none', @vals;
1253             },
1254             },
1255            
1256             'border-top' => {
1257             format => "'border-top-width' || 'border-top-style' ||
1258             'border-top-color'",
1259             serialise => sub {
1260             my $p = shift;
1261             my $ret = '';
1262             for(qw/ width style color /) {
1263             length $p->{"border-top-$_"}
1264             and $ret .= $p->{"border-top-$_"}." ";
1265             }
1266             chop $ret;
1267             $ret
1268             },
1269             },
1270             'border-right' => {
1271             format => "'border-right-width' || 'border-right-style' ||
1272             'border-right-color'",
1273             serialise => sub {
1274             my $p = shift;
1275             my $ret = '';
1276             for(qw/ width style color /) {
1277             length $p->{"border-right-$_"}
1278             and $ret .= $p->{"border-right-$_"}." ";
1279             }
1280             chop $ret;
1281             $ret
1282             },
1283             },
1284             'border-bottom' => {
1285             format => "'border-bottom-width' || 'border-bottom-style' ||
1286             'border-bottom-color'",
1287             serialise => sub {
1288             my $p = shift;
1289             my $ret = '';
1290             for(qw/ width style color /) {
1291             length $p->{"border-bottom-$_"}
1292             and $ret .= $p->{"border-bottom-$_"}." ";
1293             }
1294             chop $ret;
1295             $ret
1296             },
1297             },
1298             'border-left' => {
1299             format => "'border-left-width' || 'border-left-style' ||
1300             'border-left-color'",
1301             serialise => sub {
1302             my $p = shift;
1303             my $ret = '';
1304             for(qw/ width style color /) {
1305             length $p->{"border-left-$_"}
1306             and $ret .= $p->{"border-left-$_"}." ";
1307             }
1308             chop $ret;
1309             $ret
1310             },
1311             },
1312            
1313             'border-top-color' => {
1314             format => '<colour>',
1315             default => "",
1316             inherit => 0,
1317             },
1318             'border-right-color' => {
1319             format => '<colour>',
1320             default => "",
1321             inherit => 0,
1322             },
1323             'border-bottom-color' => {
1324             format => '<colour>',
1325             default => "",
1326             inherit => 0,
1327             },
1328             'border-left-color' => {
1329             format => '<colour>',
1330             default => "",
1331             inherit => 0,
1332             },
1333            
1334             'border-top-style' => {
1335             format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
1336             inset|outset',
1337             default => 'none',
1338             inherit => 0,
1339             },
1340             'border-right-style' => {
1341             format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
1342             inset|outset',
1343             default => 'none',
1344             inherit => 0,
1345             },
1346             'border-bottom-style' => {
1347             format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
1348             inset|outset',
1349             default => 'none',
1350             inherit => 0,
1351             },
1352             'border-left-style' => {
1353             format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
1354             inset|outset',
1355             default => 'none',
1356             inherit => 0,
1357             },
1358            
1359             'border-top-width' => {
1360             format => '<length>|thin|thick|medium',
1361             default => 'medium',
1362             inherit => 0,
1363             },
1364             'border-right-width' => {
1365             format => '<length>|thin|thick|medium',
1366             default => 'medium',
1367             inherit => 0,
1368             },
1369             'border-bottom-width' => {
1370             format => '<length>|thin|thick|medium',
1371             default => 'medium',
1372             inherit => 0,
1373             },
1374             'border-left-width' => {
1375             format => '<length>|thin|thick|medium',
1376             default => 'medium',
1377             inherit => 0,
1378             },
1379            
1380             'border-width' => {
1381             format => "(<length>|thin|thick|medium)
1382             [ (<length>|thin|thick|medium)
1383             [ (<length>|thin|thick|medium)
1384             (<length>|thin|thick|medium)?
1385             ]?
1386             ]?",
1387             properties => {
1388             'border-top-width' => [1],
1389             'border-right-width' => [2,1],
1390             'border-bottom-width' => [3,1],
1391             'border-left-width' => [4,2,1],
1392             },
1393             serialise => sub {
1394             my $p = shift;
1395             my @vals = map $p->{"border-$_-width"},
1396             qw/top right bottom left/;
1397             $vals[3] eq $vals[1] and pop @vals,
1398             $vals[2] eq $vals[0] and pop @vals,
1399             $vals[1] eq $vals[0] and pop @vals;
1400             return join " ", map length $_ ? $_ : 'medium', @vals;
1401             },
1402             },
1403            
1404             border => {
1405             format => "(<length>|thin|thick|medium) ||
1406             (none|hidden|dotted|dashed|solid|double|groove|ridge|
1407             inset|outset) || (<colour>)",
1408             properties => {
1409             'border-top-width' => [1],
1410             'border-right-width' => [1],
1411             'border-bottom-width' => [1],
1412             'border-left-width' => [1],
1413             'border-top-style' => [2],
1414             'border-right-style' => [2],
1415             'border-bottom-style' => [2],
1416             'border-left-style' => [2],
1417             'border-top-color' => [3],
1418             'border-right-color' => [3],
1419             'border-bottom-color' => [3],
1420             'border-left-color' => [3],
1421             },
1422             serialise => sub {
1423             my $p = shift;
1424             my $ret = '';
1425             for(qw/ width style color /) {
1426             my $temp = $p->{"border-top-$_"};
1427             for my $side(qw/ right bottom left /) {
1428             $temp eq $p->{"border-$side-$_"} or return "";
1429             }
1430             length $temp and $ret .= "$temp ";
1431             }
1432             chop $ret;
1433             $ret
1434             },
1435             },
1436            
1437             bottom => {
1438             format => '<length>|<percentage>|auto',
1439             default => 'auto',
1440             inherit => 0,
1441             },
1442            
1443             'caption-side' => {
1444             format => 'top|bottom',
1445             default => 'top',
1446             inherit => 1,
1447             },
1448            
1449             clear => {
1450             format => 'none|left|right|both',
1451             default => 'none',
1452             inherit => 0,
1453             },
1454            
1455             clip => {
1456             format => '<shape>|auto',
1457             default => 'auto',
1458             inherit => 0,
1459             },
1460            
1461             color => {
1462             format => '<colour>',
1463             default => 'rgba(0,0,0,1)',
1464             inherit => 1,
1465             },
1466            
1467             content => {
1468             format => '( normal|none|open-quote|close-quote|no-open-quote|
1469             no-close-quote|<string>|<url>|<counter>|<attr> )+',
1470             default => 'normal',
1471             inherit => 0,
1472             list => 1,
1473             },
1474            
1475             'counter-increment' => {
1476             format => '[(<identifier>) (<integer>)? ]+ | none',
1477             default => 'none',
1478             inherit => 0,
1479             list => 1,
1480             },
1481             'counter-reset' => {
1482             format => '[(<identifier>) (<integer>)? ]+ | none',
1483             default => 'none',
1484             inherit => 0,
1485             list => 1,
1486             },
1487            
1488             'cue-after' => {
1489             format => '<url>|none',
1490             default => 'none',
1491             inherit => 0,
1492             },
1493             'cue-before' => {
1494             format => '<url>|none',
1495             default => 'none',
1496             inherit => 0,
1497             },
1498            
1499             cue =>{
1500             format => '(<url>|none) (<url>|none)?',
1501             properties => {
1502             'cue-before' => [1],
1503             'cue-after' => [2,1],
1504             },
1505             serialise => sub {
1506             my $p = shift;
1507             my @vals = @$p{"cue-before", "cue-after"};
1508             $vals[1] eq $vals[0] and pop @vals;
1509             return join " ", map length $_ ? $_ : 'none', @vals;
1510             },
1511             },
1512            
1513             cursor => {
1514             format => '[(<url>) ,]*
1515             (auto|crosshair|default|pointer|move|e-resize|
1516             ne-resize|nw-resize|n-resize|se-resize|sw-resize|
1517             s-resize|w-resize|text|wait|help|progress)',
1518             default => 'auto',
1519             inherit => 1,
1520             list => 1,
1521             },
1522            
1523             direction => {
1524             format => 'ltr|rtl',
1525             default => 'ltr',
1526             inherit => 1,
1527             },
1528            
1529             display => {
1530             format => 'inline|block|list-item|run-in|inline-block|table|
1531             inline-table|table-row-group|table-header-group|
1532             table-footer-group|table-row|table-column-group|
1533             table-column|table-cell|table-caption|none',
1534             default => 'inline',
1535             inherit => 0,
1536             },
1537            
1538             elevation => {
1539             format => '<angle>|below|level|above|higher|lower',
1540             default => '0',
1541             inherit => 1,
1542             },
1543            
1544             'empty-cells' => {
1545             format => 'show|hide',
1546             default => 'show',
1547             inherit => 1,
1548             },
1549            
1550             float => {
1551             format => 'left|right|none',
1552             default => 'none',
1553             inherit => 0,
1554             },
1555            
1556             'font-family' => { # aka typeface
1557             format => '(serif|sans-serif|cursive|fantasy|monospace|
1558             <str/words>)
1559             [,(serif|sans-serif|cursive|fantasy|monospace|
1560             <str/words>)]*',
1561             default => 'Times, serif',
1562             inherit => 1,
1563             list => 1,
1564             },
1565            
1566             'font-size' => {
1567             format => 'xx-small|x-small|small|medium|large|x-large|xx-large|
1568             larger|smaller|<length>|<percentage>',
1569             default => 'medium',
1570             inherit => 1,
1571             },
1572            
1573             'font-style' => {
1574             format => 'normal|italic|oblique',
1575             default => 'normal',
1576             inherit => 1,
1577             },
1578            
1579             'font-variant' => {
1580             format => 'normal | small-caps',
1581             default => 'normal',
1582             inherit => 1,
1583             },
1584            
1585             'font-weight' => {
1586             format => 'normal|bold|bolder|lighter|
1587             100|200|300|400|500|600|700|800|900',
1588             default => 'normal',
1589             inherit => 1,
1590             },
1591            
1592             font => {
1593             format => "[ 'font-style' || 'font-variant' || 'font-weight' ]?
1594             'font-size' [ / 'line-height' ]? 'font-family'",
1595             special_values => {
1596             caption => '13px Lucida Grande, sans-serif',
1597             icon => '13px Lucida Grande, sans-serif',
1598             menu => '13px Lucida Grande, sans-serif',
1599             'message-box' => '13px Lucida Grande, sans-serif',
1600             'small-caption' => '11px Lucida Grande, sans-serif',
1601             'status-bar' => '10px Lucida Grande, sans-serif',
1602             },
1603             serialise => sub {
1604             my $p = shift;
1605             my $ret = '';
1606             for(qw/ style variant weight /) {
1607             length $p->{"font-$_"}
1608             and $ret .= $p->{"font-$_"}." ";
1609             }
1610             $ret .= length $p->{'font-size'}
1611             ? $p->{'font-size'}
1612             : 'medium';
1613             $ret .= "/$p->{'line-height'}" if length $p->{'line-height'};
1614             $ret .= " " . ($p->{'font-family'} || "Times, serif");
1615             $ret
1616             },
1617             },
1618            
1619             height => {
1620             format => '<length>|<percentage>|auto',
1621             default => 'auto',
1622             inherit => 0,
1623             },
1624            
1625             left => {
1626             format => '<length>|<percentage>|auto',
1627             default => 'auto',
1628             inherit => 0,
1629             },
1630            
1631             'letter-spacing' => { # aka tracking
1632             format => 'normal|<length>',
1633             default => 'normal',
1634             inherit => 1,
1635             },
1636            
1637             'line-height' => { # aka leading
1638             format => 'normal|<number>|<length>|<percentage>',
1639             default => "normal",
1640             inherit => 1,
1641             },
1642            
1643             'list-style-image' => {
1644             format => '<url>|none',
1645             default => 'none',
1646             inherit => 1,
1647             },
1648            
1649             'list-style-position' => {
1650             format => 'inside|outside',
1651             default => 'outside',
1652             inherit => 1,
1653             },
1654            
1655             'list-style-type' => {
1656             format => 'disc|circle|square|decimal|decimal-leading-zero|
1657             lower-roman|upper-roman|lower-greek|lower-latin|
1658             upper-latin|armenian|georgian|lower-alpha|
1659             upper-alpha',
1660             default => 'disc',
1661             inherit => 1,
1662             },
1663            
1664             'list-style' => {
1665             format => "'list-style-type'||'list-style-position'||
1666             'list-style-image'",
1667             serialise => sub {
1668             my $p = shift;
1669             my $ret = '';
1670             for(qw/ type position image /) {
1671             $p->{"list-style-$_"}
1672             and $ret .= $p->{"list-style-$_"}." ";
1673             }
1674             chop $ret;
1675             $ret || 'disc'
1676             },
1677             },
1678            
1679             'margin-right' => {
1680             format => '<length>|<percentage>|auto',
1681             default => '0',
1682             inherit => 0,
1683             },
1684             'margin-left' => {
1685             format => '<length>|<percentage>|auto',
1686             default => '0',
1687             inherit => 0,
1688             },
1689             'margin-top' => {
1690             format => '<length>|<percentage>|auto',
1691             default => '0',
1692             inherit => 0,
1693             },
1694             'margin-bottom' => {
1695             format => '<length>|<percentage>|auto',
1696             default => '0',
1697             inherit => 0,
1698             },
1699            
1700             margin => {
1701             format => "(<length>|<percentage>|auto)
1702             [ (<length>|<percentage>|auto)
1703             [ (<length>|<percentage>|auto)
1704             (<length>|<percentage>|auto)?
1705             ]?
1706             ]?",
1707             properties => {
1708             'margin-top' => [1],
1709             'margin-right' => [2,1],
1710             'margin-bottom' => [3,1],
1711             'margin-left' => [4,2,1],
1712             },
1713             serialise => sub {
1714             my $p = shift;
1715             my @vals = map $p->{"margin-$_"},
1716             qw/top right bottom left/;
1717             $vals[3] eq $vals[1] and pop @vals,
1718             $vals[2] eq $vals[0] and pop @vals,
1719             $vals[1] eq $vals[0] and pop @vals;
1720             return join " ", map $_ || 0, @vals;
1721             },
1722             },
1723            
1724             'max-height' => {
1725             format => '<length>|<percentage>|none',
1726             default => 'none',
1727             inherit => 0,
1728             },
1729             'max-width' => {
1730             format => '<length>|<percentage>|none',
1731             default => 'none',
1732             inherit => 0,
1733             },
1734             'min-height' => {
1735             format => '<length>|<percentage>|none',
1736             default => 'none',
1737             inherit => 0,
1738             },
1739             'min-width' => {
1740             format => '<length>|<percentage>|none',
1741             default => 'none',
1742             inherit => 0,
1743             },
1744            
1745             orphans => {
1746             format => '<integer>',
1747             default => 2,
1748             inherit => 1,
1749             },
1750            
1751             'outline-color' => {
1752             format => '<colour>|invert',
1753             default => 'invert',
1754             inherit => 0,
1755             },
1756            
1757             'outline-style' => {
1758             format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
1759             inset|outset',
1760             default => 'none',
1761             inherit => 0,
1762             },
1763            
1764             'outline-width' => {
1765             format => '<length>|thin|thick|medium',
1766             default => 'medium',
1767             inherit => 0,
1768             },
1769            
1770             outline => {
1771             format => "'outline-color'||'outline-style'||'outline-width'",
1772             serialise => sub {
1773             my $p = shift;
1774             my $ret = '';
1775             for(qw/ color style width /) {
1776             length $p->{"outline-$_"}
1777             and $ret .= $p->{"outline-$_"}." ";
1778             }
1779             chop $ret;
1780             length $ret ? $ret : 'invert';
1781             },
1782             },
1783            
1784             overflow => {
1785             format => 'visible|hidden|scroll|auto',
1786             default => 'visible',
1787             inherit => 0,
1788             },
1789            
1790             'padding-top' => {
1791             format => '<length>|<percentage>',
1792             default => 0,
1793             inherit => 0,
1794             },
1795             'padding-right' => {
1796             format => '<length>|<percentage>',
1797             default => 0,
1798             inherit => 0,
1799             },
1800             'padding-bottom' => {
1801             format => '<length>|<percentage>',
1802             default => 0,
1803             inherit => 0,
1804             },
1805             'padding-left' => {
1806             format => '<length>|<percentage>',
1807             default => 0,
1808             inherit => 0,
1809             },
1810            
1811             padding => {
1812             format => "(<length>|<percentage>)
1813             [ (<length>|<percentage>)
1814             [ (<length>|<percentage>)
1815             (<length>|<percentage>)?
1816             ]?
1817             ]?",
1818             properties => {
1819             'padding-top' => [1],
1820             'padding-right' => [2,1],
1821             'padding-bottom' => [3,1],
1822             'padding-left' => [4,2,1],
1823             },
1824             serialise => sub {
1825             my $p = shift;
1826             my @vals = map $p->{"padding-$_"},
1827             qw/top right bottom left/;
1828             $vals[3] eq $vals[1] and pop @vals,
1829             $vals[2] eq $vals[0] and pop @vals,
1830             $vals[1] eq $vals[0] and pop @vals;
1831             return join " ", map $_ || 0, @vals;
1832             },
1833             },
1834            
1835             'page-break-after' => {
1836             format => 'auto|always|avoid|left|right',
1837             default => 'auto',
1838             inherit => 0,
1839             },
1840             'page-break-before' => {
1841             format => 'auto|always|avoid|left|right',
1842             default => 'auto',
1843             inherit => 0,
1844             },
1845            
1846             'page-break-inside' => {
1847             format => 'avoid|auto',
1848             default => 'auto',
1849             inherit => 1,
1850             },
1851            
1852             'pause-after' => {
1853             format => '<time>|<percentage>',
1854             default => 0,
1855             inherit => 0,
1856             },
1857             'pause-before' => {
1858             format => '<time>|<percentage>',
1859             default => 0,
1860             inherit => 0,
1861             },
1862            
1863             pause => {
1864             format => '(<time>|<percentage>)(<time>|<percentage>)?',
1865             properties => {
1866             'pause-before' => [1],
1867             'pause-after' => [2,1],
1868             }
1869             },
1870            
1871             'pitch-range' => {
1872             format => '<number>',
1873             default => 50,
1874             inherit => 1,
1875             },
1876            
1877             pitch => {
1878             format => '<frequency>|x-low|low|medium|high|x-high',
1879             default => 'medium',
1880             inherit => 1,
1881             },
1882            
1883             'play-during' => {
1884             format => '<url> [ mix || repeat ]? | auto | none',
1885             default => 'auto',
1886             inherit => 0,
1887             },
1888            
1889             position => {
1890             format => 'static|relative|absolute|fixed',
1891             default => 'relative',
1892             inherit => 0,
1893             },
1894            
1895             quotes => {
1896             format => '[(<string>)(<string>)]+|none',
1897             default => 'none',
1898             inherit => 1,
1899             list => 1,
1900             },
1901            
1902             richness => {
1903             format => '<number>',
1904             default => 50,
1905             inherit => 1,
1906             },
1907            
1908             right => {
1909             format => '<length>|<percentage>|auto',
1910             default => 'auto',
1911             inherit => 0,
1912             },
1913            
1914             'speak-header' => {
1915             format => 'once|always',
1916             default => 'once',
1917             inherit => 1,
1918             },
1919            
1920             'speak-numeral' => {
1921             format => 'digits|continuous',
1922             default => 'continuous',
1923             inherit => 1,
1924             },
1925            
1926             'speak-punctuation' => {
1927             format => 'code|none',
1928             default => 'none',
1929             inherit => 1,
1930             },
1931            
1932             speak => {
1933             format => 'normal|none|spell-out',
1934             default => 'normal',
1935             inherit => 1,
1936             },
1937            
1938             'speech-rate' => {
1939             format => '<number>|x-slow|slow|medium|fast|x-fast|faster|slower',
1940             default => 'medium',
1941             inherit => 1,
1942             },
1943            
1944             stress => {
1945             format => '<number>',
1946             default => 50,
1947             inherit => 1,
1948             },
1949            
1950             'table-layout' => {
1951             format => 'auto|fixed',
1952             default => 'auto',
1953             inherit => 0,
1954             },
1955            
1956             'text-align' => {
1957             format => 'left|right|center|justify|auto',
1958             default => 'auto',
1959             inherit => 1,
1960             },
1961            
1962             'text-decoration' => {
1963             format => 'none | underline||overline||line-through||blink ',
1964             default => 'none',
1965             inherit => 0,
1966             },
1967            
1968             'text-indent' => {
1969             format => '<length>|<percentage>',
1970             default => 0,
1971             inherit => 1,
1972             },
1973            
1974             'text-transform' => {
1975             format => 'capitalize|uppercase|lowercase|none',
1976             default => 'none',
1977             inherit => 1,
1978             },
1979            
1980             top => {
1981             format => '<length>|<percentage>|auto',
1982             default => 'auto',
1983             inherit => 0,
1984             },
1985            
1986             'unicode-bidi' => {
1987             format => 'normal|embed|bidi-override',
1988             default => 'normal',
1989             inherit => 0,
1990             },
1991            
1992             'vertical-align' => {
1993             format => 'baseline|sub|super|top|text-top|middle|bottom|
1994             text-bottom|<percentage>|<length>',
1995             default => 'baseline',
1996             inherit => 0,
1997             },
1998            
1999             visibility => {
2000             format => 'visible|hidden|collapse',
2001             default => 'visible',
2002             inherit => 1,
2003             },
2004            
2005             'voice-family' => {
2006             format => '(male|female|child|<str/words>)
2007             [, (male|female|child|<str/words>) ]*',
2008             default => '',
2009             inherit => 1,
2010             list => 1,
2011             },
2012            
2013             volume => {
2014             format => '<number>|<percentage>|silent|x-soft|soft|medium|loud|
2015             x-loud',
2016             default => 'medium',
2017             inherit => 1,
2018             },
2019            
2020             'white-space' => {
2021             format => 'normal|pre|nowrap|pre-wrap|pre-line',
2022             default => 'normal',
2023             inherit => 1,
2024             },
2025            
2026             widows => {
2027             format => '<integer>',
2028             default => 2,
2029             inherit => 1,
2030             },
2031            
2032             width => {
2033             format => '<length>|<percentage>|auto',
2034             default => 'auto',
2035             inherit => 0,
2036             },
2037            
2038             'word-spacing' => {
2039             format => 'normal|<length>',
2040             default => 'normal',
2041             inherit => 1,
2042             },
2043            
2044             'z-index' => {
2045             format => 'auto|<integer>',
2046             default => 'auto',
2047             inherit => 0,
2048             },
2049             );
2050             $CSS21->add_property( $_ => $properties{$_} ) for keys %properties;
2051              
2052             =pod
2053              
2054             =cut
2055              
2056             our $Default = $CSS21;
2057              
2058             =head1 SEE ALSO
2059              
2060             L<CSS::DOM>