File Coverage

blib/lib/CSS/DOM/PropertyParser.pm
Criterion Covered Total %
statement 233 235 99.1
branch 167 180 92.7
condition 27 36 75.0
subroutine 29 29 100.0
pod 7 8 87.5
total 463 488 94.8


line stmt bran cond sub pod time code
1             package CSS::DOM::PropertyParser;
2              
3             $VERSION = '0.17';
4              
5 4     4   3580 use warnings; no warnings qw 'utf8 parenthesis';
  4     4   7  
  4         147  
  4         17  
  4         11  
  4         139  
6 4     4   18 use strict;
  4         7  
  4         92  
7              
8 4     4   17 use constant 1.03 (); # multiple
  4         64  
  4         102  
9 4     4   19 use CSS::DOM'Constants ':primitive', ':value';
  4         5  
  4         814  
10 4     4   715 use CSS'DOM'Util;
  4         7  
  4         394  
11              
12 4     4   23 use constant old_perl => $] < 5.01;
  4         7  
  4         228  
13 4     4   17 { no strict 'refs'; delete ${__PACKAGE__.'::'}{old_perl} }
  4         16  
  4         214  
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         6  
  4         189  
20 4     4   17 { no strict 'refs'; delete ${__PACKAGE__.'::'}{naughty_perl} }
  4         18  
  4         3118  
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 1376 bless{}, shift
32             }
33              
34             sub add_property {
35 474     474 1 715 $_[0]{$_[1]}=$_[2]
36             }
37              
38             sub get_property {
39 2332 100   2332 1 6827 exists $_[0]{$_[1]} ? $_[0]{$_[1]} : ()
40             }
41              
42             sub delete_property {
43 1 50   1 1 4 delete $_[0]{$_[1]} or ()
44             }
45              
46             sub property_names {
47 1     1 1 4 sort keys %{$_[0]};
  1         9  
48             }
49              
50             sub subproperty_names {
51 1414 50   1414 1 2902 exists $_[0]{$_[1]} or return;
52 1414         2064 my $p = $_[0]{$_[1]};
53 1414         3289 my @p = $p->{format} =~ /'([^']+)'/g;
54             exists $p->{properties} && $p->{properties} and
55 1414 50 66     2710 push @p, keys %{$p->{properties}};
  135         389  
56 1414         2259 @p;
57             }
58              
59             sub clone {
60             # exists &dclone or require Storable, "Storable"->import('dclone');
61             # return dclone($_[0]);
62 1     1 1 379 require Clone;
63 1         2506 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             sub match { SUB: {
77 1197     1197 1 1624 my ($self,$property) = (shift,shift);
  1203         1972  
78 1203 50       2302 return unless exists $self->{$property};
79              
80             # Prepare the value
81 1203         2059 (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         2630 my @subproperties = $self->subproperty_names($property);
89 1203         1371 my $shorthand = @subproperties;
90 1203         1596 my $spec = $self->{$property};
91              
92             # Check for special values
93 1203 100 100     2267 if(exists $spec->{special_values}
      66        
94             && $types eq 'i'
95             && exists $spec->{special_values}{$prepped->[0]}) {
96 6         19 @_ = ($self,$property,$spec->{special_values}{$prepped->[0]});
97 6         20 redo SUB;
98             }
99              
100             # Check for inherit
101 1197 100 100     3200 if($types eq 'i' and $prepped->[0] eq 'inherit') {
102 6         16 my @arg_array = (
103             $$tokens[0],'CSS::DOM::Value', type => CSS_INHERIT, css => $$tokens[0]
104             );
105 6 50       13 if($shorthand) {
106 0         0 return { map +($_ => \@arg_array), @subproperties };
107             }
108 6         28 else { return @arg_array }
109             }
110              
111             # Localise other vars used by the hairy regexps
112 1191         2148 local our (@Match,%Match,@valtypes,@List);
113 1191         1434 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 , in case it is
122             # shared with another parser object with another definition for
123             # list-style-type.
124 1191         1434 my $format = $$spec{format};
125 1191 100       2584 for(
126             @subproperties,
127             $format =~ ''
128             ? scalar(old_perl && delete $compiled{$format}, 'list-style-type')
129             : ()
130             ) {
131 670 100       1035 next unless exists $self->{$_};
132 669         844 my $format = $self->{$_}{format};
133 669         619 old_perl and $compiled{$format} and delete $compiled{$format};
134 669   66     1221 $compiled{$format} ||= _compile_format($format)
135             }
136              
137             # Prepare this property’s format pattern
138 1191   66     2634 my $pattern = $compiled{$format} ||= _compile_format($format);
139              
140             # Do the actual pattern matching
141 1191 100       90476 $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       2716 if($shorthand) {
147 118         300 my $retval = {%Match};
148 118 100       269 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         133 my @arglistified;
153              
154 118         224 for(@subproperties) {
155 621 100       963 if(exists $retval->{$_}) {
156 106         120 @{ $retval->{$_} } = _make_arg_list( @{ $retval->{$_} } );
  106         328  
  106         198  
157             }
158             else {
159 515         516 my $set;
160 515 50 66     1192 if($subprops and exists $subprops->{$_}) {
161 393         429 for my $c( @{ $subprops->{$_} } ) { # capture nums
  393         573  
162             # find the first one that matched something
163 446 100 66     987 if( $Match[$c] and length $Match[$c][0] ) {
164 273 100       457 @{ $Match[$c] } = _make_arg_list( @{ $Match[$c] } )
  157         391  
  157         268  
165             unless $arglistified[$c]++;
166 273         326 ++$set;
167 273         439 $retval->{$_} = $Match[$c];
168 273         306 last;
169             }
170             }
171             }
172 515 100       865 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         362 my $default = $self->{$_}{default};
177 4     4   26 no warnings 'uninitialized';
  4         54  
  4         1530  
178 242 100       566 $retval->{$_} = length $default
179             ? [
180             $self->match($_, $default)
181             ]
182             : ""
183             }
184             }
185             }
186 118         957 $retval;
187             }
188             else { # simple
189 995         1208 my $css = join "", @{ (_space_out($types,$tokens))[1] };
  995         1724  
190             #use DDS; Dump \@List if exists $$spec{list} && $$spec{list};
191             return _make_arg_list(
192             $types, $tokens,
193             exists $$spec{list} && $$spec{list}
194             ? \@List
195 995 100 66     3440 : (\@valtypes, $prepped)
196             );
197             }
198             }}
199              
200             sub _make_arg_list {
201 1424     1424   2296 my($types, $tokens) = (shift,shift);
202 1424         1947 my($stypes,$stokens) = _space_out($types, $tokens);
203 1424         2519 my $css = join "", @$stokens;
204 1424 100       2249 if(@_ == 1) { # list property
205 90         110 my $list = shift @'_;
206 90 100       183 my $sep = @$list <= 1 ? '' : do {
207 46         73 my $range_start = $$list[0][4];
208 46         98 my $range_end = $$list[1][4] - length($$list[1][4]) - 1;
209 46         163 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         168 join "", @$stokens[1...$#$stokens-1];
214             };
215             return $css, "CSS::DOM::Value::List",
216             separator => $sep, css => $css,
217             values => [ map {
218 90         188 my @args = _make_arg_list(
  166         306  
219             @$_[0...3]
220             );
221 166         224 shift @args, shift @args;
222             \@args
223 166         1168 } @$list ];
224             }
225             else{
226 1334         1796 my($valtypes, $prepped) = @_;
227 1334         2881 my @valtypes = grep defined, @$valtypes;
228 1334 100 66     2360 if(@valtypes != 1 and
      66        
229             $valtypes[0] != CSS_COUNTER || do { # The code in this block is to
230 4     4   29 no warnings 'uninitialized'; # distinguish between counter(id,
  4         36  
  4         4896  
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         469 return $css => "CSS::DOM::Value", type => CSS_CUSTOM, value => $css;
238             }
239 1270         1554 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             : do{
266 222         272 my @vals;
267 222         548 while($types =~ /([%D1])/g) {
268             push @vals, [
269             type =>
270             $1 eq '%' ? CSS_PERCENTAGE
271 64 50       281 : $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         1670 }
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     19263 ? $s2c{unescape do{($$tokens[$-[2]] =~ '(\D+)')[0]}}
  7 100       33  
    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   3439 my($types,$tokens) = @_;
304 2465 50       4284 Carp'cluck() if ref $tokens ne 'ARRAY';
305 2465         3740 $tokens = [@$tokens];
306 2465         2779 my @posses;
307 2465         5137 $types =~ s/(?<=[^(f])(?![),]|\z)/
308 577 100       1177 if($tokens->[-1+pos $types] =~ m=^[+-]\z=) {
309 86         206 ''
310             }
311             else {
312 491         583 push @posses, pos $types; 's'
  491         1217  
313             }
314             /ge;
315 2465         4076 splice @$tokens, $_, 0, ' ' for reverse @posses;
316 2465         5669 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   2296 defined &unescape or
324             require CSS::DOM::Util, 'CSS::DOM::Util'->import('unescape');
325 1203         1438 my($types,$tokens);
326 1203 100       2031 if(@_ > 1) {
327 915         1517 ($types,$tokens)= @_;
328             }
329             else {
330 288         1229 require CSS::DOM::Parser;
331 288         695 ($types, $tokens) = CSS::DOM::Parser'tokenise($_[0]);
332             }
333              
334             # strip out all whitespace tokens
335             {
336 1203         1337 my @posses;
  1203         1658  
337 1203         2217 $tokens = [@$tokens]; # We have to copy it as it may be referenced
338 1203         2125 $types =~ s/s/push @posses,pos$types;''/gem; # elsewhere.
  343         494  
  343         613  
339 1203         2407 splice@$tokens,$_,1 for reverse @posses;
340             }
341            
342 1203         1515 my @prepped;
343             my @alt_type;
344 1203         2401 for(0..$#$tokens) {
345 1843         2850 my $type = substr $types, $_, 1;
346 1843         1816 my $thing;
347 1843 100       4922 if($type =~ /[if#]/) {
    100          
    100          
    100          
348 1119         2599 $thing = lc unescape($$tokens[$_]);
349 1119 100       2064 if($type eq 'i') {
    100          
350 1061 100       6555 if($thing =~ /^$colour_names_re\z/o) { $alt_type[$_] = 'c' }
  216 100       372  
351 31         59 elsif($thing =~ /^$system_colour_names_re\z/o) { $alt_type[$_] = 's' }
352             }
353             elsif($type eq '#') {
354 13 100       56 $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         696 ($thing = $$tokens[$_]) =~ s/^[.0-9]+//;
360 200         465 $thing = lc unescape($thing);
361 200 100       699 if($thing =~ /^(?:deg|g?rad)\z/) { $alt_type[$_] = 'a'}#ngle
  24 100       50  
362             elsif($thing =~ /^(?:e[mx]|p[xtc]|in|[cm]m)\z/) {
363 147         271 $alt_type[$_] = 'l'#ength
364             }
365             }
366             elsif($type eq '1') { # number
367 141         228 $thing = 0+$$tokens[$_]; # change 0.000 to 0, etc.
368             }
369             elsif($type eq 'd') { # delimiter
370 45 100       112 $alt_type[$_] = '+' if $$tokens[$_] =~ /^[+-]\z/;
371             }
372 1843 100       3643 defined $alt_type[$_] or $alt_type[$_] = '';
373 1843         3298 push @prepped, $thing;
374             }
375              
376 1203         3555 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         12  
  4         630  
404              
405             sub _compile_format {
406 116     116   162 my $format = shift;
407 116         133 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 (counter-reset: red),
423             # ‘red’ matched by (color: red) and ‘red’ matched by
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             # ‘()? ()+’, 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       227 my $pattern = $no_match_stuff
475             ? '' : '(?{local @match=(@match,[]); local @list=(@list,[])})(?:';
476             # We add (?: to account for top-level alternations.
477              
478 116         217 my @group_start = length $pattern; # holds the position within $pattern of
479             # the last group start
480 116         160 my @permut_marker = []; # where a || occurs (array of arrays; each group
481             # has its own array on this stack)
482 116         126 my @capture_nums;
483 116         155 my $last_capture = 0;
484              
485             # For each piece of the format, add to the pattern.
486 116         506 while(
487             $format =~ /(\s+)|(\|\|)|<([^>]+)>|([a-z-]+)|([0-9]+)|'([^']+)'|(.)/g
488             ) {
489 1687 100       3049 next if $1; # ignore whitespace
490              
491             # cygwin hack:
492             use constant { # re-evals for before and after captures
493 4         3753 cap_start => naughty_perl ? '(?{local @pos=(@pos,pos)})' : '',
494             cap_end => naughty_perl ? '(?{local @pos=@pos; --$#pos})' : '',
495 4     4   24 };
  4         5  
496              
497 1413 100       3341 if($2) { # ||
    100          
    100          
    100          
    100          
    100          
498 32         36 push @{ $permut_marker[-1] }, length $pattern;
  32         93  
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             # represents the following four:
532             # counter()
533             # counter(,'list-style-type')
534             # counters(,)
535             # counters(,,'list-style-type')
536 138 50       1380 : $3 eq 'counter' ? do {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
537 4         7 our $Self;
538             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 4         9 : '(?!)'
543             : '(??{
544             exists $$Self{"list-style-type"}
545             ? $compiled{$$Self{"list-style-type"}{format}}
546             : $Fail
547             })'
548             ;
549 4         27 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         1622 $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         55 $pattern .=
624             '(?:' . cap_start
625             . '(1)(?(?{$$tokens[-1+pos]eq"' . $5 . '"})|(?!))'
626             . "(?{ $type_is_ CSS_NUMBER })"
627             . cap_end . ")";
628             }
629             elsif($6) { # '...' reference
630 35         151 $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 713         1533 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       722 if(do{$7 =~ /^[])|]\z/}) { # end of a group
  659         1354  
665 569         676 my $markers = pop @permut_marker;
666 569 100       822 if(@$markers) { # Oh no!
667 6         14 unshift @$markers, $group_start[-1];
668 6         14 _make_permutations($pattern, $markers);
669             }
670 569         582 pop @group_start;
671 569 100       1209 $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       743 if(do{$7 =~ /^[[(|]\z/}) { # start of a group
  659         1507  
697 569 100       1028 $pattern
698             .= '(?:'
699             . (cap_start.'(')
700             x ($7 eq '(')
701             unless $7 eq '|';
702 569         648 push @group_start, length $pattern;
703 569         646 push @permut_marker, [];
704 569 100       1854 $7 eq '(' and push @capture_nums, ++$last_capture;
705             }
706             }
707             else {
708 54         220 $pattern .= do{$7 =~ /^[?*+]\z/} ? $7
709 54 100       60 : do{$7 =~ /^[;{},:]\z/} ? quotemeta $7
  9 100       47  
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       155 if(@{$permut_marker[0]}) {
  116         241  
717 10         18 unshift @{ $permut_marker[0] }, $group_start[0];
  10         24  
718 10         27 _make_permutations($pattern, $permut_marker[0]);
719             }
720              
721             # Deal with the match vars
722 116 50       251 $pattern .= ')(?{@Match=@{$match[-1]};@List=@{$list[-1]};%Match=%match})'
723             unless $no_match_stuff;
724              
725 4     4   27 use re 'eval';
  4         7  
  4         8956  
726 116         133697 return qr/$pattern/;
727             }
728              
729             sub _make_permutations { # args: pattern, \@markers
730             # pattern is modified in-place
731 16     16   26 my $markers = pop;
732 16         36 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         24 my @alts;
737 16         32 for(reverse @$markers) {
738 48         126 unshift @alts, substr $pattern, $_, length $pattern, '';
739             }
740            
741             # Do the permutations
742 16         57 $pattern .= _permute(@alts);
743             }
744             }
745              
746             sub _permute {
747 160 100   160   248 if(@_ == 2) { return "(?:$_[0]$_[1]?|$_[1]$_[0]?)" }
  115         1984  
748             else {
749             return
750 45         128 "(?:"
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             =encoding utf8
774              
775             =head1 NAME
776              
777             CSS::DOM::PropertyParser - Parser for CSS property values
778              
779             =head1 VERSION
780              
781             Version 0.17
782              
783             =head1 SYNOPSIS
784              
785             use CSS::DOM::PropertyParser;
786            
787             $spec = new CSS::DOM::PropertyParser; # empty
788             # OR
789             $spec = $CSS::DOM::PropertyParser::Default->clone;
790            
791             $spec->add_property(
792             overflow => {
793             format => 'visible|hidden|scroll|auto',
794             default => 'visible',
795             inherit => 0,
796             }
797             );
798            
799             $hashref = $spec->get_property('overflow');
800            
801             $hashref = $spec->delete_property('overflow');
802            
803             @names = $spec->property_names;
804              
805             =head1 DESCRIPTION
806              
807             Objects of this class provide lists of supported properties for L
808             style sheets. They also describe the syntax and parsing of those
809             properties' values.
810              
811             Some CSS properties simply have their own values (e.g., overflow); some
812             are abbreviated forms of several other properties (e.g., font). These are
813             referred to in this documentation as 'simple' and 'shorthand' properties.
814              
815             =head1 CONSTRUCTOR
816              
817             C<$spec = new CSS::DOM::PropertyParser> returns an object that does not
818             recognise any properties, to which you
819             can add your own properties.
820              
821             There are two parser objects that come with this module. These are
822             C<$CSS::DOM::PropertyParser::CSS21>, which contains all of CSS 2.1, and
823             C<$CSS::DOM::PropertyParser::Default>, which is currently identical to the
824             former, but to which parts of CSS 3 which eventually be added.
825              
826             If one of the default specs will do, you don't need a constructor. Simply
827             pass it to the L constructor. If you want to modify it, clone it
828             first, using the C method (as shown in the L). It is
829             often convenient to clone the C<$Default> spec and delete those properties
830             that are not supported.
831              
832             =head1 METHODS
833              
834             =for comment
835             =head2 Methods for Controlling Property Specifications
836              
837             =over 4
838              
839             =item clone
840              
841             Returns a deep clone of the object. (It's deep so that you can modify the
842             hashes/arrays inside it without modifying the original.)
843              
844             =item add_property ( $name, \%spec )
845              
846             Adds the specification for the named property. See
847             L, below.
848              
849             =item get_property ( $name )
850              
851             Returns the hashref passed to the previous method.
852              
853             =item delete_property ( $name )
854              
855             Deletes the property and returns the hash ref.
856              
857             =item property_names
858              
859             Returns a list of the names of supported properties.
860              
861             =item subproperty_names ( $name )
862              
863             Returns a list of the names of C<$name>'s sub-properties if it is a
864             shorthand property.
865              
866             =item match
867              
868             Currently for internal use only. See the source code for documentation.
869             Use at your own risk.
870              
871             =back
872              
873             =begin comment
874              
875             Once I’ve made CSS::DOM::Parser’s tokenise routine public (after a bit of
876             polishing) (or broken it out into a separate module, CSS::Tokeniser), I’ll
877             add this to the docs. I also actually have to modify ‘match’ to use this
878             interface, of course.
879              
880             =head2 Methods Used by L
881              
882             If you are thinking of writing a subclass of PropertyParser, you need to be
883             aware of these methods.
884              
885             Instead of writing a subclass, you can create your
886             own class that does not inherit from PropertyParser use that. It will need
887             to
888             implement these methods here. The methods listed above can be omitted.
889              
890             =over
891              
892             =item match ( $property, $value )
893              
894             =item match ( $property, $token_types, \@tokens )
895              
896             This checks to see whether C<$value> is a valid value for the C<$property>,
897             parsing it if it is. C<$token_types> and C<@tokens> are the values returned
898             by C.
899              
900             Return values are as follows:
901              
902             If the value doesn't match: empty list.
903              
904             If the property is a simple one: (0) the CSS
905             code for the value (possibly normalised), (1) the class to which a value
906             object belongs, (2..) arguments to be passed to the constructor.
907              
908             For a shorthand property, the return value is a single hash ref, the keys
909             being sub-property names and the values array refs containing what would be
910             returned for a simple property.
911              
912             A custom class or subclass can return a L instead of the
913             class and constructor args, in which case the first return value can
914             simply be C (it should return C<(undef, $object)>).
915              
916             Examples (return value starts on the line following each method call):
917              
918             # $prim stands for "CSS::DOM::Value::Primitive"
919             # $list stands for "CSS::DOM::Value::List"
920            
921             $prop_parser->match('background-position','top left');
922             'top left', 'CSS::DOM::Value', CSS_CUSTOM, 'top left'
923            
924             $prop_parser->match('background-position','inherit');
925             'inherit', 'CSS::DOM::Value', CSS_INHERIT
926            
927             $prop_parser->match('top','1em');
928             '1em', $prim, type => CSS_EMS, value => 1
929              
930             $prop_parser->match('content','"\66oo"');
931             '"\66oo"', $prim, type => CSS_STRING, value => foo
932            
933             $prop_parser->match('clip','rect( 5px, 6px, 7px, 8px )');
934             'rect(5px, 6px, 7px, 8px)', $prim,
935             type => CSS_RECT,
936             value => [ [ type => CSS_PX, value => 5 ],
937             [ type => CSS_PX, value => 6 ],
938             [ type => CSS_PX, value => 7 ],
939             [ type => CSS_PX, value => 8 ] ]
940            
941             $prop_parser->match('color','#fff');
942             '#fff', $prim, type => CSS_RGBCOLOR, value => '#fff'
943            
944             $prop_parser->match('color','rgba(255,0,0,.5)');
945             'rgba(255, 0, 0, .5)', $prim, type => CSS_RGBCOLOR,
946             value => [ [ type => CSS_NUMBER, value => 255 ],
947             [ type => CSS_NUMBER, value => 0 ],
948             [ type => CSS_NUMBER, value => 0 ],
949             [ type => CSS_NUMBER, value => .5 ] ]
950            
951             $prop_parser->match('content','counter(foo,disc)');
952             'counter(foo, disc)', $list,
953             separator => ' ',
954             values => [
955             [
956             type => CSS_COUNTER,
957             value => [
958             [ type => CSS_IDENT, value => 'foo' ],
959             undef,
960             [ type => CSS_IDENT, value => 'disc' ],
961             ]
962             ],
963             ]
964            
965             $prop_parser->match('font-family','Lucida Grande');
966             'Lucida Grande', $list,
967             separator => ', ',
968             values => [
969             [ type => CSS_STRING, value => 'Lucida Grande' ],
970             ]
971              
972             $prop_parser->match('counter-reset','Lucida Grande');
973             'Lucida Grande', $list,
974             separator => ' ',
975             values => [
976             [ type => CSS_IDENT, value => 'Lucida' ],
977             [ type => CSS_IDENT, value => 'Grande' ],
978             ]
979            
980             $prop_parser->match('font','bold 13px Lucida Grande');
981             {
982             'font-style' => [
983             'normal', $prim, type => CSS_IDENT, value => 'normal'
984             ],
985             'font-variant' => [
986             'normal', $prim, type => CSS_IDENT, value => 'normal'
987             ],
988             'font-weight' => [
989             'bold', $prim, type => CSS_IDENT, value => 'bold'
990             ],
991             'font-size' => [ '13px', $prim, type => CSS_PX, value => 13 ],
992             'line-height' => [
993             'normal', $prim, type => CSS_IDENT, value => 'normal'
994             ],
995             'font-family' => [ 'Lucida Grande', $list,
996             separator => ', ',
997             values => [
998             [ type => CSS_STRING, value => 'Lucida Grande' ],
999             ]
1000             ]
1001             }
1002              
1003             =item whatever
1004              
1005             ~~~
1006             CSS::DOM::Style currently relies on the internal formatting of the hash
1007             refs. I want to allow custom property parser classes to do away with hash
1008             refs
1009             altogether, so I will need extra methods here that Style will use instead.
1010              
1011             =back
1012              
1013             =end comment
1014              
1015             =head1 HOW INDIVIDUAL PROPERTIES ARE SPECIFIED
1016              
1017             Before you read this the first time, look at the L below, and
1018             then come back and use this for reference.
1019              
1020             The specification for an individual property is a hash ref. There are
1021             several keys that each hash ref can have:
1022              
1023             =over
1024              
1025             =item format
1026              
1027             This is set to a string that describes the format of the property. The
1028             syntax used is based on the CSS 2.1 spec, but is not exactly the same.
1029             Unlike regular expressions, these formats are applied to properties on a
1030             token-by-token basis, not one character at a time. (This means that
1031             C<100|200> cannot be written as C<[1|2]00>, as that would mean
1032             S>.)
1033              
1034             Whitespace is ignored in the format and in the CSS property except as a
1035             token separator.
1036              
1037             There are several metachars (in order of precedence):
1038              
1039             [...] grouping (like (?:...) )
1040             (...) capturing group (just like a regexp)
1041             ? optional
1042             * zero or more
1043             + one or more
1044             || alternates that can come in any order and are optional,
1045             but at least one must be specified (the order will be
1046             retained if possible)
1047             | alternates, exactly one of which is required
1048              
1049             In addition, the following datatypes can be specified in angle brackets:
1050              
1051             A number with a 'deg', 'rad' or 'grad' suffix
1052             attr(...)
1053             (You can omit the 'u' if you want to.) One of CSS's
1054             predefined colour or system colour names, or a #
1055             followed by 3 or 6 hex digits, or the 'rgb(...)'
1056             format (rgba is supported, too)
1057             counter(...)
1058             A unit of Hz or kHz
1059             An identifier token
1060             An integer (really?!)
1061             Number followed by a length unit (em, ex, px, in, cm,
1062             mm, pt, pc)
1063             A number token
1064             Number followed by %
1065             rect(...)
1066             A string token
1067             A sequence of identifiers or a single string (e.g., a
1068             font name)
1069            
1070             A URL token
1071              
1072             The format for a shorthand property can contain the name of a sub-property
1073             in single ASCII quotes.
1074              
1075             All other characters are understood verbatim.
1076              
1077             It is not necessary to include the word 'inherit' in the format, since
1078             every property supports that.
1079              
1080             C<< >> makes use of the specification for the list-style-type
1081             property. So if you modify the latter, it will affect C<< >> as
1082             well.
1083              
1084             =item default
1085              
1086             The default value. This only applies to simple properties.
1087              
1088             =item inherit
1089              
1090             Whether the property is inherited.
1091              
1092             =item special_values
1093              
1094             A hash ref of values that are replaced with other values (e.g.,
1095             S '13px sans-serif' >>>.) The keys
1096             are lowercase identifier names.
1097              
1098             This feature only applies to single identifiers. In fact, it exists solely
1099             for the font property's use.
1100              
1101             =item list
1102              
1103             Set to true if the property is a list of values. The capturing parentheses
1104             in the format determine the individual values of the list.
1105              
1106             This applies to simple properties only.
1107              
1108             =item properties
1109              
1110             For a shorthand property, list the sub-properties here. The keys are the
1111             property names. The values are array refs. The elements within the arrays
1112             are numbers indicating which captures in the format are to be used for the
1113             sub-property's value. They are tried one after the other. Whichever is the
1114             first that matches (null matches not counting) is used.
1115              
1116             Sub-properties that are referenced in the C need not be listed
1117             here.
1118              
1119             =item serialise
1120              
1121             For shorthand properties only. Set this to a subroutine that serialises the
1122             property. It is called with a hashref of sub-properties as its sole
1123             argument. The values of the hash are blank for properties that are set to
1124             their initial values. This sub is only called when all sub-properties are
1125             set.
1126              
1127             =back
1128              
1129             =head2 Example
1130              
1131             =cut
1132              
1133             0&&q r
1134              
1135             =for ;
1136              
1137             our $CSS21 = new CSS::DOM::PropertyParser;
1138             my %properties = (
1139             azimuth => {
1140             format => ' |
1141             [ left-side | far-left | left | center-left |
1142             center | center-right | right | far-right |
1143             right-inside ] || behind
1144             | leftwards | rightwards',
1145             default => '0',
1146             inherit => 1,
1147             },
1148              
1149             'background-attachment' => {
1150             format => 'scroll | fixed',
1151             default => 'scroll',
1152             inherit => 0,
1153             },
1154              
1155             'background-color' => {
1156             format => '',
1157             default => 'transparent',
1158             inherit => 0,
1159             },
1160              
1161             'background-image' => {
1162             format => ' | none',
1163             default => 'none',
1164             inherit => 0,
1165             },
1166            
1167             'background-position' => {
1168             format => '[||left|right]
1169             [||top|center|bottom]? |
1170             [top|bottom] [left|center|right]? |
1171             center [||left|right|top|bottom|
1172             center]?',
1173             default => '0% 0%',
1174             inherit => 0,
1175             },
1176            
1177             'background-repeat' => {
1178             format => 'repeat | repeat-x | repeat-y | no-repeat',
1179             default => 'repeat',
1180             inherit => 0,
1181             },
1182            
1183             background => {
1184             format => "'background-color' || 'background-image' ||
1185             'background-repeat' || 'background-attachment' ||
1186             'background-position'",
1187             serialise => sub {
1188             my $p = shift;
1189             my $ret = '';
1190             for(qw/ background-color background-image background-repeat
1191             background-attachment background-position /) {
1192             length $p->{$_} and $ret .= "$p->{$_} ";
1193             }
1194             chop $ret;
1195             length $ret ? $ret : 'none'
1196             },
1197             },
1198            
1199             'border-collapse' => {
1200             format => 'collapse | separate',
1201             inherit => 1,
1202             default => 'separate',
1203             },
1204            
1205             'border-color' => {
1206             format => '()[()[()()?]?]?',
1207             properties => {
1208             'border-top-color' => [1],
1209             'border-right-color' => [2,1],
1210             'border-bottom-color' => [3,1],
1211             'border-left-color' => [4,2,1],
1212             },
1213             serialise => sub {
1214             my $p = shift;
1215             my @vals = map $p->{"border-$_-color"},
1216             qw/top right bottom left/;
1217             $vals[3] eq $vals[1] and pop @vals,
1218             $vals[2] eq $vals[0] and pop @vals,
1219             $vals[1] eq $vals[0] and pop @vals;
1220             return join " ", @vals;
1221             },
1222             },
1223            
1224             'border-spacing' => {
1225             format => ' ?',
1226             default => '0',
1227             inherit => 1,
1228             },
1229            
1230             'border-style' => {
1231             format => "(none|hidden|dotted|dashed|solid|double|groove|ridge|
1232             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             (none|hidden|dotted|dashed|solid|double|groove|
1238             ridge|inset|outset)?
1239             ]?
1240             ]?",
1241             properties => {
1242             'border-top-style' => [1],
1243             'border-right-style' => [2,1],
1244             'border-bottom-style' => [3,1],
1245             'border-left-style' => [4,2,1],
1246             },
1247             serialise => sub {
1248             my $p = shift;
1249             my @vals = map $p->{"border-$_-style"},
1250             qw/top right bottom left/;
1251             $vals[3] eq $vals[1] and pop @vals,
1252             $vals[2] eq $vals[0] and pop @vals,
1253             $vals[1] eq $vals[0] and pop @vals;
1254             return join " ", map $_||'none', @vals;
1255             },
1256             },
1257            
1258             'border-top' => {
1259             format => "'border-top-width' || 'border-top-style' ||
1260             'border-top-color'",
1261             serialise => sub {
1262             my $p = shift;
1263             my $ret = '';
1264             for(qw/ width style color /) {
1265             length $p->{"border-top-$_"}
1266             and $ret .= $p->{"border-top-$_"}." ";
1267             }
1268             chop $ret;
1269             $ret
1270             },
1271             },
1272             'border-right' => {
1273             format => "'border-right-width' || 'border-right-style' ||
1274             'border-right-color'",
1275             serialise => sub {
1276             my $p = shift;
1277             my $ret = '';
1278             for(qw/ width style color /) {
1279             length $p->{"border-right-$_"}
1280             and $ret .= $p->{"border-right-$_"}." ";
1281             }
1282             chop $ret;
1283             $ret
1284             },
1285             },
1286             'border-bottom' => {
1287             format => "'border-bottom-width' || 'border-bottom-style' ||
1288             'border-bottom-color'",
1289             serialise => sub {
1290             my $p = shift;
1291             my $ret = '';
1292             for(qw/ width style color /) {
1293             length $p->{"border-bottom-$_"}
1294             and $ret .= $p->{"border-bottom-$_"}." ";
1295             }
1296             chop $ret;
1297             $ret
1298             },
1299             },
1300             'border-left' => {
1301             format => "'border-left-width' || 'border-left-style' ||
1302             'border-left-color'",
1303             serialise => sub {
1304             my $p = shift;
1305             my $ret = '';
1306             for(qw/ width style color /) {
1307             length $p->{"border-left-$_"}
1308             and $ret .= $p->{"border-left-$_"}." ";
1309             }
1310             chop $ret;
1311             $ret
1312             },
1313             },
1314            
1315             'border-top-color' => {
1316             format => '',
1317             default => "",
1318             inherit => 0,
1319             },
1320             'border-right-color' => {
1321             format => '',
1322             default => "",
1323             inherit => 0,
1324             },
1325             'border-bottom-color' => {
1326             format => '',
1327             default => "",
1328             inherit => 0,
1329             },
1330             'border-left-color' => {
1331             format => '',
1332             default => "",
1333             inherit => 0,
1334             },
1335            
1336             'border-top-style' => {
1337             format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
1338             inset|outset',
1339             default => 'none',
1340             inherit => 0,
1341             },
1342             'border-right-style' => {
1343             format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
1344             inset|outset',
1345             default => 'none',
1346             inherit => 0,
1347             },
1348             'border-bottom-style' => {
1349             format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
1350             inset|outset',
1351             default => 'none',
1352             inherit => 0,
1353             },
1354             'border-left-style' => {
1355             format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
1356             inset|outset',
1357             default => 'none',
1358             inherit => 0,
1359             },
1360            
1361             'border-top-width' => {
1362             format => '|thin|thick|medium',
1363             default => 'medium',
1364             inherit => 0,
1365             },
1366             'border-right-width' => {
1367             format => '|thin|thick|medium',
1368             default => 'medium',
1369             inherit => 0,
1370             },
1371             'border-bottom-width' => {
1372             format => '|thin|thick|medium',
1373             default => 'medium',
1374             inherit => 0,
1375             },
1376             'border-left-width' => {
1377             format => '|thin|thick|medium',
1378             default => 'medium',
1379             inherit => 0,
1380             },
1381            
1382             'border-width' => {
1383             format => "(|thin|thick|medium)
1384             [ (|thin|thick|medium)
1385             [ (|thin|thick|medium)
1386             (|thin|thick|medium)?
1387             ]?
1388             ]?",
1389             properties => {
1390             'border-top-width' => [1],
1391             'border-right-width' => [2,1],
1392             'border-bottom-width' => [3,1],
1393             'border-left-width' => [4,2,1],
1394             },
1395             serialise => sub {
1396             my $p = shift;
1397             my @vals = map $p->{"border-$_-width"},
1398             qw/top right bottom left/;
1399             $vals[3] eq $vals[1] and pop @vals,
1400             $vals[2] eq $vals[0] and pop @vals,
1401             $vals[1] eq $vals[0] and pop @vals;
1402             return join " ", map length $_ ? $_ : 'medium', @vals;
1403             },
1404             },
1405            
1406             border => {
1407             format => "(|thin|thick|medium) ||
1408             (none|hidden|dotted|dashed|solid|double|groove|ridge|
1409             inset|outset) || ()",
1410             properties => {
1411             'border-top-width' => [1],
1412             'border-right-width' => [1],
1413             'border-bottom-width' => [1],
1414             'border-left-width' => [1],
1415             'border-top-style' => [2],
1416             'border-right-style' => [2],
1417             'border-bottom-style' => [2],
1418             'border-left-style' => [2],
1419             'border-top-color' => [3],
1420             'border-right-color' => [3],
1421             'border-bottom-color' => [3],
1422             'border-left-color' => [3],
1423             },
1424             serialise => sub {
1425             my $p = shift;
1426             my $ret = '';
1427             for(qw/ width style color /) {
1428             my $temp = $p->{"border-top-$_"};
1429             for my $side(qw/ right bottom left /) {
1430             $temp eq $p->{"border-$side-$_"} or return "";
1431             }
1432             length $temp and $ret .= "$temp ";
1433             }
1434             chop $ret;
1435             $ret
1436             },
1437             },
1438            
1439             bottom => {
1440             format => '||auto',
1441             default => 'auto',
1442             inherit => 0,
1443             },
1444            
1445             'caption-side' => {
1446             format => 'top|bottom',
1447             default => 'top',
1448             inherit => 1,
1449             },
1450            
1451             clear => {
1452             format => 'none|left|right|both',
1453             default => 'none',
1454             inherit => 0,
1455             },
1456            
1457             clip => {
1458             format => '|auto',
1459             default => 'auto',
1460             inherit => 0,
1461             },
1462            
1463             color => {
1464             format => '',
1465             default => 'rgba(0,0,0,1)',
1466             inherit => 1,
1467             },
1468            
1469             content => {
1470             format => '( normal|none|open-quote|close-quote|no-open-quote|
1471             no-close-quote|||| )+',
1472             default => 'normal',
1473             inherit => 0,
1474             list => 1,
1475             },
1476            
1477             'counter-increment' => {
1478             format => '[() ()? ]+ | none',
1479             default => 'none',
1480             inherit => 0,
1481             list => 1,
1482             },
1483             'counter-reset' => {
1484             format => '[() ()? ]+ | none',
1485             default => 'none',
1486             inherit => 0,
1487             list => 1,
1488             },
1489            
1490             'cue-after' => {
1491             format => '|none',
1492             default => 'none',
1493             inherit => 0,
1494             },
1495             'cue-before' => {
1496             format => '|none',
1497             default => 'none',
1498             inherit => 0,
1499             },
1500            
1501             cue =>{
1502             format => '(|none) (|none)?',
1503             properties => {
1504             'cue-before' => [1],
1505             'cue-after' => [2,1],
1506             },
1507             serialise => sub {
1508             my $p = shift;
1509             my @vals = @$p{"cue-before", "cue-after"};
1510             $vals[1] eq $vals[0] and pop @vals;
1511             return join " ", map length $_ ? $_ : 'none', @vals;
1512             },
1513             },
1514            
1515             cursor => {
1516             format => '[() ,]*
1517             (auto|crosshair|default|pointer|move|e-resize|
1518             ne-resize|nw-resize|n-resize|se-resize|sw-resize|
1519             s-resize|w-resize|text|wait|help|progress)',
1520             default => 'auto',
1521             inherit => 1,
1522             list => 1,
1523             },
1524            
1525             direction => {
1526             format => 'ltr|rtl',
1527             default => 'ltr',
1528             inherit => 1,
1529             },
1530            
1531             display => {
1532             format => 'inline|block|list-item|run-in|inline-block|table|
1533             inline-table|table-row-group|table-header-group|
1534             table-footer-group|table-row|table-column-group|
1535             table-column|table-cell|table-caption|none',
1536             default => 'inline',
1537             inherit => 0,
1538             },
1539            
1540             elevation => {
1541             format => '|below|level|above|higher|lower',
1542             default => '0',
1543             inherit => 1,
1544             },
1545            
1546             'empty-cells' => {
1547             format => 'show|hide',
1548             default => 'show',
1549             inherit => 1,
1550             },
1551            
1552             float => {
1553             format => 'left|right|none',
1554             default => 'none',
1555             inherit => 0,
1556             },
1557            
1558             'font-family' => { # aka typeface
1559             format => '(serif|sans-serif|cursive|fantasy|monospace|
1560             )
1561             [,(serif|sans-serif|cursive|fantasy|monospace|
1562             )]*',
1563             default => 'Times, serif',
1564             inherit => 1,
1565             list => 1,
1566             },
1567            
1568             'font-size' => {
1569             format => 'xx-small|x-small|small|medium|large|x-large|xx-large|
1570             larger|smaller||',
1571             default => 'medium',
1572             inherit => 1,
1573             },
1574            
1575             'font-style' => {
1576             format => 'normal|italic|oblique',
1577             default => 'normal',
1578             inherit => 1,
1579             },
1580            
1581             'font-variant' => {
1582             format => 'normal | small-caps',
1583             default => 'normal',
1584             inherit => 1,
1585             },
1586            
1587             'font-weight' => {
1588             format => 'normal|bold|bolder|lighter|
1589             100|200|300|400|500|600|700|800|900',
1590             default => 'normal',
1591             inherit => 1,
1592             },
1593            
1594             font => {
1595             format => "[ 'font-style' || 'font-variant' || 'font-weight' ]?
1596             'font-size' [ / 'line-height' ]? 'font-family'",
1597             special_values => {
1598             caption => '13px Lucida Grande, sans-serif',
1599             icon => '13px Lucida Grande, sans-serif',
1600             menu => '13px Lucida Grande, sans-serif',
1601             'message-box' => '13px Lucida Grande, sans-serif',
1602             'small-caption' => '11px Lucida Grande, sans-serif',
1603             'status-bar' => '10px Lucida Grande, sans-serif',
1604             },
1605             serialise => sub {
1606             my $p = shift;
1607             my $ret = '';
1608             for(qw/ style variant weight /) {
1609             length $p->{"font-$_"}
1610             and $ret .= $p->{"font-$_"}." ";
1611             }
1612             $ret .= length $p->{'font-size'}
1613             ? $p->{'font-size'}
1614             : 'medium';
1615             $ret .= "/$p->{'line-height'}" if length $p->{'line-height'};
1616             $ret .= " " . ($p->{'font-family'} || "Times, serif");
1617             $ret
1618             },
1619             },
1620            
1621             height => {
1622             format => '||auto',
1623             default => 'auto',
1624             inherit => 0,
1625             },
1626            
1627             left => {
1628             format => '||auto',
1629             default => 'auto',
1630             inherit => 0,
1631             },
1632            
1633             'letter-spacing' => { # aka tracking
1634             format => 'normal|',
1635             default => 'normal',
1636             inherit => 1,
1637             },
1638            
1639             'line-height' => { # aka leading
1640             format => 'normal|||',
1641             default => "normal",
1642             inherit => 1,
1643             },
1644            
1645             'list-style-image' => {
1646             format => '|none',
1647             default => 'none',
1648             inherit => 1,
1649             },
1650            
1651             'list-style-position' => {
1652             format => 'inside|outside',
1653             default => 'outside',
1654             inherit => 1,
1655             },
1656            
1657             'list-style-type' => {
1658             format => 'disc|circle|square|decimal|decimal-leading-zero|
1659             lower-roman|upper-roman|lower-greek|lower-latin|
1660             upper-latin|armenian|georgian|lower-alpha|
1661             upper-alpha',
1662             default => 'disc',
1663             inherit => 1,
1664             },
1665            
1666             'list-style' => {
1667             format => "'list-style-type'||'list-style-position'||
1668             'list-style-image'",
1669             serialise => sub {
1670             my $p = shift;
1671             my $ret = '';
1672             for(qw/ type position image /) {
1673             $p->{"list-style-$_"}
1674             and $ret .= $p->{"list-style-$_"}." ";
1675             }
1676             chop $ret;
1677             $ret || 'disc'
1678             },
1679             },
1680            
1681             'margin-right' => {
1682             format => '||auto',
1683             default => '0',
1684             inherit => 0,
1685             },
1686             'margin-left' => {
1687             format => '||auto',
1688             default => '0',
1689             inherit => 0,
1690             },
1691             'margin-top' => {
1692             format => '||auto',
1693             default => '0',
1694             inherit => 0,
1695             },
1696             'margin-bottom' => {
1697             format => '||auto',
1698             default => '0',
1699             inherit => 0,
1700             },
1701            
1702             margin => {
1703             format => "(||auto)
1704             [ (||auto)
1705             [ (||auto)
1706             (||auto)?
1707             ]?
1708             ]?",
1709             properties => {
1710             'margin-top' => [1],
1711             'margin-right' => [2,1],
1712             'margin-bottom' => [3,1],
1713             'margin-left' => [4,2,1],
1714             },
1715             serialise => sub {
1716             my $p = shift;
1717             my @vals = map $p->{"margin-$_"},
1718             qw/top right bottom left/;
1719             $vals[3] eq $vals[1] and pop @vals,
1720             $vals[2] eq $vals[0] and pop @vals,
1721             $vals[1] eq $vals[0] and pop @vals;
1722             return join " ", map $_ || 0, @vals;
1723             },
1724             },
1725            
1726             'max-height' => {
1727             format => '||none',
1728             default => 'none',
1729             inherit => 0,
1730             },
1731             'max-width' => {
1732             format => '||none',
1733             default => 'none',
1734             inherit => 0,
1735             },
1736             'min-height' => {
1737             format => '||none',
1738             default => 'none',
1739             inherit => 0,
1740             },
1741             'min-width' => {
1742             format => '||none',
1743             default => 'none',
1744             inherit => 0,
1745             },
1746            
1747             orphans => {
1748             format => '',
1749             default => 2,
1750             inherit => 1,
1751             },
1752            
1753             'outline-color' => {
1754             format => '|invert',
1755             default => 'invert',
1756             inherit => 0,
1757             },
1758            
1759             'outline-style' => {
1760             format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
1761             inset|outset',
1762             default => 'none',
1763             inherit => 0,
1764             },
1765            
1766             'outline-width' => {
1767             format => '|thin|thick|medium',
1768             default => 'medium',
1769             inherit => 0,
1770             },
1771            
1772             outline => {
1773             format => "'outline-color'||'outline-style'||'outline-width'",
1774             serialise => sub {
1775             my $p = shift;
1776             my $ret = '';
1777             for(qw/ color style width /) {
1778             length $p->{"outline-$_"}
1779             and $ret .= $p->{"outline-$_"}." ";
1780             }
1781             chop $ret;
1782             length $ret ? $ret : 'invert';
1783             },
1784             },
1785            
1786             overflow => {
1787             format => 'visible|hidden|scroll|auto',
1788             default => 'visible',
1789             inherit => 0,
1790             },
1791            
1792             'padding-top' => {
1793             format => '|',
1794             default => 0,
1795             inherit => 0,
1796             },
1797             'padding-right' => {
1798             format => '|',
1799             default => 0,
1800             inherit => 0,
1801             },
1802             'padding-bottom' => {
1803             format => '|',
1804             default => 0,
1805             inherit => 0,
1806             },
1807             'padding-left' => {
1808             format => '|',
1809             default => 0,
1810             inherit => 0,
1811             },
1812            
1813             padding => {
1814             format => "(|)
1815             [ (|)
1816             [ (|)
1817             (|)?
1818             ]?
1819             ]?",
1820             properties => {
1821             'padding-top' => [1],
1822             'padding-right' => [2,1],
1823             'padding-bottom' => [3,1],
1824             'padding-left' => [4,2,1],
1825             },
1826             serialise => sub {
1827             my $p = shift;
1828             my @vals = map $p->{"padding-$_"},
1829             qw/top right bottom left/;
1830             $vals[3] eq $vals[1] and pop @vals,
1831             $vals[2] eq $vals[0] and pop @vals,
1832             $vals[1] eq $vals[0] and pop @vals;
1833             return join " ", map $_ || 0, @vals;
1834             },
1835             },
1836            
1837             'page-break-after' => {
1838             format => 'auto|always|avoid|left|right',
1839             default => 'auto',
1840             inherit => 0,
1841             },
1842             'page-break-before' => {
1843             format => 'auto|always|avoid|left|right',
1844             default => 'auto',
1845             inherit => 0,
1846             },
1847            
1848             'page-break-inside' => {
1849             format => 'avoid|auto',
1850             default => 'auto',
1851             inherit => 1,
1852             },
1853            
1854             'pause-after' => {
1855             format => '
1856             default => 0,
1857             inherit => 0,
1858             },
1859             'pause-before' => {
1860             format => '
1861             default => 0,
1862             inherit => 0,
1863             },
1864            
1865             pause => {
1866             format => '(
1867             properties => {
1868             'pause-before' => [1],
1869             'pause-after' => [2,1],
1870             }
1871             },
1872            
1873             'pitch-range' => {
1874             format => '',
1875             default => 50,
1876             inherit => 1,
1877             },
1878            
1879             pitch => {
1880             format => '|x-low|low|medium|high|x-high',
1881             default => 'medium',
1882             inherit => 1,
1883             },
1884            
1885             'play-during' => {
1886             format => ' [ mix || repeat ]? | auto | none',
1887             default => 'auto',
1888             inherit => 0,
1889             },
1890            
1891             position => {
1892             format => 'static|relative|absolute|fixed',
1893             default => 'relative',
1894             inherit => 0,
1895             },
1896            
1897             quotes => {
1898             format => '[()()]+|none',
1899             default => 'none',
1900             inherit => 1,
1901             list => 1,
1902             },
1903            
1904             richness => {
1905             format => '',
1906             default => 50,
1907             inherit => 1,
1908             },
1909            
1910             right => {
1911             format => '||auto',
1912             default => 'auto',
1913             inherit => 0,
1914             },
1915            
1916             'speak-header' => {
1917             format => 'once|always',
1918             default => 'once',
1919             inherit => 1,
1920             },
1921            
1922             'speak-numeral' => {
1923             format => 'digits|continuous',
1924             default => 'continuous',
1925             inherit => 1,
1926             },
1927            
1928             'speak-punctuation' => {
1929             format => 'code|none',
1930             default => 'none',
1931             inherit => 1,
1932             },
1933            
1934             speak => {
1935             format => 'normal|none|spell-out',
1936             default => 'normal',
1937             inherit => 1,
1938             },
1939            
1940             'speech-rate' => {
1941             format => '|x-slow|slow|medium|fast|x-fast|faster|slower',
1942             default => 'medium',
1943             inherit => 1,
1944             },
1945            
1946             stress => {
1947             format => '',
1948             default => 50,
1949             inherit => 1,
1950             },
1951            
1952             'table-layout' => {
1953             format => 'auto|fixed',
1954             default => 'auto',
1955             inherit => 0,
1956             },
1957            
1958             'text-align' => {
1959             format => 'left|right|center|justify|auto',
1960             default => 'auto',
1961             inherit => 1,
1962             },
1963            
1964             'text-decoration' => {
1965             format => 'none | underline||overline||line-through||blink ',
1966             default => 'none',
1967             inherit => 0,
1968             },
1969            
1970             'text-indent' => {
1971             format => '|',
1972             default => 0,
1973             inherit => 1,
1974             },
1975            
1976             'text-transform' => {
1977             format => 'capitalize|uppercase|lowercase|none',
1978             default => 'none',
1979             inherit => 1,
1980             },
1981            
1982             top => {
1983             format => '||auto',
1984             default => 'auto',
1985             inherit => 0,
1986             },
1987            
1988             'unicode-bidi' => {
1989             format => 'normal|embed|bidi-override',
1990             default => 'normal',
1991             inherit => 0,
1992             },
1993            
1994             'vertical-align' => {
1995             format => 'baseline|sub|super|top|text-top|middle|bottom|
1996             text-bottom||',
1997             default => 'baseline',
1998             inherit => 0,
1999             },
2000            
2001             visibility => {
2002             format => 'visible|hidden|collapse',
2003             default => 'visible',
2004             inherit => 1,
2005             },
2006            
2007             'voice-family' => {
2008             format => '(male|female|child|)
2009             [, (male|female|child|) ]*',
2010             default => '',
2011             inherit => 1,
2012             list => 1,
2013             },
2014            
2015             volume => {
2016             format => '||silent|x-soft|soft|medium|loud|
2017             x-loud',
2018             default => 'medium',
2019             inherit => 1,
2020             },
2021            
2022             'white-space' => {
2023             format => 'normal|pre|nowrap|pre-wrap|pre-line',
2024             default => 'normal',
2025             inherit => 1,
2026             },
2027            
2028             widows => {
2029             format => '',
2030             default => 2,
2031             inherit => 1,
2032             },
2033            
2034             width => {
2035             format => '||auto',
2036             default => 'auto',
2037             inherit => 0,
2038             },
2039            
2040             'word-spacing' => {
2041             format => 'normal|',
2042             default => 'normal',
2043             inherit => 1,
2044             },
2045            
2046             'z-index' => {
2047             format => 'auto|',
2048             default => 'auto',
2049             inherit => 0,
2050             },
2051             );
2052             $CSS21->add_property( $_ => $properties{$_} ) for keys %properties;
2053              
2054             =pod
2055              
2056             =cut
2057              
2058             our $Default = $CSS21;
2059              
2060             =head1 SEE ALSO
2061              
2062             L