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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.70 - (hide annotations) (download)
Sat Aug 16 08:37:40 2008 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.69: +13 -2 lines
++ whatpm/Whatpm/CSS/ChangeLog	16 Aug 2008 08:37:36 -0000
	* Parser.pm (cursor): Support for 'hand' keyword.

2008-08-16  Wakaba  <wakaba@suika.fam.cx>

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24