/[suikacvs]/markup/html/whatpm/Whatpm/CSS/Parser.pm
Suika

Diff of /markup/html/whatpm/Whatpm/CSS/Parser.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.3 by wakaba, Sun Dec 23 11:19:23 2007 UTC revision 1.72 by wakaba, Mon Sep 15 23:45:34 2008 UTC
# Line 2  package Whatpm::CSS::Parser; Line 2  package Whatpm::CSS::Parser;
2  use strict;  use strict;
3  use Whatpm::CSS::Tokenizer qw(:token);  use Whatpm::CSS::Tokenizer qw(:token);
4  require Whatpm::CSS::SelectorsParser;  require Whatpm::CSS::SelectorsParser;
5    require Whatpm::CSS::MediaQueryParser;
6    
7  sub new ($) {  sub new ($) {
8    my $self = bless {onerror => sub { }, must_level => 'm',    my $self = bless {
9                      unsupported_level => 'unsupported'}, shift;      level => {
10          must => 'm',
11          uncertain => 'u',
12        },
13        lookup_namespace_uri => sub { undef },
14      }, shift;
15      # $self->{base_uri}
16      # $self->{unitless_px} = 1/0
17      # $self->{hashless_rgb} = 1/0
18    
19      ## Default error handler
20      $self->{onerror} = sub {
21        my %opt = @_;
22        require Carp;
23        Carp::carp
24            (sprintf 'Document <%s>: Line %d column %d (token %s): %s%s',
25                 defined $opt{uri} ? ${$opt{uri}} : 'thisdocument:/',
26                 $opt{token}->{line},
27                 $opt{token}->{column},
28                 Whatpm::CSS::Tokenizer->serialize_token ($opt{token}),
29                 $opt{type},
30                 defined $opt{value} ? " (value $opt{value})" : '');
31      };
32    
33      ## Media-dependent RGB color range clipper
34      $self->{clip_color} = sub {
35        shift; #my $self = shift;
36        my $value = shift;
37        if (defined $value and $value->[0] eq 'RGBA') {
38          my ($r, $g, $b) = @$value[1, 2, 3];
39          $r = 0 if $r < 0;  $r = 255 if $r > 255;
40          $g = 0 if $g < 0;  $g = 255 if $g > 255;
41          $b = 0 if $b < 0;  $b = 255 if $b > 255;
42          return ['RGBA', $r, $g, $b, $value->[4]];
43        }
44        return $value;
45      };
46    
47      ## System dependent font expander
48      $self->{get_system_font} = sub {
49        #my ($self, $normalized_system_font_name, $font_properties) = @_;
50        ## Modify $font_properties hash (except for 'font-family' property).
51        return $_[2];
52      };
53    
54    return $self;    return $self;
55  } # new  } # new
# Line 15  sub BEFORE_DECLARATION_STATE () { 1 } Line 59  sub BEFORE_DECLARATION_STATE () { 1 }
59  sub IGNORED_STATEMENT_STATE () { 2 }  sub IGNORED_STATEMENT_STATE () { 2 }
60  sub IGNORED_DECLARATION_STATE () { 3 }  sub IGNORED_DECLARATION_STATE () { 3 }
61    
62    our $Prop; ## By CSS property name
63    our $Attr; ## By CSSOM attribute name
64    our $Key; ## By internal key
65    
66    sub init ($) {
67      my $self = shift;
68      delete $self->{style_sheet};
69      delete $self->{unitless_px};
70      delete $self->{hashless_rgb};
71      delete $self->{href};
72      delete $self->{base_uri};
73    } # init
74    
75  sub parse_char_string ($$) {  sub parse_char_string ($$) {
76    my $self = $_[0];    my $self = $_[0];
77    
# Line 22  sub parse_char_string ($$) { Line 79  sub parse_char_string ($$) {
79    pos ($s) = 0;    pos ($s) = 0;
80    my $line = 1;    my $line = 1;
81    my $column = 0;    my $column = 0;
   
   my $_onerror = $self->{onerror};  
   my $onerror = sub {  
     $_onerror->(@_, line => $line, column => $column);  
   };  
82        
83    my $tt = Whatpm::CSS::Tokenizer->new;    my $tt = Whatpm::CSS::Tokenizer->new;
84    $tt->{onerror} = $onerror;    my $onerror = $tt->{onerror} = $self->{onerror};
85    $tt->{get_char} = sub {    $tt->{get_char} = sub ($) {
86      if (pos $s < length $s) {      if (pos $s < length $s) {
87        my $c = ord substr $s, pos ($s)++, 1;        my $c = ord substr $s, pos ($s)++, 1;
88        if ($c == 0x000A) {        if ($c == 0x000A) {
# Line 46  sub parse_char_string ($$) { Line 98  sub parse_char_string ($$) {
98        } else {        } else {
99          $column++;          $column++;
100        }        }
101          $_[0]->{line} = $line;
102          $_[0]->{column} = $column;
103        return $c;        return $c;
104      } else {      } else {
105          $_[0]->{column} = $column + 1; ## Set the same number always.
106        return -1;        return -1;
107      }      }
108    }; # $tt->{get_char}    }; # $tt->{get_char}
109    $tt->init;    $tt->init;
110    
111    my $sp = Whatpm::CSS::SelectorsParser->new;    my $sp = Whatpm::CSS::SelectorsParser->new;
112    $sp->{onerror} = $onerror;    $sp->{onerror} = $self->{onerror};
113      $sp->{level} = $self->{level};
114    $sp->{must_level} = $self->{must_level};    $sp->{must_level} = $self->{must_level};
115    $sp->{pseudo_element} = $self->{pseudo_element};    $sp->{pseudo_element} = $self->{pseudo_element};
116    $sp->{pseudo_class} = $self->{pseudo_class};    $sp->{pseudo_class} = $self->{pseudo_class};
117    
118    ## TODO:    my $mp = Whatpm::CSS::MediaQueryParser->new;
119    #$sp->{lookup_namespace_uri} = ...;    $mp->{onerror} = $self->{onerror};
120      $mp->{level} = $self->{level};
121      $mp->{must_level} = $self->{must_level};
122      $mp->{unsupported_level} = $self->{unsupported_level};
123    
124    ## TODO: Supported pseudo classes and elements...    my $nsmap = {prefix_to_uri => {}, uri_to_prefixes => {}};
125      # $nsmap->{prefix_to_uri}->{p/""} = uri/undef
126      # $nsmap->{uri_to_prefixes}->{uri} = ["p|"/"",...]/undef
127      # $nsmap->{has_namespace} = 1/0
128      $self->{lookup_namespace_uri} =
129      $sp->{lookup_namespace_uri} = sub { ## TODO: case
130        return $nsmap->{prefix_to_uri}->{lc $_[0]}; # $_[0] is '' (default) or prefix
131      }; # $sp->{lookup_namespace_uri}
132    
133    require Message::DOM::CSSStyleSheet;    require Message::DOM::CSSStyleSheet;
134    require Message::DOM::CSSRule;    require Message::DOM::CSSRule;
135    require Message::DOM::CSSStyleDeclaration;    require Message::DOM::CSSStyleDeclaration;
136    
137      $self->{base_uri} = $self->{href} unless defined $self->{base_uri};
138      $sp->{href} = $self->{href};
139    
140    my $state = BEFORE_STATEMENT_STATE;    my $state = BEFORE_STATEMENT_STATE;
141    my $t = $tt->get_next_token;    my $t = $tt->get_next_token;
142    
143    my $open_rules = [[]];    my $open_rules = [[]];
144    my $current_rules = $open_rules->[-1];    my $current_rules = $open_rules->[-1];
145      my $parent_rules = [];
146    my $current_decls;    my $current_decls;
147    my $closing_tokens = [];    my $closing_tokens = [];
148    my $charset_allowed = 1;    my $charset_allowed = 1;
149      my $namespace_allowed = 1;
150      my $import_allowed = 1;
151      my $media_allowed = 1;
152    
153      my $ss = $self->{style_sheet} ||= Message::DOM::CSSStyleSheet->____new
154          (css_rules => $open_rules->[0],
155           ## TODO: href
156           ## TODO: owner_node
157           ## TODO: media
158           type => 'text/css'); ## TODO: OK?
159      $$ss->{manakai_base_uri} = $self->{base_uri};
160      $$ss->{_parser} = $self;
161      $$ss->{_nsmap} = $nsmap;
162    
163    S: {    S: {
164      if ($state == BEFORE_STATEMENT_STATE) {      if ($state == BEFORE_STATEMENT_STATE) {
# Line 85  sub parse_char_string ($$) { Line 168  sub parse_char_string ($$) {
168                $t->{type} == CDC_TOKEN;                $t->{type} == CDC_TOKEN;
169    
170        if ($t->{type} == ATKEYWORD_TOKEN) {        if ($t->{type} == ATKEYWORD_TOKEN) {
171          if ($t->{value} eq 'charset') {          my $at_rule_name = lc $t->{value}; ## TODO: case
172            if ($at_rule_name eq 'namespace') {
173            $t = $tt->get_next_token;            $t = $tt->get_next_token;
174            $t = $tt->get_next_token while $t->{type} == S_TOKEN;            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
175    
176            if ($t->{type} == STRING_TOKEN) {            my $prefix;
177              my $encoding = $t->{value};            if ($t->{type} == IDENT_TOKEN) {
178                $prefix = lc $t->{value};
179                ## TODO: case (Unicode lowercase)
180                            
181              $t = $tt->get_next_token;              $t = $tt->get_next_token;
182              $t = $tt->get_next_token while $t->{type} == S_TOKEN;              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
183              }
184    
185              if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
186                my $uri = $t->{value};
187                            
188                $t = $tt->get_next_token;
189                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
190    
191                ## ISSUE: On handling of empty namespace URI, Firefox 2 and
192                ## Opera 9 work differently (See SuikaWiki:namespace).
193                ## TODO: We need to check what we do once it is specced.
194    
195              if ($t->{type} == SEMICOLON_TOKEN) {              if ($t->{type} == SEMICOLON_TOKEN) {
196                if ($charset_allowed) {                if ($namespace_allowed) {
197                    my $p = $prefix;
198                    $nsmap->{has_namespace} = 1;
199                    if (defined $prefix) {
200                      $nsmap->{prefix_to_uri}->{$prefix} = $uri;
201                      $p .= '|';
202                    } else {
203                      $nsmap->{prefix_to_uri}->{''} = $uri;
204                      $p = '';
205                    }
206                    for my $u (keys %{$nsmap->{uri_to_prefixes}}) {
207                      next if $u eq $uri;
208                      my $list = $nsmap->{uri_to_prefixes}->{$u};
209                      next unless $list;
210                      for (reverse 0..$#$list) {
211                        splice @$list, $_, 1, () if $list->[$_] eq $p;
212                      }
213                    }
214                    push @{$nsmap->{uri_to_prefixes}->{$uri} ||= []}, $p;
215                  push @$current_rules,                  push @$current_rules,
216                      Message::DOM::CSSCharsetRule->____new ($encoding);                      Message::DOM::CSSNamespaceRule->____new ($prefix, $uri);
217                  undef $charset_allowed;                  undef $charset_allowed;
218                    undef $import_allowed;
219                } else {                } else {
220                   $onerror->(type => 'at:charset:not allowed',                  $onerror->(type => 'at-rule not allowed',
221                              level => $self->{must_level},                             text => 'namespace',
222                              token => $t);                             level => $self->{level}->{must},
223                               uri => \$self->{href},
224                               token => $t);
225                }                }
   
               ## TODO: Detect the conformance errors for @charset...  
226                                
227                $t = $tt->get_next_token;                $t = $tt->get_next_token;
228                ## Stay in the state.                ## Stay in the state.
# Line 118  sub parse_char_string ($$) { Line 234  sub parse_char_string ($$) {
234              #              #
235            }            }
236    
237            $onerror->(type => 'syntax error:at:charset',            $onerror->(type => 'at-rule syntax error',
238                       level => $self->{must_level},                       text => 'namespace',
239                         level => $self->{level}->{must},
240                         uri => \$self->{href},
241                       token => $t);                       token => $t);
242          ## NOTE: When adding support for new at-rule, insert code            #
243          ## "undef $charset_allowed" as appropriate.          } elsif ($at_rule_name eq 'import') {
244              if ($import_allowed) {
245                $t = $tt->get_next_token;
246                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
247                my $mq = [];
248                if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
249                  my $uri = $t->{value};
250                  $t = $tt->get_next_token;
251                  $t = $tt->get_next_token while $t->{type} == S_TOKEN;
252                  if ($t->{type} == IDENT_TOKEN or
253                      $t->{type} == DIMENSION_TOKEN or
254                      $t->{type} == NUMBER_TOKEN or
255                      $t->{type} == LPAREN_TOKEN) {
256                    ($t, $mq) = $mp->_parse_mq_with_tokenizer ($t, $tt);
257                    $t = $tt->get_next_token while $t->{type} == S_TOKEN;
258                  }
259                  if ($mq and $t->{type} == SEMICOLON_TOKEN) {
260                    ## TODO: error or warning
261                    ## TODO: White space definition
262                    $uri =~ s/^[\x09\x0A\x0D\x20]+//;
263                    $uri =~ s/[\x09\x0A\x0D\x20]+\z//;
264    
265                    my $imported = Message::DOM::CSSStyleSheet->____new
266                        (css_rules => [],
267                         ## TODO: href
268                         parent_style_sheet => $ss,
269                         ## TODO: media
270                         type => 'text/css', ## TODO: OK?
271                         _parser => $self, _nsmap => {});
272    
273                    my $rule = Message::DOM::CSSImportRule->____new
274                        ($uri, \($self->{base_uri}), $mq, $ss);
275    
276                    $$imported->{owner_rule} = $rule;
277                    Scalar::Util::weaken ($$imported->{owner_rule});
278                    Scalar::Util::weaken ($$imported->{parent_style_sheet});
279    
280                    push @$current_rules, $rule;
281    
282                    undef $charset_allowed;
283    
284                    $t = $tt->get_next_token;
285                    ## Stay in the state.
286                    redo S;
287                  }
288                }
289    
290                $onerror->(type => 'at-rule syntax error',
291                           text => 'import',
292                           level => $self->{level}->{must},
293                           uri => \$self->{href},
294                           token => $t)
295                    if defined $mq; ## NOTE: Otherwise, already raised in MQ parser
296                
297                #
298              } else {
299                $onerror->(type => 'at-rule not allowed',
300                           text => 'import',
301                           level => $self->{level}->{must},
302                           uri => \$self->{href},
303                           token => $t);
304                
305                #
306              }
307            } elsif ($at_rule_name eq 'media') {
308              if ($media_allowed) {
309                $t = $tt->get_next_token;
310                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
311                
312                my $q;
313                ($t, $q) = $mp->_parse_mq_with_tokenizer ($t, $tt);
314                if ($q) {
315                  if ($t->{type} == LBRACE_TOKEN) {
316                    undef $charset_allowed;
317                    undef $namespace_allowed;
318                    undef $import_allowed;
319                    undef $media_allowed;
320                    my $rule = Message::DOM::CSSMediaRule->____new
321                        ($q, my $v = []);
322                    push @$current_rules, $rule;
323                    push @$parent_rules, $rule;
324                    push @$open_rules, $current_rules = $v;
325                    $t = $tt->get_next_token;
326                    ## Stay in the state.
327                    redo S;
328                  } else {
329                    $onerror->(type => 'at-rule syntax error',
330                               text => 'media',
331                               level => $self->{level}->{must},
332                               uri => \$self->{href},
333                               token => $t);
334                  }
335    
336                  #
337                }
338                
339                #
340              } else { ## Nested @media rule
341                $onerror->(type => 'at-rule not allowed',
342                           text => 'media',
343                           level => $self->{level}->{must},
344                           uri => \$self->{href},
345                           token => $t);
346                
347                #
348              }
349            } elsif ($at_rule_name eq 'charset') {
350              if ($charset_allowed) {
351                $t = $tt->get_next_token;
352                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
353    
354                if ($t->{type} == STRING_TOKEN) {
355                  my $encoding = $t->{value};
356                  
357                  $t = $tt->get_next_token;
358                  $t = $tt->get_next_token while $t->{type} == S_TOKEN;
359                
360                  if ($t->{type} == SEMICOLON_TOKEN) {
361                    push @$current_rules,
362                        Message::DOM::CSSCharsetRule->____new ($encoding);
363                    undef $charset_allowed;
364    
365                    ## TODO: Detect the conformance errors for @charset...
366                  
367                    $t = $tt->get_next_token;
368                    ## Stay in the state.
369                    redo S;
370                  } else {
371                    #
372                  }
373                } else {
374                  #
375                }
376                
377                $onerror->(type => 'at-rule syntax error',
378                           text => 'charset',
379                           level => $self->{level}->{must},
380                           uri => \$self->{href},
381                           token => $t);
382                #
383              } else {
384                $onerror->(type => 'at-rule not allowed',
385                           text => 'charset',
386                           level => $self->{level}->{must},
387                           uri => \$self->{href},
388                           token => $t);
389                #
390              }
391          } else {          } else {
392            $onerror->(type => 'not supported:at:'.$t->{value},            $onerror->(type => 'unknown at-rule',
393                       level => $self->{unsupported_level},                       level => $self->{level}->{uncertain},
394                       token => $t);                       uri => \$self->{href},
395                         token => $t,
396                         value => $t->{value});
397          }          }
398    
399          $t = $tt->get_next_token;          ## Reprocess.
400            #$t = $tt->get_next_token;
401          $state = IGNORED_STATEMENT_STATE;          $state = IGNORED_STATEMENT_STATE;
402          redo S;          redo S;
403        } elsif (@$open_rules > 1 and $t->{type} == RBRACE_TOKEN) {        } elsif (@$open_rules > 1 and $t->{type} == RBRACE_TOKEN) {
404          pop @$open_rules;          pop @$open_rules;
405            $media_allowed = 1;
406            $current_rules = $open_rules->[-1];
407          ## Stay in the state.          ## Stay in the state.
408          $t = $tt->get_next_token;          $t = $tt->get_next_token;
409          redo S;          redo S;
410        } elsif ($t->{type} == EOF_TOKEN) {        } elsif ($t->{type} == EOF_TOKEN) {
411          if (@$open_rules > 1) {          if (@$open_rules > 1) {
412            $onerror->(type => 'syntax error:block not closed',            $onerror->(type => 'block not closed',
413                       level => $self->{must_level},                       level => $self->{level}->{must},
414                         uri => \$self->{href},
415                       token => $t);                       token => $t);
416          }          }
417    
418          last S;          last S;
419        } else {        } else {
420          undef $charset_allowed;          undef $charset_allowed;
421            undef $namespace_allowed;
422            undef $import_allowed;
423    
424          ($t, my $selectors) = $sp->_parse_selectors_with_tokenizer          ($t, my $selectors) = $sp->_parse_selectors_with_tokenizer
425              ($tt, LBRACE_TOKEN, $t);              ($tt, LBRACE_TOKEN, $t);
# Line 164  sub parse_char_string ($$) { Line 437  sub parse_char_string ($$) {
437            $t = $tt->get_next_token;            $t = $tt->get_next_token;
438            redo S;            redo S;
439          } else {          } else {
440            $onerror->(type => 'syntax error:after selectors',            $onerror->(type => 'no declaration block',
441                       level => $self->{must_level},                       level => $self->{level}->{must},
442                         uri => \$self->{href},
443                       token => $t);                       token => $t);
444    
445            ## Stay in the state.            ## Stay in the state.
# Line 177  sub parse_char_string ($$) { Line 451  sub parse_char_string ($$) {
451        ## NOTE: DELIM? in declaration will be removed:        ## NOTE: DELIM? in declaration will be removed:
452        ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.        ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.
453    
454          my $prop_def;
455          my $prop_value;
456          my $prop_flag = '';
457        $t = $tt->get_next_token while $t->{type} == S_TOKEN;        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
458        if ($t->{type} == IDENT_TOKEN) { # property        if ($t->{type} == IDENT_TOKEN) { # property
459          ## TODO: If supported, ...          my $prop_name = lc $t->{value}; ## TODO: case folding
460            my $prop_name_t = $t;
461          $t = $tt->get_next_token;          $t = $tt->get_next_token;
462          #          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
463        } elsif ($t->{type} == RBRACE_TOKEN) {          if ($t->{type} == COLON_TOKEN) {
464              $t = $tt->get_next_token;
465              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
466    
467              $prop_def = $Prop->{$prop_name};
468              if ($prop_def and $self->{prop}->{$prop_name}) {
469                ($t, $prop_value)
470                    = $prop_def->{parse}->($self, $prop_name, $tt, $t, $onerror);
471                if ($prop_value) {
472                  ## NOTE: {parse} don't have to consume trailing spaces.
473                  $t = $tt->get_next_token while $t->{type} == S_TOKEN;
474    
475                  if ($t->{type} == EXCLAMATION_TOKEN) {
476                    $t = $tt->get_next_token;
477                    $t = $tt->get_next_token while $t->{type} == S_TOKEN;
478                    if ($t->{type} == IDENT_TOKEN and
479                        lc $t->{value} eq 'important') { ## TODO: case folding
480                      $prop_flag = 'important';
481                      
482                      $t = $tt->get_next_token;
483                      $t = $tt->get_next_token while $t->{type} == S_TOKEN;
484    
485                      #
486                    } else {
487                      $onerror->(type => 'priority syntax error',
488                                 level => $self->{level}->{must},
489                                 uri => \$self->{href},
490                                 token => $t);
491                      
492                      ## Reprocess.
493                      $state = IGNORED_DECLARATION_STATE;
494                      redo S;
495                    }
496                  }
497    
498                  #
499                } else {
500                  ## Syntax error.
501            
502                  ## Reprocess.
503                  $state = IGNORED_DECLARATION_STATE;
504                  redo S;
505                }
506              } else {
507                $onerror->(type => 'unknown property',
508                           level => $self->{level}->{uncertain},
509                           token => $prop_name_t, value => $prop_name,
510                           uri => \$self->{href});
511    
512                #
513                $state = IGNORED_DECLARATION_STATE;
514                redo S;
515              }
516            } else {
517              $onerror->(type => 'no property colon',
518                         level => $self->{level}->{must},
519                         uri => \$self->{href},
520                         token => $t);
521    
522              #
523              $state = IGNORED_DECLARATION_STATE;
524              redo S;
525            }
526          }
527    
528          if ($t->{type} == RBRACE_TOKEN) {
529          $t = $tt->get_next_token;          $t = $tt->get_next_token;
530          $state = BEFORE_STATEMENT_STATE;          $state = BEFORE_STATEMENT_STATE;
531          redo S;          #redo S;
532          } elsif ($t->{type} == SEMICOLON_TOKEN) {
533            $t = $tt->get_next_token;
534            ## Stay in the state.
535            #redo S;
536        } elsif ($t->{type} == EOF_TOKEN) {        } elsif ($t->{type} == EOF_TOKEN) {
537          $onerror->(type => 'syntax error:ruleset not closed',          $onerror->(type => 'block not closed',
538                     level => $self->{must_level},                     level => $self->{level}->{must},
539                       uri => \$self->{href},
540                     token => $t);                     token => $t);
541          ## Reprocess.          ## Reprocess.
542          $state = BEFORE_STATEMENT_STATE;          $state = BEFORE_STATEMENT_STATE;
543            #redo S;
544          } else {
545            if ($prop_value) {
546              $onerror->(type => 'no property semicolon',
547                         level => $self->{level}->{must},
548                         uri => \$self->{href},
549                         token => $t);
550            } else {
551              $onerror->(type => 'no property name',
552                         level => $self->{level}->{must},
553                         uri => \$self->{href},
554                         token => $t);
555            }
556    
557            #
558            $state = IGNORED_DECLARATION_STATE;
559          redo S;          redo S;
560        }        }
561    
562        #        my $important = ($prop_flag eq 'important');
563        $state = IGNORED_DECLARATION_STATE;        for my $set_prop_name (keys %{$prop_value or {}}) {
564            my $set_prop_def = $Prop->{$set_prop_name};
565            $$current_decls->{$set_prop_def->{key}}
566                = [$prop_value->{$set_prop_name}, $prop_flag]
567                if $important or
568                    not $$current_decls->{$set_prop_def->{key}} or
569                    $$current_decls->{$set_prop_def->{key}}->[1] ne 'important';
570          }
571        redo S;        redo S;
572      } elsif ($state == IGNORED_STATEMENT_STATE or      } elsif ($state == IGNORED_STATEMENT_STATE or
573               $state == IGNORED_DECLARATION_STATE) {               $state == IGNORED_DECLARATION_STATE) {
# Line 221  sub parse_char_string ($$) { Line 591  sub parse_char_string ($$) {
591              ## Stay in the state.              ## Stay in the state.
592              redo S;              redo S;
593            }            }
594            } elsif ({
595              RBRACE_TOKEN, 1,
596              #RBRACKET_TOKEN, 1,
597              #RPAREN_TOKEN, 1,
598              SEMICOLON_TOKEN, 1,
599            }->{$t->{type}}) {
600              $t = $tt->get_next_token;
601              ## Stay in the state.
602              #
603          } else {          } else {
604            #            #
605          }          }
# Line 230  sub parse_char_string ($$) { Line 609  sub parse_char_string ($$) {
609            $state = $state == IGNORED_STATEMENT_STATE            $state = $state == IGNORED_STATEMENT_STATE
610                ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;                ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
611            redo S;            redo S;
612          } elsif ($state == IGNORED_DECLARATION_STATE and          } elsif ($t->{type} == RBRACE_TOKEN) {
613                   $t->{type} == RBRACE_TOKEN) {            if ($state == IGNORED_DECLARATION_STATE) {
614            $t = $tt->get_next_token;              $t = $tt->get_next_token;
615            $state = BEFORE_STATEMENT_STATE;              $state = BEFORE_STATEMENT_STATE;
616            redo S;              redo S;
617              } else {
618                ## NOTE: Maybe this state cannot be reached.
619                $t = $tt->get_next_token;
620                ## Stay in the state.
621                redo S;
622              }
623          } elsif ($t->{type} == EOF_TOKEN) {          } elsif ($t->{type} == EOF_TOKEN) {
624            ## Reprocess.            ## Reprocess.
625            $state = $state == IGNORED_STATEMENT_STATE            $state = $state == IGNORED_STATEMENT_STATE
626                ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;                ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
627            redo S;            redo S;
628            #} elsif ($t->{type} == RBRACKET_TOKEN or $t->{type} == RPAREN_TOKEN) {
629            #  $t = $tt->get_next_token;
630            #  ## Stay in the state.
631            #  #
632          } else {          } else {
633            #            #
634          }          }
# Line 248  sub parse_char_string ($$) { Line 637  sub parse_char_string ($$) {
637        while (not {        while (not {
638          EOF_TOKEN, 1,          EOF_TOKEN, 1,
639          RBRACE_TOKEN, 1,          RBRACE_TOKEN, 1,
640          RBRACKET_TOKEN, 1,          ## NOTE: ']' and ')' are disabled for browser compatibility.
641          RPAREN_TOKEN, 1,          #RBRACKET_TOKEN, 1,
642            #RPAREN_TOKEN, 1,
643          SEMICOLON_TOKEN, 1,          SEMICOLON_TOKEN, 1,
644        }->{$t->{type}}) {        }->{$t->{type}}) {
645          if ($t->{type} == LBRACE_TOKEN) {          if ($t->{type} == LBRACE_TOKEN) {
646            push @$closing_tokens, RBRACE_TOKEN;            push @$closing_tokens, RBRACE_TOKEN;
647          } elsif ($t->{type} == LBRACKET_TOKEN) {          #} elsif ($t->{type} == LBRACKET_TOKEN) {
648            push @$closing_tokens, RBRACKET_TOKEN;          #  push @$closing_tokens, RBRACKET_TOKEN;
649          } elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {          #} elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {
650            push @$closing_tokens, RPAREN_TOKEN;          #  push @$closing_tokens, RPAREN_TOKEN;
651          }          }
652    
653          $t = $tt->get_next_token;          $t = $tt->get_next_token;
# Line 271  sub parse_char_string ($$) { Line 661  sub parse_char_string ($$) {
661      }      }
662    } # S    } # S
663    
664    my $ss = Message::DOM::CSSStyleSheet->____new    for (@{$$ss->{css_rules}}) {
665        (css_rules => $open_rules->[0],      $$_->{parent_style_sheet} = $ss;
666         ## TODO: href      Scalar::Util::weaken ($$_->{parent_style_sheet});
667         ## TODO: owner_node    }
668         ## TODO: media    for my $parent_rule (@$parent_rules) {
669         type => 'text/css', ## TODO: OK?      for (@{$$parent_rule->{css_rules}}) {
670         _parser => $self);        $$_->{parent_rule} = $parent_rule;
671          Scalar::Util::weaken ($$_->{parent_rule});
672        }
673      }
674    
675    return $ss;    return $ss;
676  } # parse_char_string  } # parse_char_string
677    
678    my $compute_as_specified = sub ($$$$) {
679      #my ($self, $element, $prop_name, $specified_value) = @_;
680      return $_[3];
681    }; # $compute_as_specified
682    
683    my $x11_colors = {
684                      'aliceblue' =>        [0xf0, 0xf8, 0xff],
685                      'antiquewhite' =>     [0xfa, 0xeb, 0xd7],
686                      'aqua' =>     [0x00, 0xff, 0xff],
687                      'aquamarine' =>       [0x7f, 0xff, 0xd4],
688                      'azure' =>    [0xf0, 0xff, 0xff],
689                      'beige' =>    [0xf5, 0xf5, 0xdc],
690                      'bisque' =>   [0xff, 0xe4, 0xc4],
691                      'black' =>    [0x00, 0x00, 0x00],
692                      'blanchedalmond' =>   [0xff, 0xeb, 0xcd],
693                      'blue' =>     [0x00, 0x00, 0xff],
694                      'blueviolet' =>       [0x8a, 0x2b, 0xe2],
695                      'brown' =>    [0xa5, 0x2a, 0x2a],
696                      'burlywood' =>        [0xde, 0xb8, 0x87],
697                      'cadetblue' =>        [0x5f, 0x9e, 0xa0],
698                      'chartreuse' =>       [0x7f, 0xff, 0x00],
699                      'chocolate' =>        [0xd2, 0x69, 0x1e],
700                      'coral' =>    [0xff, 0x7f, 0x50],
701                      'cornflowerblue' =>   [0x64, 0x95, 0xed],
702                      'cornsilk' => [0xff, 0xf8, 0xdc],
703                      'crimson' =>  [0xdc, 0x14, 0x3c],
704                      'cyan' =>     [0x00, 0xff, 0xff],
705                      'darkblue' => [0x00, 0x00, 0x8b],
706                      'darkcyan' => [0x00, 0x8b, 0x8b],
707                      'darkgoldenrod' =>    [0xb8, 0x86, 0x0b],
708                      'darkgray' => [0xa9, 0xa9, 0xa9],
709                      'darkgreen' =>        [0x00, 0x64, 0x00],
710                      'darkgrey' => [0xa9, 0xa9, 0xa9],
711                      'darkkhaki' =>        [0xbd, 0xb7, 0x6b],
712                      'darkmagenta' =>      [0x8b, 0x00, 0x8b],
713                      'darkolivegreen' =>   [0x55, 0x6b, 0x2f],
714                      'darkorange' =>       [0xff, 0x8c, 0x00],
715                      'darkorchid' =>       [0x99, 0x32, 0xcc],
716                      'darkred' =>  [0x8b, 0x00, 0x00],
717                      'darksalmon' =>       [0xe9, 0x96, 0x7a],
718                      'darkseagreen' =>     [0x8f, 0xbc, 0x8f],
719                      'darkslateblue' =>    [0x48, 0x3d, 0x8b],
720                      'darkslategray' =>    [0x2f, 0x4f, 0x4f],
721                      'darkslategrey' =>    [0x2f, 0x4f, 0x4f],
722                      'darkturquoise' =>    [0x00, 0xce, 0xd1],
723                      'darkviolet' =>       [0x94, 0x00, 0xd3],
724                      'deeppink' => [0xff, 0x14, 0x93],
725                      'deepskyblue' =>      [0x00, 0xbf, 0xff],
726                      'dimgray' =>  [0x69, 0x69, 0x69],
727                      'dimgrey' =>  [0x69, 0x69, 0x69],
728                      'dodgerblue' =>       [0x1e, 0x90, 0xff],
729                      'firebrick' =>        [0xb2, 0x22, 0x22],
730                      'floralwhite' =>      [0xff, 0xfa, 0xf0],
731                      'forestgreen' =>      [0x22, 0x8b, 0x22],
732                      'fuchsia' =>  [0xff, 0x00, 0xff],
733                      'gainsboro' =>        [0xdc, 0xdc, 0xdc],
734                      'ghostwhite' =>       [0xf8, 0xf8, 0xff],
735                      'gold' =>     [0xff, 0xd7, 0x00],
736                      'goldenrod' =>        [0xda, 0xa5, 0x20],
737                      'gray' =>     [0x80, 0x80, 0x80],
738                      'green' =>    [0x00, 0x80, 0x00],
739                      'greenyellow' =>      [0xad, 0xff, 0x2f],
740                      'grey' =>     [0x80, 0x80, 0x80],
741                      'honeydew' => [0xf0, 0xff, 0xf0],
742                      'hotpink' =>  [0xff, 0x69, 0xb4],
743                      'indianred' =>        [0xcd, 0x5c, 0x5c],
744                      'indigo' =>   [0x4b, 0x00, 0x82],
745                      'ivory' =>    [0xff, 0xff, 0xf0],
746                      'khaki' =>    [0xf0, 0xe6, 0x8c],
747                      'lavender' => [0xe6, 0xe6, 0xfa],
748                      'lavenderblush' =>    [0xff, 0xf0, 0xf5],
749                      'lawngreen' =>        [0x7c, 0xfc, 0x00],
750                      'lemonchiffon' =>     [0xff, 0xfa, 0xcd],
751                      'lightblue' =>        [0xad, 0xd8, 0xe6],
752                      'lightcoral' =>       [0xf0, 0x80, 0x80],
753                      'lightcyan' =>        [0xe0, 0xff, 0xff],
754                      'lightgoldenrodyellow' =>     [0xfa, 0xfa, 0xd2],
755                      'lightgray' =>        [0xd3, 0xd3, 0xd3],
756                      'lightgreen' =>       [0x90, 0xee, 0x90],
757                      'lightgrey' =>        [0xd3, 0xd3, 0xd3],
758                      'lightpink' =>        [0xff, 0xb6, 0xc1],
759                      'lightsalmon' =>      [0xff, 0xa0, 0x7a],
760                      'lightseagreen' =>    [0x20, 0xb2, 0xaa],
761                      'lightskyblue' =>     [0x87, 0xce, 0xfa],
762                      'lightslategray' =>   [0x77, 0x88, 0x99],
763                      'lightslategrey' =>   [0x77, 0x88, 0x99],
764                      'lightsteelblue' =>   [0xb0, 0xc4, 0xde],
765                      'lightyellow' =>      [0xff, 0xff, 0xe0],
766                      'lime' =>     [0x00, 0xff, 0x00],
767                      'limegreen' =>        [0x32, 0xcd, 0x32],
768                      'linen' =>    [0xfa, 0xf0, 0xe6],
769                      'magenta' =>  [0xff, 0x00, 0xff],
770                      'maroon' =>   [0x80, 0x00, 0x00],
771                      'mediumaquamarine' => [0x66, 0xcd, 0xaa],
772                      'mediumblue' =>       [0x00, 0x00, 0xcd],
773                      'mediumorchid' =>     [0xba, 0x55, 0xd3],
774                      'mediumpurple' =>     [0x93, 0x70, 0xdb],
775                      'mediumseagreen' =>   [0x3c, 0xb3, 0x71],
776                      'mediumslateblue' =>  [0x7b, 0x68, 0xee],
777                      'mediumspringgreen' =>        [0x00, 0xfa, 0x9a],
778                      'mediumturquoise' =>  [0x48, 0xd1, 0xcc],
779                      'mediumvioletred' =>  [0xc7, 0x15, 0x85],
780                      'midnightblue' =>     [0x19, 0x19, 0x70],
781                      'mintcream' =>        [0xf5, 0xff, 0xfa],
782                      'mistyrose' =>        [0xff, 0xe4, 0xe1],
783                      'moccasin' => [0xff, 0xe4, 0xb5],
784                      'navajowhite' =>      [0xff, 0xde, 0xad],
785                      'navy' =>     [0x00, 0x00, 0x80],
786                      'oldlace' =>  [0xfd, 0xf5, 0xe6],
787                      'olive' =>    [0x80, 0x80, 0x00],
788                      'olivedrab' =>        [0x6b, 0x8e, 0x23],
789                      'orange' =>   [0xff, 0xa5, 0x00],
790                      'orangered' =>        [0xff, 0x45, 0x00],
791                      'orchid' =>   [0xda, 0x70, 0xd6],
792                      'palegoldenrod' =>    [0xee, 0xe8, 0xaa],
793                      'palegreen' =>        [0x98, 0xfb, 0x98],
794                      'paleturquoise' =>    [0xaf, 0xee, 0xee],
795                      'palevioletred' =>    [0xdb, 0x70, 0x93],
796                      'papayawhip' =>       [0xff, 0xef, 0xd5],
797                      'peachpuff' =>        [0xff, 0xda, 0xb9],
798                      'peru' =>     [0xcd, 0x85, 0x3f],
799                      'pink' =>     [0xff, 0xc0, 0xcb],
800                      'plum' =>     [0xdd, 0xa0, 0xdd],
801                      'powderblue' =>       [0xb0, 0xe0, 0xe6],
802                      'purple' =>   [0x80, 0x00, 0x80],
803                      'red' =>      [0xff, 0x00, 0x00],
804                      'rosybrown' =>        [0xbc, 0x8f, 0x8f],
805                      'royalblue' =>        [0x41, 0x69, 0xe1],
806                      'saddlebrown' =>      [0x8b, 0x45, 0x13],
807                      'salmon' =>   [0xfa, 0x80, 0x72],
808                      'sandybrown' =>       [0xf4, 0xa4, 0x60],
809                      'seagreen' => [0x2e, 0x8b, 0x57],
810                      'seashell' => [0xff, 0xf5, 0xee],
811                      'sienna' =>   [0xa0, 0x52, 0x2d],
812                      'silver' =>   [0xc0, 0xc0, 0xc0],
813                      'skyblue' =>  [0x87, 0xce, 0xeb],
814                      'slateblue' =>        [0x6a, 0x5a, 0xcd],
815                      'slategray' =>        [0x70, 0x80, 0x90],
816                      'slategrey' =>        [0x70, 0x80, 0x90],
817                      'snow' =>     [0xff, 0xfa, 0xfa],
818                      'springgreen' =>      [0x00, 0xff, 0x7f],
819                      'steelblue' =>        [0x46, 0x82, 0xb4],
820                      'tan' =>      [0xd2, 0xb4, 0x8c],
821                      'teal' =>     [0x00, 0x80, 0x80],
822                      'thistle' =>  [0xd8, 0xbf, 0xd8],
823                      'tomato' =>   [0xff, 0x63, 0x47],
824                      'turquoise' =>        [0x40, 0xe0, 0xd0],
825                      'violet' =>   [0xee, 0x82, 0xee],
826                      'wheat' =>    [0xf5, 0xde, 0xb3],
827                      'white' =>    [0xff, 0xff, 0xff],
828                      'whitesmoke' =>       [0xf5, 0xf5, 0xf5],
829                      'yellow' =>   [0xff, 0xff, 0x00],
830                      'yellowgreen' =>      [0x9a, 0xcd, 0x32],
831    }; # $x11_colors
832    
833    my $system_colors = {
834      activeborder => 1, activecaption => 1, appworkspace => 1, background => 1,
835      buttonface => 1, buttonhighlight => 1, buttonshadow => 1, buttontext => 1,
836      captiontext => 1, graytext => 1, highlight => 1, highlighttext => 1,
837      inactiveborder => 1, inactivecaption => 1, inactivecaptiontext => 1,
838      infobackground => 1, infotext => 1, menu => 1, menutext => 1,
839      scrollbar => 1, threeddarkshadow => 1, threedface => 1, threedhighlight => 1,
840      threedlightshadow => 1, threedshadow => 1, window => 1, windowframe => 1,
841      windowtext => 1,
842    }; # $system_colors
843    
844    my $parse_color = sub {
845      my ($self, $prop_name, $tt, $t, $onerror) = @_;
846    
847      ## See
848      ## <http://suika.fam.cx/gate/2005/sw/%3Ccolor%3E>,
849      ## <http://suika.fam.cx/gate/2005/sw/rgb>,
850      ## <http://suika.fam.cx/gate/2005/sw/-moz-rgba>,
851      ## <http://suika.fam.cx/gate/2005/sw/hsl>,
852      ## <http://suika.fam.cx/gate/2005/sw/-moz-hsla>, and
853      ## <http://suika.fam.cx/gate/2005/sw/color>
854      ## for browser compatibility issue.
855    
856      ## NOTE: Implementing CSS3 Color CR (2003), except for attr(),
857      ## rgba(), and hsla().
858      ## NOTE: rgb(...{EOF} is not supported (only Opera does).
859    
860      if ($t->{type} == IDENT_TOKEN) {
861        my $value = lc $t->{value}; ## TODO: case
862        if ($x11_colors->{$value} or
863            $system_colors->{$value}) {
864          ## NOTE: "For systems that do not have a corresponding value, the
865          ## specified value should be mapped to the nearest system value, or to
866          ## a default color." [CSS 2.1].
867          $t = $tt->get_next_token;
868          return ($t, {$prop_name => ['KEYWORD', $value]});
869        } elsif ({
870          transparent => 1, ## For 'background-color' in CSS2.1, everywhre in CSS3.
871          flavor => 1, ## CSS3.
872          invert => 1, ## For 'outline-color' in CSS2.1.
873          '-moz-use-text-color' => 1, ## For <border-color> in Gecko.
874          '-manakai-default' => 1, ## CSS2.1 initial for 'color'
875          '-manakai-invert-or-currentcolor' => 1, ## CSS2.1 initial4'outline-color'
876        }->{$value} and $self->{prop_value}->{$prop_name}->{$value}) {
877          $t = $tt->get_next_token;
878          return ($t, {$prop_name => ['KEYWORD', $value]});
879        } elsif ($value eq 'currentcolor' or $value eq '-moz-use-text-color') {
880          $t = $tt->get_next_token;
881          if ($prop_name eq 'color') {
882            return ($t, {$prop_name => ['INHERIT']});
883          } else {
884            return ($t, {$prop_name => ['KEYWORD', $value]});
885          }
886        } elsif ($value eq 'inherit') {
887          $t = $tt->get_next_token;
888          return ($t, {$prop_name => ['INHERIT']});
889        }
890      }
891    
892      if ($t->{type} == HASH_TOKEN or
893          ($self->{hashless_rgb} and {
894            IDENT_TOKEN, 1,
895            NUMBER_TOKEN, 1,
896            DIMENSION_TOKEN, 1,
897          }->{$t->{type}})) {
898        my $v = lc (defined $t->{number} ? $t->{number} : '' . $t->{value}); ## TODO: case
899        if ($v =~ /\A([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})\z/) {
900          $t = $tt->get_next_token;
901          return ($t, {$prop_name => ['RGBA', hex $1, hex $2, hex $3, 1]});
902        } elsif ($v =~ /\A([0-9a-f])([0-9a-f])([0-9a-f])\z/) {
903          $t = $tt->get_next_token;
904          return ($t, {$prop_name => ['RGBA', hex $1.$1, hex $2.$2,
905                                      hex $3.$3, 1]});
906        }
907      }
908    
909      if ($t->{type} == FUNCTION_TOKEN) {
910        my $func = lc $t->{value}; ## TODO: case
911        if ($func eq 'rgb') {
912          $t = $tt->get_next_token;
913          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
914          my $sign = 1;
915          if ($t->{type} == MINUS_TOKEN) {
916            $sign = -1;
917            $t = $tt->get_next_token;
918          } elsif ($t->{type} == PLUS_TOKEN) {
919            $t = $tt->get_next_token;
920          }
921          if ($t->{type} == NUMBER_TOKEN) {
922            my $r = $t->{number} * $sign;
923            $t = $tt->get_next_token;
924            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
925            if ($t->{type} == COMMA_TOKEN) {
926              $t = $tt->get_next_token;
927              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
928              $sign = 1;
929              if ($t->{type} == MINUS_TOKEN) {
930                $sign = -1;
931                $t = $tt->get_next_token;
932              } elsif ($t->{type} == PLUS_TOKEN) {
933                $t = $tt->get_next_token;
934              }
935              if ($t->{type} == NUMBER_TOKEN) {
936                my $g = $t->{number} * $sign;
937                $t = $tt->get_next_token;
938                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
939                if ($t->{type} == COMMA_TOKEN) {
940                  $t = $tt->get_next_token;
941                  $t = $tt->get_next_token while $t->{type} == S_TOKEN;
942                  $sign = 1;
943                  if ($t->{type} == MINUS_TOKEN) {
944                    $sign = -1;
945                    $t = $tt->get_next_token;
946                  } elsif ($t->{type} == PLUS_TOKEN) {
947                    $t = $tt->get_next_token;
948                  }
949                  if ($t->{type} == NUMBER_TOKEN) {
950                    my $b = $t->{number} * $sign;
951                    $t = $tt->get_next_token;
952                    $t = $tt->get_next_token while $t->{type} == S_TOKEN;
953                    if ($t->{type} == RPAREN_TOKEN) {
954                      $t = $tt->get_next_token;
955                      return ($t,
956                              {$prop_name =>
957                               $self->{clip_color}->($self,
958                                                     ['RGBA', $r, $g, $b, 1])});
959                    }
960                  }
961                }
962              }
963            }
964          } elsif ($t->{type} == PERCENTAGE_TOKEN) {
965            my $r = $t->{number} * 255 / 100 * $sign;
966            $t = $tt->get_next_token;
967            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
968            if ($t->{type} == COMMA_TOKEN) {
969              $t = $tt->get_next_token;
970              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
971              $sign = 1;
972              if ($t->{type} == MINUS_TOKEN) {
973                $sign = -1;
974                $t = $tt->get_next_token;
975              } elsif ($t->{type} == PLUS_TOKEN) {
976                $t = $tt->get_next_token;
977              }
978              if ($t->{type} == PERCENTAGE_TOKEN) {
979                my $g = $t->{number} * 255 / 100 * $sign;
980                $t = $tt->get_next_token;
981                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
982                if ($t->{type} == COMMA_TOKEN) {
983                  $t = $tt->get_next_token;
984                  $t = $tt->get_next_token while $t->{type} == S_TOKEN;
985                  $sign = 1;
986                  if ($t->{type} == MINUS_TOKEN) {
987                    $sign = -1;
988                    $t = $tt->get_next_token;
989                  } elsif ($t->{type} == PLUS_TOKEN) {
990                    $t = $tt->get_next_token;
991                  }
992                  if ($t->{type} == PERCENTAGE_TOKEN) {
993                    my $b = $t->{number} * 255 / 100 * $sign;
994                    $t = $tt->get_next_token;
995                    $t = $tt->get_next_token while $t->{type} == S_TOKEN;
996                    if ($t->{type} == RPAREN_TOKEN) {
997                      $t = $tt->get_next_token;
998                      return ($t,
999                              {$prop_name =>
1000                               $self->{clip_color}->($self,
1001                                                     ['RGBA', $r, $g, $b, 1])});
1002                    }
1003                  }
1004                }
1005              }
1006            }
1007          }
1008        } elsif ($func eq 'hsl') {
1009          $t = $tt->get_next_token;
1010          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1011          my $sign = 1;
1012          if ($t->{type} == MINUS_TOKEN) {
1013            $sign = -1;
1014            $t = $tt->get_next_token;
1015          } elsif ($t->{type} == PLUS_TOKEN) {
1016            $t = $tt->get_next_token;
1017          }
1018          if ($t->{type} == NUMBER_TOKEN) {
1019            my $h = (((($t->{number} * $sign) % 360) + 360) % 360) / 360;
1020            $t = $tt->get_next_token;
1021            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1022            if ($t->{type} == COMMA_TOKEN) {
1023              $t = $tt->get_next_token;
1024              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1025              $sign = 1;
1026              if ($t->{type} == MINUS_TOKEN) {
1027                $sign = -1;
1028                $t = $tt->get_next_token;
1029              } elsif ($t->{type} == PLUS_TOKEN) {
1030                $t = $tt->get_next_token;
1031              }
1032              if ($t->{type} == PERCENTAGE_TOKEN) {
1033                my $s = $t->{number} * $sign / 100;
1034                $s = 0 if $s < 0;
1035                $s = 1 if $s > 1;
1036                $t = $tt->get_next_token;
1037                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1038                if ($t->{type} == COMMA_TOKEN) {
1039                  $t = $tt->get_next_token;
1040                  $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1041                  $sign = 1;
1042                  if ($t->{type} == MINUS_TOKEN) {
1043                    $sign = -1;
1044                    $t = $tt->get_next_token;
1045                  } elsif ($t->{type} == PLUS_TOKEN) {
1046                    $t = $tt->get_next_token;
1047                  }
1048                  if ($t->{type} == PERCENTAGE_TOKEN) {
1049                    my $l = $t->{number} * $sign / 100;
1050                    $l = 0 if $l < 0;
1051                    $l = 1 if $l > 1;
1052                    $t = $tt->get_next_token;
1053                    $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1054                    if ($t->{type} == RPAREN_TOKEN) {
1055                      my $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
1056                      my $m1 = $l * 2 - $m2;
1057                      my $hue2rgb = sub ($$$) {
1058                        my ($m1, $m2, $h) = @_;
1059                        $h++ if $h < 0;
1060                        $h-- if $h > 1;
1061                        return $m1 + ($m2 - $m1) * $h * 6 if $h * 6 < 1;
1062                        return $m2 if $h * 2 < 1;
1063                        return $m1 + ($m2 - $m1) * (2/3 - $h) * 6 if $h * 3 < 2;
1064                        return $m1;
1065                      };
1066                      $t = $tt->get_next_token;
1067                      return ($t,
1068                              {$prop_name =>
1069                               $self->{clip_color}
1070                                   ->($self,
1071                                      ['RGBA',
1072                                       $hue2rgb->($m1, $m2, $h + 1/3) * 255,
1073                                       $hue2rgb->($m1, $m2, $h) * 255,
1074                                       $hue2rgb->($m1, $m2, $h - 1/3) * 255, 1])});
1075                    }
1076                  }
1077                }
1078              }
1079            }
1080          }
1081        }
1082      }
1083      
1084      $onerror->(type => 'CSS syntax error', text => 'color',
1085                 level => $self->{level}->{must},
1086                 uri => \$self->{href},
1087                 token => $t);
1088      
1089      return ($t, undef);
1090    }; # $parse_color
1091    
1092    $Prop->{color} = {
1093      css => 'color',
1094      dom => 'color',
1095      key => 'color',
1096      parse => $parse_color,
1097      initial => ['KEYWORD', '-manakai-default'],
1098      inherited => 1,
1099      compute => sub ($$$$) {
1100        my ($self, $element, $prop_name, $specified_value) = @_;
1101    
1102        if (defined $specified_value) {
1103          if ($specified_value->[0] eq 'KEYWORD') {
1104            if ($x11_colors->{$specified_value->[1]}) {
1105              return ['RGBA', @{$x11_colors->{$specified_value->[1]}}, 1];
1106            } elsif ($specified_value->[1] eq 'transparent') {
1107              return ['RGBA', 0, 0, 0, 0];
1108            } elsif ($specified_value->[1] eq 'currentcolor' or
1109                     $specified_value->[1] eq '-moz-use-text-color' or
1110                     ($specified_value->[1] eq '-manakai-invert-or-currentcolor'and
1111                      not $self->{has_invert})) {
1112              unless ($prop_name eq 'color') {
1113                return $self->get_computed_value ($element, 'color');
1114              } else {
1115                ## NOTE: This is an error, since it should have been
1116                ## converted to 'inherit' at parse time.
1117                return ['KEYWORD', '-manakai-default'];
1118              }
1119            } elsif ($specified_value->[1] eq '-manakai-invert-or-currentcolor') {
1120              return ['KEYWORD', 'invert'];
1121            }
1122          }
1123        }
1124        
1125        return $specified_value;
1126      },
1127    };
1128    $Attr->{color} = $Prop->{color};
1129    $Key->{color} = $Prop->{color};
1130    
1131    $Prop->{'background-color'} = {
1132      css => 'background-color',
1133      dom => 'background_color',
1134      key => 'background_color',
1135      parse => $parse_color,
1136      serialize_multiple => sub {
1137        my $self = shift;
1138    
1139        my $r = {};
1140        my $has_all;
1141        
1142        my $x = $self->background_position_x;
1143        my $y = $self->background_position_y;
1144        my $xi = $self->get_property_priority ('background-position-x');
1145        my $yi = $self->get_property_priority ('background-position-y');
1146        if (length $x) {
1147          if (length $y) {
1148            if ($xi eq $yi) {
1149              if ($x eq 'inherit') {
1150                if ($y eq 'inherit') {
1151                  $r->{'background-position'} = ['inherit', $xi];
1152                  $has_all = 1;
1153                } else {
1154                  $r->{'background-position-x'} = [$x, $xi];
1155                  $r->{'background-position-y'} = [$y, $yi];
1156                }
1157              } elsif ($y eq 'inherit') {
1158                $r->{'background-position-x'} = [$x, $xi];
1159                $r->{'background-position-y'} = [$y, $yi];
1160              } else {
1161                $r->{'background-position'} = [$x . ' ' . $y, $xi];
1162                $has_all = 1;
1163              }
1164            } else {
1165              $r->{'background-position-x'} = [$x, $xi];
1166              $r->{'background-position-y'} = [$y, $yi];
1167            }
1168          } else {
1169            $r->{'background-position-x'} = [$x, $xi];
1170          }
1171        } else {
1172          if (length $y) {
1173            $r->{'background-position-y'} = [$y, $yi];
1174          } else {
1175            #
1176          }
1177        }
1178        
1179        for my $prop (qw/color image repeat attachment/) {
1180          my $prop_name = 'background_'.$prop;
1181          my $value = $self->$prop_name;
1182          if (length $value) {
1183            my $i = $self->get_property_priority ('background-'.$prop);
1184            undef $has_all unless $xi eq $i;
1185            $r->{'background-'.$prop} = [$value, $i];
1186          } else {
1187            undef $has_all;
1188          }
1189        }
1190    
1191        if ($has_all) {
1192          my @v;
1193          push @v, $r->{'background-color'}
1194              unless $r->{'background-color'}->[0] eq 'transparent';
1195          push @v, $r->{'background-image'}
1196              unless $r->{'background-image'}->[0] eq 'none';
1197          push @v, $r->{'background-repeat'}
1198              unless $r->{'background-repeat'}->[0] eq 'repeat';
1199          push @v, $r->{'background-attachment'}
1200              unless $r->{'background-attachment'}->[0] eq 'scroll';
1201          push @v, $r->{'background-position'}
1202              unless $r->{'background-position'}->[0] eq '0% 0%';
1203          if (@v) {
1204            my $inherit = 0;
1205            for (@v) {
1206              $inherit++ if $_->[0] eq 'inherit';
1207            }
1208            if ($inherit == 5) {
1209              return {background => ['inherit', $xi]};
1210            } elsif ($inherit) {
1211              return $r;
1212            } else {
1213              return {background => [(join ' ', map {$_->[0]} @v), $xi]};
1214            }
1215          } else {
1216            return {background => ['transparent none repeat scroll 0% 0%', $xi]};
1217          }
1218        } else {
1219          return $r;
1220        }
1221      },
1222      initial => ['KEYWORD', 'transparent'],
1223      #inherited => 0,
1224      compute => $Prop->{color}->{compute},
1225    };
1226    $Attr->{background_color} = $Prop->{'background-color'};
1227    $Key->{background_color} = $Prop->{'background-color'};
1228    
1229    $Prop->{'border-top-color'} = {
1230      css => 'border-top-color',
1231      dom => 'border_top_color',
1232      key => 'border_top_color',
1233      parse => $parse_color,
1234      serialize_multiple => sub {
1235        my $self = shift;
1236        ## NOTE: This algorithm returns the same result as that of Firefox 2
1237        ## in many case, but not always.
1238        my $r = {
1239          'border-top-color' => [$self->border_top_color,
1240                                 $self->get_property_priority
1241                                     ('border-top-color')],
1242          'border-top-style' => [$self->border_top_style,
1243                                 $self->get_property_priority
1244                                     ('border-top-style')],
1245          'border-top-width' => [$self->border_top_width,
1246                                 $self->get_property_priority
1247                                     ('border-top-width')],
1248          'border-right-color' => [$self->border_right_color,
1249                                   $self->get_property_priority
1250                                       ('border-right-color')],
1251          'border-right-style' => [$self->border_right_style,
1252                                   $self->get_property_priority
1253                                       ('border-right-style')],
1254          'border-right-width' => [$self->border_right_width,
1255                                   $self->get_property_priority
1256                                       ('border-right-width')],
1257          'border-bottom-color' => [$self->border_bottom_color,
1258                                    $self->get_property_priority
1259                                        ('border-bottom-color')],
1260          'border-bottom-style' => [$self->border_bottom_style,
1261                                    $self->get_property_priority
1262                                        ('border-bottom-style')],
1263          'border-bottom-width' => [$self->border_bottom_width,
1264                                    $self->get_property_priority
1265                                        ('border-bottom-width')],
1266          'border-left-color' => [$self->border_left_color,
1267                                  $self->get_property_priority
1268                                      ('border-leftcolor')],
1269          'border-left-style' => [$self->border_left_style,
1270                                  $self->get_property_priority
1271                                      ('border-left-style')],
1272          'border-left-width' => [$self->border_left_width,
1273                                  $self->get_property_priority
1274                                      ('border-left-width')],
1275        };
1276        my $i = 0;
1277        for my $prop (qw/border-top border-right border-bottom border-left/) {
1278          if (length $r->{$prop.'-color'}->[0] and
1279              length $r->{$prop.'-style'}->[0] and
1280              length $r->{$prop.'-width'}->[0] and
1281              $r->{$prop.'-color'}->[1] eq $r->{$prop.'-style'}->[1] and
1282              $r->{$prop.'-color'}->[1] eq $r->{$prop.'-width'}->[1]) {
1283            my $inherit = 0;
1284            $inherit++ if $r->{$prop.'-color'}->[0] eq 'inherit';
1285            $inherit++ if $r->{$prop.'-style'}->[0] eq 'inherit';
1286            $inherit++ if $r->{$prop.'-width'}->[0] eq 'inherit';
1287            if ($inherit == 3) {
1288              $r->{$prop} = $r->{$prop.'-color'};
1289            } elsif ($inherit) {
1290              next;
1291            } else {
1292              $r->{$prop} = [$r->{$prop.'-width'}->[0] . ' ' .
1293                                 $r->{$prop.'-style'}->[0] . ' ' .
1294                                 $r->{$prop.'-color'}->[0],
1295                             $r->{$prop.'-color'}->[1]];
1296            }
1297            delete $r->{$prop.'-width'};
1298            delete $r->{$prop.'-style'};
1299            delete $r->{$prop.'-color'};
1300            $i++;
1301          }
1302        }
1303        if ($i == 4 and
1304            $r->{'border-top'}->[0] eq $r->{'border-right'}->[0] and
1305            $r->{'border-right'}->[0] eq $r->{'border-bottom'}->[0] and
1306            $r->{'border-bottom'}->[0] eq $r->{'border-left'}->[0] and
1307            $r->{'border-top'}->[1] eq $r->{'border-right'}->[1] and
1308            $r->{'border-right'}->[1] eq $r->{'border-bottom'}->[1] and
1309            $r->{'border-bottom'}->[1] eq $r->{'border-left'}->[1]) {
1310          return {border => $r->{'border-top'}};
1311        }
1312    
1313        unless ($i) {
1314          for my $prop (qw/color style width/) {
1315            if (defined $r->{'border-top-'.$prop} and
1316                defined $r->{'border-bottom-'.$prop} and
1317                defined $r->{'border-right-'.$prop} and
1318                defined $r->{'border-left-'.$prop} and
1319                length $r->{'border-top-'.$prop}->[0] and
1320                length $r->{'border-bottom-'.$prop}->[0] and
1321                length $r->{'border-right-'.$prop}->[0] and
1322                length $r->{'border-left-'.$prop}->[0] and
1323                $r->{'border-top-'.$prop}->[1]
1324                    eq $r->{'border-bottom-'.$prop}->[1] and
1325                $r->{'border-top-'.$prop}->[1]
1326                    eq $r->{'border-right-'.$prop}->[1] and
1327                $r->{'border-top-'.$prop}->[1]
1328                    eq $r->{'border-left-'.$prop}->[1]) {
1329              my @v = ($r->{'border-top-'.$prop},
1330                       $r->{'border-right-'.$prop},
1331                       $r->{'border-bottom-'.$prop},
1332                       $r->{'border-left-'.$prop});
1333              my $inherit = 0;
1334              for (@v) {
1335                $inherit++ if $_->[0] eq 'inherit';
1336              }
1337              if ($inherit == 4) {
1338                $r->{'border-'.$prop}
1339                    = ['inherit', $r->{'border-top-'.$prop}->[1]];
1340              } elsif ($inherit) {
1341                next;
1342              } else {
1343                pop @v
1344                    if $r->{'border-right-'.$prop}->[0]
1345                        eq $r->{'border-left-'.$prop}->[0];
1346                pop @v
1347                    if $r->{'border-bottom-'.$prop}->[0]
1348                        eq $r->{'border-top-'.$prop}->[0];
1349                pop @v
1350                    if $r->{'border-right-'.$prop}->[0]
1351                        eq $r->{'border-top-'.$prop}->[0];
1352                $r->{'border-'.$prop} = [(join ' ', map {$_->[0]} @v),
1353                                         $r->{'border-top-'.$prop}->[1]];
1354              }
1355              delete $r->{'border-top-'.$prop};
1356              delete $r->{'border-bottom-'.$prop};
1357              delete $r->{'border-right-'.$prop};
1358              delete $r->{'border-left-'.$prop};
1359            }
1360          }
1361        }
1362    
1363        delete $r->{$_} for grep {not length $r->{$_}->[0]} keys %$r;
1364        return $r;
1365      },
1366      initial => ['KEYWORD', 'currentcolor'],
1367      #inherited => 0,
1368      compute => $Prop->{color}->{compute},
1369    };
1370    $Attr->{border_top_color} = $Prop->{'border-top-color'};
1371    $Key->{border_top_color} = $Prop->{'border-top-color'};
1372    
1373    $Prop->{'border-right-color'} = {
1374      css => 'border-right-color',
1375      dom => 'border_right_color',
1376      key => 'border_right_color',
1377      parse => $parse_color,
1378      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
1379      initial => ['KEYWORD', 'currentcolor'],
1380      #inherited => 0,
1381      compute => $Prop->{color}->{compute},
1382    };
1383    $Attr->{border_right_color} = $Prop->{'border-right-color'};
1384    $Key->{border_right_color} = $Prop->{'border-right-color'};
1385    
1386    $Prop->{'border-bottom-color'} = {
1387      css => 'border-bottom-color',
1388      dom => 'border_bottom_color',
1389      key => 'border_bottom_color',
1390      parse => $parse_color,
1391      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
1392      initial => ['KEYWORD', 'currentcolor'],
1393      #inherited => 0,
1394      compute => $Prop->{color}->{compute},
1395    };
1396    $Attr->{border_bottom_color} = $Prop->{'border-bottom-color'};
1397    $Key->{border_bottom_color} = $Prop->{'border-bottom-color'};
1398    
1399    $Prop->{'border-left-color'} = {
1400      css => 'border-left-color',
1401      dom => 'border_left_color',
1402      key => 'border_left_color',
1403      parse => $parse_color,
1404      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
1405      initial => ['KEYWORD', 'currentcolor'],
1406      #inherited => 0,
1407      compute => $Prop->{color}->{compute},
1408    };
1409    $Attr->{border_left_color} = $Prop->{'border-left-color'};
1410    $Key->{border_left_color} = $Prop->{'border-left-color'};
1411    
1412    $Prop->{'outline-color'} = {
1413      css => 'outline-color',
1414      dom => 'outline_color',
1415      key => 'outline_color',
1416      parse => $parse_color,
1417      serialize_multiple => sub {
1418        my $self = shift;
1419        my $oc = $self->outline_color;
1420        my $os = $self->outline_style;
1421        my $ow = $self->outline_width;
1422        my $r = {};
1423        if (length $oc and length $os and length $ow) {
1424          $r->{outline} = [$ow . ' ' . $os . ' ' . $oc];
1425        } else {
1426          $r->{'outline-color'} = [$oc] if length $oc;
1427          $r->{'outline-style'} = [$os] if length $os;
1428          $r->{'outline-width'} = [$ow] if length $ow;
1429        }
1430        return $r;
1431      },
1432      initial => ['KEYWORD', '-manakai-invert-or-currentcolor'],
1433      #inherited => 0,
1434      compute => $Prop->{color}->{compute},
1435    };
1436    $Attr->{outline_color} = $Prop->{'outline-color'};
1437    $Key->{outline_color} = $Prop->{'outline-color'};
1438    
1439    my $one_keyword_parser = sub {
1440      my ($self, $prop_name, $tt, $t, $onerror) = @_;
1441    
1442      if ($t->{type} == IDENT_TOKEN) {
1443        my $prop_value = lc $t->{value}; ## TODO: case folding
1444        if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
1445            $self->{prop_value}->{$prop_name}->{$prop_value}) {
1446          $t = $tt->get_next_token;
1447          return ($t, {$prop_name => ["KEYWORD", $prop_value]});
1448        } elsif (my $v = $Prop->{$prop_name}->{keyword_replace}->{$prop_value}) {
1449          if ($self->{prop_value}->{$prop_name}->{$v}) {
1450            $t = $tt->get_next_token;
1451            return ($t, {$prop_name => ["KEYWORD", $v]});
1452          }
1453        } elsif ($prop_value eq 'inherit') {
1454          $t = $tt->get_next_token;
1455          return ($t, {$prop_name => ['INHERIT']});
1456        }
1457      }
1458      
1459      $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
1460                 level => $self->{level}->{must},
1461                 uri => \$self->{href},
1462                 token => $t);
1463      return ($t, undef);
1464    };
1465    
1466    $Prop->{display} = {
1467      css => 'display',
1468      dom => 'display',
1469      key => 'display',
1470      parse => $one_keyword_parser,
1471      keyword => {
1472        ## CSS 2.1
1473        block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
1474        'list-item' => 1, none => 1,
1475        table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
1476        'table-column-group' => 1, 'table-header-group' => 1,
1477        'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
1478        ## CSS 2.0
1479        compact => 1, marker => 1,
1480      },
1481      initial => ["KEYWORD", "inline"],
1482      #inherited => 0,
1483      compute => sub {
1484        my ($self, $element, $prop_name, $specified_value) = @_;
1485        ## NOTE: CSS 2.1 Section 9.7.
1486    
1487        ## WARNING: |compute| for 'float' property invoke this CODE
1488        ## in some case.  Careless modification might cause a infinite loop.
1489    
1490        if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1491          if ($specified_value->[1] eq 'none') {
1492            ## Case 1 [CSS 2.1]
1493            return $specified_value;
1494          } else {
1495            my $position = $self->get_computed_value ($element, 'position');
1496            if ($position->[0] eq 'KEYWORD' and
1497                ($position->[1] eq 'absolute' or
1498                 $position->[1] eq 'fixed')) {
1499              ## Case 2 [CSS 2.1]
1500              #
1501            } else {
1502              my $float = $self->get_computed_value ($element, 'float');
1503              if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
1504                ## Caes 3 [CSS 2.1]
1505                #
1506              } elsif (not defined $element->manakai_parent_element) {
1507                ## Case 4 [CSS 2.1]
1508                #
1509              } elsif ($specified_value->[1] eq 'marker') {
1510                ## TODO: If ::after or ::before, then 'marker'.  Otherwise,
1511                return ['KEYWORD', 'inline'];
1512              } else {
1513                ## Case 5 [CSS 2.1]
1514                return $specified_value;
1515              }
1516            }
1517            
1518            return ["KEYWORD",
1519                    {
1520                     'inline-table' => 'table',
1521                     inline => 'block',
1522                     'run-in' => 'block',
1523                     'table-row-group' => 'block',
1524                     'table-column' => 'block',
1525                     'table-column-group' => 'block',
1526                     'table-header-group' => 'block',
1527                     'table-footer-group' => 'block',
1528                     'table-row' => 'block',
1529                     'table-cell' => 'block',
1530                     'table-caption' => 'block',
1531                     'inline-block' => 'block',
1532    
1533                     ## NOTE: Not in CSS 2.1, but maybe...
1534                     compact => 'block',
1535                     marker => 'block',
1536                    }->{$specified_value->[1]} || $specified_value->[1]];
1537          }
1538        } else {
1539          return $specified_value; ## Maybe an error of the implementation.
1540        }
1541      },
1542    };
1543    $Attr->{display} = $Prop->{display};
1544    $Key->{display} = $Prop->{display};
1545    
1546    $Prop->{position} = {
1547      css => 'position',
1548      dom => 'position',
1549      key => 'position',
1550      parse => $one_keyword_parser,
1551      keyword => {
1552        static => 1, relative => 1, absolute => 1, fixed => 1,
1553      },
1554      initial => ["KEYWORD", "static"],
1555      #inherited => 0,
1556      compute => $compute_as_specified,
1557    };
1558    $Attr->{position} = $Prop->{position};
1559    $Key->{position} = $Prop->{position};
1560    
1561    $Prop->{float} = {
1562      css => 'float',
1563      dom => 'css_float',
1564      key => 'float',
1565      parse => $one_keyword_parser,
1566      keyword => {
1567        left => 1, right => 1, none => 1,
1568      },
1569      initial => ["KEYWORD", "none"],
1570      #inherited => 0,
1571      compute => sub {
1572        my ($self, $element, $prop_name, $specified_value) = @_;
1573        ## NOTE: CSS 2.1 Section 9.7.
1574    
1575        ## WARNING: |compute| for 'display' property invoke this CODE
1576        ## in some case.  Careless modification might cause a infinite loop.
1577        
1578        if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1579          if ($specified_value->[1] eq 'none') {
1580            ## Case 1 [CSS 2.1]
1581            return $specified_value;
1582          } else {
1583            my $position = $self->get_computed_value ($element, 'position');
1584            if ($position->[0] eq 'KEYWORD' and
1585                ($position->[1] eq 'absolute' or
1586                 $position->[1] eq 'fixed')) {
1587              ## Case 2 [CSS 2.1]
1588              return ["KEYWORD", "none"];
1589            }
1590          }
1591        }
1592    
1593        ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
1594        ## on computed value of 'float' property.
1595        
1596        ## Case 3, 4, and 5 [CSS 2.1]
1597        return $specified_value;
1598      },
1599    };
1600    $Attr->{css_float} = $Prop->{float};
1601    $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
1602    $Key->{float} = $Prop->{float};
1603    
1604    $Prop->{clear} = {
1605      css => 'clear',
1606      dom => 'clear',
1607      key => 'clear',
1608      parse => $one_keyword_parser,
1609      keyword => {
1610        left => 1, right => 1, none => 1, both => 1,
1611      },
1612      initial => ["KEYWORD", "none"],
1613      #inherited => 0,
1614      compute => $compute_as_specified,
1615    };
1616    $Attr->{clear} = $Prop->{clear};
1617    $Key->{clear} = $Prop->{clear};
1618    
1619    $Prop->{direction} = {
1620      css => 'direction',
1621      dom => 'direction',
1622      key => 'direction',
1623      parse => $one_keyword_parser,
1624      keyword => {
1625        ltr => 1, rtl => 1,
1626      },
1627      initial => ["KEYWORD", "ltr"],
1628      inherited => 1,
1629      compute => $compute_as_specified,
1630    };
1631    $Attr->{direction} = $Prop->{direction};
1632    $Key->{direction} = $Prop->{direction};
1633    
1634    $Prop->{'unicode-bidi'} = {
1635      css => 'unicode-bidi',
1636      dom => 'unicode_bidi',
1637      key => 'unicode_bidi',
1638      parse => $one_keyword_parser,
1639      keyword => {
1640        normal => 1, embed => 1, 'bidi-override' => 1,
1641      },
1642      initial => ["KEYWORD", "normal"],
1643      #inherited => 0,
1644      compute => $compute_as_specified,
1645    };
1646    $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
1647    $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
1648    
1649    $Prop->{'overflow-x'} = {
1650      css => 'overflow-x',
1651      dom => 'overflow_x',
1652      key => 'overflow_x',
1653      parse => $one_keyword_parser,
1654      serialize_multiple => sub {
1655        my $self = shift;
1656        
1657        my $x = $self->overflow_x;
1658        my $xi = $self->get_property_priority ('overflow-x');
1659        my $y = $self->overflow_y;
1660        my $yi = $self->get_property_priority ('overflow-y');
1661    
1662        if (length $x) {
1663          if (length $y) {
1664            if ($x eq $y and $xi eq $yi) {
1665              return {overflow => [$x, $xi]};
1666            } else {
1667              return {'overflow-x' => [$x, $xi], 'overflow-y' => [$y, $yi]};
1668            }
1669          } else {
1670            return {'overflow-x' => [$x, $xi]};
1671          }
1672        } else {
1673          if (length $y) {
1674            return {'overflow-y' => [$y, $yi]};
1675          } else {
1676            return {};
1677          }
1678        }
1679      },
1680      keyword => {
1681        visible => 1, hidden => 1, scroll => 1, auto => 1,
1682        '-moz-hidden-unscrollable' => 1, '-webkit-marquee' => 1,
1683      },
1684      initial => ["KEYWORD", "visible"],
1685      #inherited => 0,
1686      compute => $compute_as_specified,
1687    };
1688    $Attr->{overflow_x} = $Prop->{'overflow-x'};
1689    $Key->{overflow_x} = $Prop->{'overflow-x'};
1690    
1691    $Prop->{'overflow-y'} = {
1692      css => 'overflow-y',
1693      dom => 'overflow_y',
1694      key => 'overflow_y',
1695      parse => $one_keyword_parser,
1696      serialize_multiple => $Prop->{'overflow-x'}->{serialize_multiple},
1697      keyword => $Prop->{'overflow-x'}->{keyword},
1698      initial => ["KEYWORD", "visible"],
1699      #inherited => 0,
1700      compute => $compute_as_specified,
1701    };
1702    $Attr->{overflow_y} = $Prop->{'overflow-y'};
1703    $Key->{overflow_y} = $Prop->{'overflow-y'};
1704    
1705    $Prop->{overflow} = {
1706      css => 'overflow',
1707      dom => 'overflow',
1708      parse => sub {
1709        my ($self, $prop_name, $tt, $t, $onerror) = @_;
1710        my ($t, $pv) = $one_keyword_parser->($self, $prop_name, $tt, $t, $onerror);
1711        if (defined $pv) {
1712          return ($t, {'overflow-x' => $pv->{overflow},
1713                       'overflow-y' => $pv->{overflow}});
1714        } else {
1715          return ($t, $pv);
1716        }
1717      },
1718      keyword => $Prop->{'overflow-x'}->{keyword},
1719      serialize_multiple => $Prop->{'overflow-x'}->{serialize_multiple},
1720    };
1721    $Attr->{overflow} = $Prop->{overflow};
1722    
1723    $Prop->{visibility} = {
1724      css => 'visibility',
1725      dom => 'visibility',
1726      key => 'visibility',
1727      parse => $one_keyword_parser,
1728      keyword => {
1729        visible => 1, hidden => 1, collapse => 1,
1730      },
1731      initial => ["KEYWORD", "visible"],
1732      #inherited => 0,
1733      compute => $compute_as_specified,
1734    };
1735    $Attr->{visibility} = $Prop->{visibility};
1736    $Key->{visibility} = $Prop->{visibility};
1737    
1738    $Prop->{'list-style-type'} = {
1739      css => 'list-style-type',
1740      dom => 'list_style_type',
1741      key => 'list_style_type',
1742      parse => $one_keyword_parser,
1743      keyword => {
1744        ## CSS 2.1
1745        qw/
1746          disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
1747          lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
1748          upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
1749          none 1
1750        /,
1751        ## CSS 2.0
1752        hebrew => 1, 'cjk-ideographic' => 1, hiragana => 1, katakana => 1,
1753        'hiragana-iroha' => 1, 'katakana-iroha' => 1,
1754      },
1755      initial => ["KEYWORD", 'disc'],
1756      inherited => 1,
1757      compute => $compute_as_specified,
1758    };
1759    $Attr->{list_style_type} = $Prop->{'list-style-type'};
1760    $Key->{list_style_type} = $Prop->{'list-style-type'};
1761    
1762    $Prop->{'list-style-position'} = {
1763      css => 'list-style-position',
1764      dom => 'list_style_position',
1765      key => 'list_style_position',
1766      parse => $one_keyword_parser,
1767      keyword => {
1768        inside => 1, outside => 1,
1769      },
1770      initial => ["KEYWORD", 'outside'],
1771      inherited => 1,
1772      compute => $compute_as_specified,
1773    };
1774    $Attr->{list_style_position} = $Prop->{'list-style-position'};
1775    $Key->{list_style_position} = $Prop->{'list-style-position'};
1776    
1777    $Prop->{'page-break-before'} = {
1778      css => 'page-break-before',
1779      dom => 'page_break_before',
1780      key => 'page_break_before',
1781      parse => $one_keyword_parser,
1782      keyword => {
1783        auto => 1, always => 1, avoid => 1, left => 1, right => 1,
1784      },
1785      initial => ["KEYWORD", 'auto'],
1786      #inherited => 0,
1787      compute => $compute_as_specified,
1788    };
1789    $Attr->{page_break_before} = $Prop->{'page-break-before'};
1790    $Key->{page_break_before} = $Prop->{'page-break-before'};
1791    
1792    $Prop->{'page-break-after'} = {
1793      css => 'page-break-after',
1794      dom => 'page_break_after',
1795      key => 'page_break_after',
1796      parse => $one_keyword_parser,
1797      keyword => {
1798        auto => 1, always => 1, avoid => 1, left => 1, right => 1,
1799      },
1800      initial => ["KEYWORD", 'auto'],
1801      #inherited => 0,
1802      compute => $compute_as_specified,
1803    };
1804    $Attr->{page_break_after} = $Prop->{'page-break-after'};
1805    $Key->{page_break_after} = $Prop->{'page-break-after'};
1806    
1807    $Prop->{'page-break-inside'} = {
1808      css => 'page-break-inside',
1809      dom => 'page_break_inside',
1810      key => 'page_break_inside',
1811      parse => $one_keyword_parser,
1812      keyword => {
1813        auto => 1, avoid => 1,
1814      },
1815      initial => ["KEYWORD", 'auto'],
1816      inherited => 1,
1817      compute => $compute_as_specified,
1818    };
1819    $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
1820    $Key->{page_break_inside} = $Prop->{'page-break-inside'};
1821    
1822    $Prop->{'background-repeat'} = {
1823      css => 'background-repeat',
1824      dom => 'background_repeat',
1825      key => 'background_repeat',
1826      parse => $one_keyword_parser,
1827      serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
1828      keyword => {
1829        repeat => 1, 'repeat-x' => 1, 'repeat-y' => 1, 'no-repeat' => 1,
1830      },
1831      initial => ["KEYWORD", 'repeat'],
1832      #inherited => 0,
1833      compute => $compute_as_specified,
1834    };
1835    $Attr->{background_repeat} = $Prop->{'background-repeat'};
1836    $Key->{backgroud_repeat} = $Prop->{'background-repeat'};
1837    
1838    $Prop->{'background-attachment'} = {
1839      css => 'background-attachment',
1840      dom => 'background_attachment',
1841      key => 'background_attachment',
1842      parse => $one_keyword_parser,
1843      serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
1844      keyword => {
1845        scroll => 1, fixed => 1,
1846      },
1847      initial => ["KEYWORD", 'scroll'],
1848      #inherited => 0,
1849      compute => $compute_as_specified,
1850    };
1851    $Attr->{background_attachment} = $Prop->{'background-attachment'};
1852    $Key->{backgroud_attachment} = $Prop->{'background-attachment'};
1853    
1854    $Prop->{'font-style'} = {
1855      css => 'font-style',
1856      dom => 'font_style',
1857      key => 'font_style',
1858      parse => $one_keyword_parser,
1859      keyword => {
1860        normal => 1, italic => 1, oblique => 1,
1861      },
1862      initial => ["KEYWORD", 'normal'],
1863      inherited => 1,
1864      compute => $compute_as_specified,
1865    };
1866    $Attr->{font_style} = $Prop->{'font-style'};
1867    $Key->{font_style} = $Prop->{'font-style'};
1868    
1869    $Prop->{'font-variant'} = {
1870      css => 'font-variant',
1871      dom => 'font_variant',
1872      key => 'font_variant',
1873      parse => $one_keyword_parser,
1874      keyword => {
1875        normal => 1, 'small-caps' => 1,
1876      },
1877      initial => ["KEYWORD", 'normal'],
1878      inherited => 1,
1879      compute => $compute_as_specified,
1880    };
1881    $Attr->{font_variant} = $Prop->{'font-variant'};
1882    $Key->{font_variant} = $Prop->{'font-variant'};
1883    
1884    $Prop->{'text-align'} = {
1885      css => 'text-align',
1886      dom => 'text_align',
1887      key => 'text_align',
1888      parse => $one_keyword_parser,
1889      keyword => {
1890        left => 1, right => 1, center => 1, justify => 1, ## CSS 2
1891        begin => 1, end => 1, ## CSS 3
1892      },
1893      initial => ["KEYWORD", 'begin'],
1894      inherited => 1,
1895      compute => $compute_as_specified,
1896    };
1897    $Attr->{text_align} = $Prop->{'text-align'};
1898    $Key->{text_align} = $Prop->{'text-align'};
1899    
1900    $Prop->{'text-transform'} = {
1901      css => 'text-transform',
1902      dom => 'text_transform',
1903      key => 'text_transform',
1904      parse => $one_keyword_parser,
1905      keyword => {
1906        capitalize => 1, uppercase => 1, lowercase => 1, none => 1,
1907      },
1908      initial => ["KEYWORD", 'none'],
1909      inherited => 1,
1910      compute => $compute_as_specified,
1911    };
1912    $Attr->{text_transform} = $Prop->{'text-transform'};
1913    $Key->{text_transform} = $Prop->{'text-transform'};
1914    
1915    $Prop->{'white-space'} = {
1916      css => 'white-space',
1917      dom => 'white_space',
1918      key => 'white_space',
1919      parse => $one_keyword_parser,
1920      keyword => {
1921        normal => 1, pre => 1, nowrap => 1, 'pre-wrap' => 1, 'pre-line' => 1,
1922      },
1923      keyword_replace => {
1924        '-moz-pre-wrap' => 'pre-wrap', '-o-pre-wrap' => 'pre-wrap',
1925      },
1926      initial => ["KEYWORD", 'normal'],
1927      inherited => 1,
1928      compute => $compute_as_specified,
1929    };
1930    $Attr->{white_space} = $Prop->{'white-space'};
1931    $Key->{white_space} = $Prop->{'white-space'};
1932    
1933    $Prop->{'caption-side'} = {
1934      css => 'caption-side',
1935      dom => 'caption_side',
1936      key => 'caption_side',
1937      parse => $one_keyword_parser,
1938      keyword => {
1939        ## CSS 2.1
1940        top => 1, bottom => 1,
1941        ## CSS 2
1942        left => 1, right => 1,
1943      },
1944      initial => ['KEYWORD', 'top'],
1945      inherited => 1,
1946      compute => $compute_as_specified,
1947    };
1948    $Attr->{caption_side} = $Prop->{'caption-side'};
1949    $Key->{caption_side} = $Prop->{'caption-side'};
1950    
1951    $Prop->{'table-layout'} = {
1952      css => 'table-layout',
1953      dom => 'table_layout',
1954      key => 'table_layout',
1955      parse => $one_keyword_parser,
1956      keyword => {
1957        auto => 1, fixed => 1,
1958      },
1959      initial => ['KEYWORD', 'auto'],
1960      #inherited => 0,
1961      compute => $compute_as_specified,
1962    };
1963    $Attr->{table_layout} = $Prop->{'table-layout'};
1964    $Key->{table_layout} = $Prop->{'table-layout'};
1965    
1966    $Prop->{'border-collapse'} = {
1967      css => 'border-collapse',
1968      dom => 'border_collapse',
1969      key => 'border_collapse',
1970      parse => $one_keyword_parser,
1971      keyword => {
1972        collapse => 1, separate => 1,
1973      },
1974      initial => ['KEYWORD', 'separate'],
1975      inherited => 1,
1976      compute => $compute_as_specified,
1977    };
1978    $Attr->{border_collapse} = $Prop->{'border-collapse'};
1979    $Key->{border_collapse} = $Prop->{'border-collapse'};
1980    
1981    $Prop->{'empty-cells'} = {
1982      css => 'empty-cells',
1983      dom => 'empty_cells',
1984      key => 'empty_cells',
1985      parse => $one_keyword_parser,
1986      keyword => {
1987        show => 1, hide => 1,
1988      },
1989      initial => ['KEYWORD', 'show'],
1990      inherited => 1,
1991      compute => $compute_as_specified,
1992    };
1993    $Attr->{empty_cells} = $Prop->{'empty-cells'};
1994    $Key->{empty_cells} = $Prop->{'empty-cells'};
1995    
1996    $Prop->{'z-index'} = {
1997      css => 'z-index',
1998      dom => 'z_index',
1999      key => 'z_index',
2000      parse => sub {
2001        my ($self, $prop_name, $tt, $t, $onerror) = @_;
2002    
2003        my $has_sign;
2004        my $sign = 1;
2005        if ($t->{type} == MINUS_TOKEN) {
2006          $sign = -1;
2007          $has_sign = 1;
2008          $t = $tt->get_next_token;
2009        } elsif ($t->{type} == PLUS_TOKEN) {
2010          $has_sign = 1;
2011          $t = $tt->get_next_token;
2012        }
2013    
2014        if ($t->{type} == NUMBER_TOKEN) {
2015          ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
2016          ## browser compatibility issue.
2017          my $value = $t->{number};
2018          $t = $tt->get_next_token;
2019          return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
2020        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2021          my $value = lc $t->{value}; ## TODO: case
2022          if ($value eq 'auto') {
2023            ## NOTE: |z-index| is the default value and therefore it must be
2024            ## supported anyway.
2025            $t = $tt->get_next_token;
2026            return ($t, {$prop_name => ["KEYWORD", 'auto']});
2027          } elsif ($value eq 'inherit') {
2028            $t = $tt->get_next_token;
2029            return ($t, {$prop_name => ['INHERIT']});
2030          }
2031        }
2032        
2033        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2034                   level => $self->{level}->{must},
2035                   uri => \$self->{href},
2036                   token => $t);
2037        return ($t, undef);
2038      },
2039      initial => ['KEYWORD', 'auto'],
2040      #inherited => 0,
2041      compute => $compute_as_specified,
2042    };
2043    $Attr->{z_index} = $Prop->{'z-index'};
2044    $Key->{z_index} = $Prop->{'z-index'};
2045    
2046    $Prop->{'font-size-adjust'} = {
2047      css => 'font-size-adjust',
2048      dom => 'font_size_adjust',
2049      key => 'font_size_adjust',
2050      parse => sub {
2051        my ($self, $prop_name, $tt, $t, $onerror) = @_;
2052    
2053        my $has_sign;
2054        my $sign = 1;
2055        if ($t->{type} == MINUS_TOKEN) {
2056          $sign = -1;
2057          $has_sign = 1;
2058          $t = $tt->get_next_token;
2059        } elsif ($t->{type} == PLUS_TOKEN) {
2060          $has_sign = 1;
2061          $t = $tt->get_next_token;
2062        }
2063    
2064        if ($t->{type} == NUMBER_TOKEN) {
2065          my $value = $t->{number};
2066          $t = $tt->get_next_token;
2067          return ($t, {$prop_name => ["NUMBER", $sign * $value]});
2068        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2069          my $value = lc $t->{value}; ## TODO: case
2070          if ($value eq 'none') {
2071            $t = $tt->get_next_token;
2072            return ($t, {$prop_name => ["KEYWORD", $value]});
2073          } elsif ($value eq 'inherit') {
2074            $t = $tt->get_next_token;
2075            return ($t, {$prop_name => ['INHERIT']});
2076          }
2077        }
2078        
2079        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2080                   level => $self->{level}->{must},
2081                   uri => \$self->{href},
2082                   token => $t);
2083        return ($t, undef);
2084      },
2085      initial => ['KEYWORD', 'none'],
2086      inherited => 1,
2087      compute => $compute_as_specified,
2088    };
2089    $Attr->{font_size_adjust} = $Prop->{'font-size-adjust'};
2090    $Key->{font_size_adjust} = $Prop->{'font-size-adjust'};
2091    
2092    $Prop->{orphans} = {
2093      css => 'orphans',
2094      dom => 'orphans',
2095      key => 'orphans',
2096      parse => sub {
2097        my ($self, $prop_name, $tt, $t, $onerror) = @_;
2098    
2099        my $has_sign;
2100        my $sign = 1;
2101        if ($t->{type} == MINUS_TOKEN) {
2102          $t = $tt->get_next_token;
2103          $has_sign = 1;
2104          $sign = -1;
2105        } elsif ($t->{type} == PLUS_TOKEN) {
2106          $t = $tt->get_next_token;
2107          $has_sign = 1;
2108        }
2109    
2110        if ($t->{type} == NUMBER_TOKEN) {
2111          ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
2112          ## <http://suika.fam.cx/gate/2005/sw/widows> for
2113          ## browser compatibility issue.
2114          my $value = $t->{number};
2115          $t = $tt->get_next_token;
2116          return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
2117        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2118          my $value = lc $t->{value}; ## TODO: case
2119          if ($value eq 'inherit') {
2120            $t = $tt->get_next_token;
2121            return ($t, {$prop_name => ['INHERIT']});
2122          }
2123        }
2124        
2125        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2126                   level => $self->{level}->{must},
2127                   uri => \$self->{href},
2128                   token => $t);
2129        return ($t, undef);
2130      },
2131      initial => ['NUMBER', 2],
2132      inherited => 1,
2133      compute => $compute_as_specified,
2134    };
2135    $Attr->{orphans} = $Prop->{orphans};
2136    $Key->{orphans} = $Prop->{orphans};
2137    
2138    $Prop->{widows} = {
2139      css => 'widows',
2140      dom => 'widows',
2141      key => 'widows',
2142      parse => $Prop->{orphans}->{parse},
2143      initial => ['NUMBER', 2],
2144      inherited => 1,
2145      compute => $compute_as_specified,
2146    };
2147    $Attr->{widows} = $Prop->{widows};
2148    $Key->{widows} = $Prop->{widows};
2149    
2150    $Prop->{opacity} = {
2151      css => 'opacity',
2152      dom => 'opacity',
2153      key => 'opacity',
2154      parse => sub {
2155        my ($self, $prop_name, $tt, $t, $onerror) = @_;
2156    
2157        my $has_sign;
2158        my $sign = 1;
2159        if ($t->{type} == MINUS_TOKEN) {
2160          $t = $tt->get_next_token;
2161          $has_sign = 1;
2162          $sign = -1;
2163        } elsif ($t->{type} == PLUS_TOKEN) {
2164          $t = $tt->get_next_token;
2165          $has_sign = 1;
2166        }
2167    
2168        if ($t->{type} == NUMBER_TOKEN) {
2169          ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/opacity> for
2170          ## browser compatibility issue.
2171          my $value = $t->{number};
2172          $t = $tt->get_next_token;
2173          return ($t, {$prop_name => ["NUMBER", $sign * $value]});
2174        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2175          my $value = lc $t->{value}; ## TODO: case
2176          if ($value eq 'inherit') {
2177            $t = $tt->get_next_token;
2178            return ($t, {$prop_name => ['INHERIT']});
2179          }
2180        }
2181        
2182        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2183                   level => $self->{level}->{must},
2184                   uri => \$self->{href},
2185                   token => $t);
2186        return ($t, undef);
2187      },
2188      initial => ['NUMBER', 2],
2189      inherited => 1,
2190      compute => sub {
2191        my ($self, $element, $prop_name, $specified_value) = @_;
2192    
2193        if (defined $specified_value) {
2194          if ($specified_value->[0] eq 'NUMBER') {
2195            if ($specified_value->[1] < 0) {
2196              return ['NUMBER', 0];
2197            } elsif ($specified_value->[1] > 1) {
2198              return ['NUMBER', 1];
2199            }
2200          }
2201        }
2202    
2203        return $specified_value;
2204      },
2205      serialize_multiple => sub {
2206        ## NOTE: This CODE is necessary to avoid two 'opacity' properties
2207        ## are outputed in |cssText| (for 'opacity' and for '-moz-opacity').
2208        return {opacity => [shift->opacity]},
2209      },
2210    };
2211    $Attr->{opacity} = $Prop->{opacity};
2212    $Key->{opacity} = $Prop->{opacity};
2213    
2214    $Prop->{'-moz-opacity'} = $Prop->{opacity};
2215    $Attr->{_moz_opacity} = $Attr->{opacity};
2216    
2217    my $length_unit = {
2218      em => 1, ex => 1, px => 1,
2219      in => 1, cm => 1, mm => 1, pt => 1, pc => 1,
2220    };
2221    
2222    my $length_percentage_keyword_parser = sub ($$$$$) {
2223      my ($self, $prop_name, $tt, $t, $onerror) = @_;
2224    
2225      ## NOTE: Allowed keyword must have true value for $self->{prop_value}->{$_}.
2226    
2227        my $sign = 1;
2228        my $has_sign;
2229        if ($t->{type} == MINUS_TOKEN) {
2230          $t = $tt->get_next_token;
2231          $has_sign = 1;
2232          $sign = -1;
2233        } elsif ($t->{type} == PLUS_TOKEN) {
2234          $t = $tt->get_next_token;
2235          $has_sign = 1;
2236        }
2237        my $allow_negative = $Prop->{$prop_name}->{allow_negative};
2238    
2239        if ($t->{type} == DIMENSION_TOKEN) {
2240          my $value = $t->{number} * $sign;
2241          my $unit = lc $t->{value}; ## TODO: case
2242          if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
2243            $t = $tt->get_next_token;
2244            return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2245          }
2246        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2247          my $value = $t->{number} * $sign;
2248          if ($allow_negative or $value >= 0) {
2249            $t = $tt->get_next_token;
2250            return ($t, {$prop_name => ['PERCENTAGE', $value]});
2251          }
2252        } elsif ($t->{type} == NUMBER_TOKEN and
2253                 ($self->{unitless_px} or $t->{number} == 0)) {
2254          my $value = $t->{number} * $sign;
2255          if ($allow_negative or $value >=0) {
2256            $t = $tt->get_next_token;
2257            return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
2258          }
2259        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2260          my $value = lc $t->{value}; ## TODO: case
2261          if ($Prop->{$prop_name}->{keyword}->{$value}) {
2262            if ($Prop->{$prop_name}->{keyword}->{$value} == 1 or
2263                $self->{prop_value}->{$prop_name}->{$value}) {
2264              $t = $tt->get_next_token;
2265              return ($t, {$prop_name => ['KEYWORD', $value]});        
2266            }
2267          } elsif ($value eq 'inherit') {
2268            $t = $tt->get_next_token;
2269            return ($t, {$prop_name => ['INHERIT']});
2270          }
2271          ## NOTE: In the "else" case, don't procede the |$t| pointer
2272          ## for the support of 'border-top' property (and similar ones).
2273        }
2274        
2275        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2276                   level => $self->{level}->{must},
2277                   uri => \$self->{href},
2278                   token => $t);
2279        return ($t, undef);
2280    }; # $length_percentage_keyword_parser
2281    
2282    my $length_keyword_parser = sub {
2283      my ($self, $prop_name, $tt, $t, $onerror) = @_;
2284    
2285        my $has_sign;
2286        my $sign = 1;
2287        if ($t->{type} == MINUS_TOKEN) {
2288          $t = $tt->get_next_token;
2289          $has_sign = 1;
2290          $sign = -1;
2291        } elsif ($t->{type} == PLUS_TOKEN) {
2292          $t = $tt->get_next_token;
2293          $has_sign = 1;
2294        }
2295        my $allow_negative = $Prop->{$prop_name}->{allow_negative};
2296    
2297        if ($t->{type} == DIMENSION_TOKEN) {
2298          my $value = $t->{number} * $sign;
2299          my $unit = lc $t->{value}; ## TODO: case
2300          if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
2301            $t = $tt->get_next_token;
2302            return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2303          }
2304        } elsif ($t->{type} == NUMBER_TOKEN and
2305                 ($self->{unitless_px} or $t->{number} == 0)) {
2306          my $value = $t->{number} * $sign;
2307          if ($allow_negative or $value >= 0) {
2308            $t = $tt->get_next_token;
2309            return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
2310          }
2311        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2312          my $value = lc $t->{value}; ## TODO: case
2313          if ($Prop->{$prop_name}->{keyword}->{$value}) {
2314            $t = $tt->get_next_token;
2315            return ($t, {$prop_name => ['KEYWORD', $value]});        
2316          } elsif ($value eq 'inherit') {
2317            $t = $tt->get_next_token;
2318            return ($t, {$prop_name => ['INHERIT']});
2319          }
2320        }
2321        
2322        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2323                   level => $self->{level}->{must},
2324                   uri => \$self->{href},
2325                   token => $t);
2326        return ($t, undef);
2327    }; # $length_keyword_parser
2328    
2329    $Prop->{'font-size'} = {
2330      css => 'font-size',
2331      dom => 'font_size',
2332      key => 'font_size',
2333      parse => $length_percentage_keyword_parser,
2334      #allow_negative => 0,
2335      keyword => {
2336               'xx-small' => 1, 'x-small' => 1, small => 1, medium => 1,
2337               large => 1, 'x-large' => 1, 'xx-large' => 1,
2338               '-manakai-xxx-large' => 1, '-webkit-xxx-large' => 1,
2339               larger => 1, smaller => 1,
2340      },
2341      initial => ['KEYWORD', 'medium'],
2342      inherited => 1,
2343      compute => sub {
2344        my ($self, $element, $prop_name, $specified_value) = @_;
2345        
2346        if (defined $specified_value) {
2347          if ($specified_value->[0] eq 'DIMENSION') {
2348            my $unit = $specified_value->[2];
2349            my $value = $specified_value->[1];
2350    
2351            if ($unit eq 'em' or $unit eq 'ex') {
2352              $value *= 0.5 if $unit eq 'ex';
2353              ## TODO: Preferred way to determine the |ex| size is defined
2354              ## in CSS 2.1.
2355    
2356              my $parent_element = $element->manakai_parent_element;
2357              if (defined $parent_element) {
2358                $value *= $self->get_computed_value ($parent_element, $prop_name)
2359                    ->[1];
2360              } else {
2361                $value *= $self->{font_size}->[3]; # medium
2362              }
2363              $unit = 'px';
2364            } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
2365              ($value *= 12, $unit = 'pc') if $unit eq 'pc';
2366              ($value /= 72, $unit = 'in') if $unit eq 'pt';
2367              ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
2368              ($value *= 10, $unit = 'mm') if $unit eq 'cm';
2369              ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
2370            }
2371            ## else: consistency error
2372    
2373            return ['DIMENSION', $value, $unit];
2374          } elsif ($specified_value->[0] eq 'PERCENTAGE') {
2375            my $parent_element = $element->manakai_parent_element;
2376            my $parent_cv;
2377            if (defined $parent_element) {
2378              $parent_cv = $self->get_computed_value
2379                  ($parent_element, $prop_name);
2380            } else {
2381              $parent_cv = [undef, $self->{font_size}->[3]];
2382            }
2383            return ['DIMENSION', $parent_cv->[1] * $specified_value->[1] / 100,
2384                    'px'];
2385          } elsif ($specified_value->[0] eq 'KEYWORD') {
2386            if ($specified_value->[1] eq 'larger') {
2387              my $parent_element = $element->manakai_parent_element;
2388              if (defined $parent_element) {
2389                my $parent_cv = $self->get_computed_value
2390                    ($parent_element, $prop_name);
2391                return ['DIMENSION',
2392                        $self->{get_larger_font_size}->($self, $parent_cv->[1]),
2393                        'px'];
2394              } else { ## 'larger' relative to 'medium', initial of 'font-size'
2395                return ['DIMENSION', $self->{font_size}->[4], 'px'];
2396              }
2397            } elsif ($specified_value->[1] eq 'smaller') {
2398              my $parent_element = $element->manakai_parent_element;
2399              if (defined $parent_element) {
2400                my $parent_cv = $self->get_computed_value
2401                    ($parent_element, $prop_name);
2402                return ['DIMENSION',
2403                        $self->{get_smaller_font_size}->($self, $parent_cv->[1]),
2404                        'px'];
2405              } else { ## 'smaller' relative to 'medium', initial of 'font-size'
2406                return ['DIMENSION', $self->{font_size}->[2], 'px'];
2407              }
2408            } else {
2409              ## TODO: different computation in quirks mode?
2410              return ['DIMENSION', $self->{font_size}->[{
2411                'xx-small' => 0,
2412                'x-small' => 1,
2413                small => 2,
2414                medium => 3,
2415                large => 4,
2416                'x-large' => 5,
2417                'xx-large' => 6,
2418                '-manakai-xxx-large' => 7,
2419                '-webkit-xxx-large' => 7,
2420              }->{$specified_value->[1]}], 'px'];
2421            }
2422          }
2423        }
2424        
2425        return $specified_value;
2426      },
2427    };
2428    $Attr->{font_size} = $Prop->{'font-size'};
2429    $Key->{font_size} = $Prop->{'font-size'};
2430    
2431    my $compute_length = sub {
2432      my ($self, $element, $prop_name, $specified_value) = @_;
2433      
2434      if (defined $specified_value) {
2435        if ($specified_value->[0] eq 'DIMENSION') {
2436          my $unit = $specified_value->[2];
2437          my $value = $specified_value->[1];
2438    
2439          if ($unit eq 'em' or $unit eq 'ex') {
2440            $value *= 0.5 if $unit eq 'ex';
2441            ## TODO: Preferred way to determine the |ex| size is defined
2442            ## in CSS 2.1.
2443    
2444            $value *= $self->get_computed_value ($element, 'font-size')->[1];
2445            $unit = 'px';
2446          } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
2447            ($value *= 12, $unit = 'pc') if $unit eq 'pc';
2448            ($value /= 72, $unit = 'in') if $unit eq 'pt';
2449            ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
2450            ($value *= 10, $unit = 'mm') if $unit eq 'cm';
2451            ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
2452          }
2453    
2454          return ['DIMENSION', $value, $unit];
2455        }
2456      }
2457      
2458      return $specified_value;
2459    }; # $compute_length
2460    
2461    $Prop->{'letter-spacing'} = {
2462      css => 'letter-spacing',
2463      dom => 'letter_spacing',
2464      key => 'letter_spacing',
2465      parse => $length_keyword_parser,
2466      allow_negative => 1,
2467      keyword => {normal => 1},
2468      initial => ['KEYWORD', 'normal'],
2469      inherited => 1,
2470      compute => $compute_length,
2471    };
2472    $Attr->{letter_spacing} = $Prop->{'letter-spacing'};
2473    $Key->{letter_spacing} = $Prop->{'letter-spacing'};
2474    
2475    $Prop->{'word-spacing'} = {
2476      css => 'word-spacing',
2477      dom => 'word_spacing',
2478      key => 'word_spacing',
2479      parse => $length_keyword_parser,
2480      allow_negative => 1,
2481      keyword => {normal => 1},
2482      initial => ['KEYWORD', 'normal'],
2483      inherited => 1,
2484      compute => $compute_length,
2485    };
2486    $Attr->{word_spacing} = $Prop->{'word-spacing'};
2487    $Key->{word_spacing} = $Prop->{'word-spacing'};
2488    
2489    $Prop->{'-manakai-border-spacing-x'} = {
2490      css => '-manakai-border-spacing-x',
2491      dom => '_manakai_border_spacing_x',
2492      key => 'border_spacing_x',
2493      parse => $length_keyword_parser,
2494      #allow_negative => 0,
2495      #keyword => {},
2496      serialize_multiple => sub {
2497        my $self = shift;
2498        
2499        my $x = $self->_manakai_border_spacing_x;
2500        my $y = $self->_manakai_border_spacing_y;
2501        my $xi = $self->get_property_priority ('-manakai-border-spacing-x');
2502        my $yi = $self->get_property_priority ('-manakai-border-spacing-y');
2503        if (length $x) {
2504          if (length $y) {
2505            if ($xi eq $yi) {
2506              if ($x eq $y) {
2507                return {'border-spacing' => [$x, $xi]};
2508              } else {
2509                if ($x eq 'inherit' or $y eq 'inherit') {
2510                  return {'-manakai-border-spacing-x' => [$x, $xi],
2511                          '-manakai-border-spacing-y' => [$y, $yi]};
2512                } else {
2513                  return {'border-spacing' => [$x . ' ' . $y, $xi]};
2514                }
2515              }
2516            } else {
2517              return {'-manakai-border-spacing-x' => [$x, $xi],
2518                      '-manakai-border-spacing-y' => [$y, $yi]};
2519            }
2520          } else {
2521            return {'-manakai-border-spacing-x' => [$x, $xi]};
2522          }
2523        } else {
2524          if (length $y) {
2525            return {'-manakai-border-spacing-y' => [$y, $yi]};
2526          } else {
2527            return {};
2528          }
2529        }
2530      },
2531      initial => ['DIMENSION', 0, 'px'],
2532      inherited => 1,
2533      compute => $compute_length,
2534    };
2535    $Attr->{_manakai_border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
2536    $Key->{border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
2537    
2538    $Prop->{'-manakai-border-spacing-y'} = {
2539      css => '-manakai-border-spacing-y',
2540      dom => '_manakai_border_spacing_y',
2541      key => 'border_spacing_y',
2542      parse => $length_keyword_parser,
2543      #allow_negative => 0,
2544      #keyword => {},
2545      serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
2546          ->{serialize_multiple},
2547      initial => ['DIMENSION', 0, 'px'],
2548      inherited => 1,
2549      compute => $compute_length,
2550    };
2551    $Attr->{_manakai_border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
2552    $Key->{border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
2553    
2554    $Attr->{marker_offset} =
2555    $Key->{marker_offset} =
2556    $Prop->{'marker-offset'} = {
2557      css => 'marker-offset',
2558      dom => 'marker_offset',
2559      key => 'marker_offset',
2560      parse => $length_keyword_parser,
2561      allow_negative => 1,
2562      keyword => {auto => 1},
2563      initial => ['KEYWORD', 'auto'],
2564      #inherited => 0,
2565      compute => $compute_length,
2566    };
2567    
2568    $Prop->{'margin-top'} = {
2569      css => 'margin-top',
2570      dom => 'margin_top',
2571      key => 'margin_top',
2572      parse => $length_percentage_keyword_parser,
2573      allow_negative => 1,
2574      keyword => {auto => 1},
2575      serialize_multiple => sub {
2576        my $self = shift;
2577    
2578        ## NOTE: Same as |serialize_multiple| of 'padding-top'.
2579    
2580        my $use_shorthand = 1;
2581        my $t = $self->margin_top;
2582        undef $use_shorthand unless length $t;
2583        my $t_i = $self->get_property_priority ('margin-top');
2584        my $r = $self->margin_right;
2585        undef $use_shorthand
2586            if not length $r or
2587                ($r eq 'inherit' and $t ne 'inherit') or
2588                ($t eq 'inherit' and $r ne 'inherit');
2589        my $r_i = $self->get_property_priority ('margin-right');
2590        undef $use_shorthand unless $r_i eq $t_i;
2591        my $b = $self->margin_bottom;
2592        undef $use_shorthand
2593            if not length $b or
2594                ($b eq 'inherit' and $t ne 'inherit') or
2595                ($t eq 'inherit' and $b ne 'inherit');
2596        my $b_i = $self->get_property_priority ('margin-bottom');
2597        undef $use_shorthand unless $b_i eq $t_i;
2598        my $l = $self->margin_left;
2599        undef $use_shorthand
2600            if not length $l or
2601                ($l eq 'inherit' and $t ne 'inherit') or
2602                ($t eq 'inherit' and $l ne 'inherit');
2603        my $l_i = $self->get_property_priority ('margin-left');
2604        undef $use_shorthand unless $l_i eq $t_i;
2605    
2606        if ($use_shorthand) {
2607          $b .= ' ' . $l if $r ne $l;
2608          $r .= ' ' . $b if $t ne $b;
2609          $t .= ' ' . $r if $t ne $r;
2610          return {margin => [$t, $t_i]};
2611        } else {
2612          my $v = {};
2613          if (length $t) {
2614            $v->{'margin-top'} = [$t, $t_i];
2615          }
2616          if (length $r) {
2617            $v->{'margin-right'} = [$r, $r_i];
2618          }
2619          if (length $b) {
2620            $v->{'margin-bottom'} = [$b, $b_i];
2621          }
2622          if (length $l) {
2623            $v->{'margin-left'} = [$l, $l_i];
2624          }
2625          return $v;
2626        }
2627      },
2628      initial => ['DIMENSION', 0, 'px'],
2629      #inherited => 0,
2630      compute => $compute_length,
2631    };
2632    $Attr->{margin_top} = $Prop->{'margin-top'};
2633    $Key->{margin_top} = $Prop->{'margin-top'};
2634    
2635    $Prop->{'margin-bottom'} = {
2636      css => 'margin-bottom',
2637      dom => 'margin_bottom',
2638      key => 'margin_bottom',
2639      parse => $Prop->{'margin-top'}->{parse},
2640      allow_negative => 1,
2641      keyword => {auto => 1},
2642      serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2643      initial => ['DIMENSION', 0, 'px'],
2644      #inherited => 0,
2645      compute => $compute_length,
2646    };
2647    $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
2648    $Key->{margin_bottom} = $Prop->{'margin-bottom'};
2649    
2650    $Prop->{'margin-right'} = {
2651      css => 'margin-right',
2652      dom => 'margin_right',
2653      key => 'margin_right',
2654      parse => $Prop->{'margin-top'}->{parse},
2655      allow_negative => 1,
2656      keyword => {auto => 1},
2657      serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2658      initial => ['DIMENSION', 0, 'px'],
2659      #inherited => 0,
2660      compute => $compute_length,
2661    };
2662    $Attr->{margin_right} = $Prop->{'margin-right'};
2663    $Key->{margin_right} = $Prop->{'margin-right'};
2664    
2665    $Prop->{'margin-left'} = {
2666      css => 'margin-left',
2667      dom => 'margin_left',
2668      key => 'margin_left',
2669      parse => $Prop->{'margin-top'}->{parse},
2670      allow_negative => 1,
2671      keyword => {auto => 1},
2672      serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2673      initial => ['DIMENSION', 0, 'px'],
2674      #inherited => 0,
2675      compute => $compute_length,
2676    };
2677    $Attr->{margin_left} = $Prop->{'margin-left'};
2678    $Key->{margin_left} = $Prop->{'margin-left'};
2679    
2680    $Prop->{top} = {
2681      css => 'top',
2682      dom => 'top',
2683      key => 'top',
2684      parse => $Prop->{'margin-top'}->{parse},
2685      allow_negative => 1,
2686      keyword => {auto => 1},
2687      initial => ['KEYWORD', 'auto'],
2688      #inherited => 0,
2689      compute_multiple => sub {
2690        my ($self, $element, $eid, $prop_name) = @_;
2691    
2692        my $pos_value = $self->get_computed_value ($element, 'position');
2693        if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2694          if ($pos_value->[1] eq 'static') {
2695            $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
2696            $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
2697            return;
2698          } elsif ($pos_value->[1] eq 'relative') {
2699            my $top_specified = $self->get_specified_value_no_inherit
2700              ($element, 'top');
2701            if (defined $top_specified and
2702                ($top_specified->[0] eq 'DIMENSION' or
2703                 $top_specified->[0] eq 'PERCENTAGE')) {
2704              my $tv = $self->{computed_value}->{$eid}->{top}
2705                  = $compute_length->($self, $element, 'top', $top_specified);
2706              $self->{computed_value}->{$eid}->{bottom}
2707                  = [$tv->[0], -$tv->[1], $tv->[2]];
2708            } else { # top: auto
2709              my $bottom_specified = $self->get_specified_value_no_inherit
2710                  ($element, 'bottom');
2711              if (defined $bottom_specified and
2712                  ($bottom_specified->[0] eq 'DIMENSION' or
2713                   $bottom_specified->[0] eq 'PERCENTAGE')) {
2714                my $tv = $self->{computed_value}->{$eid}->{bottom}
2715                    = $compute_length->($self, $element, 'bottom',
2716                                        $bottom_specified);
2717                $self->{computed_value}->{$eid}->{top}
2718                    = [$tv->[0], -$tv->[1], $tv->[2]];
2719              } else { # bottom: auto
2720                $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
2721                $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
2722              }
2723            }
2724            return;
2725          }
2726        }
2727    
2728        my $top_specified = $self->get_specified_value_no_inherit
2729            ($element, 'top');
2730        $self->{computed_value}->{$eid}->{top}
2731            = $compute_length->($self, $element, 'top', $top_specified);
2732        my $bottom_specified = $self->get_specified_value_no_inherit
2733            ($element, 'bottom');
2734        $self->{computed_value}->{$eid}->{bottom}
2735            = $compute_length->($self, $element, 'bottom', $bottom_specified);
2736      },
2737    };
2738    $Attr->{top} = $Prop->{top};
2739    $Key->{top} = $Prop->{top};
2740    
2741    $Prop->{bottom} = {
2742      css => 'bottom',
2743      dom => 'bottom',
2744      key => 'bottom',
2745      parse => $Prop->{'margin-top'}->{parse},
2746      allow_negative => 1,
2747      keyword => {auto => 1},
2748      initial => ['KEYWORD', 'auto'],
2749      #inherited => 0,
2750      compute_multiple => $Prop->{top}->{compute_multiple},
2751    };
2752    $Attr->{bottom} = $Prop->{bottom};
2753    $Key->{bottom} = $Prop->{bottom};
2754    
2755    $Prop->{left} = {
2756      css => 'left',
2757      dom => 'left',
2758      key => 'left',
2759      parse => $Prop->{'margin-top'}->{parse},
2760      allow_negative => 1,
2761      keyword => {auto => 1},
2762      initial => ['KEYWORD', 'auto'],
2763      #inherited => 0,
2764      compute_multiple => sub {
2765        my ($self, $element, $eid, $prop_name) = @_;
2766    
2767        my $pos_value = $self->get_computed_value ($element, 'position');
2768        if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2769          if ($pos_value->[1] eq 'static') {
2770            $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
2771            $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
2772            return;
2773          } elsif ($pos_value->[1] eq 'relative') {
2774            my $left_specified = $self->get_specified_value_no_inherit
2775                ($element, 'left');
2776            if (defined $left_specified and
2777                ($left_specified->[0] eq 'DIMENSION' or
2778                 $left_specified->[0] eq 'PERCENTAGE')) {
2779              my $right_specified = $self->get_specified_value_no_inherit
2780                  ($element, 'right');
2781              if (defined $right_specified and
2782                  ($right_specified->[0] eq 'DIMENSION' or
2783                   $right_specified->[0] eq 'PERCENTAGE')) {
2784                my $direction = $self->get_computed_value ($element, 'direction');
2785                if (defined $direction and $direction->[0] eq 'KEYWORD' and
2786                    $direction->[0] eq 'ltr') {
2787                  my $tv = $self->{computed_value}->{$eid}->{left}
2788                      = $compute_length->($self, $element, 'left',
2789                                          $left_specified);
2790                  $self->{computed_value}->{$eid}->{right}
2791                      = [$tv->[0], -$tv->[1], $tv->[2]];
2792                } else {
2793                  my $tv = $self->{computed_value}->{$eid}->{right}
2794                      = $compute_length->($self, $element, 'right',
2795                                          $right_specified);
2796                  $self->{computed_value}->{$eid}->{left}
2797                      = [$tv->[0], -$tv->[1], $tv->[2]];
2798                }
2799              } else {
2800                my $tv = $self->{computed_value}->{$eid}->{left}
2801                    = $compute_length->($self, $element, 'left', $left_specified);
2802                $self->{computed_value}->{$eid}->{right}
2803                    = [$tv->[0], -$tv->[1], $tv->[2]];
2804              }
2805            } else { # left: auto
2806              my $right_specified = $self->get_specified_value_no_inherit
2807                  ($element, 'right');
2808              if (defined $right_specified and
2809                  ($right_specified->[0] eq 'DIMENSION' or
2810                   $right_specified->[0] eq 'PERCENTAGE')) {
2811                my $tv = $self->{computed_value}->{$eid}->{right}
2812                    = $compute_length->($self, $element, 'right',
2813                                        $right_specified);
2814                $self->{computed_value}->{$eid}->{left}
2815                    = [$tv->[0], -$tv->[1], $tv->[2]];
2816              } else { # right: auto
2817                $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
2818                $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
2819              }
2820            }
2821            return;
2822          }
2823        }
2824    
2825        my $left_specified = $self->get_specified_value_no_inherit
2826            ($element, 'left');
2827        $self->{computed_value}->{$eid}->{left}
2828            = $compute_length->($self, $element, 'left', $left_specified);
2829        my $right_specified = $self->get_specified_value_no_inherit
2830            ($element, 'right');
2831        $self->{computed_value}->{$eid}->{right}
2832            = $compute_length->($self, $element, 'right', $right_specified);
2833      },
2834    };
2835    $Attr->{left} = $Prop->{left};
2836    $Key->{left} = $Prop->{left};
2837    
2838    $Prop->{right} = {
2839      css => 'right',
2840      dom => 'right',
2841      key => 'right',
2842      parse => $Prop->{'margin-top'}->{parse},
2843      allow_negative => 1,
2844      keyword => {auto => 1},
2845      initial => ['KEYWORD', 'auto'],
2846      #inherited => 0,
2847      compute_multiple => $Prop->{left}->{compute_multiple},
2848    };
2849    $Attr->{right} = $Prop->{right};
2850    $Key->{right} = $Prop->{right};
2851    
2852    $Prop->{width} = {
2853      css => 'width',
2854      dom => 'width',
2855      key => 'width',
2856      parse => $Prop->{'margin-top'}->{parse},
2857      #allow_negative => 0,
2858      keyword => {
2859        auto => 1,
2860        
2861        ## Firefox 3
2862        '-moz-max-content' => 2, '-moz-min-content' => 2,
2863        '-moz-available' => 2, '-moz-fit-content' => 2,
2864            ## NOTE: By "2", it represents that the parser must be configured
2865            ## to allow these values.
2866      },
2867      initial => ['KEYWORD', 'auto'],
2868      #inherited => 0,
2869      compute => $compute_length,
2870          ## NOTE: See <http://suika.fam.cx/gate/2005/sw/width> for
2871          ## browser compatibility issues.
2872    };
2873    $Attr->{width} = $Prop->{width};
2874    $Key->{width} = $Prop->{width};
2875    
2876    $Prop->{'min-width'} = {
2877      css => 'min-width',
2878      dom => 'min_width',
2879      key => 'min_width',
2880      parse => $Prop->{'margin-top'}->{parse},
2881      #allow_negative => 0,
2882      keyword => {
2883        ## Firefox 3
2884        '-moz-max-content' => 2, '-moz-min-content' => 2,
2885        '-moz-available' => 2, '-moz-fit-content' => 2,
2886      },
2887      initial => ['DIMENSION', 0, 'px'],
2888      #inherited => 0,
2889      compute => $compute_length,
2890    };
2891    $Attr->{min_width} = $Prop->{'min-width'};
2892    $Key->{min_width} = $Prop->{'min-width'};
2893    
2894    $Prop->{'max-width'} = {
2895      css => 'max-width',
2896      dom => 'max_width',
2897      key => 'max_width',
2898      parse => $Prop->{'margin-top'}->{parse},
2899      #allow_negative => 0,
2900      keyword => {
2901        none => 1,
2902        
2903        ## Firefox 3
2904        '-moz-max-content' => 2, '-moz-min-content' => 2,
2905        '-moz-available' => 2, '-moz-fit-content' => 2,
2906      },
2907      initial => ['KEYWORD', 'none'],
2908      #inherited => 0,
2909      compute => $compute_length,
2910    };
2911    $Attr->{max_width} = $Prop->{'max-width'};
2912    $Key->{max_width} = $Prop->{'max-width'};
2913    
2914    $Prop->{height} = {
2915      css => 'height',
2916      dom => 'height',
2917      key => 'height',
2918      parse => $Prop->{'margin-top'}->{parse},
2919      #allow_negative => 0,
2920      keyword => {auto => 1},
2921      initial => ['KEYWORD', 'auto'],
2922      #inherited => 0,
2923      compute => $compute_length,
2924          ## NOTE: See <http://suika.fam.cx/gate/2005/sw/height> for
2925          ## browser compatibility issues.
2926    };
2927    $Attr->{height} = $Prop->{height};
2928    $Key->{height} = $Prop->{height};
2929    
2930    $Prop->{'min-height'} = {
2931      css => 'min-height',
2932      dom => 'min_height',
2933      key => 'min_height',
2934      parse => $Prop->{'margin-top'}->{parse},
2935      #allow_negative => 0,
2936      #keyword => {},
2937      initial => ['DIMENSION', 0, 'px'],
2938      #inherited => 0,
2939      compute => $compute_length,
2940    };
2941    $Attr->{min_height} = $Prop->{'min-height'};
2942    $Key->{min_height} = $Prop->{'min-height'};
2943    
2944    $Prop->{'max-height'} = {
2945      css => 'max-height',
2946      dom => 'max_height',
2947      key => 'max_height',
2948      parse => $Prop->{'margin-top'}->{parse},
2949      #allow_negative => 0,
2950      keyword => {none => 1},
2951      initial => ['KEYWORD', 'none'],
2952      #inherited => 0,
2953      compute => $compute_length,
2954    };
2955    $Attr->{max_height} = $Prop->{'max-height'};
2956    $Key->{max_height} = $Prop->{'max-height'};
2957    
2958    $Prop->{'line-height'} = {
2959      css => 'line-height',
2960      dom => 'line_height',
2961      key => 'line_height',
2962      parse => sub {
2963        my ($self, $prop_name, $tt, $t, $onerror) = @_;
2964    
2965        ## NOTE: Similar to 'margin-top', but different handling
2966        ## for unitless numbers.
2967    
2968        my $has_sign;
2969        my $sign = 1;
2970        if ($t->{type} == MINUS_TOKEN) {
2971          $t = $tt->get_next_token;
2972          $sign = -1;
2973          $has_sign = 1;
2974        } elsif ($t->{type} == PLUS_TOKEN) {
2975          $t = $tt->get_next_token;
2976          $has_sign = 1;
2977        }
2978    
2979        if ($t->{type} == DIMENSION_TOKEN) {
2980          my $value = $t->{number} * $sign;
2981          my $unit = lc $t->{value}; ## TODO: case
2982          if ($length_unit->{$unit} and $value >= 0) {
2983            $t = $tt->get_next_token;
2984            return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2985          }
2986        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2987          my $value = $t->{number} * $sign;
2988          if ($value >= 0) {
2989            $t = $tt->get_next_token;
2990            return ($t, {$prop_name => ['PERCENTAGE', $value]});
2991          }
2992        } elsif ($t->{type} == NUMBER_TOKEN) {
2993          my $value = $t->{number} * $sign;
2994          if ($value >= 0) {
2995            $t = $tt->get_next_token;
2996            return ($t, {$prop_name => ['NUMBER', $value]});
2997          }
2998        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2999          my $value = lc $t->{value}; ## TODO: case
3000          if ($value eq 'normal') {
3001            $t = $tt->get_next_token;
3002            return ($t, {$prop_name => ['KEYWORD', $value]});        
3003          } elsif ($value eq 'inherit') {
3004            $t = $tt->get_next_token;
3005            return ($t, {$prop_name => ['INHERIT']});
3006          }
3007        }
3008        
3009        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3010                   level => $self->{level}->{must},
3011                   uri => \$self->{href},
3012                   token => $t);
3013        return ($t, undef);
3014      },
3015      initial => ['KEYWORD', 'normal'],
3016      inherited => 1,
3017      compute => $compute_length,
3018    };
3019    $Attr->{line_height} = $Prop->{'line-height'};
3020    $Key->{line_height} = $Prop->{'line-height'};
3021    
3022    $Prop->{'vertical-align'} = {
3023      css => 'vertical-align',
3024      dom => 'vertical_align',
3025      key => 'vertical_align',
3026      parse => $Prop->{'margin-top'}->{parse},
3027      allow_negative => 1,
3028      keyword => {
3029        baseline => 1, sub => 1, super => 1, top => 1, 'text-top' => 1,
3030        middle => 1, bottom => 1, 'text-bottom' => 1,
3031      },
3032      ## NOTE: Currently, we don't support option to select subset of keywords
3033      ## supported by application (i.e.
3034      ## $parser->{prop_value}->{'line-height'->{$keyword}).  Should we support
3035      ## it?
3036      initial => ['KEYWORD', 'baseline'],
3037      #inherited => 0,
3038      compute => $compute_length,
3039          ## NOTE: See <http://suika.fam.cx/gate/2005/sw/vertical-align> for
3040          ## browser compatibility issues.
3041    };
3042    $Attr->{vertical_align} = $Prop->{'vertical-align'};
3043    $Key->{vertical_align} = $Prop->{'vertical-align'};
3044    
3045    $Prop->{'text-indent'} = {
3046      css => 'text-indent',
3047      dom => 'text_indent',
3048      key => 'text_indent',
3049      parse => $Prop->{'margin-top'}->{parse},
3050      allow_negative => 1,
3051      keyword => {},
3052      initial => ['DIMENSION', 0, 'px'],
3053      inherited => 1,
3054      compute => $compute_length,
3055    };
3056    $Attr->{text_indent} = $Prop->{'text-indent'};
3057    $Key->{text_indent} = $Prop->{'text-indent'};
3058    
3059    $Prop->{'background-position-x'} = {
3060      css => 'background-position-x',
3061      dom => 'background_position_x',
3062      key => 'background_position_x',
3063      parse => $Prop->{'margin-top'}->{parse},
3064      allow_negative => 1,
3065      keyword => {left => 1, center => 1, right => 1},
3066      initial => ['PERCENTAGE', 0],
3067      #inherited => 0,
3068      compute => sub {
3069        my ($self, $element, $prop_name, $specified_value) = @_;
3070    
3071        if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3072          my $v = {
3073            left => 0, center => 50, right => 100, top => 0, bottom => 100,
3074          }->{$specified_value->[1]};
3075          if (defined $v) {
3076            return ['PERCENTAGE', $v];
3077          } else {
3078            return $specified_value;
3079          }
3080        } else {
3081          return $compute_length->(@_);
3082        }
3083      },
3084      serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
3085    };
3086    $Attr->{background_position_x} = $Prop->{'background-position-x'};
3087    $Key->{background_position_x} = $Prop->{'background-position-x'};
3088    
3089    $Prop->{'background-position-y'} = {
3090      css => 'background-position-y',
3091      dom => 'background_position_y',
3092      key => 'background_position_y',
3093      parse => $Prop->{'margin-top'}->{parse},
3094      allow_negative => 1,
3095      keyword => {top => 1, center => 1, bottom => 1},
3096      serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
3097      initial => ['PERCENTAGE', 0],
3098      #inherited => 0,
3099      compute => $Prop->{'background-position-x'}->{compute},
3100    };
3101    $Attr->{background_position_y} = $Prop->{'background-position-y'};
3102    $Key->{background_position_y} = $Prop->{'background-position-y'};
3103    
3104    $Prop->{'padding-top'} = {
3105      css => 'padding-top',
3106      dom => 'padding_top',
3107      key => 'padding_top',
3108      parse => $Prop->{'margin-top'}->{parse},
3109      #allow_negative => 0,
3110      #keyword => {},
3111      serialize_multiple => sub {
3112        my $self = shift;
3113    
3114        ## NOTE: Same as |serialize_multiple| of 'margin-top'.
3115    
3116        my $use_shorthand = 1;
3117        my $t = $self->padding_top;
3118        undef $use_shorthand unless length $t;
3119        my $t_i = $self->get_property_priority ('padding-top');
3120        my $r = $self->padding_right;
3121        undef $use_shorthand
3122            if not length $r or
3123                ($r eq 'inherit' and $t ne 'inherit') or
3124                ($t eq 'inherit' and $r ne 'inherit');
3125        my $r_i = $self->get_property_priority ('padding-right');
3126        undef $use_shorthand unless $r_i eq $t_i;
3127        my $b = $self->padding_bottom;
3128        undef $use_shorthand
3129            if not length $b or
3130                ($b eq 'inherit' and $t ne 'inherit') or
3131                ($t eq 'inherit' and $b ne 'inherit');
3132        my $b_i = $self->get_property_priority ('padding-bottom');
3133        undef $use_shorthand unless $b_i eq $t_i;
3134        my $l = $self->padding_left;
3135        undef $use_shorthand
3136            if not length $l or
3137                ($l eq 'inherit' and $t ne 'inherit') or
3138                ($t eq 'inherit' and $l ne 'inherit');
3139        my $l_i = $self->get_property_priority ('padding-left');
3140        undef $use_shorthand unless $l_i eq $t_i;
3141    
3142        if ($use_shorthand) {
3143          $b .= ' ' . $l if $r ne $l;
3144          $r .= ' ' . $b if $t ne $b;
3145          $t .= ' ' . $r if $t ne $r;
3146          return {padding => [$t, $t_i]};
3147        } else {
3148          my $v = {};
3149          if (length $t) {
3150            $v->{'padding-top'} = [$t, $t_i];
3151          }
3152          if (length $r) {
3153            $v->{'padding-right'} = [$r, $r_i];
3154          }
3155          if (length $b) {
3156            $v->{'padding-bottom'} = [$b, $b_i];
3157          }
3158          if (length $l) {
3159            $v->{'padding-left'} = [$l, $l_i];
3160          }
3161          return $v;
3162        }
3163      },
3164      initial => ['DIMENSION', 0, 'px'],
3165      #inherited => 0,
3166      compute => $compute_length,
3167    };
3168    $Attr->{padding_top} = $Prop->{'padding-top'};
3169    $Key->{padding_top} = $Prop->{'padding-top'};
3170    
3171    $Prop->{'padding-bottom'} = {
3172      css => 'padding-bottom',
3173      dom => 'padding_bottom',
3174      key => 'padding_bottom',
3175      parse => $Prop->{'padding-top'}->{parse},
3176      #allow_negative => 0,
3177      #keyword => {},
3178      serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3179      initial => ['DIMENSION', 0, 'px'],
3180      #inherited => 0,
3181      compute => $compute_length,
3182    };
3183    $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
3184    $Key->{padding_bottom} = $Prop->{'padding-bottom'};
3185    
3186    $Prop->{'padding-right'} = {
3187      css => 'padding-right',
3188      dom => 'padding_right',
3189      key => 'padding_right',
3190      parse => $Prop->{'padding-top'}->{parse},
3191      #allow_negative => 0,
3192      #keyword => {},
3193      serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3194      initial => ['DIMENSION', 0, 'px'],
3195      #inherited => 0,
3196      compute => $compute_length,
3197    };
3198    $Attr->{padding_right} = $Prop->{'padding-right'};
3199    $Key->{padding_right} = $Prop->{'padding-right'};
3200    
3201    $Prop->{'padding-left'} = {
3202      css => 'padding-left',
3203      dom => 'padding_left',
3204      key => 'padding_left',
3205      parse => $Prop->{'padding-top'}->{parse},
3206      #allow_negative => 0,
3207      #keyword => {},
3208      serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3209      initial => ['DIMENSION', 0, 'px'],
3210      #inherited => 0,
3211      compute => $compute_length,
3212    };
3213    $Attr->{padding_left} = $Prop->{'padding-left'};
3214    $Key->{padding_left} = $Prop->{'padding-left'};
3215    
3216    $Prop->{'border-top-width'} = {
3217      css => 'border-top-width',
3218      dom => 'border_top_width',
3219      key => 'border_top_width',
3220      parse => $Prop->{'margin-top'}->{parse},
3221      #allow_negative => 0,
3222      keyword => {thin => 1, medium => 1, thick => 1},
3223      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3224      initial => ['KEYWORD', 'medium'],
3225      #inherited => 0,
3226      compute => sub {
3227        my ($self, $element, $prop_name, $specified_value) = @_;
3228    
3229        ## NOTE: Used for 'border-top-width', 'border-right-width',
3230        ## 'border-bottom-width', 'border-right-width', and
3231        ## 'outline-width'.
3232    
3233        my $style_prop = $prop_name;
3234        $style_prop =~ s/width/style/;
3235        my $style = $self->get_computed_value ($element, $style_prop);
3236        if (defined $style and $style->[0] eq 'KEYWORD' and
3237            ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
3238          return ['DIMENSION', 0, 'px'];
3239        }
3240    
3241        my $value = $compute_length->(@_);
3242        if (defined $value and $value->[0] eq 'KEYWORD') {
3243          if ($value->[1] eq 'thin') {
3244            return ['DIMENSION', 1, 'px']; ## Firefox/Opera
3245          } elsif ($value->[1] eq 'medium') {
3246            return ['DIMENSION', 3, 'px']; ## Firefox/Opera
3247          } elsif ($value->[1] eq 'thick') {
3248            return ['DIMENSION', 5, 'px']; ## Firefox
3249          }
3250        }
3251        return $value;
3252      },
3253      ## NOTE: CSS3 will allow <percentage> as an option in <border-width>.
3254      ## Opera 9 has already implemented it.
3255    };
3256    $Attr->{border_top_width} = $Prop->{'border-top-width'};
3257    $Key->{border_top_width} = $Prop->{'border-top-width'};
3258    
3259    $Prop->{'border-right-width'} = {
3260      css => 'border-right-width',
3261      dom => 'border_right_width',
3262      key => 'border_right_width',
3263      parse => $Prop->{'border-top-width'}->{parse},
3264      #allow_negative => 0,
3265      keyword => {thin => 1, medium => 1, thick => 1},
3266      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3267      initial => ['KEYWORD', 'medium'],
3268      #inherited => 0,
3269      compute => $Prop->{'border-top-width'}->{compute},
3270    };
3271    $Attr->{border_right_width} = $Prop->{'border-right-width'};
3272    $Key->{border_right_width} = $Prop->{'border-right-width'};
3273    
3274    $Prop->{'border-bottom-width'} = {
3275      css => 'border-bottom-width',
3276      dom => 'border_bottom_width',
3277      key => 'border_bottom_width',
3278      parse => $Prop->{'border-top-width'}->{parse},
3279      #allow_negative => 0,
3280      keyword => {thin => 1, medium => 1, thick => 1},
3281      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3282      initial => ['KEYWORD', 'medium'],
3283      #inherited => 0,
3284      compute => $Prop->{'border-top-width'}->{compute},
3285    };
3286    $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
3287    $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
3288    
3289    $Prop->{'border-left-width'} = {
3290      css => 'border-left-width',
3291      dom => 'border_left_width',
3292      key => 'border_left_width',
3293      parse => $Prop->{'border-top-width'}->{parse},
3294      #allow_negative => 0,
3295      keyword => {thin => 1, medium => 1, thick => 1},
3296      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3297      initial => ['KEYWORD', 'medium'],
3298      #inherited => 0,
3299      compute => $Prop->{'border-top-width'}->{compute},
3300    };
3301    $Attr->{border_left_width} = $Prop->{'border-left-width'};
3302    $Key->{border_left_width} = $Prop->{'border-left-width'};
3303    
3304    $Prop->{'outline-width'} = {
3305      css => 'outline-width',
3306      dom => 'outline_width',
3307      key => 'outline_width',
3308      parse => $Prop->{'border-top-width'}->{parse},
3309      #allow_negative => 0,
3310      keyword => {thin => 1, medium => 1, thick => 1},
3311      serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
3312      initial => ['KEYWORD', 'medium'],
3313      #inherited => 0,
3314      compute => $Prop->{'border-top-width'}->{compute},
3315    };
3316    $Attr->{outline_width} = $Prop->{'outline-width'};
3317    $Key->{outline_width} = $Prop->{'outline-width'};
3318    
3319    $Prop->{'font-weight'} = {
3320      css => 'font-weight',
3321      dom => 'font_weight',
3322      key => 'font_weight',
3323      parse => sub {
3324        my ($self, $prop_name, $tt, $t, $onerror) = @_;
3325    
3326        my $has_sign;
3327        if ($t->{type} == PLUS_TOKEN) {
3328          $has_sign = 1;
3329          $t = $tt->get_next_token;
3330        }
3331    
3332        if ($t->{type} == NUMBER_TOKEN) {
3333          ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
3334          ## browser compatibility issue.
3335          my $value = $t->{number};
3336          $t = $tt->get_next_token;
3337          if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
3338            return ($t, {$prop_name => ['WEIGHT', $value, 0]});
3339          }
3340        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
3341          my $value = lc $t->{value}; ## TODO: case
3342          if ({
3343               normal => 1, bold => 1, bolder => 1, lighter => 1,
3344              }->{$value}) {
3345            $t = $tt->get_next_token;
3346            return ($t, {$prop_name => ['KEYWORD', $value]});
3347          } elsif ($value eq 'inherit') {
3348            $t = $tt->get_next_token;
3349            return ($t, {$prop_name => ['INHERIT']});
3350          }
3351        }
3352        
3353        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3354                   level => $self->{level}->{must},
3355                   uri => \$self->{href},
3356                   token => $t);
3357        return ($t, undef);
3358      },
3359      initial => ['KEYWORD', 'normal'],
3360      inherited => 1,
3361      compute => sub {
3362        my ($self, $element, $prop_name, $specified_value) = @_;
3363    
3364        if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3365          if ($specified_value->[1] eq 'normal') {
3366            return ['WEIGHT', 400, 0];
3367          } elsif ($specified_value->[1] eq 'bold') {
3368            return ['WEIGHT', 700, 0];
3369          } elsif ($specified_value->[1] eq 'bolder') {
3370            my $parent_element = $element->manakai_parent_element;
3371            if (defined $parent_element) {
3372              my $parent_value = $self->get_cascaded_value
3373                  ($parent_element, $prop_name); ## NOTE: What Firefox does.
3374              return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
3375            } else {
3376              return ['WEIGHT', 400, 1];
3377            }
3378          } elsif ($specified_value->[1] eq 'lighter') {
3379            my $parent_element = $element->manakai_parent_element;
3380            if (defined $parent_element) {
3381              my $parent_value = $self->get_cascaded_value
3382                  ($parent_element, $prop_name); ## NOTE: What Firefox does.
3383              return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
3384            } else {
3385              return ['WEIGHT', 400, 1];
3386            }
3387          }
3388        #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
3389          #
3390        }
3391    
3392        return $specified_value;
3393      },
3394    };
3395    $Attr->{font_weight} = $Prop->{'font-weight'};
3396    $Key->{font_weight} = $Prop->{'font-weight'};
3397    
3398    my $uri_or_none_parser = sub {
3399        my ($self, $prop_name, $tt, $t, $onerror) = @_;
3400    
3401        if ($t->{type} == URI_TOKEN) {
3402          my $value = $t->{value};
3403          $t = $tt->get_next_token;
3404          return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
3405        } elsif ($t->{type} == IDENT_TOKEN) {
3406          my $value = lc $t->{value}; ## TODO: case
3407          $t = $tt->get_next_token;
3408          if ($value eq 'none') {
3409            ## NOTE: |none| is the default value and therefore it must be
3410            ## supported anyway.
3411            return ($t, {$prop_name => ["KEYWORD", 'none']});
3412          } elsif ($value eq 'inherit') {
3413            return ($t, {$prop_name => ['INHERIT']});
3414          }
3415        ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
3416        #} elsif ($t->{type} == URI_INVALID_TOKEN) {
3417        #  my $value = $t->{value};
3418        #  $t = $tt->get_next_token;
3419        #  if ($t->{type} == EOF_TOKEN) {
3420        #    $onerror->(type => 'uri not closed',
3421        #               level => $self->{level}->{must},
3422        #               uri => \$self->{href},
3423        #               token => $t);
3424        #    
3425        #    return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
3426        #  }
3427        }
3428        
3429        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3430                   level => $self->{level}->{must},
3431                   uri => \$self->{href},
3432                   token => $t);
3433        return ($t, undef);
3434    }; # $uri_or_none_parser
3435    
3436    my $compute_uri_or_none = sub {
3437        my ($self, $element, $prop_name, $specified_value) = @_;
3438        
3439        if (defined $specified_value and
3440            $specified_value->[0] eq 'URI' and
3441            defined $specified_value->[2]) {
3442          require Message::DOM::DOMImplementation;
3443          return ['URI',
3444                  Message::DOM::DOMImplementation->create_uri_reference
3445                      ($specified_value->[1])
3446                      ->get_absolute_reference (${$specified_value->[2]})
3447                      ->get_uri_reference,
3448                  $specified_value->[2]];
3449        }
3450    
3451        return $specified_value;
3452    }; # $compute_uri_or_none
3453    
3454    $Prop->{'list-style-image'} = {
3455      css => 'list-style-image',
3456      dom => 'list_style_image',
3457      key => 'list_style_image',
3458      parse => $uri_or_none_parser,
3459      initial => ['KEYWORD', 'none'],
3460      inherited => 1,
3461      compute => $compute_uri_or_none,
3462    };
3463    $Attr->{list_style_image} = $Prop->{'list-style-image'};
3464    $Key->{list_style_image} = $Prop->{'list-style-image'};
3465    
3466    $Prop->{'background-image'} = {
3467      css => 'background-image',
3468      dom => 'background_image',
3469      key => 'background_image',
3470      parse => $uri_or_none_parser,
3471      serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
3472      initial => ['KEYWORD', 'none'],
3473      #inherited => 0,
3474      compute => $compute_uri_or_none,
3475    };
3476    $Attr->{background_image} = $Prop->{'background-image'};
3477    $Key->{background_image} = $Prop->{'background-image'};
3478    
3479    $Attr->{font_stretch} =
3480    $Key->{font_stretch} =
3481    $Prop->{'font-stretch'} = {
3482      css => 'font-stretch',
3483      dom => 'font_stretch',
3484      key => 'font_stretch',
3485      parse => $one_keyword_parser,
3486      keyword => {
3487        qw/normal 1 wider 1 narrower 1 ultra-condensed 1 extra-condensed 1
3488           condensed 1 semi-condensed 1 semi-expanded 1 expanded 1
3489           extra-expanded 1 ultra-expanded 1/,
3490      },
3491      initial => ["KEYWORD", 'normal'],
3492      inherited => 1,
3493      compute => sub {
3494        my ($self, $element, $prop_name, $specified_value) = @_;
3495    
3496        if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3497          if ($specified_value->[1] eq 'wider') {
3498            my $parent = $element->manakai_parent_element;
3499            if ($parent) {
3500              my $computed = $self->get_computed_value ($parent, $prop_name);
3501              if (defined $computed and $computed->[0] eq 'KEYWORD') {
3502                return ['KEYWORD', {
3503                                    'ultra-condensed' => 'extra-condensed',
3504                                    'extra-condensed' => 'condensed',
3505                                    'condensed' => 'semi-condensed',
3506                                    'semi-condensed' => 'normal',
3507                                    'normal' => 'semi-expanded',
3508                                    'semi-expanded' => 'expanded',
3509                                    'expanded' => 'extra-expanded',
3510                                    'extra-expanded' => 'ultra-expanded',
3511                                    'ultra-expanded' => 'ultra-expanded',
3512                                   }->{$computed->[1]} || $computed->[1]];
3513              } else { ## This is an implementation error.
3514                #
3515              }
3516            } else {
3517              return ['KEYWORD', 'semi-expanded'];
3518            }
3519          } elsif ($specified_value->[1] eq 'narrower') {
3520            my $parent = $element->manakai_parent_element;
3521            if ($parent) {
3522              my $computed = $self->get_computed_value ($parent, $prop_name);
3523              if (defined $computed and $computed->[0] eq 'KEYWORD') {
3524                return ['KEYWORD', {
3525                                    'ultra-condensed' => 'ultra-condensed',
3526                                    'extra-condensed' => 'ultra-condensed',
3527                                    'condensed' => 'extra-condensed',
3528                                    'semi-condensed' => 'condensed',
3529                                    'normal' => 'semi-condensed',
3530                                    'semi-expanded' => 'normal',
3531                                    'expanded' => 'semi-expanded',
3532                                    'extra-expanded' => 'expanded',
3533                                    'ultra-expanded' => 'extra-expanded',
3534                                   }->{$computed->[1]} || $computed->[1]];
3535              } else { ## This is an implementation error.
3536                #
3537              }
3538            } else {
3539              return ['KEYWORD', 'semi-condensed'];
3540            }
3541          }
3542        }
3543    
3544        return $specified_value;
3545      },
3546    };
3547    
3548    $Attr->{writing_mode} =
3549    $Key->{writing_mode} =
3550    $Prop->{'writing-mode'} = {
3551      css => 'writing-mode',
3552      dom => 'writing_mode',
3553      key => 'writing_mode',
3554      parse => $one_keyword_parser,
3555      keyword => {
3556        'lr' => 1, 'lr-tb' => 1,
3557        'rl' => 1, 'rl-tb' => 1,
3558        'tb' => 1, 'tb-rl' => 1,
3559      },
3560      initial => ['KEYWORD', 'lr-tb'],
3561      inherited => 1,
3562      compute => sub {
3563        my ($self, $element, $prop_name, $specified_value) = @_;
3564    
3565        ## ISSUE: Not defined by any standard.
3566    
3567        if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3568          if ($specified_value->[1] eq 'lr') {
3569            return ['KEYWORD', 'lr-tb'];
3570          } elsif ($specified_value->[1] eq 'rl') {
3571            return ['KEYWORD', 'rl-tb'];
3572          } elsif ($specified_value->[1] eq 'tb') {
3573            return ['KEYWORD', 'tb-rl'];
3574          }
3575        }
3576    
3577        return $specified_value;
3578      },
3579    };
3580    
3581    $Attr->{text_anchor} =
3582    $Key->{text_anchor} =
3583    $Prop->{'text-anchor'} = {
3584      css => 'text-anchor',
3585      dom => 'text_anchor', ## TODO: manakai extension.  Documentation.
3586      key => 'text_anchor',
3587      parse => $one_keyword_parser,
3588      keyword => {
3589        start => 1, middle => 1, end => 1,
3590      },
3591      initial => ['KEYWORD', 'start'],
3592      inherited => 1,
3593      compute => $compute_as_specified,
3594    };
3595    
3596    $Attr->{dominant_baseline} =
3597    $Key->{dominant_baseline} =
3598    $Prop->{'dominant-baseline'} = {
3599      css => 'dominant-baseline',
3600      dom => 'dominant_baseline', ## TODO: manakai extension.  Documentation.
3601      key => 'dominant_baseline',
3602      parse => $one_keyword_parser,
3603      keyword => {
3604        qw/auto 1 use-script 1 no-change 1 reset-size 1 ideographic 1 alphabetic 1
3605           hanging 1 mathematical 1 central 1 middle 1 text-after-edge 1
3606           text-before-edge 1/
3607      },
3608      initial => ['KEYWORD', 'auto'],
3609      inherited => 0,
3610      compute => $compute_as_specified,
3611    };
3612    
3613    $Attr->{alignment_baseline} =
3614    $Key->{alignment_baseline} =
3615    $Prop->{'alignment-baseline'} = {
3616      css => 'alignment-baseline',
3617      dom => 'alignment_baseline', ## TODO: manakai extension.  Documentation.
3618      key => 'alignment_baseline',
3619      parse => $one_keyword_parser,
3620      keyword => {
3621        qw/auto 1 baseline 1 before-edge 1 text-before-edge 1 middle 1 central 1
3622           after-edge 1 text-after-edge 1 ideographic 1 alphabetic 1 hanging 1
3623           mathematical 1/
3624      },
3625      initial => ['KEYWORD', 'auto'],
3626      inherited => 0,
3627      compute => $compute_as_specified,
3628    };
3629    
3630    my $border_style_keyword = {
3631      none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
3632      double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
3633    };
3634    
3635    $Prop->{'border-top-style'} = {
3636      css => 'border-top-style',
3637      dom => 'border_top_style',
3638      key => 'border_top_style',
3639      parse => $one_keyword_parser,
3640      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3641      keyword => $border_style_keyword,
3642      initial => ["KEYWORD", "none"],
3643      #inherited => 0,
3644      compute => $compute_as_specified,
3645    };
3646    $Attr->{border_top_style} = $Prop->{'border-top-style'};
3647    $Key->{border_top_style} = $Prop->{'border-top-style'};
3648    
3649    $Prop->{'border-right-style'} = {
3650      css => 'border-right-style',
3651      dom => 'border_right_style',
3652      key => 'border_right_style',
3653      parse => $one_keyword_parser,
3654      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3655      keyword => $border_style_keyword,
3656      initial => ["KEYWORD", "none"],
3657      #inherited => 0,
3658      compute => $compute_as_specified,
3659    };
3660    $Attr->{border_right_style} = $Prop->{'border-right-style'};
3661    $Key->{border_right_style} = $Prop->{'border-right-style'};
3662    
3663    $Prop->{'border-bottom-style'} = {
3664      css => 'border-bottom-style',
3665      dom => 'border_bottom_style',
3666      key => 'border_bottom_style',
3667      parse => $one_keyword_parser,
3668      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3669      keyword => $border_style_keyword,
3670      initial => ["KEYWORD", "none"],
3671      #inherited => 0,
3672      compute => $compute_as_specified,
3673    };
3674    $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
3675    $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
3676    
3677    $Prop->{'border-left-style'} = {
3678      css => 'border-left-style',
3679      dom => 'border_left_style',
3680      key => 'border_left_style',
3681      parse => $one_keyword_parser,
3682      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3683      keyword => $border_style_keyword,
3684      initial => ["KEYWORD", "none"],
3685      #inherited => 0,
3686      compute => $compute_as_specified,
3687    };
3688    $Attr->{border_left_style} = $Prop->{'border-left-style'};
3689    $Key->{border_left_style} = $Prop->{'border-left-style'};
3690    
3691    $Prop->{'outline-style'} = {
3692      css => 'outline-style',
3693      dom => 'outline_style',
3694      key => 'outline_style',
3695      parse => $one_keyword_parser,
3696      serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
3697      keyword => {%$border_style_keyword},
3698      initial => ['KEYWORD', 'none'],
3699      #inherited => 0,
3700      compute => $compute_as_specified,
3701    };
3702    $Attr->{outline_style} = $Prop->{'outline-style'};
3703    $Key->{outline_style} = $Prop->{'outline-style'};
3704    delete $Prop->{'outline-style'}->{keyword}->{hidden};
3705    
3706    my $generic_font_keywords = {
3707      serif => 1, 'sans-serif' => 1, cursive => 1,
3708      fantasy => 1, monospace => 1, '-manakai-default' => 1,
3709      '-manakai-caption' => 1, '-manakai-icon' => 1,
3710      '-manakai-menu' => 1, '-manakai-message-box' => 1,
3711      '-manakai-small-caption' => 1, '-manakai-status-bar' => 1,
3712    };
3713    ## NOTE: "All five generic font families are defined to exist in all CSS
3714    ## implementations (they need not necessarily map to five distinct actual
3715    ## fonts)." [CSS 2.1].
3716    ## NOTE: "If no font with the indicated characteristics exists on a given
3717    ## platform, the user agent should either intelligently substitute (e.g., a
3718    ## smaller version of the 'caption' font might be used for the 'small-caption'
3719    ## font), or substitute a user agent default font." [CSS 2.1].
3720    
3721    $Prop->{'font-family'} = {
3722      css => 'font-family',
3723      dom => 'font_family',
3724      key => 'font_family',
3725      parse => sub {
3726        my ($self, $prop_name, $tt, $t, $onerror) = @_;
3727    
3728        ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
3729        ## how chaotic browsers are!
3730    
3731        ## NOTE: Opera 9 allows NUMBER and DIMENSION as part of
3732        ## <font-family>, while Firefox 2 does not.
3733    
3734        my @prop_value;
3735    
3736        my $font_name = '';
3737        my $may_be_generic = 1;
3738        my $may_be_inherit = ($prop_name ne 'font');
3739        my $has_s = 0;
3740        F: {
3741          if ($t->{type} == IDENT_TOKEN) {
3742            undef $may_be_inherit if $has_s or length $font_name;
3743            undef $may_be_generic if $has_s or length $font_name;
3744            $font_name .= ' ' if $has_s;
3745            $font_name .= $t->{value};
3746            undef $has_s;
3747            $t = $tt->get_next_token;
3748          } elsif ($t->{type} == STRING_TOKEN) {
3749            $font_name .= ' ' if $has_s;
3750            $font_name .= $t->{value};
3751            undef $may_be_inherit;
3752            undef $may_be_generic;
3753            undef $has_s;
3754            $t = $tt->get_next_token;
3755          } elsif ($t->{type} == COMMA_TOKEN) { ## TODO: case
3756            if ($may_be_generic and $generic_font_keywords->{lc $font_name}) {
3757              push @prop_value, ['KEYWORD', $font_name];
3758            } elsif (not $may_be_generic or length $font_name) {
3759              push @prop_value, ["STRING", $font_name];
3760            }
3761            undef $may_be_inherit;
3762            $may_be_generic = 1;
3763            undef $has_s;
3764            $font_name = '';
3765            $t = $tt->get_next_token;
3766            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3767          } elsif ($t->{type} == S_TOKEN) {
3768            $has_s = 1;
3769            $t = $tt->get_next_token;
3770          } else {
3771            if ($may_be_generic and $generic_font_keywords->{lc $font_name}) {
3772              push @prop_value, ['KEYWORD', $font_name]; ## TODO: case
3773            } elsif (not $may_be_generic or length $font_name) {
3774              push @prop_value, ['STRING', $font_name];
3775            } else {
3776              $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3777                         level => $self->{level}->{must},
3778                         uri => \$self->{href},
3779                         token => $t);
3780              return ($t, undef);
3781            }
3782            last F;
3783          }
3784          redo F;
3785        } # F
3786    
3787        if ($may_be_inherit and
3788            @prop_value == 1 and
3789            $prop_value[0]->[0] eq 'STRING' and
3790            lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
3791          return ($t, {$prop_name => ['INHERIT']});
3792        } else {
3793          unshift @prop_value, 'FONT';
3794          return ($t, {$prop_name => \@prop_value});
3795        }
3796      },
3797      initial => ['FONT', ['KEYWORD', '-manakai-default']],
3798      inherited => 1,
3799      compute => $compute_as_specified,
3800    };
3801    $Attr->{font_family} = $Prop->{'font-family'};
3802    $Key->{font_family} = $Prop->{'font-family'};
3803    
3804    $Prop->{cursor} = {
3805      css => 'cursor',
3806      dom => 'cursor',
3807      key => 'cursor',
3808      parse => sub {
3809        my ($self, $prop_name, $tt, $t, $onerror) = @_;
3810    
3811        ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
3812        ## compatibility issues.
3813    
3814        my @prop_value = ('CURSOR');
3815    
3816        F: {
3817          if ($t->{type} == IDENT_TOKEN) {
3818            my $v = lc $t->{value}; ## TODO: case
3819            if ($Prop->{$prop_name}->{keyword}->{$v}) {
3820              push @prop_value, ['KEYWORD', $v];
3821              $t = $tt->get_next_token;
3822              last F;
3823            } elsif ($v eq 'hand' and
3824                     $Prop->{$prop_name}->{keyword}->{pointer}) {
3825              ## TODO: add test
3826              $onerror->(type => 'CSS cursor hand',
3827                         level => $self->{level}->{must}, # not valid <'cursor'>
3828                         uri => \$self->{href},
3829                         token => $t);
3830              push @prop_value, ['KEYWORD', 'pointer'];
3831              $t = $tt->get_next_token;
3832              last F;
3833            } elsif ($v eq 'inherit' and @prop_value == 1) {
3834              $t = $tt->get_next_token;
3835              return ($t, {$prop_name => ['INHERIT']});
3836            } else {
3837              $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3838                         level => $self->{level}->{must},
3839                         uri => \$self->{href},
3840                         token => $t);
3841              return ($t, undef);
3842            }
3843          } elsif ($t->{type} == URI_TOKEN) {
3844            push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
3845            $t = $tt->get_next_token;
3846          } else {
3847            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3848                       level => $self->{level}->{must},
3849                       uri => \$self->{href},
3850                       token => $t);
3851            return ($t, undef);
3852          }
3853        
3854          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3855          if ($t->{type} == COMMA_TOKEN) {
3856            $t = $tt->get_next_token;
3857            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3858            redo F;
3859          }
3860        } # F
3861    
3862        return ($t, {$prop_name => \@prop_value});
3863      },
3864      keyword => {
3865        auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
3866        'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
3867        'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
3868        'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
3869      },
3870      initial => ['CURSOR', ['KEYWORD', 'auto']],
3871      inherited => 1,
3872      compute => sub {
3873        my ($self, $element, $prop_name, $specified_value) = @_;
3874    
3875        if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
3876          my @new_value = ('CURSOR');
3877          for my $value (@$specified_value[1..$#$specified_value]) {
3878            if ($value->[0] eq 'URI') {
3879              if (defined $value->[2]) {
3880                require Message::DOM::DOMImplementation;
3881                push @new_value, ['URI',
3882                                  Message::DOM::DOMImplementation
3883                                      ->create_uri_reference ($value->[1])
3884                                      ->get_absolute_reference (${$value->[2]})
3885                                      ->get_uri_reference,
3886                                  $value->[2]];
3887              } else {
3888                push @new_value, $value;
3889              }
3890            } else {
3891              push @new_value, $value;
3892            }
3893          }
3894          return \@new_value;
3895        }
3896    
3897        return $specified_value;
3898      },
3899    };
3900    $Attr->{cursor} = $Prop->{cursor};
3901    $Key->{cursor} = $Prop->{cursor};
3902    
3903    $Prop->{'border-style'} = {
3904      css => 'border-style',
3905      dom => 'border_style',
3906      parse => sub {
3907        my ($self, $prop_name, $tt, $t, $onerror) = @_;
3908    
3909        my %prop_value;
3910        if ($t->{type} == IDENT_TOKEN) {
3911          my $prop_value = lc $t->{value}; ## TODO: case folding
3912          $t = $tt->get_next_token;
3913          if ($border_style_keyword->{$prop_value} and
3914              $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
3915            $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
3916          } elsif ($prop_value eq 'inherit') {
3917            $prop_value{'border-top-style'} = ["INHERIT"];
3918            $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
3919            $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
3920            $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3921            return ($t, \%prop_value);
3922          } else {
3923            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3924                       level => $self->{level}->{must},
3925                       uri => \$self->{href},
3926                       token => $t);
3927            return ($t, undef);
3928          }
3929          $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
3930          $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
3931          $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3932        } else {
3933          $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3934                     level => $self->{level}->{must},
3935                     uri => \$self->{href},
3936                     token => $t);
3937          return ($t, undef);
3938        }
3939    
3940        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3941        if ($t->{type} == IDENT_TOKEN) {
3942          my $prop_value = lc $t->{value}; ## TODO: case folding
3943          $t = $tt->get_next_token;
3944          if ($border_style_keyword->{$prop_value} and
3945              $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
3946            $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
3947          } else {
3948            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3949                       level => $self->{level}->{must},
3950                       uri => \$self->{href},
3951                       token => $t);
3952            return ($t, undef);
3953          }
3954          $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3955    
3956          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3957          if ($t->{type} == IDENT_TOKEN) {
3958            my $prop_value = lc $t->{value}; ## TODO: case folding
3959            $t = $tt->get_next_token;
3960            if ($border_style_keyword->{$prop_value} and
3961                $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
3962              $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
3963            } else {
3964              $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3965                         level => $self->{level}->{must},
3966                         uri => \$self->{href},
3967                         token => $t);
3968              return ($t, undef);
3969            }
3970            
3971            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3972            if ($t->{type} == IDENT_TOKEN) {
3973              my $prop_value = lc $t->{value}; ## TODO: case folding
3974              $t = $tt->get_next_token;
3975              if ($border_style_keyword->{$prop_value} and
3976                  $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
3977                $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
3978              } else {
3979                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3980                           level => $self->{level}->{must},
3981                           uri => \$self->{href},
3982                           token => $t);
3983                return ($t, undef);
3984              }
3985            }
3986          }
3987        }        
3988    
3989        return ($t, \%prop_value);
3990      },
3991      serialize_shorthand => sub {
3992        my $self = shift;
3993    
3994        my @v;
3995        push @v, $self->border_top_style;
3996        my $i = $self->get_property_priority ('border-top-style');
3997        return {} unless length $v[-1];
3998        push @v, $self->border_right_style;
3999        return {} unless length $v[-1];
4000        return {} unless $i eq $self->get_property_priority ('border-right-style');
4001        push @v, $self->border_bottom_style;
4002        return {} unless length $v[-1];
4003        return {} unless $i eq $self->get_property_priority ('border-bottom-style');
4004        push @v, $self->border_left_style;
4005        return {} unless length $v[-1];
4006        return {} unless $i eq $self->get_property_priority ('border-left-style');
4007    
4008        my $v = 0;
4009        for (0..3) {
4010          $v++ if $v[$_] eq 'inherit';
4011        }
4012        if ($v == 4) {
4013          return {'border-style' => ['inherit', $i]};
4014        } elsif ($v) {
4015          return {};
4016        }
4017    
4018        pop @v if $v[1] eq $v[3];
4019        pop @v if $v[0] eq $v[2];
4020        pop @v if $v[0] eq $v[1];
4021        return {'border-style' => [(join ' ', @v), $i]};
4022      },
4023      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4024    };
4025    $Attr->{border_style} = $Prop->{'border-style'};
4026    
4027    $Prop->{'border-color'} = {
4028      css => 'border-color',
4029      dom => 'border_color',
4030      parse => sub {
4031        my ($self, $prop_name, $tt, $t, $onerror) = @_;
4032    
4033        my %prop_value;
4034        ($t, my $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4035        if (not defined $pv) {
4036          return ($t, undef);
4037        }
4038        $prop_value{'border-top-color'} = $pv->{'border-color'};
4039        $prop_value{'border-bottom-color'} = $prop_value{'border-top-color'};
4040        $prop_value{'border-right-color'} = $prop_value{'border-top-color'};
4041        $prop_value{'border-left-color'}= $prop_value{'border-right-color'};
4042        if ($prop_value{'border-top-color'}->[0] eq 'INHERIT') {
4043          return ($t, \%prop_value);
4044        }
4045    
4046        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4047        if ({
4048             IDENT_TOKEN, 1,
4049             HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4050             FUNCTION_TOKEN, 1,
4051            }->{$t->{type}}) {
4052          ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4053          if (not defined $pv) {
4054            return ($t, undef);
4055          } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
4056            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4057                       level => $self->{level}->{must},
4058                       uri => \$self->{href},
4059                       token => $t);
4060            return ($t, undef);
4061          }
4062          $prop_value{'border-right-color'} = $pv->{'border-color'};
4063          $prop_value{'border-left-color'}= $prop_value{'border-right-color'};
4064    
4065          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4066          if ({
4067               IDENT_TOKEN, 1,
4068               HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4069               FUNCTION_TOKEN, 1,
4070              }->{$t->{type}}) {
4071            ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4072            if (not defined $pv) {
4073              return ($t, undef);
4074            } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
4075              $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4076                         level => $self->{level}->{must},
4077                         uri => \$self->{href},
4078                         token => $t);
4079              return ($t, undef);
4080            }
4081            $prop_value{'border-bottom-color'} = $pv->{'border-color'};
4082    
4083            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4084            if ({
4085                 IDENT_TOKEN, 1,
4086                 HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4087                 FUNCTION_TOKEN, 1,
4088                }->{$t->{type}}) {
4089              ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4090              if (not defined $pv) {
4091                return ($t, undef);
4092              } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
4093                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4094                           level => $self->{level}->{must},
4095                           uri => \$self->{href},
4096                           token => $t);
4097                return ($t, undef);
4098              }
4099              $prop_value{'border-left-color'} = $pv->{'border-color'};
4100            }
4101          }
4102        }
4103        
4104        return ($t, \%prop_value);
4105      },
4106      serialize_shorthand => sub {
4107        my $self = shift;
4108    
4109        my @v;
4110        push @v, $self->border_top_color;
4111        my $i = $self->get_property_priority ('border-top-color');
4112        return {} unless length $v[-1];
4113        push @v, $self->border_right_color;
4114        return {} unless length $v[-1];
4115        return {} unless $i eq $self->get_property_priority ('border-right-color');
4116        push @v, $self->border_bottom_color;
4117        return {} unless length $v[-1];
4118        return {} unless $i eq $self->get_property_priority ('border-bottom-color');
4119        push @v, $self->border_left_color;
4120        return {} unless length $v[-1];
4121        return {} unless $i eq $self->get_property_priority ('border-left-color');
4122    
4123        my $v = 0;
4124        for (0..3) {
4125          $v++ if $v[$_] eq 'inherit';
4126        }
4127        if ($v == 4) {
4128          return {'border-color' => ['inherit', $i]};
4129        } elsif ($v) {
4130          return {};
4131        }
4132    
4133        pop @v if $v[1] eq $v[3];
4134        pop @v if $v[0] eq $v[2];
4135        pop @v if $v[0] eq $v[1];
4136        return {'border-color' => [(join ' ', @v), $i]};
4137      },
4138      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4139    };
4140    $Attr->{border_color} = $Prop->{'border-color'};
4141    
4142    $Prop->{'border-top'} = {
4143      css => 'border-top',
4144      dom => 'border_top',
4145      parse => sub {
4146        my ($self, $prop_name, $tt, $t, $onerror) = @_;
4147    
4148        ## TODO: Need to be rewritten.
4149    
4150        my %prop_value;
4151        my $pv;
4152        ## NOTE: Since $onerror is disabled for three invocations below,
4153        ## some informative warning messages (if they are added someday) will not
4154        ## be reported.
4155        ($t, $pv) = $parse_color->($self, $prop_name.'-color', $tt, $t, sub {});
4156        if (defined $pv) {
4157          if ($pv->{$prop_name.'-color'}->[0] eq 'INHERIT') {
4158            return ($t, {$prop_name.'-color' => ['INHERIT'],
4159                         $prop_name.'-style' => ['INHERIT'],
4160                         $prop_name.'-width' => ['INHERIT']});
4161          } else {
4162            $prop_value{$prop_name.'-color'} = $pv->{$prop_name.'-color'};
4163          }
4164        } else {
4165          ($t, $pv) = $Prop->{'border-top-width'}->{parse}
4166              ->($self, $prop_name.'-width', $tt, $t, sub {});
4167          if (defined $pv) {
4168            $prop_value{$prop_name.'-width'} = $pv->{$prop_name.'-width'};
4169          } else {
4170            ($t, $pv) = $Prop->{'border-top-style'}->{parse}
4171                ->($self, $prop_name.'-style', $tt, $t, sub {});
4172            if (defined $pv) {
4173              $prop_value{$prop_name.'-style'} = $pv->{$prop_name.'-style'};
4174            } else {
4175              $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4176                         level => $self->{level}->{must},
4177                         uri => \$self->{href},
4178                         token => $t);
4179              return ($t, undef);
4180            }
4181          }
4182        }
4183    
4184        for (1..2) {
4185          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4186          if ($t->{type} == IDENT_TOKEN) {
4187            my $prop_value = lc $t->{value}; ## TODO: case
4188            if ($border_style_keyword->{$prop_value} and
4189                $self->{prop_value}->{'border-top-style'}->{$prop_value} and
4190                not defined $prop_value{$prop_name.'-style'}) {
4191              $prop_value{$prop_name.'-style'} = ['KEYWORD', $prop_value];
4192              $t = $tt->get_next_token;
4193              next;
4194            } elsif ({thin => 1, medium => 1, thick => 1}->{$prop_value} and
4195                     not defined $prop_value{$prop_name.'-width'}) {
4196              $prop_value{$prop_name.'-width'} = ['KEYWORD', $prop_value];
4197              $t = $tt->get_next_token;
4198              next;
4199            }
4200          }
4201    
4202          undef $pv;
4203          ($t, $pv) = $parse_color->($self, $prop_name.'-color', $tt, $t, $onerror)
4204              if not defined $prop_value{$prop_name.'-color'} and
4205                  {
4206                    IDENT_TOKEN, 1,
4207                    HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4208                    FUNCTION_TOKEN, 1,
4209                  }->{$t->{type}};
4210          if (defined $pv) {
4211            if ($pv->{$prop_name.'-color'}->[0] eq 'INHERIT') {
4212              $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4213                         level => $self->{level}->{must},
4214                         uri => \$self->{href},
4215                         token => $t);
4216            } else {
4217              $prop_value{$prop_name.'-color'} = $pv->{$prop_name.'-color'};
4218            }
4219          } else {
4220            undef $pv;
4221            ($t, $pv) = $Prop->{'border-top-width'}->{parse}
4222                ->($self, $prop_name.'-width',
4223                   $tt, $t, $onerror)
4224                if not defined $prop_value{$prop_name.'-width'} and
4225                    {
4226                      DIMENSION_TOKEN, 1,
4227                      NUMBER_TOKEN, 1,
4228                      IDENT_TOKEN, 1,
4229                      MINUS_TOKEN, 1,
4230                    }->{$t->{type}};
4231            if (defined $pv) {
4232              if ($pv->{$prop_name.'-width'}->[0] eq 'INHERIT') {
4233                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4234                           level => $self->{level}->{must},
4235                           uri => \$self->{href},
4236                           token => $t);
4237              } else {
4238                $prop_value{$prop_name.'-width'} = $pv->{$prop_name.'-width'};
4239              }
4240            } else {
4241              last;
4242            }
4243          }    
4244        }
4245    
4246        $prop_value{$prop_name.'-color'}
4247            ||= $Prop->{$prop_name.'-color'}->{initial};
4248        $prop_value{$prop_name.'-width'}
4249            ||= $Prop->{$prop_name.'-width'}->{initial};
4250        $prop_value{$prop_name.'-style'}
4251            ||= $Prop->{$prop_name.'-style'}->{initial};
4252        
4253        return ($t, \%prop_value);
4254      },
4255      serialize_shorthand => sub {
4256        my $self = shift;
4257    
4258        my $w = $self->border_top_width;
4259        return {} unless length $w;
4260        my $i = $self->get_property_priority ('border-top-width');
4261        my $s = $self->border_top_style;
4262        return {} unless length $s;
4263        return {} unless $i eq $self->get_property_priority ('border-top-style');
4264        my $c = $self->border_top_color;
4265        return {} unless length $c;
4266        return {} unless $i eq $self->get_property_priority ('border-top-color');
4267    
4268        my $v = 0;
4269        $v++ if $w eq 'inherit';
4270        $v++ if $s eq 'inherit';
4271        $v++ if $c eq 'inherit';
4272        if ($v == 3) {
4273          return {'border-top' => ['inherit', $i]};
4274        } elsif ($v) {
4275          return {};
4276        }
4277    
4278        return {'border-top' => [$w . ' ' . $s . ' ' . $c, $i]};
4279      },
4280      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4281    };
4282    $Attr->{border_top} = $Prop->{'border-top'};
4283    
4284    $Prop->{'border-right'} = {
4285      css => 'border-right',
4286      dom => 'border_right',
4287      parse => $Prop->{'border-top'}->{parse},
4288      serialize_shorthand => sub {
4289        my $self = shift;
4290    
4291        my $w = $self->border_right_width;
4292        return {} unless length $w;
4293        my $i = $self->get_property_priority ('border-right-width');
4294        my $s = $self->border_right_style;
4295        return {} unless length $s;
4296        return {} unless $i eq $self->get_property_priority ('border-right-style');
4297        my $c = $self->border_right_color;
4298        return {} unless length $c;
4299        return {} unless $i eq $self->get_property_priority ('border-right-color');
4300    
4301        my $v = 0;
4302        $v++ if $w eq 'inherit';
4303        $v++ if $s eq 'inherit';
4304        $v++ if $c eq 'inherit';
4305        if ($v == 3) {
4306          return {'border-right' => ['inherit', $i]};
4307        } elsif ($v) {
4308          return {};
4309        }
4310    
4311        return {'border-right' => [$w . ' ' . $s . ' ' . $c, $i]};
4312      },
4313      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4314    };
4315    $Attr->{border_right} = $Prop->{'border-right'};
4316    
4317    $Prop->{'border-bottom'} = {
4318      css => 'border-bottom',
4319      dom => 'border_bottom',
4320      parse => $Prop->{'border-top'}->{parse},
4321      serialize_shorthand => sub {
4322        my $self = shift;
4323    
4324        my $w = $self->border_bottom_width;
4325        return {} unless length $w;
4326        my $i = $self->get_property_priority ('border-bottom-width');
4327        my $s = $self->border_bottom_style;
4328        return {} unless length $s;
4329        return {} unless $i eq $self->get_property_priority ('border-bottom-style');
4330        my $c = $self->border_bottom_color;
4331        return {} unless length $c;
4332        return {} unless $i eq $self->get_property_priority ('border-bottom-color');
4333    
4334        my $v = 0;
4335        $v++ if $w eq 'inherit';
4336        $v++ if $s eq 'inherit';
4337        $v++ if $c eq 'inherit';
4338        if ($v == 3) {
4339          return {'border-bottom' => ['inherit', $i]};
4340        } elsif ($v) {
4341          return {};
4342        }
4343    
4344        return {'border-bottom' => [$w . ' ' . $s . ' ' . $c, $i]};
4345      },
4346      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4347    };
4348    $Attr->{border_bottom} = $Prop->{'border-bottom'};
4349    
4350    $Prop->{'border-left'} = {
4351      css => 'border-left',
4352      dom => 'border_left',
4353      parse => $Prop->{'border-top'}->{parse},
4354      serialize_shorthand => sub {
4355        my $self = shift;
4356    
4357        my $w = $self->border_left_width;
4358        return {} unless length $w;
4359        my $i = $self->get_property_priority ('border-left-width');
4360        my $s = $self->border_left_style;
4361        return {} unless length $s;
4362        return {} unless $i eq $self->get_property_priority ('border-left-style');
4363        my $c = $self->border_left_color;
4364        return {} unless length $c;
4365        return {} unless $i eq $self->get_property_priority ('border-left-color');
4366    
4367        my $v = 0;
4368        $v++ if $w eq 'inherit';
4369        $v++ if $s eq 'inherit';
4370        $v++ if $c eq 'inherit';
4371        if ($v == 3) {
4372          return {'border-left' => ['inherit', $i]};
4373        } elsif ($v) {
4374          return {};
4375        }
4376    
4377        return {'border-left' => [$w . ' ' . $s . ' ' . $c, $i]};
4378      },
4379      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4380    };
4381    $Attr->{border_left} = $Prop->{'border-left'};
4382    
4383    ## TODO: -moz-outline -> outline
4384    
4385    $Prop->{outline} = {
4386      css => 'outline',
4387      dom => 'outline',
4388      parse => $Prop->{'border-top'}->{parse},
4389      serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
4390    };
4391    $Attr->{outline} = $Prop->{outline};
4392    
4393    $Prop->{border} = {
4394      css => 'border',
4395      dom => 'border',
4396      parse => sub {
4397        my ($self, $prop_name, $tt, $t, $onerror) = @_;
4398        my $prop_value;
4399        ($t, $prop_value) = $Prop->{'border-top'}->{parse}
4400            ->($self, 'border-top', $tt, $t, $onerror);
4401        return ($t, undef) unless defined $prop_value;
4402        
4403        for (qw/border-right border-bottom border-left/) {
4404          $prop_value->{$_.'-color'} = $prop_value->{'border-top-color'}
4405              if defined $prop_value->{'border-top-color'};
4406          $prop_value->{$_.'-style'} = $prop_value->{'border-top-style'}
4407              if defined $prop_value->{'border-top-style'};
4408          $prop_value->{$_.'-width'} = $prop_value->{'border-top-width'}
4409              if defined $prop_value->{'border-top-width'};
4410        }
4411        return ($t, $prop_value);
4412      },
4413      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4414    };
4415    $Attr->{border} = $Prop->{border};
4416    
4417    $Prop->{margin} = {
4418      css => 'margin',
4419      dom => 'margin',
4420      parse => sub {
4421        my ($self, $prop_name, $tt, $t, $onerror) = @_;
4422    
4423        my %prop_value;
4424    
4425        my $sign = 1;
4426        my $has_sign;
4427        if ($t->{type} == MINUS_TOKEN) {
4428          $t = $tt->get_next_token;
4429          $has_sign = 1;
4430          $sign = -1;
4431        } elsif ($t->{type} == PLUS_TOKEN) {
4432          $t = $tt->get_next_token;
4433          $has_sign = 1;
4434        }
4435    
4436        if ($t->{type} == DIMENSION_TOKEN) {
4437          my $value = $t->{number} * $sign;
4438          my $unit = lc $t->{value}; ## TODO: case
4439          $t = $tt->get_next_token;
4440          if ($length_unit->{$unit}) {
4441            $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
4442          } else {
4443            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4444                       level => $self->{level}->{must},
4445                       uri => \$self->{href},
4446                       token => $t);
4447            return ($t, undef);
4448          }
4449        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4450          my $value = $t->{number} * $sign;
4451          $t = $tt->get_next_token;
4452          $prop_value{'margin-top'} = ['PERCENTAGE', $value];
4453        } elsif ($t->{type} == NUMBER_TOKEN and
4454                 ($self->{unitless_px} or $t->{number} == 0)) {
4455          my $value = $t->{number} * $sign;
4456          $t = $tt->get_next_token;
4457          $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
4458        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4459          my $prop_value = lc $t->{value}; ## TODO: case folding
4460          $t = $tt->get_next_token;
4461          if ($prop_value eq 'auto') {
4462            $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
4463          } elsif ($prop_value eq 'inherit') {
4464            $prop_value{'margin-top'} = ['INHERIT'];
4465            $prop_value{'margin-right'} = $prop_value{'margin-top'};
4466            $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
4467            $prop_value{'margin-left'} = $prop_value{'margin-right'};
4468            return ($t, \%prop_value);
4469          } else {
4470            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4471                       level => $self->{level}->{must},
4472                       uri => \$self->{href},
4473                       token => $t);
4474            return ($t, undef);
4475          }
4476        } else {
4477          $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4478                     level => $self->{level}->{must},
4479                     uri => \$self->{href},
4480                     token => $t);
4481          return ($t, undef);
4482        }
4483        $prop_value{'margin-right'} = $prop_value{'margin-top'};
4484        $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
4485        $prop_value{'margin-left'} = $prop_value{'margin-right'};
4486    
4487        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4488        undef $has_sign;
4489        $sign = 1;
4490        if ($t->{type} == MINUS_TOKEN) {
4491          $t = $tt->get_next_token;
4492          $has_sign = 1;
4493          $sign = -1;
4494        } elsif ($t->{type} == PLUS_TOKEN) {
4495          $t = $tt->get_next_token;
4496          $has_sign = 1;
4497        }
4498    
4499        if ($t->{type} == DIMENSION_TOKEN) {
4500          my $value = $t->{number} * $sign;
4501          my $unit = lc $t->{value}; ## TODO: case
4502          $t = $tt->get_next_token;
4503          if ($length_unit->{$unit}) {
4504            $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
4505          } else {
4506            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4507                       level => $self->{level}->{must},
4508                       uri => \$self->{href},
4509                       token => $t);
4510            return ($t, undef);
4511          }
4512        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4513          my $value = $t->{number} * $sign;
4514          $t = $tt->get_next_token;
4515          $prop_value{'margin-right'} = ['PERCENTAGE', $value];
4516        } elsif ($t->{type} == NUMBER_TOKEN and
4517                 ($self->{unitless_px} or $t->{number} == 0)) {
4518          my $value = $t->{number} * $sign;
4519          $t = $tt->get_next_token;
4520          $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
4521        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4522          my $prop_value = lc $t->{value}; ## TODO: case folding
4523          $t = $tt->get_next_token;
4524          if ($prop_value eq 'auto') {
4525            $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
4526          } else {
4527            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4528                       level => $self->{level}->{must},
4529                       uri => \$self->{href},
4530                       token => $t);
4531            return ($t, undef);
4532          }
4533        } else {
4534          if ($has_sign) {
4535            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4536                       level => $self->{level}->{must},
4537                       uri => \$self->{href},
4538                       token => $t);
4539            return ($t, undef);
4540          }
4541          return ($t, \%prop_value);
4542        }
4543        $prop_value{'margin-left'} = $prop_value{'margin-right'};
4544    
4545        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4546        undef $has_sign;
4547        $sign = 1;
4548        if ($t->{type} == MINUS_TOKEN) {
4549          $t = $tt->get_next_token;
4550          $has_sign = 1;
4551          $sign = -1;
4552        } elsif ($t->{type} == PLUS_TOKEN) {
4553          $t = $tt->get_next_token;
4554          $has_sign = 1;
4555        }
4556    
4557        if ($t->{type} == DIMENSION_TOKEN) {
4558          my $value = $t->{number} * $sign;
4559          my $unit = lc $t->{value}; ## TODO: case
4560          $t = $tt->get_next_token;
4561          if ($length_unit->{$unit}) {
4562            $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
4563          } else {
4564            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4565                       level => $self->{level}->{must},
4566                       uri => \$self->{href},
4567                       token => $t);
4568            return ($t, undef);
4569          }
4570        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4571          my $value = $t->{number} * $sign;
4572          $t = $tt->get_next_token;
4573          $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
4574        } elsif ($t->{type} == NUMBER_TOKEN and
4575                 ($self->{unitless_px} or $t->{number} == 0)) {
4576          my $value = $t->{number} * $sign;
4577          $t = $tt->get_next_token;
4578          $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
4579        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4580          my $prop_value = lc $t->{value}; ## TODO: case folding
4581          $t = $tt->get_next_token;
4582          if ($prop_value eq 'auto') {
4583            $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
4584          } else {
4585            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4586                       level => $self->{level}->{must},
4587                       uri => \$self->{href},
4588                       token => $t);
4589            return ($t, undef);
4590          }
4591        } else {
4592          if ($has_sign) {
4593            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4594                       level => $self->{level}->{must},
4595                       uri => \$self->{href},
4596                       token => $t);
4597            return ($t, undef);
4598          }
4599          return ($t, \%prop_value);
4600        }
4601    
4602        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4603        undef $has_sign;
4604        $sign = 1;
4605        if ($t->{type} == MINUS_TOKEN) {
4606          $t = $tt->get_next_token;
4607          $has_sign = 1;
4608          $sign = -1;
4609        } elsif ($t->{type} == PLUS_TOKEN) {
4610          $t = $tt->get_next_token;
4611          $has_sign = 1;
4612        }
4613    
4614        if ($t->{type} == DIMENSION_TOKEN) {
4615          my $value = $t->{number} * $sign;
4616          my $unit = lc $t->{value}; ## TODO: case
4617          $t = $tt->get_next_token;
4618          if ($length_unit->{$unit}) {
4619            $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
4620          } else {
4621            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4622                       level => $self->{level}->{must},
4623                       uri => \$self->{href},
4624                       token => $t);
4625            return ($t, undef);
4626          }
4627        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4628          my $value = $t->{number} * $sign;
4629          $t = $tt->get_next_token;
4630          $prop_value{'margin-left'} = ['PERCENTAGE', $value];
4631        } elsif ($t->{type} == NUMBER_TOKEN and
4632                 ($self->{unitless_px} or $t->{number} == 0)) {
4633          my $value = $t->{number} * $sign;
4634          $t = $tt->get_next_token;
4635          $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
4636        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4637          my $prop_value = lc $t->{value}; ## TODO: case folding
4638          $t = $tt->get_next_token;
4639          if ($prop_value eq 'auto') {
4640            $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
4641          } else {
4642            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4643                       level => $self->{level}->{must},
4644                       uri => \$self->{href},
4645                       token => $t);
4646            return ($t, undef);
4647          }
4648        } else {
4649          if ($has_sign) {
4650            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4651                       level => $self->{level}->{must},
4652                       uri => \$self->{href},
4653                       token => $t);
4654            return ($t, undef);
4655          }
4656          return ($t, \%prop_value);
4657        }
4658    
4659        return ($t, \%prop_value);
4660      },
4661      serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
4662    };
4663    $Attr->{margin} = $Prop->{margin};
4664    
4665    $Prop->{padding} = {
4666      css => 'padding',
4667      dom => 'padding',
4668      parse => sub {
4669        my ($self, $prop_name, $tt, $t, $onerror) = @_;
4670    
4671        my %prop_value;
4672    
4673        my $sign = 1;
4674        if ($t->{type} == MINUS_TOKEN) {
4675          $t = $tt->get_next_token;
4676          $sign = -1;
4677        }
4678    
4679        if ($t->{type} == DIMENSION_TOKEN) {
4680          my $value = $t->{number} * $sign;
4681          my $unit = lc $t->{value}; ## TODO: case
4682          $t = $tt->get_next_token;
4683          if ($length_unit->{$unit} and $value >= 0) {
4684            $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
4685          } else {
4686            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4687                       level => $self->{level}->{must},
4688                       uri => \$self->{href},
4689                       token => $t);
4690            return ($t, undef);
4691          }
4692        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4693          my $value = $t->{number} * $sign;
4694          $t = $tt->get_next_token;
4695          $prop_value{'padding-top'} = ['PERCENTAGE', $value];
4696          unless ($value >= 0) {
4697            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4698                       level => $self->{level}->{must},
4699                       uri => \$self->{href},
4700                       token => $t);
4701            return ($t, undef);
4702          }
4703        } elsif ($t->{type} == NUMBER_TOKEN and
4704                 ($self->{unitless_px} or $t->{number} == 0)) {
4705          my $value = $t->{number} * $sign;
4706          $t = $tt->get_next_token;
4707          $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
4708          unless ($value >= 0) {
4709            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4710                       level => $self->{level}->{must},
4711                       uri => \$self->{href},
4712                       token => $t);
4713            return ($t, undef);
4714          }
4715        } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4716          my $prop_value = lc $t->{value}; ## TODO: case folding
4717          $t = $tt->get_next_token;
4718          if ($prop_value eq 'inherit') {
4719            $prop_value{'padding-top'} = ['INHERIT'];
4720            $prop_value{'padding-right'} = $prop_value{'padding-top'};
4721            $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
4722            $prop_value{'padding-left'} = $prop_value{'padding-right'};
4723            return ($t, \%prop_value);
4724          } else {
4725            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4726                       level => $self->{level}->{must},
4727                       uri => \$self->{href},
4728                       token => $t);
4729            return ($t, undef);
4730          }
4731        } else {
4732          $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4733                     level => $self->{level}->{must},
4734                     uri => \$self->{href},
4735                     token => $t);
4736          return ($t, undef);
4737        }
4738        $prop_value{'padding-right'} = $prop_value{'padding-top'};
4739        $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
4740        $prop_value{'padding-left'} = $prop_value{'padding-right'};
4741    
4742        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4743        $sign = 1;
4744        if ($t->{type} == MINUS_TOKEN) {
4745          $t = $tt->get_next_token;
4746          $sign = -1;
4747        }
4748    
4749        if ($t->{type} == DIMENSION_TOKEN) {
4750          my $value = $t->{number} * $sign;
4751          my $unit = lc $t->{value}; ## TODO: case
4752          $t = $tt->get_next_token;
4753          if ($length_unit->{$unit} and $value >= 0) {
4754            $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
4755          } else {
4756            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4757                       level => $self->{level}->{must},
4758                       uri => \$self->{href},
4759                       token => $t);
4760            return ($t, undef);
4761          }
4762        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4763          my $value = $t->{number} * $sign;
4764          $t = $tt->get_next_token;
4765          $prop_value{'padding-right'} = ['PERCENTAGE', $value];
4766          unless ($value >= 0) {
4767            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4768                       level => $self->{level}->{must},
4769                       uri => \$self->{href},
4770                       token => $t);
4771            return ($t, undef);
4772          }
4773        } elsif ($t->{type} == NUMBER_TOKEN and
4774                 ($self->{unitless_px} or $t->{number} == 0)) {
4775          my $value = $t->{number} * $sign;
4776          $t = $tt->get_next_token;
4777          $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
4778          unless ($value >= 0) {
4779            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4780                       level => $self->{level}->{must},
4781                       uri => \$self->{href},
4782                       token => $t);
4783            return ($t, undef);
4784          }
4785        } else {
4786          if ($sign < 0) {
4787            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4788                       level => $self->{level}->{must},
4789                       uri => \$self->{href},
4790                       token => $t);
4791            return ($t, undef);
4792          }
4793          return ($t, \%prop_value);
4794        }
4795        $prop_value{'padding-left'} = $prop_value{'padding-right'};
4796    
4797        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4798        $sign = 1;
4799        if ($t->{type} == MINUS_TOKEN) {
4800          $t = $tt->get_next_token;
4801          $sign = -1;
4802        }
4803    
4804        if ($t->{type} == DIMENSION_TOKEN) {
4805          my $value = $t->{number} * $sign;
4806          my $unit = lc $t->{value}; ## TODO: case
4807          $t = $tt->get_next_token;
4808          if ($length_unit->{$unit} and $value >= 0) {
4809            $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
4810          } else {
4811            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4812                       level => $self->{level}->{must},
4813                       uri => \$self->{href},
4814                       token => $t);
4815            return ($t, undef);
4816          }
4817        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4818          my $value = $t->{number} * $sign;
4819          $t = $tt->get_next_token;
4820          $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
4821          unless ($value >= 0) {
4822            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4823                       level => $self->{level}->{must},
4824                       uri => \$self->{href},
4825                       token => $t);
4826            return ($t, undef);
4827          }
4828        } elsif ($t->{type} == NUMBER_TOKEN and
4829                 ($self->{unitless_px} or $t->{number} == 0)) {
4830          my $value = $t->{number} * $sign;
4831          $t = $tt->get_next_token;
4832          $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
4833          unless ($value >= 0) {
4834            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4835                       level => $self->{level}->{must},
4836                       uri => \$self->{href},
4837                       token => $t);
4838            return ($t, undef);
4839          }
4840        } else {
4841          if ($sign < 0) {
4842            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4843                       level => $self->{level}->{must},
4844                       uri => \$self->{href},
4845                       token => $t);
4846            return ($t, undef);
4847          }
4848          return ($t, \%prop_value);
4849        }
4850    
4851        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4852        $sign = 1;
4853        if ($t->{type} == MINUS_TOKEN) {
4854          $t = $tt->get_next_token;
4855          $sign = -1;
4856        }
4857    
4858        if ($t->{type} == DIMENSION_TOKEN) {
4859          my $value = $t->{number} * $sign;
4860          my $unit = lc $t->{value}; ## TODO: case
4861          $t = $tt->get_next_token;
4862          if ($length_unit->{$unit} and $value >= 0) {
4863            $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
4864          } else {
4865            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4866                       level => $self->{level}->{must},
4867                       uri => \$self->{href},
4868                       token => $t);
4869            return ($t, undef);
4870          }
4871        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4872          my $value = $t->{number} * $sign;
4873          $t = $tt->get_next_token;
4874          $prop_value{'padding-left'} = ['PERCENTAGE', $value];
4875          unless ($value >= 0) {
4876            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4877                       level => $self->{level}->{must},
4878                       uri => \$self->{href},
4879                       token => $t);
4880            return ($t, undef);
4881          }
4882        } elsif ($t->{type} == NUMBER_TOKEN and
4883                 ($self->{unitless_px} or $t->{number} == 0)) {
4884          my $value = $t->{number} * $sign;
4885          $t = $tt->get_next_token;
4886          $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
4887          unless ($value >= 0) {
4888            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4889                       level => $self->{level}->{must},
4890                       uri => \$self->{href},
4891                       token => $t);
4892            return ($t, undef);
4893          }
4894        } else {
4895          if ($sign < 0) {
4896            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4897                       level => $self->{level}->{must},
4898                       uri => \$self->{href},
4899                       token => $t);
4900            return ($t, undef);
4901          }
4902          return ($t, \%prop_value);
4903        }
4904    
4905        return ($t, \%prop_value);
4906      },
4907      serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
4908    };
4909    $Attr->{padding} = $Prop->{padding};
4910    
4911    $Prop->{'border-spacing'} = {
4912      css => 'border-spacing',
4913      dom => 'border_spacing',
4914      parse => sub {
4915        my ($self, $prop_name, $tt, $t, $onerror) = @_;
4916    
4917        my %prop_value;
4918        my $has_sign;
4919        my $sign = 1;
4920        if ($t->{type} == MINUS_TOKEN) {
4921          $t = $tt->get_next_token;
4922          $has_sign = 1;
4923          $sign = -1;
4924        } elsif ($t->{type} == PLUS_TOKEN) {
4925          $t = $tt->get_next_token;
4926          $has_sign = 1;
4927        }
4928    
4929        if ($t->{type} == DIMENSION_TOKEN) {
4930          my $value = $t->{number} * $sign;
4931          my $unit = lc $t->{value}; ## TODO: case
4932          if ($length_unit->{$unit} and $value >= 0) {
4933            $t = $tt->get_next_token;
4934            $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, $unit];
4935          } else {
4936            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4937                       level => $self->{level}->{must},
4938                       uri => \$self->{href},
4939                       token => $t);
4940            return ($t, undef);
4941          }
4942        } elsif ($t->{type} == NUMBER_TOKEN and
4943                 ($self->{unitless_px} or $t->{number} == 0)) {
4944          my $value = $t->{number} * $sign;
4945          $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, 'px'];
4946          if ($value >= 0) {
4947            $t = $tt->get_next_token;
4948          } else {
4949            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4950                       level => $self->{level}->{must},
4951                       uri => \$self->{href},
4952                       token => $t);
4953            return ($t, undef);
4954          }
4955        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4956          my $prop_value = lc $t->{value}; ## TODO: case folding
4957          if ($prop_value eq 'inherit') {
4958            $t = $tt->get_next_token;
4959            $prop_value{'-manakai-border-spacing-x'} = ['INHERIT'];
4960            $prop_value{'-manakai-border-spacing-y'}
4961                = $prop_value{'-manakai-border-spacing-x'};
4962            return ($t, \%prop_value);
4963          } else {
4964            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4965                       level => $self->{level}->{must},
4966                       uri => \$self->{href},
4967                       token => $t);
4968            return ($t, undef);
4969          }
4970        } else {
4971          $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4972                     level => $self->{level}->{must},
4973                     uri => \$self->{href},
4974                     token => $t);
4975          return ($t, undef);
4976        }
4977        $prop_value{'-manakai-border-spacing-y'}
4978            = $prop_value{'-manakai-border-spacing-x'};
4979    
4980        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4981        undef $has_sign;
4982        $sign = 1;
4983        if ($t->{type} == MINUS_TOKEN) {
4984          $t = $tt->get_next_token;
4985          $has_sign = 1;
4986          $sign = -1;
4987        } elsif ($t->{type} == PLUS_TOKEN) {
4988          $t = $tt->get_next_token;
4989          $has_sign = 1;
4990        }
4991    
4992        if ($t->{type} == DIMENSION_TOKEN) {
4993          my $value = $t->{number} * $sign;
4994          my $unit = lc $t->{value}; ## TODO: case
4995          if ($length_unit->{$unit} and $value >= 0) {
4996            $t = $tt->get_next_token;
4997            $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, $unit];
4998          } else {
4999            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5000                       level => $self->{level}->{must},
5001                       uri => \$self->{href},
5002                       token => $t);
5003            return ($t, undef);
5004          }
5005        } elsif ($t->{type} == NUMBER_TOKEN and
5006                 ($self->{unitless_px} or $t->{number} == 0)) {
5007          my $value = $t->{number} * $sign;
5008          $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, 'px'];
5009          if ($value >= 0) {
5010            $t = $tt->get_next_token;
5011          } else {
5012            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5013                       level => $self->{level}->{must},
5014                       uri => \$self->{href},
5015                       token => $t);
5016            return ($t, undef);
5017          }
5018        } else {
5019          if ($has_sign) {
5020            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5021                       level => $self->{level}->{must},
5022                       uri => \$self->{href},
5023                       token => $t);
5024            return ($t, undef);
5025          }
5026          return ($t, \%prop_value);
5027        }
5028    
5029        return ($t, \%prop_value);
5030      },
5031      serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
5032          ->{serialize_multiple},
5033    };
5034    $Attr->{border_spacing} = $Prop->{'border-spacing'};
5035    
5036    ## NOTE: See <http://suika.fam.cx/gate/2005/sw/background-position> for
5037    ## browser compatibility problems.
5038    $Prop->{'background-position'} = {
5039      css => 'background-position',
5040      dom => 'background_position',
5041      parse => sub {
5042        my ($self, $prop_name, $tt, $t, $onerror) = @_;
5043    
5044        my %prop_value;
5045    
5046        my $sign = 1;
5047        my $has_sign;
5048        if ($t->{type} == MINUS_TOKEN) {
5049          $t = $tt->get_next_token;
5050          $has_sign = 1;
5051          $sign = -1;
5052        } elsif ($t->{type} == PLUS_TOKEN) {
5053          $t = $tt->get_next_token;
5054          $has_sign = 1;
5055        }
5056    
5057        if ($t->{type} == DIMENSION_TOKEN) {
5058          my $value = $t->{number} * $sign;
5059          my $unit = lc $t->{value}; ## TODO: case
5060          if ($length_unit->{$unit}) {
5061            $t = $tt->get_next_token;
5062            $prop_value{'background-position-x'} = ['DIMENSION', $value, $unit];
5063            $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5064          } else {
5065            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5066                       level => $self->{level}->{must},
5067                       uri => \$self->{href},
5068                       token => $t);
5069            return ($t, undef);
5070          }
5071        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5072          my $value = $t->{number} * $sign;
5073          $t = $tt->get_next_token;
5074          $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
5075          $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5076        } elsif ($t->{type} == NUMBER_TOKEN and
5077                 ($self->{unitless_px} or $t->{number} == 0)) {
5078          my $value = $t->{number} * $sign;
5079          $t = $tt->get_next_token;
5080          $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
5081          $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5082        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5083          my $prop_value = lc $t->{value}; ## TODO: case folding
5084          if ($prop_value eq 'left' or $prop_value eq 'right') {
5085            $t = $tt->get_next_token;
5086            $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5087            $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5088          } elsif ($prop_value eq 'center') {
5089            $t = $tt->get_next_token;
5090            $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5091    
5092            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5093            if ($t->{type} == IDENT_TOKEN) {
5094              my $prop_value = lc $t->{value}; ## TODO: case folding
5095              if ($prop_value eq 'left' or $prop_value eq 'right') {
5096                $prop_value{'background-position-y'}
5097                    = $prop_value{'background-position-x'};
5098                $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5099                $t = $tt->get_next_token;
5100                return ($t, \%prop_value);
5101              }
5102            } else {
5103              $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5104            }
5105          } elsif ($prop_value eq 'top' or $prop_value eq 'bottom') {
5106            $t = $tt->get_next_token;
5107            $prop_value{'background-position-y'} = ['KEYWORD', $prop_value];
5108    
5109            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5110            if ($t->{type} == IDENT_TOKEN) {
5111              my $prop_value = lc $t->{value}; ## TODO: case folding
5112              if ({left => 1, center => 1, right => 1}->{$prop_value}) {
5113                $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5114                $t = $tt->get_next_token;
5115                return ($t, \%prop_value);
5116              }
5117            }
5118            $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
5119            return ($t, \%prop_value);
5120          } elsif ($prop_value eq 'inherit') {
5121            $t = $tt->get_next_token;
5122            $prop_value{'background-position-x'} = ['INHERIT'];
5123            $prop_value{'background-position-y'} = ['INHERIT'];
5124            return ($t, \%prop_value);
5125          } else {
5126            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5127                       level => $self->{level}->{must},
5128                       uri => \$self->{href},
5129                       token => $t);
5130            return ($t, undef);
5131          }
5132        } else {
5133          $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5134                     level => $self->{level}->{must},
5135                     uri => \$self->{href},
5136                     token => $t);
5137          return ($t, undef);
5138        }
5139    
5140        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5141        undef $has_sign;
5142        $sign = 1;
5143        if ($t->{type} == MINUS_TOKEN) {
5144          $t = $tt->get_next_token;
5145          $has_sign = 1;
5146          $sign = -1;
5147        } elsif ($t->{type} == PLUS_TOKEN) {
5148          $t = $tt->get_next_token;
5149          $has_sign = 1;
5150        }
5151    
5152        if ($t->{type} == DIMENSION_TOKEN) {
5153          my $value = $t->{number} * $sign;
5154          my $unit = lc $t->{value}; ## TODO: case
5155          if ($length_unit->{$unit}) {
5156            $t = $tt->get_next_token;
5157            $prop_value{'background-position-y'} = ['DIMENSION', $value, $unit];
5158          } else {
5159            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5160                       level => $self->{level}->{must},
5161                       uri => \$self->{href},
5162                       token => $t);
5163            return ($t, undef);
5164          }
5165        } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5166          my $value = $t->{number} * $sign;
5167          $t = $tt->get_next_token;
5168          $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5169        } elsif ($t->{type} == NUMBER_TOKEN and
5170                 ($self->{unitless_px} or $t->{number} == 0)) {
5171          my $value = $t->{number} * $sign;
5172          $t = $tt->get_next_token;
5173          $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5174        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5175          my $value = lc $t->{value}; ## TODO: case
5176          if ({top => 1, center => 1, bottom => 1}->{$value}) {
5177            $prop_value{'background-position-y'} = ['KEYWORD', $value];
5178            $t = $tt->get_next_token;
5179          }
5180        } else {
5181          if ($has_sign) {
5182            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5183                       level => $self->{level}->{must},
5184                       uri => \$self->{href},
5185                       token => $t);
5186            return ($t, undef);
5187          }
5188          return ($t, \%prop_value);
5189        }
5190    
5191        return ($t, \%prop_value);
5192      },
5193      serialize_shorthand => sub {
5194        my $self = shift;
5195    
5196        my $r = {};
5197    
5198        my $x = $self->background_position_x;
5199        my $y = $self->background_position_y;
5200        my $xi = $self->get_property_priority ('background-position-x');
5201        my $yi = $self->get_property_priority ('background-position-y');
5202        if (length $x) {
5203          if (length $y) {
5204            if ($xi eq $yi) {
5205              if ($x eq 'inherit') {
5206                if ($y eq 'inherit') {
5207                  $r->{'background-position'} = ['inherit', $xi];
5208                } else {
5209                  $r->{'background-position-x'} = [$x, $xi];
5210                  $r->{'background-position-y'} = [$y, $yi];
5211                }
5212              } elsif ($y eq 'inherit') {
5213                $r->{'background-position-x'} = [$x, $xi];
5214                $r->{'background-position-y'} = [$y, $yi];
5215              } else {
5216                $r->{'background-position'} = [$x . ' ' . $y, $xi];
5217              }
5218            } else {
5219              $r->{'background-position-x'} = [$x, $xi];
5220              $r->{'background-position-y'} = [$y, $yi];
5221            }
5222          } else {
5223            $r->{'background-position-x'} = [$x, $xi];
5224          }
5225        } else {
5226          if (length $y) {
5227            $r->{'background-position-y'} = [$y, $yi];
5228          } else {
5229            #
5230          }
5231        }
5232    
5233        return $r;
5234      },
5235      serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
5236    };
5237    $Attr->{background_position} = $Prop->{'background-position'};
5238    
5239    $Prop->{background} = {
5240      css => 'background',
5241      dom => 'background',
5242      parse => sub {
5243        my ($self, $prop_name, $tt, $t, $onerror) = @_;
5244        my %prop_value;
5245        B: for (1..5) {
5246          my $has_sign;
5247          my $sign = 1;
5248          if ($t->{type} == MINUS_TOKEN) {
5249            $sign = -1;
5250            $has_sign = 1;
5251            $t = $tt->get_next_token;
5252          } elsif ($t->{type} == PLUS_TOKEN) {
5253            $has_sign = 1;
5254            $t = $tt->get_next_token;
5255          }
5256    
5257          if (not $has_sign and $t->{type} == IDENT_TOKEN) {
5258            my $value = lc $t->{value}; ## TODO: case
5259            if ($Prop->{'background-repeat'}->{keyword}->{$value} and
5260                $self->{prop_value}->{'background-repeat'}->{$value} and
5261                not defined $prop_value{'background-repeat'}) {
5262              $prop_value{'background-repeat'} = ['KEYWORD', $value];
5263              $t = $tt->get_next_token;
5264            } elsif ($Prop->{'background-attachment'}->{keyword}->{$value} and
5265                     $self->{prop_value}->{'background-attachment'}->{$value} and
5266                     not defined $prop_value{'background-attachment'}) {
5267              $prop_value{'background-attachment'} = ['KEYWORD', $value];
5268              $t = $tt->get_next_token;
5269            } elsif ($value eq 'none' and
5270                     not defined $prop_value{'background-image'}) {
5271              $prop_value{'background-image'} = ['KEYWORD', $value];
5272              $t = $tt->get_next_token;
5273            } elsif ({left => 1, center => 1, right => 1}->{$value} and
5274                     not defined $prop_value{'background-position-x'}) {
5275              $prop_value{'background-position-x'} = ['KEYWORD', $value];
5276              $t = $tt->get_next_token;
5277              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5278              my $sign = 1;
5279              my $has_sign;
5280              if ($t->{type} == MINUS_TOKEN) {
5281                $sign = -1;
5282                $has_sign = 1;
5283                $t = $tt->get_next_token;
5284              } elsif ($t->{type} == PLUS_TOKEN) {
5285                $has_sign = 1;
5286                $t = $tt->get_next_token;
5287              }
5288              if (not $has_sign and $t->{type} == IDENT_TOKEN) {
5289                my $value = lc $t->{value}; ## TODO: case
5290                if ({top => 1, bottom => 1, center => 1}->{$value}) {
5291                  $prop_value{'background-position-y'} = ['KEYWORD', $value];
5292                  $t = $tt->get_next_token;
5293                } elsif ($prop_value{'background-position-x'}->[1] eq 'center' and
5294                         $value eq 'left' or $value eq 'right') {
5295                  $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5296                  $prop_value{'background-position-x'} = ['KEYWORD', $value];
5297                  $t = $tt->get_next_token;
5298                } else {
5299                  $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5300                }
5301              } elsif ($t->{type} == DIMENSION_TOKEN) {
5302                my $value = $t->{number} * $sign;
5303                my $unit = lc $t->{value}; ## TODO: case
5304                $t = $tt->get_next_token;
5305                if ($length_unit->{$unit}) {
5306                  $prop_value{'background-position-y'}
5307                      = ['DIMENSION', $value, $unit];
5308                } else {
5309                  $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5310                             level => $self->{level}->{must},
5311                             uri => \$self->{href},
5312                             token => $t);
5313                  return ($t, undef);
5314                }
5315              } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5316                my $value = $t->{number} * $sign;
5317                $t = $tt->get_next_token;
5318                $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5319              } elsif ($t->{type} == NUMBER_TOKEN and
5320                       ($self->{unitless_px} or $t->{number} == 0)) {
5321                my $value = $t->{number} * $sign;
5322                $t = $tt->get_next_token;
5323                $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5324              } elsif ($has_sign) {
5325                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5326                           level => $self->{level}->{must},
5327                           uri => \$self->{href},
5328                           token => $t);
5329                return ($t, undef);
5330              } else {
5331                $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5332              }
5333            } elsif (($value eq 'top' or $value eq 'bottom') and
5334                     not defined $prop_value{'background-position-y'}) {
5335              $prop_value{'background-position-y'} = ['KEYWORD', $value];
5336              $t = $tt->get_next_token;
5337              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5338              if ($t->{type} == IDENT_TOKEN and ## TODO: case
5339                  {
5340                    left => 1, center => 1, right => 1,
5341                  }->{my $value = lc $t->{value}}) {
5342                $prop_value{'background-position-x'} = ['KEYWORD', $value];
5343                $t = $tt->get_next_token;
5344              } else {
5345                $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
5346              }
5347            } elsif ($value eq 'inherit' and not keys %prop_value) {
5348              $prop_value{'background-color'} =
5349              $prop_value{'background-image'} =
5350              $prop_value{'background-repeat'} =
5351              $prop_value{'background-attachment'} =
5352              $prop_value{'background-position-x'} =
5353              $prop_value{'background-position-y'} = ['INHERIT'];
5354              $t = $tt->get_next_token;
5355              return ($t, \%prop_value);
5356            } elsif (not defined $prop_value{'background-color'} or
5357                     not keys %prop_value) {
5358              ($t, my $pv) = $parse_color->($self, 'background', $tt, $t,
5359                                            $onerror);
5360              if (defined $pv) {
5361                $prop_value{'background-color'} = $pv->{background};
5362              } else {
5363                ## NOTE: An error should already be raiased.
5364                return ($t, undef);
5365              }
5366            }
5367          } elsif (($t->{type} == DIMENSION_TOKEN or
5368                    $t->{type} == PERCENTAGE_TOKEN or
5369                    ($t->{type} == NUMBER_TOKEN and
5370                     ($t->{unitless_px} or $t->{number} == 0))) and
5371                   not defined $prop_value{'background-position-x'}) {
5372            if ($t->{type} == DIMENSION_TOKEN) {
5373              my $value = $t->{number} * $sign;
5374              my $unit = lc $t->{value}; ## TODO: case
5375              $t = $tt->get_next_token;
5376              if ($length_unit->{$unit}) {
5377                $prop_value{'background-position-x'}
5378                    = ['DIMENSION', $value, $unit];
5379              } else {
5380                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5381                           level => $self->{level}->{must},
5382                           uri => \$self->{href},
5383                           token => $t);
5384                return ($t, undef);
5385              }
5386            } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5387              my $value = $t->{number} * $sign;
5388              $t = $tt->get_next_token;
5389              $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
5390            } elsif ($t->{type} == NUMBER_TOKEN and
5391                     ($self->{unitless_px} or $t->{number} == 0)) {
5392              my $value = $t->{number} * $sign;
5393              $t = $tt->get_next_token;
5394              $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
5395            } else {
5396              ## NOTE: Should not be happened.
5397              last B;
5398            }
5399            
5400            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5401            if ($t->{type} == MINUS_TOKEN) {
5402              $sign = -1;
5403              $has_sign = 1;
5404              $t = $tt->get_next_token;
5405            } elsif ($t->{type} == PLUS_TOKEN) {
5406              $has_sign = 1;
5407              $t = $tt->get_next_token;
5408            } else {
5409              undef $has_sign;
5410              $sign = 1;
5411            }
5412    
5413            if ($t->{type} == DIMENSION_TOKEN) {
5414              my $value = $t->{number} * $sign;
5415              my $unit = lc $t->{value}; ## TODO: case
5416              $t = $tt->get_next_token;
5417              if ($length_unit->{$unit}) {
5418                $prop_value{'background-position-y'}
5419                    = ['DIMENSION', $value, $unit];
5420              } else {
5421                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5422                           level => $self->{level}->{must},
5423                           uri => \$self->{href},
5424                           token => $t);
5425                return ($t, undef);
5426              }
5427            } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5428              my $value = $t->{number} * $sign;
5429              $t = $tt->get_next_token;
5430              $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5431            } elsif ($t->{type} == NUMBER_TOKEN and
5432                     ($self->{unitless_px} or $t->{number} == 0)) {
5433              my $value = $t->{number} * $sign;
5434              $t = $tt->get_next_token;
5435              $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5436            } elsif ($t->{type} == IDENT_TOKEN) {
5437              my $value = lc $t->{value}; ## TODO: case
5438              if ({top => 1, center => 1, bottom => 1}->{$value}) {
5439                $prop_value{'background-position-y'} = ['KEYWORD', $value];
5440                $t = $tt->get_next_token;
5441              } else {
5442                $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5443              }
5444            } else {
5445              $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5446              if ($has_sign) {
5447                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5448                           level => $self->{level}->{must},
5449                           uri => \$self->{href},
5450                           token => $t);
5451                return ($t, undef);
5452              }
5453            }
5454          } elsif (not $has_sign and
5455                   $t->{type} == URI_TOKEN and
5456                   not defined $prop_value{'background-image'}) {
5457            $prop_value{'background-image'}
5458                = ['URI', $t->{value}, \($self->{base_uri})];
5459            $t = $tt->get_next_token;
5460          } else {
5461            if (keys %prop_value and not $has_sign) {
5462              last B;
5463            } else {
5464              $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5465                         level => $self->{level}->{must},
5466                         uri => \$self->{href},
5467                         token => $t);
5468              return ($t, undef);
5469            }
5470          }
5471    
5472          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5473        } # B
5474    
5475        $prop_value{$_} ||= $Prop->{$_}->{initial}
5476            for qw/background-image background-attachment background-repeat
5477                   background-color background-position-x background-position-y/;
5478    
5479        return ($t, \%prop_value);
5480      },
5481    ## TODO: background: #fff does not work.
5482      serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
5483    };
5484    $Attr->{background} = $Prop->{background};
5485    
5486    $Prop->{font} = {
5487      css => 'font',
5488      dom => 'font',
5489      parse => sub {
5490        my ($self, $prop_name, $tt, $t, $onerror) = @_;
5491    
5492        my %prop_value;
5493    
5494        A: for (1..3) {
5495          if ($t->{type} == IDENT_TOKEN) {
5496            my $value = lc $t->{value}; ## TODO: case
5497            if ($value eq 'normal') {
5498              $t = $tt->get_next_token;
5499            } elsif ($Prop->{'font-style'}->{keyword}->{$value} and
5500                     $self->{prop_value}->{'font-style'}->{$value} and
5501                     not defined $prop_value{'font-style'}) {
5502              $prop_value{'font-style'} = ['KEYWORD', $value];
5503              $t = $tt->get_next_token;
5504            } elsif ($Prop->{'font-variant'}->{keyword}->{$value} and
5505                     $self->{prop_value}->{'font-variant'}->{$value} and
5506                     not defined $prop_value{'font-variant'}) {
5507              $prop_value{'font-variant'} = ['KEYWORD', $value];
5508              $t = $tt->get_next_token;
5509            } elsif ({normal => 1, bold => 1,
5510                      bolder => 1, lighter => 1}->{$value} and
5511                     not defined $prop_value{'font-weight'}) {
5512              $prop_value{'font-weight'} = ['KEYWORD', $value];
5513              $t = $tt->get_next_token;
5514            } elsif ($value eq 'inherit' and 0 == keys %prop_value) {
5515              $t = $tt->get_next_token;
5516              return ($t, {'font-style' => ['INHERIT'],
5517                           'font-variant' => ['INHERIT'],
5518                           'font-weight' => ['INHERIT'],
5519                           'font-size' => ['INHERIT'],
5520                           'font-size-adjust' => ['INHERIT'],
5521                           'font-stretch' => ['INHERIT'],
5522                           'line-height' => ['INHERIT'],
5523                           'font-family' => ['INHERIT']});
5524            } elsif ({
5525                      caption => 1, icon => 1, menu => 1,
5526                      'message-box' => 1, 'small-caption' => 1, 'status-bar' => 1,
5527                     }->{$value} and 0 == keys %prop_value) {
5528              $t = $tt->get_next_token;
5529              return ($t, $self->{get_system_font}->($self, $value, {
5530                'font-style' => $Prop->{'font-style'}->{initial},
5531                'font-variant' => $Prop->{'font-variant'}->{initial},
5532                'font-weight' => $Prop->{'font-weight'}->{initial},
5533                'font-size' => $Prop->{'font-size'}->{initial},
5534                'font-size-adjust' => $Prop->{'font-size-adjust'}->{initial},
5535                'font-stretch' => $Prop->{'font-stretch'}->{initial},
5536                'line-height' => $Prop->{'line-height'}->{initial},
5537                'font-family' => ['FONT', ['KEYWORD', '-manakai-'.$value]],
5538              }));
5539            } else {
5540              if (keys %prop_value) {
5541                last A;
5542              } else {
5543                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5544                           level => $self->{level}->{must},
5545                           uri => \$self->{href},
5546                           token => $t);
5547                return ($t, undef);
5548              }
5549            }
5550          } elsif ($t->{type} == NUMBER_TOKEN) {
5551            if ({100 => 1, 200 => 1, 300 => 1, 400 => 1, 500 => 1,
5552                 600 => 1, 700 => 1, 800 => 1, 900 => 1}->{$t->{number}}) {
5553              $prop_value{'font-weight'} = ['WEIGHT', $t->{number}, 0];
5554              $t = $tt->get_next_token;
5555            } else {
5556              last A;
5557            }
5558          } elsif ($t->{type} == PLUS_TOKEN) {
5559            $t = $tt->get_next_token;
5560            if ($t->{type} == NUMBER_TOKEN) {
5561              if ({100 => 1, 200 => 1, 300 => 1, 400 => 1, 500 => 1,
5562                   600 => 1, 700 => 1, 800 => 1, 900 => 1}->{$t->{number}}) {
5563                $prop_value{'font-weight'} = ['WEIGHT', $t->{number}, 0];
5564                $t = $tt->get_next_token;
5565              } else {
5566                ## NOTE: <'font-size'> or invalid
5567                last A;
5568              }
5569            } elsif ($t->{type} == DIMENSION_TOKEN or
5570                     $t->{type} == PERCENTAGE_TOKEN) {
5571              ## NOTE: <'font-size'> or invalid
5572              last A;
5573            } else {
5574              $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5575                         level => $self->{level}->{must},
5576                         uri => \$self->{href},
5577                         token => $t);
5578              return ($t, undef);
5579            }
5580          } else {
5581            last A;
5582          }
5583    
5584          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5585        } # A
5586        
5587        for (qw/font-style font-variant font-weight/) {
5588          $prop_value{$_} = $Prop->{$_}->{initial} unless defined $prop_value{$_};
5589        }
5590          
5591        ($t, my $pv) = $Prop->{'font-size'}->{parse}
5592            ->($self, 'font', $tt, $t, $onerror);
5593        return ($t, undef) unless defined $pv;
5594        if ($pv->{font}->[0] eq 'INHERIT') {
5595          $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5596                     level => $self->{level}->{must},
5597                     uri => \$self->{href},
5598                     token => $t);
5599          return ($t, undef);
5600        }
5601        $prop_value{'font-size'} = $pv->{font};
5602    
5603        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5604        if ($t->{type} == DELIM_TOKEN and $t->{value} eq '/') {
5605          $t = $tt->get_next_token;
5606          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5607          ($t, my $pv) = $Prop->{'line-height'}->{parse}
5608              ->($self, 'font', $tt, $t, $onerror);
5609          return ($t, undef) unless defined $pv;
5610          if ($pv->{font}->[0] eq 'INHERIT') {
5611            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5612                       level => $self->{level}->{must},
5613                       uri => \$self->{href},
5614                       token => $t);
5615            return ($t, undef);
5616          }
5617          $prop_value{'line-height'} = $pv->{font};
5618          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5619        } else {
5620          $prop_value{'line-height'} = $Prop->{'line-height'}->{initial};
5621        }
5622    
5623        undef $pv;
5624        ($t, $pv) = $Prop->{'font-family'}->{parse}
5625            ->($self, 'font', $tt, $t, $onerror);
5626        return ($t, undef) unless defined $pv;
5627        $prop_value{'font-family'} = $pv->{font};
5628    
5629        $prop_value{'font-size-adjust'} = $Prop->{'font-size-adjust'}->{initial};
5630        $prop_value{'font-stretch'} = $Prop->{'font-stretch'}->{initial};
5631    
5632        return ($t, \%prop_value);
5633      },
5634      serialize_shorthand => sub {
5635        my $self = shift;
5636        
5637        local $Error::Depth = $Error::Depth + 1;
5638        my $style = $self->font_style;
5639        my $i = $self->get_property_priority ('font-style');
5640        return {} unless length $style;
5641        my $variant = $self->font_variant;
5642        return {} unless length $variant;
5643        return {} if $i ne $self->get_property_priority ('font-variant');
5644        my $weight = $self->font_weight;
5645        return {} unless length $weight;
5646        return {} if $i ne $self->get_property_priority ('font-weight');
5647        my $size = $self->font_size;
5648        return {} unless length $size;
5649        return {} if $i ne $self->get_property_priority ('font-size');
5650        my $height = $self->line_height;
5651        return {} unless length $height;
5652        return {} if $i ne $self->get_property_priority ('line-height');
5653        my $family = $self->font_family;
5654        return {} unless length $family;
5655        return {} if $i ne $self->get_property_priority ('font-family');
5656    
5657        my $v = 0;
5658        for ($style, $variant, $weight, $size, $height, $family) {
5659          $v++ if $_ eq 'inherit';
5660        }
5661        if ($v == 6) {
5662          return {font => ['inherit', $i]};
5663        } elsif ($v) {
5664          return {};
5665        }
5666        
5667        my @v;
5668        push @v, $style unless $style eq 'normal';
5669        push @v, $variant unless $variant eq 'normal';
5670        push @v, $weight unless $weight eq 'normal';
5671        push @v, $size.($height eq 'normal' ? '' : '/'.$height);
5672        push @v, $family;
5673        return {font => [(join ' ', @v), $i]};
5674      },
5675    };
5676    $Attr->{font} = $Prop->{font};
5677    
5678    $Prop->{'border-width'} = {
5679      css => 'border-width',
5680      dom => 'border_width',
5681      parse => sub {
5682        my ($self, $prop_name, $tt, $t, $onerror) = @_;
5683    
5684        my %prop_value;
5685    
5686        my $has_sign;
5687        my $sign = 1;
5688        if ($t->{type} == MINUS_TOKEN) {
5689          $t = $tt->get_next_token;
5690          $has_sign = 1;
5691          $sign = -1;
5692        } elsif ($t->{type} == PLUS_TOKEN) {
5693          $t = $tt->get_next_token;
5694          $has_sign = 1;
5695        }
5696    
5697        if ($t->{type} == DIMENSION_TOKEN) {
5698          my $value = $t->{number} * $sign;
5699          my $unit = lc $t->{value}; ## TODO: case
5700          if ($length_unit->{$unit} and $value >= 0) {
5701            $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
5702            $t = $tt->get_next_token;
5703          } else {
5704            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5705                       level => $self->{level}->{must},
5706                       uri => \$self->{href},
5707                       token => $t);
5708            return ($t, undef);
5709          }
5710        } elsif ($t->{type} == NUMBER_TOKEN and
5711                 ($self->{unitless_px} or $t->{number} == 0)) {
5712          my $value = $t->{number} * $sign;
5713          if ($value >= 0) {
5714            $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
5715            $t = $tt->get_next_token;
5716          } else {
5717            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5718                       level => $self->{level}->{must},
5719                       uri => \$self->{href},
5720                       token => $t);
5721            return ($t, undef);
5722          }
5723        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5724          my $prop_value = lc $t->{value}; ## TODO: case folding
5725          if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5726            $t = $tt->get_next_token;
5727            $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
5728          } elsif ($prop_value eq 'inherit') {
5729            $t = $tt->get_next_token;
5730            $prop_value{'border-top-width'} = ['INHERIT'];
5731            $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
5732            $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
5733            $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
5734            return ($t, \%prop_value);
5735          } else {
5736            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5737                       level => $self->{level}->{must},
5738                       uri => \$self->{href},
5739                       token => $t);
5740            return ($t, undef);
5741          }
5742        } else {
5743          $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5744                     level => $self->{level}->{must},
5745                     uri => \$self->{href},
5746                     token => $t);
5747          return ($t, undef);
5748        }
5749        $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
5750        $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
5751        $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
5752    
5753        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5754        if ($t->{type} == MINUS_TOKEN) {
5755          $t = $tt->get_next_token;
5756          $has_sign = 1;
5757          $sign = -1;
5758        } elsif ($t->{type} == PLUS_TOKEN) {
5759          $t = $tt->get_next_token;
5760          $has_sign = 1;
5761          $sign = 1;
5762        } else {
5763          undef $has_sign;
5764          $sign = 1;
5765        }
5766    
5767        if ($t->{type} == DIMENSION_TOKEN) {
5768          my $value = $t->{number} * $sign;
5769          my $unit = lc $t->{value}; ## TODO: case
5770          if ($length_unit->{$unit} and $value >= 0) {
5771            $t = $tt->get_next_token;
5772            $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
5773          } else {
5774            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5775                       level => $self->{level}->{must},
5776                       uri => \$self->{href},
5777                       token => $t);
5778            return ($t, undef);
5779          }
5780        } elsif ($t->{type} == NUMBER_TOKEN and
5781                 ($self->{unitless_px} or $t->{number} == 0)) {
5782          my $value = $t->{number} * $sign;
5783          if ($value >= 0) {
5784            $t = $tt->get_next_token;
5785            $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
5786          } else {
5787            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5788                       level => $self->{level}->{must},
5789                       uri => \$self->{href},
5790                       token => $t);
5791            return ($t, undef);
5792          }
5793        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5794          my $prop_value = lc $t->{value}; ## TODO: case
5795          if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5796            $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
5797            $t = $tt->get_next_token;
5798          }
5799        } else {
5800          if ($has_sign) {
5801            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5802                       level => $self->{level}->{must},
5803                       uri => \$self->{href},
5804                       token => $t);
5805            return ($t, undef);
5806          }
5807          return ($t, \%prop_value);
5808        }
5809        $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
5810    
5811        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5812        if ($t->{type} == MINUS_TOKEN) {
5813          $t = $tt->get_next_token;
5814          $has_sign = 1;
5815          $sign = -1;
5816        } elsif ($t->{type} == PLUS_TOKEN) {
5817          $t = $tt->get_next_token;
5818          $has_sign = 1;
5819          $sign = 1;
5820        } else {
5821          undef $has_sign;
5822          $sign = 1;
5823        }
5824    
5825        if ($t->{type} == DIMENSION_TOKEN) {
5826          my $value = $t->{number} * $sign;
5827          my $unit = lc $t->{value}; ## TODO: case
5828          if ($length_unit->{$unit} and $value >= 0) {
5829            $t = $tt->get_next_token;
5830            $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
5831          } else {
5832            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5833                       level => $self->{level}->{must},
5834                       uri => \$self->{href},
5835                       token => $t);
5836            return ($t, undef);
5837          }
5838        } elsif ($t->{type} == NUMBER_TOKEN and
5839                 ($self->{unitless_px} or $t->{number} == 0)) {
5840          my $value = $t->{number} * $sign;
5841          if ($value >= 0) {
5842            $t = $tt->get_next_token;
5843            $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
5844          } else {
5845            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5846                       level => $self->{level}->{must},
5847                       uri => \$self->{href},
5848                       token => $t);
5849            return ($t, undef);
5850          }
5851        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5852          my $prop_value = lc $t->{value}; ## TODO: case
5853          if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5854            $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
5855            $t = $tt->get_next_token;
5856          }
5857        } else {
5858          if ($has_sign) {
5859            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5860                       level => $self->{level}->{must},
5861                       uri => \$self->{href},
5862                       token => $t);
5863            return ($t, undef);
5864          }
5865          return ($t, \%prop_value);
5866        }
5867    
5868        $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5869        if ($t->{type} == MINUS_TOKEN) {
5870          $t = $tt->get_next_token;
5871          $has_sign = 1;
5872          $sign = -1;
5873        } elsif ($t->{type} == PLUS_TOKEN) {
5874          $t = $tt->get_next_token;
5875          $has_sign = 1;
5876          $sign = 1;
5877        } else {
5878          undef $has_sign;
5879          $sign = 1;
5880        }
5881    
5882        if ($t->{type} == DIMENSION_TOKEN) {
5883          my $value = $t->{number} * $sign;
5884          my $unit = lc $t->{value}; ## TODO: case
5885          if ($length_unit->{$unit} and $value >= 0) {
5886            $t = $tt->get_next_token;
5887            $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
5888          } else {
5889            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5890                       level => $self->{level}->{must},
5891                       uri => \$self->{href},
5892                       token => $t);
5893            return ($t, undef);
5894          }
5895        } elsif ($t->{type} == NUMBER_TOKEN and
5896                 ($self->{unitless_px} or $t->{number} == 0)) {
5897          my $value = $t->{number} * $sign;
5898          if ($value >= 0) {
5899            $t = $tt->get_next_token;
5900            $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
5901          } else {
5902            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5903                       level => $self->{level}->{must},
5904                       uri => \$self->{href},
5905                       token => $t);
5906            return ($t, undef);
5907          }
5908        } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5909          my $prop_value = lc $t->{value}; ## TODO: case
5910          if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5911            $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
5912            $t = $tt->get_next_token;
5913          }
5914        } else {
5915          if ($has_sign) {
5916            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5917                       level => $self->{level}->{must},
5918                       uri => \$self->{href},
5919                       token => $t);
5920            return ($t, undef);
5921          }
5922          return ($t, \%prop_value);
5923        }
5924    
5925        return ($t, \%prop_value);
5926      },
5927      serialize_shorthand => sub {
5928        my $self = shift;
5929    
5930        my @v;
5931        push @v, $self->border_top_width;
5932        my $i = $self->get_property_priority ('border-top-width');
5933        return {} unless length $v[-1];
5934        push @v, $self->border_right_width;
5935        return {} unless length $v[-1];
5936        return {} unless $i eq $self->get_property_priority ('border-right-width');
5937        push @v, $self->border_bottom_width;
5938        return {} unless length $v[-1];
5939        return {} unless $i eq $self->get_property_priority ('border-bottom-width');
5940        push @v, $self->border_left_width;
5941        return {} unless length $v[-1];
5942        return {} unless $i eq $self->get_property_priority ('border-left-width');
5943    
5944        my $v = 0;
5945        for (0..3) {
5946          $v++ if $v[$_] eq 'inherit';
5947        }
5948        if ($v == 4) {
5949          return {'border-width' => ['inherit', $i]};
5950        } elsif ($v) {
5951          return {};
5952        }
5953    
5954        pop @v if $v[1] eq $v[3];
5955        pop @v if $v[0] eq $v[2];
5956        pop @v if $v[0] eq $v[1];
5957        return {'border-width' => [(join ' ', @v), $i]};
5958      },
5959      serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
5960    };
5961    $Attr->{border_width} = $Prop->{'border-width'};
5962    
5963    $Prop->{'list-style'} = {
5964      css => 'list-style',
5965      dom => 'list_style',
5966      parse => sub {
5967        my ($self, $prop_name, $tt, $t, $onerror) = @_;
5968    
5969        my %prop_value;
5970        my $none = 0;
5971    
5972        F: for my $f (1..3) {
5973          if ($t->{type} == IDENT_TOKEN) {
5974            my $prop_value = lc $t->{value}; ## TODO: case folding
5975            $t = $tt->get_next_token;
5976    
5977            if ($prop_value eq 'none') {
5978              $none++;
5979            } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
5980              if (exists $prop_value{'list-style-type'}) {
5981                $onerror->(type => 'CSS duplication', text => "'list-style-type'",
5982                           level => $self->{level}->{must},
5983                           uri => \$self->{href},
5984                           token => $t);
5985                return ($t, undef);
5986              } else {
5987                $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
5988              }
5989            } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
5990              if (exists $prop_value{'list-style-position'}) {
5991                $onerror->(type => 'CSS duplication',
5992                           text => "'list-style-position'",
5993                           level => $self->{level}->{must},
5994                           uri => \$self->{href},
5995                           token => $t);
5996                return ($t, undef);
5997              }
5998    
5999              $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
6000            } elsif ($f == 1 and $prop_value eq 'inherit') {
6001              $prop_value{'list-style-type'} = ["INHERIT"];
6002              $prop_value{'list-style-position'} = ["INHERIT"];
6003              $prop_value{'list-style-image'} = ["INHERIT"];
6004              last F;
6005            } else {
6006              if ($f == 1) {
6007                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6008                           level => $self->{level}->{must},
6009                           uri => \$self->{href},
6010                           token => $t);
6011                return ($t, undef);
6012              } else {
6013                last F;
6014              }
6015            }
6016          } elsif ($t->{type} == URI_TOKEN) {
6017            if (exists $prop_value{'list-style-image'}) {
6018              $onerror->(type => 'CSS duplication', text => "'list-style-image'",
6019                         uri => \$self->{href},
6020                         level => $self->{level}->{must},
6021                         token => $t);
6022              return ($t, undef);
6023            }
6024            
6025            $prop_value{'list-style-image'}
6026                = ['URI', $t->{value}, \($self->{base_uri})];
6027            $t = $tt->get_next_token;
6028          } else {
6029            if ($f == 1) {
6030              $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6031                         level => $self->{level}->{must},
6032                         uri => \$self->{href},
6033                         token => $t);
6034              return ($t, undef);
6035            } else {
6036              last F;
6037            }
6038          }
6039    
6040          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6041        } # F
6042        ## NOTE: No browser support |list-style: url(xxx|{EOF}.
6043    
6044        if ($none == 1) {
6045          if (exists $prop_value{'list-style-type'}) {
6046            if (exists $prop_value{'list-style-image'}) {
6047              $onerror->(type => 'CSS duplication', text => "'list-style-image'",
6048                         uri => \$self->{href},
6049                         level => $self->{level}->{must},
6050                         token => $t);
6051              return ($t, undef);
6052            } else {
6053              $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
6054            }
6055          } else {
6056            $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
6057            $prop_value{'list-style-image'} = ['KEYWORD', 'none']
6058                unless exists $prop_value{'list-style-image'};
6059          }
6060        } elsif ($none == 2) {
6061          if (exists $prop_value{'list-style-type'}) {
6062            $onerror->(type => 'CSS duplication', text => "'list-style-type'",
6063                       uri => \$self->{href},
6064                       level => $self->{level}->{must},
6065                       token => $t);
6066            return ($t, undef);
6067          }
6068          if (exists $prop_value{'list-style-image'}) {
6069            $onerror->(type => 'CSS duplication', text => "'list-style-image'",
6070                       uri => \$self->{href},
6071                       level => $self->{level}->{must},
6072                       token => $t);
6073            return ($t, undef);
6074          }
6075          
6076          $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
6077          $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
6078        } elsif ($none == 3) {
6079          $onerror->(type => 'CSS duplication', text => "'list-style-type'",
6080                     uri => \$self->{href},
6081                     level => $self->{level}->{must},
6082                     token => $t);
6083          return ($t, undef);
6084        }
6085    
6086        for (qw/list-style-type list-style-position list-style-image/) {
6087          $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
6088        }
6089    
6090        return ($t, \%prop_value);
6091      },
6092      ## NOTE: We don't merge longhands in |css_text| serialization,
6093      ## since no browser does.
6094      serialize_shorthand => sub {
6095        my $self = shift;
6096    
6097        ## NOTE: Don't omit any value even if it is the initial value,
6098        ## since WinIE is buggy.
6099        
6100        my $type = $self->list_style_type;
6101        return {} unless length $type;
6102        my $type_i = $self->get_property_priority ('list-style-type');
6103        my $image = $self->list_style_image;
6104        return {} unless length $image;
6105        my $image_i = $self->get_property_priority ('list-style-image');
6106        return {} unless $type_i eq $image_i;
6107        my $position = $self->list_style_position;
6108        return {} unless length $position;
6109        my $position_i = $self->get_property_priority ('list-style-position');
6110        return {} unless $type_i eq $position_i;
6111    
6112        return {'list-style' => [$type . ' ' . $image . ' ' . $position, $type_i]};
6113      },
6114    };
6115    $Attr->{list_style} = $Prop->{'list-style'};
6116    
6117    ## NOTE: Future version of the implementation will change the way to
6118    ## store the parsed value to support CSS 3 properties.
6119    $Prop->{'text-decoration'} = {
6120      css => 'text-decoration',
6121      dom => 'text_decoration',
6122      key => 'text_decoration',
6123      parse => sub {
6124        my ($self, $prop_name, $tt, $t, $onerror) = @_;
6125    
6126        my $value = ['DECORATION']; # , underline, overline, line-through, blink
6127    
6128        if ($t->{type} == IDENT_TOKEN) {
6129          my $v = lc $t->{value}; ## TODO: case
6130          $t = $tt->get_next_token;
6131          if ($v eq 'inherit') {
6132            return ($t, {$prop_name => ['INHERIT']});
6133          } elsif ($v eq 'none') {
6134            return ($t, {$prop_name => $value});
6135          } elsif ($v eq 'underline' and
6136                   $self->{prop_value}->{$prop_name}->{$v}) {
6137            $value->[1] = 1;
6138          } elsif ($v eq 'overline' and
6139                   $self->{prop_value}->{$prop_name}->{$v}) {
6140            $value->[2] = 1;
6141          } elsif ($v eq 'line-through' and
6142                   $self->{prop_value}->{$prop_name}->{$v}) {
6143            $value->[3] = 1;
6144          } elsif ($v eq 'blink' and
6145                   $self->{prop_value}->{$prop_name}->{$v}) {
6146            $value->[4] = 1;
6147          } else {
6148            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6149                       level => $self->{level}->{must},
6150                       uri => \$self->{href},
6151                       token => $t);
6152            return ($t, undef);
6153          }
6154        }
6155    
6156        F: {
6157          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6158          last F unless $t->{type} == IDENT_TOKEN;
6159    
6160          my $v = lc $t->{value}; ## TODO: case
6161          $t = $tt->get_next_token;
6162          if ($v eq 'underline' and
6163              $self->{prop_value}->{$prop_name}->{$v}) {
6164            $value->[1] = 1;
6165          } elsif ($v eq 'overline' and
6166                   $self->{prop_value}->{$prop_name}->{$v}) {
6167            $value->[1] = 2;
6168          } elsif ($v eq 'line-through' and
6169                   $self->{prop_value}->{$prop_name}->{$v}) {
6170            $value->[1] = 3;
6171          } elsif ($v eq 'blink' and
6172                   $self->{prop_value}->{$prop_name}->{$v}) {
6173            $value->[1] = 4;
6174          } else {
6175            last F;
6176          }
6177    
6178          redo F;
6179        } # F
6180    
6181        return ($t, {$prop_name => $value});
6182      },
6183      initial => ["KEYWORD", "none"],
6184      #inherited => 0,
6185      compute => $compute_as_specified,
6186    };
6187    $Attr->{text_decoration} = $Prop->{'text-decoration'};
6188    $Key->{text_decoration} = $Prop->{'text-decoration'};
6189    
6190    $Attr->{quotes} =
6191    $Key->{quotes} =
6192    $Prop->{quotes} = {
6193      css => 'quotes',
6194      dom => 'quotes',
6195      key => 'quotes',
6196      parse => sub {
6197        my ($self, $prop_name, $tt, $t, $onerror) = @_;
6198    
6199        my @v;
6200        A: {
6201          if ($t->{type} == STRING_TOKEN) {
6202            my $open = $t->{value};
6203            $t = $tt->get_next_token;
6204    
6205            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6206            if ($t->{type} == STRING_TOKEN) {
6207              push @v, [$open, $t->{value}];
6208              $t = $tt->get_next_token;
6209            } else {
6210              last A;
6211            }
6212          } elsif (not @v and $t->{type} == IDENT_TOKEN) {
6213            my $value = lc $t->{value}; ## TODO: case
6214            if ($value eq 'none' or $value eq '-manakai-default') {
6215              $t = $tt->get_next_token;
6216              return ($t, {$prop_name => ['KEYWORD', $value]});
6217            } elsif ($value eq 'inherit') {
6218              $t = $tt->get_next_token;
6219              return ($t, {$prop_name => ['INHERIT']});
6220            } else {
6221              last A;
6222            }
6223          } else {
6224            if (@v) {
6225              return ($t, {$prop_name => ['QUOTES', \@v]});
6226            } else {
6227              last A;
6228            }
6229          }
6230    
6231          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6232          redo A;
6233        }
6234        
6235        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6236                   level => $self->{level}->{must},
6237                   uri => \$self->{href},
6238                   token => $t);
6239        return ($t, undef);
6240      },
6241      initial => ['KEYWORD', '-manakai-default'],
6242      inherited => 1,
6243      compute => $compute_as_specified,
6244    };
6245    
6246    $Attr->{content} =
6247    $Key->{content} =
6248    $Prop->{content} = {
6249      css => 'content',
6250      dom => 'content',
6251      key => 'content',
6252      ## NOTE: See <http://suika.fam.cx/gate/2005/sw/content>.
6253      parse => sub {
6254        my ($self, $prop_name, $tt, $t, $onerror) = @_;
6255    
6256        if ($t->{type} == IDENT_TOKEN) {
6257          my $value = lc $t->{value}; ## TODO: case
6258          if ($value eq 'normal' or $value eq 'none') {
6259            $t = $tt->get_next_token;
6260            return ($t, {$prop_name => ['KEYWORD', $value]});
6261          } elsif ($value eq 'inherit') {
6262            $t = $tt->get_next_token;
6263            return ($t, {$prop_name => ['INHERIT']});
6264          }
6265        }
6266        
6267        my @v;
6268        A: {
6269          if ($t->{type} == IDENT_TOKEN) {
6270            my $value = lc $t->{value}; ## TODO: case
6271            if ({qw/open-quote 1 close-quote 1
6272                    no-open-quote 1 no-close-quote 1/}->{$value} and
6273                $self->{prop}->{quotes}) {
6274              push @v, ['KEYWORD', $value];
6275              $t = $tt->get_next_token;
6276            } else {
6277              last A;
6278            }
6279          } elsif ($t->{type} == STRING_TOKEN) {
6280            push @v, ['STRING', $t->{value}];
6281            $t = $tt->get_next_token;
6282          } elsif ($t->{type} == URI_TOKEN) {
6283            push @v, ['URI', $t->{value}, \($self->{base_uri})];
6284            $t = $tt->get_next_token;
6285          } elsif ($t->{type} == FUNCTION_TOKEN) {
6286            my $name = lc $t->{value}; ## TODO: case
6287            if ($name eq 'attr') {
6288              $t = $tt->get_next_token;
6289              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6290              if ($t->{type} == IDENT_TOKEN) {
6291                my $t_pfx;
6292                my $t_ln = $t;
6293                $t = $tt->get_next_token;
6294                if ($t->{type} == VBAR_TOKEN) {
6295                  $t = $tt->get_next_token;
6296                  if ($t->{type} == IDENT_TOKEN) {
6297                    $t_pfx = $t_ln;
6298                    $t_ln = $t;
6299                    $t = $tt->get_next_token;
6300                  } else {
6301                    last A;
6302                  }
6303                }
6304                
6305                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6306                if ($t->{type} == RPAREN_TOKEN) {
6307                  if (defined $t_pfx) {
6308                    my $pfx = $t_pfx->{value};
6309                    my $uri = $self->{lookup_namespace_uri}->($pfx);
6310                    unless (defined $uri) {
6311                      $self->{onerror}->(type => 'namespace prefix:not declared',
6312                                         level => $self->{level}->{must},
6313                                         uri => \$self->{href},
6314                                         token => $t_pfx,
6315                                         value => $pfx);
6316                      return ($t, undef);
6317                    }
6318                    push @v, ['ATTR', $uri, $t_ln->{value}];
6319                  } else {
6320                    push @v, ['ATTR', undef, $t_ln->{value}];
6321                  }
6322                  $t = $tt->get_next_token;
6323                } else {
6324                  last A;
6325                }
6326              } elsif ($t->{type} == VBAR_TOKEN) {
6327                $t = $tt->get_next_token;
6328                my $t_ln;
6329                if ($t->{type} == IDENT_TOKEN) {
6330                  $t_ln = $t;
6331                  $t = $tt->get_next_token;
6332                } else {
6333                  last A;
6334                }
6335                
6336                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6337                if ($t->{type} == RPAREN_TOKEN) {
6338                  push @v, ['ATTR', undef, $t_ln->{value}];
6339                  $t = $tt->get_next_token;
6340                } else {
6341                  last A;
6342                }
6343              } else {
6344                last A;
6345              }
6346            } elsif (($name eq 'counter' or $name eq 'counters') and
6347                     $self->{prop}->{'counter-reset'}) {
6348              $t = $tt->get_next_token;
6349              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6350              if ($t->{type} == IDENT_TOKEN) {
6351                my $t_id = $t;
6352                my $t_str;
6353                my $type;
6354                $t = $tt->get_next_token;
6355                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6356                if ($t->{type} == COMMA_TOKEN) {
6357                  $t = $tt->get_next_token;
6358                  $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6359                  if ($name eq 'counters' and $t->{type} == STRING_TOKEN) {
6360                    $t_str = $t;
6361                    $t = $tt->get_next_token;
6362                    $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6363                    if ($t->{type} == COMMA_TOKEN) {
6364                      $t = $tt->get_next_token;
6365                      $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6366                      if ($t->{type} == IDENT_TOKEN) {
6367                        $type = lc $t->{value}; ## TODO: value
6368                        if ($Prop->{'list-style-type'}->{keyword}->{$type}) {
6369                          $t = $tt->get_next_token;
6370                        } else {
6371                          last A;
6372                        }
6373                      } else {
6374                        last A;
6375                      }
6376                    }
6377                  } elsif ($name eq 'counter' and $t->{type} == IDENT_TOKEN) {
6378                    $type = lc $t->{value}; ## TODO: value
6379                    if ($Prop->{'list-style-type'}->{keyword}->{$type}) {
6380                      $t = $tt->get_next_token;
6381                    } else {
6382                      last A;
6383                    }
6384                  } else {
6385                    last A;
6386                  }
6387                } elsif ($name eq 'counters') {
6388                  last A;
6389                }
6390                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6391                if ($t->{type} == RPAREN_TOKEN) {
6392                  push @v, [uc $name, ## |COUNTER| or |COUNTERS|
6393                            $t_id->{value},
6394                            defined $t_str ? $t_str->{value} : undef,
6395                            defined $type ? $type : 'decimal'];
6396                  $t = $tt->get_next_token;
6397                } else {
6398                  last A;
6399                }
6400              } else {
6401                last A;
6402              }
6403            } else {
6404              last A;
6405            }
6406          } else {
6407            unshift @v, 'CONTENT';
6408            return ($t, {$prop_name => \@v});
6409          }
6410    
6411          $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6412          redo A;
6413        } # A
6414    
6415        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6416                   level => $self->{level}->{must},
6417                   uri => \$self->{href},
6418                   token => $t);
6419        return ($t, undef);
6420      },
6421      initial => ['KEYWORD', 'normal'],
6422      #inherited => 0,
6423      compute => $compute_as_specified,
6424          ## NOTE: This is what Opera 9 does, except for 'normal' -> 'none'.
6425          ## TODO: 'normal' -> 'none' for ::before and ::after [CSS 2.1]
6426    };
6427    
6428    $Attr->{counter_reset} =
6429    $Key->{counter_reset} =
6430    $Prop->{'counter-reset'} = {
6431      css => 'counter-reset',
6432      dom => 'counter_reset',
6433      key => 'counter_reset',
6434      ## NOTE: See <http://suika.fam.cx/gate/2005/sw/counter-reset>.
6435      parse => sub {
6436        my ($self, $prop_name, $tt, $t, $onerror) = @_;
6437    
6438        ## NOTE: For 'counter-increment' and 'counter-reset'.
6439    
6440        my @v = ($prop_name eq 'counter-increment' ? 'ADDCOUNTER' : 'SETCOUNTER');
6441        B: {
6442          if ($t->{type} == IDENT_TOKEN) {
6443            my $value = $t->{value};
6444            my $lcvalue = lc $value; ## TODO: case
6445            last B if $lcvalue ne 'inherit' and $lcvalue ne 'none';
6446            
6447            $t = $tt->get_next_token;
6448            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6449            if ($t->{type} == IDENT_TOKEN) {
6450              push @v, [$value, $prop_name eq 'counter-increment' ? 1 : 0];
6451            } elsif ($t->{type} == NUMBER_TOKEN) {
6452              push @v, [$value, int $t->{number}];
6453              $t = $tt->get_next_token;
6454            } elsif ($t->{type} == PLUS_TOKEN) {
6455              $t = $tt->get_next_token;
6456              if ($t->{type} == NUMBER_TOKEN) {
6457                push @v, [$value, int $t->{number}];
6458                $t = $tt->get_next_token;
6459              } else {
6460                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6461                           level => $self->{level}->{must},
6462                           uri => \$self->{href},
6463                           token => $t);
6464                return ($t, undef);
6465              }
6466            } elsif ($t->{type} == MINUS_TOKEN) {
6467              $t = $tt->get_next_token;
6468              if ($t->{type} == NUMBER_TOKEN) {
6469                push @v, [$value, -int $t->{number}];
6470                $t = $tt->get_next_token;
6471              } else {
6472                $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6473                           level => $self->{level}->{must},
6474                           uri => \$self->{href},
6475                           token => $t);
6476                return ($t, undef);
6477              }
6478            } else {
6479              if ($lcvalue eq 'none') {
6480                return ($t, {$prop_name => ['KEYWORD', $lcvalue]});
6481              } elsif ($lcvalue eq 'inherit') {
6482                return ($t, {$prop_name => ['INHERIT']});
6483              } else {
6484                last B;
6485              }
6486            }
6487            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6488          } else {
6489            $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6490                       level => $self->{level}->{must},
6491                       uri => \$self->{href},
6492                       token => $t);
6493            return ($t, undef);
6494          }
6495        } # B
6496    
6497        A: {
6498          if ($t->{type} == IDENT_TOKEN) {
6499            my $value = $t->{value};
6500            $t = $tt->get_next_token;
6501            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6502            if ($t->{type} == NUMBER_TOKEN) {
6503              push @v, [$value, int $t->{number}];
6504              $t = $tt->get_next_token;
6505              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6506            } elsif ($t->{type} == MINUS_TOKEN) {
6507              $t = $tt->get_next_token;
6508              if ($t->{type} == NUMBER_TOKEN) {
6509                push @v, [$value, -int $t->{number}];
6510                $t = $tt->get_next_token;
6511                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6512              } else {
6513                last A;
6514              }
6515            } elsif ($t->{type} == PLUS_TOKEN) {
6516              $t = $tt->get_next_token;
6517              if ($t->{type} == NUMBER_TOKEN) {
6518                push @v, [$value, int $t->{number}];
6519                $t = $tt->get_next_token;
6520                $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6521              } else {
6522                last A;
6523              }
6524            } else {
6525              push @v, [$value, $prop_name eq 'counter-increment' ? 1 : 0];
6526            }
6527            redo A;
6528          } else {
6529            return ($t, {$prop_name => \@v});
6530          }
6531        } # A
6532        
6533        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6534                   level => $self->{level}->{must},
6535                   uri => \$self->{href},
6536                   token => $t);
6537        return ($t, undef);
6538      },
6539      initial => ['KEYWORD', 'none'],
6540      #inherited => 0,
6541      compute => $compute_as_specified,
6542    };
6543    
6544    $Attr->{counter_increment} =
6545    $Key->{counter_increment} =
6546    $Prop->{'counter-increment'} = {
6547      css => 'counter-increment',
6548      dom => 'counter_increment',
6549      key => 'counter_increment',
6550      parse => $Prop->{'counter-reset'}->{parse},
6551      initial => ['KEYWORD', 'none'],
6552      #inherited => 0,
6553      compute => $compute_as_specified,
6554    };
6555    
6556    $Attr->{clip} =
6557    $Key->{clip} =
6558    $Prop->{clip} = {
6559      css => 'clip',
6560      dom => 'clip',
6561      key => 'clip',
6562      ## NOTE: See <http://suika.fam.cx/gate/2005/sw/clip>.
6563      parse => sub {
6564        my ($self, $prop_name, $tt, $t, $onerror) = @_;
6565    
6566        if ($t->{type} == FUNCTION_TOKEN) {
6567          my $value = lc $t->{value}; ## TODO: case
6568          if ($value eq 'rect') {
6569            $t = $tt->get_next_token;
6570            my $prop_value = ['RECT'];
6571    
6572            A: {
6573              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6574              
6575              my $has_sign;
6576              my $sign = 1;
6577              if ($t->{type} == MINUS_TOKEN) {
6578                $sign = -1;
6579                $has_sign = 1;
6580                $t = $tt->get_next_token;
6581              } elsif ($t->{type} == PLUS_TOKEN) {
6582                $has_sign = 1;
6583                $t = $tt->get_next_token;
6584              }
6585              if ($t->{type} == DIMENSION_TOKEN) {
6586                my $value = $t->{number} * $sign;
6587                my $unit = lc $t->{value}; ## TODO: case
6588                if ($length_unit->{$unit}) {
6589                  $t = $tt->get_next_token;
6590                  push @$prop_value, ['DIMENSION', $value, $unit];
6591                } else {
6592                  $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6593                             level => $self->{level}->{must},
6594                             uri => \$self->{href},
6595                             token => $t);
6596                  return ($t, undef);
6597                }
6598              } elsif ($t->{type} == NUMBER_TOKEN and
6599                       ($self->{unitless_px} or $t->{number} == 0)) {
6600                my $value = $t->{number} * $sign;
6601                $t = $tt->get_next_token;
6602                push @$prop_value, ['DIMENSION', $value, 'px'];
6603              } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
6604                my $value = lc $t->{value}; ## TODO: case
6605                if ($value eq 'auto') {
6606                  push @$prop_value, ['KEYWORD', 'auto'];
6607                  $t = $tt->get_next_token;
6608                } else {
6609                  last A;
6610                }
6611              } else {
6612                if ($has_sign) {
6613                  $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6614                             level => $self->{level}->{must},
6615                             uri => \$self->{href},
6616                             token => $t);
6617                  return ($t, undef);
6618                } else {
6619                  last A;
6620                }
6621              }
6622            
6623              $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6624              if ($#$prop_value == 4) {
6625                if ($t->{type} == RPAREN_TOKEN) {
6626                  $t = $tt->get_next_token;
6627                  return ($t, {$prop_name => $prop_value});
6628                } else {
6629                  last A;
6630                }
6631              } else {
6632                $t = $tt->get_next_token if $t->{type} == COMMA_TOKEN;
6633                redo A;
6634              }
6635            } # A
6636          }
6637        } elsif ($t->{type} == IDENT_TOKEN) {
6638          my $value = lc $t->{value}; ## TODO: case
6639          if ($value eq 'auto') {
6640            $t = $tt->get_next_token;
6641            return ($t, {$prop_name => ['KEYWORD', 'auto']});
6642          } elsif ($value eq 'inherit') {
6643            $t = $tt->get_next_token;
6644            return ($t, {$prop_name => ['INHERIT']});
6645          }
6646        }
6647    
6648        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6649                   level => $self->{level}->{must},
6650                   uri => \$self->{href},
6651                   token => $t);
6652        return ($t, undef);
6653      },
6654      initial => ['KEYWORD', 'auto'],
6655      #inherited => 0,
6656      compute => sub {
6657        my ($self, $element, $prop_name, $specified_value) = @_;
6658    
6659        if (defined $specified_value and $specified_value->[0] eq 'RECT') {
6660          my $v = ['RECT'];
6661          for (@$specified_value[1..4]) {
6662            push @$v, $compute_length->($self, $element, $prop_name, $_);
6663          }
6664          return $v;
6665        }
6666    
6667        return $specified_value;
6668      },
6669    };
6670    
6671    $Attr->{marks} =
6672    $Key->{marks} =
6673    $Prop->{marks} = {
6674      css => 'marks',
6675      dom => 'marks',
6676      key => 'marks',
6677      parse => sub {
6678        my ($self, $prop_name, $tt, $t, $onerror) = @_;
6679    
6680        if ($t->{type} == IDENT_TOKEN) {
6681          my $value = lc $t->{value}; ## TODO: case
6682          if ($value eq 'crop' and $self->{prop_value}->{$prop_name}->{$value}) {
6683            $t = $tt->get_next_token;
6684            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6685            if ($t->{type} == IDENT_TOKEN) {
6686              my $value = lc $t->{value}; ## TODO: case
6687              if ($value eq 'cross' and
6688                  $self->{prop_value}->{$prop_name}->{$value}) {
6689                $t = $tt->get_next_token;
6690                return ($t, {$prop_name => ['MARKS', 1, 1]});
6691              }
6692            }
6693            return ($t, {$prop_name => ['MARKS', 1, 0]});
6694          } elsif ($value eq 'cross' and
6695                   $self->{prop_value}->{$prop_name}->{$value}) {
6696            $t = $tt->get_next_token;
6697            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6698            if ($t->{type} == IDENT_TOKEN) {
6699              my $value = lc $t->{value}; ## TODO: case
6700              if ($value eq 'crop' and
6701                  $self->{prop_value}->{$prop_name}->{$value}) {
6702                $t = $tt->get_next_token;
6703                return ($t, {$prop_name => ['MARKS', 1, 1]});
6704              }
6705            }
6706            return ($t, {$prop_name => ['MARKS', 0, 1]});
6707          } elsif ($value eq 'none') {
6708            $t = $tt->get_next_token;
6709            return ($t, {$prop_name => ['MARKS']});
6710          } elsif ($value eq 'inherit') {
6711            $t = $tt->get_next_token;
6712            return ($t, {$prop_name => ['INHERIT']});
6713          }
6714        }
6715    
6716        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6717                   level => $self->{level}->{must},
6718                   uri => \$self->{href},
6719                   token => $t);
6720        return ($t, undef);
6721      },
6722      initial => ['MARKS', 0, 0],
6723      #inherited => 0,
6724      compute => $compute_as_specified,
6725    };
6726    
6727    $Attr->{size} =
6728    $Key->{size} =
6729    $Prop->{size} = {
6730      css => 'size',
6731      dom => 'size',
6732      key => 'size',
6733      parse => sub {
6734        my ($self, $prop_name, $tt, $t, $onerror) = @_;
6735    
6736        if ($t->{type} == IDENT_TOKEN) {
6737          my $value = lc $t->{value}; ## TODO: case
6738          if ({
6739               auto => 1, portrait => 1, landscape => 1,
6740              }->{$value}) {
6741            $t = $tt->get_next_token;
6742            return ($t, {$prop_name => ['KEYWORD', $value]});
6743          } elsif ($value eq 'inherit') {
6744            $t = $tt->get_next_token;
6745            return ($t, {$prop_name => ['INHERIT']});
6746          }
6747        }
6748    
6749        my $prop_value = ['SIZE'];
6750        A: {
6751          my $has_sign;
6752          my $sign = 1;
6753          if ($t->{type} == MINUS_TOKEN) {
6754            $has_sign = 1;
6755            $sign = -1;
6756            $t = $tt->get_next_token;
6757          } elsif ($t->{type} == PLUS_TOKEN) {
6758            $has_sign = 1;
6759            $t = $tt->get_next_token;
6760          }
6761          
6762          if ($t->{type} == DIMENSION_TOKEN) {
6763            my $value = $t->{number} * $sign;
6764            my $unit = lc $t->{value}; ## TODO: case
6765            if ($length_unit->{$unit}) {
6766              $t = $tt->get_next_token;
6767              push @$prop_value, ['DIMENSION', $value, $unit];
6768            } else {
6769              $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6770                         level => $self->{level}->{must},
6771                         uri => \$self->{href},
6772                         token => $t);
6773              return ($t, undef);
6774            }
6775          } elsif ($t->{type} == NUMBER_TOKEN and
6776                   ($self->{unitless_px} or $t->{number} == 0)) {
6777            my $value = $t->{number} * $sign;
6778            $t = $tt->get_next_token;
6779            push @$prop_value, ['DIMENSION', $value, 'px'];
6780          } else {
6781            if (@$prop_value == 2) {
6782              $prop_value->[2] = $prop_value->[1];
6783              return ($t, {$prop_name => $prop_value});
6784            } else {
6785              last A;
6786            }
6787          }
6788    
6789          if (@$prop_value == 3) {
6790            return ($t, {$prop_name => $prop_value});
6791          } else {
6792            $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6793            redo A;
6794          }
6795        } # A
6796    
6797        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6798                   level => $self->{level}->{must},
6799                   uri => \$self->{href},
6800                   token => $t);
6801        return ($t, undef);
6802      },
6803      initial => ['KEYWORD', 'auto'],
6804      #inherited => 0,
6805      compute => sub {
6806        my ($self, $element, $prop_name, $specified_value) = @_;
6807    
6808        if (defined $specified_value and $specified_value->[0] eq 'SIZE') {
6809          my $v = ['SIZE'];
6810          for (@$specified_value[1..2]) {
6811            push @$v, $compute_length->($self, $element, $prop_name, $_);
6812          }
6813          return $v;
6814        }
6815    
6816        return $specified_value;
6817      },
6818    };
6819    
6820    $Attr->{page} =
6821    $Key->{page} =
6822    $Prop->{page} = {
6823      css => 'page',
6824      dom => 'page',
6825      key => 'page',
6826      parse => sub {
6827        my ($self, $prop_name, $tt, $t, $onerror) = @_;
6828    
6829        if ($t->{type} == IDENT_TOKEN) {
6830          my $value = lc $t->{value}; ## TODO: case
6831          if ($value eq 'auto') {
6832            $t = $tt->get_next_token;
6833            return ($t, {$prop_name => ['KEYWORD', 'auto']});
6834          } else {
6835            $value = $t->{value};
6836            $t = $tt->get_next_token;
6837            return ($t, {$prop_name => ['PAGE', $value]});
6838          }
6839        }
6840    
6841        $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6842                   level => $self->{level}->{must},
6843                   uri => \$self->{href},
6844                   token => $t);
6845        return ($t, undef);
6846      },
6847      initial => ['KEYWORD', 'auto'],
6848      inherited => 1,
6849      compute => $compute_as_specified,
6850    };
6851    
6852  1;  1;
6853  ## $Date$  ## $Date$

Legend:
Removed from v.1.3  
changed lines
  Added in v.1.72

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24