/[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.71 - (hide annotations) (download)
Mon Sep 15 14:34:24 2008 UTC (16 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.70: +27 -6 lines
++ whatpm/t/ChangeLog	15 Sep 2008 14:34:19 -0000
2008-09-15  Wakaba  <wakaba@suika.fam.cx>

	* css-visual.dat: New test data for Firefox3's new 'width'
	values.

++ whatpm/Whatpm/CSS/ChangeLog	15 Sep 2008 14:34:00 -0000
2008-09-15  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm: Support for Firefox3's new 'width' keywords.

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 wakaba 1.71 if ($Prop->{$prop_name}->{keyword}->{$value} == 1 or
2256     $self->{prop_value}->{$prop_name}->{$value}) {
2257     $t = $tt->get_next_token;
2258     return ($t, {$prop_name => ['KEYWORD', $value]});
2259     }
2260 wakaba 1.62 } elsif ($value eq 'inherit') {
2261     $t = $tt->get_next_token;
2262     return ($t, {$prop_name => ['INHERIT']});
2263     }
2264     ## NOTE: In the "else" case, don't procede the |$t| pointer
2265     ## for the support of 'border-top' property (and similar ones).
2266     }
2267    
2268 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2269     level => $self->{level}->{must},
2270 wakaba 1.62 uri => \$self->{href},
2271     token => $t);
2272     return ($t, undef);
2273     }; # $length_percentage_keyword_parser
2274    
2275     my $length_keyword_parser = sub {
2276     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2277    
2278     my $has_sign;
2279     my $sign = 1;
2280     if ($t->{type} == MINUS_TOKEN) {
2281     $t = $tt->get_next_token;
2282     $has_sign = 1;
2283     $sign = -1;
2284     } elsif ($t->{type} == PLUS_TOKEN) {
2285     $t = $tt->get_next_token;
2286     $has_sign = 1;
2287     }
2288     my $allow_negative = $Prop->{$prop_name}->{allow_negative};
2289    
2290     if ($t->{type} == DIMENSION_TOKEN) {
2291     my $value = $t->{number} * $sign;
2292     my $unit = lc $t->{value}; ## TODO: case
2293     if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
2294     $t = $tt->get_next_token;
2295     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2296     }
2297     } elsif ($t->{type} == NUMBER_TOKEN and
2298     ($self->{unitless_px} or $t->{number} == 0)) {
2299     my $value = $t->{number} * $sign;
2300     if ($allow_negative or $value >= 0) {
2301 wakaba 1.49 $t = $tt->get_next_token;
2302     return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
2303     }
2304     } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2305 wakaba 1.18 my $value = lc $t->{value}; ## TODO: case
2306 wakaba 1.62 if ($Prop->{$prop_name}->{keyword}->{$value}) {
2307 wakaba 1.49 $t = $tt->get_next_token;
2308 wakaba 1.18 return ($t, {$prop_name => ['KEYWORD', $value]});
2309     } elsif ($value eq 'inherit') {
2310 wakaba 1.49 $t = $tt->get_next_token;
2311 wakaba 1.18 return ($t, {$prop_name => ['INHERIT']});
2312     }
2313     }
2314    
2315 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2316     level => $self->{level}->{must},
2317 wakaba 1.38 uri => \$self->{href},
2318 wakaba 1.18 token => $t);
2319     return ($t, undef);
2320 wakaba 1.62 }; # $length_keyword_parser
2321    
2322     $Prop->{'font-size'} = {
2323     css => 'font-size',
2324     dom => 'font_size',
2325     key => 'font_size',
2326     parse => $length_percentage_keyword_parser,
2327     #allow_negative => 0,
2328     keyword => {
2329     'xx-small' => 1, 'x-small' => 1, small => 1, medium => 1,
2330     large => 1, 'x-large' => 1, 'xx-large' => 1,
2331     '-manakai-xxx-large' => 1, '-webkit-xxx-large' => 1,
2332     larger => 1, smaller => 1,
2333 wakaba 1.18 },
2334     initial => ['KEYWORD', 'medium'],
2335     inherited => 1,
2336     compute => sub {
2337     my ($self, $element, $prop_name, $specified_value) = @_;
2338    
2339     if (defined $specified_value) {
2340     if ($specified_value->[0] eq 'DIMENSION') {
2341     my $unit = $specified_value->[2];
2342     my $value = $specified_value->[1];
2343    
2344     if ($unit eq 'em' or $unit eq 'ex') {
2345     $value *= 0.5 if $unit eq 'ex';
2346     ## TODO: Preferred way to determine the |ex| size is defined
2347     ## in CSS 2.1.
2348    
2349     my $parent_element = $element->manakai_parent_element;
2350     if (defined $parent_element) {
2351     $value *= $self->get_computed_value ($parent_element, $prop_name)
2352     ->[1];
2353     } else {
2354     $value *= $self->{font_size}->[3]; # medium
2355     }
2356     $unit = 'px';
2357     } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
2358     ($value *= 12, $unit = 'pc') if $unit eq 'pc';
2359     ($value /= 72, $unit = 'in') if $unit eq 'pt';
2360     ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
2361     ($value *= 10, $unit = 'mm') if $unit eq 'cm';
2362     ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
2363     }
2364 wakaba 1.19 ## else: consistency error
2365 wakaba 1.18
2366     return ['DIMENSION', $value, $unit];
2367     } elsif ($specified_value->[0] eq 'PERCENTAGE') {
2368     my $parent_element = $element->manakai_parent_element;
2369     my $parent_cv;
2370     if (defined $parent_element) {
2371     $parent_cv = $self->get_computed_value
2372     ($parent_element, $prop_name);
2373     } else {
2374     $parent_cv = [undef, $self->{font_size}->[3]];
2375     }
2376     return ['DIMENSION', $parent_cv->[1] * $specified_value->[1] / 100,
2377     'px'];
2378     } elsif ($specified_value->[0] eq 'KEYWORD') {
2379     if ($specified_value->[1] eq 'larger') {
2380     my $parent_element = $element->manakai_parent_element;
2381     if (defined $parent_element) {
2382     my $parent_cv = $self->get_computed_value
2383     ($parent_element, $prop_name);
2384     return ['DIMENSION',
2385     $self->{get_larger_font_size}->($self, $parent_cv->[1]),
2386     'px'];
2387     } else { ## 'larger' relative to 'medium', initial of 'font-size'
2388     return ['DIMENSION', $self->{font_size}->[4], 'px'];
2389     }
2390     } elsif ($specified_value->[1] eq 'smaller') {
2391     my $parent_element = $element->manakai_parent_element;
2392     if (defined $parent_element) {
2393     my $parent_cv = $self->get_computed_value
2394     ($parent_element, $prop_name);
2395     return ['DIMENSION',
2396     $self->{get_smaller_font_size}->($self, $parent_cv->[1]),
2397     'px'];
2398     } else { ## 'smaller' relative to 'medium', initial of 'font-size'
2399     return ['DIMENSION', $self->{font_size}->[2], 'px'];
2400     }
2401     } else {
2402 wakaba 1.49 ## TODO: different computation in quirks mode?
2403 wakaba 1.18 return ['DIMENSION', $self->{font_size}->[{
2404     'xx-small' => 0,
2405     'x-small' => 1,
2406     small => 2,
2407     medium => 3,
2408     large => 4,
2409     'x-large' => 5,
2410     'xx-large' => 6,
2411     '-manakai-xxx-large' => 7,
2412 wakaba 1.49 '-webkit-xxx-large' => 7,
2413 wakaba 1.18 }->{$specified_value->[1]}], 'px'];
2414     }
2415     }
2416     }
2417    
2418     return $specified_value;
2419     },
2420     };
2421     $Attr->{font_size} = $Prop->{'font-size'};
2422     $Key->{font_size} = $Prop->{'font-size'};
2423    
2424 wakaba 1.19 my $compute_length = sub {
2425     my ($self, $element, $prop_name, $specified_value) = @_;
2426    
2427     if (defined $specified_value) {
2428     if ($specified_value->[0] eq 'DIMENSION') {
2429     my $unit = $specified_value->[2];
2430     my $value = $specified_value->[1];
2431    
2432     if ($unit eq 'em' or $unit eq 'ex') {
2433     $value *= 0.5 if $unit eq 'ex';
2434     ## TODO: Preferred way to determine the |ex| size is defined
2435     ## in CSS 2.1.
2436    
2437     $value *= $self->get_computed_value ($element, 'font-size')->[1];
2438     $unit = 'px';
2439     } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
2440     ($value *= 12, $unit = 'pc') if $unit eq 'pc';
2441     ($value /= 72, $unit = 'in') if $unit eq 'pt';
2442     ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
2443     ($value *= 10, $unit = 'mm') if $unit eq 'cm';
2444     ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
2445     }
2446    
2447     return ['DIMENSION', $value, $unit];
2448     }
2449     }
2450    
2451     return $specified_value;
2452     }; # $compute_length
2453    
2454 wakaba 1.23 $Prop->{'letter-spacing'} = {
2455     css => 'letter-spacing',
2456     dom => 'letter_spacing',
2457     key => 'letter_spacing',
2458 wakaba 1.62 parse => $length_keyword_parser,
2459 wakaba 1.24 allow_negative => 1,
2460     keyword => {normal => 1},
2461 wakaba 1.23 initial => ['KEYWORD', 'normal'],
2462     inherited => 1,
2463     compute => $compute_length,
2464     };
2465     $Attr->{letter_spacing} = $Prop->{'letter-spacing'};
2466     $Key->{letter_spacing} = $Prop->{'letter-spacing'};
2467    
2468     $Prop->{'word-spacing'} = {
2469     css => 'word-spacing',
2470     dom => 'word_spacing',
2471     key => 'word_spacing',
2472 wakaba 1.62 parse => $length_keyword_parser,
2473 wakaba 1.24 allow_negative => 1,
2474     keyword => {normal => 1},
2475 wakaba 1.23 initial => ['KEYWORD', 'normal'],
2476     inherited => 1,
2477     compute => $compute_length,
2478     };
2479     $Attr->{word_spacing} = $Prop->{'word-spacing'};
2480     $Key->{word_spacing} = $Prop->{'word-spacing'};
2481    
2482 wakaba 1.24 $Prop->{'-manakai-border-spacing-x'} = {
2483     css => '-manakai-border-spacing-x',
2484     dom => '_manakai_border_spacing_x',
2485     key => 'border_spacing_x',
2486 wakaba 1.62 parse => $length_keyword_parser,
2487 wakaba 1.24 #allow_negative => 0,
2488     #keyword => {},
2489 wakaba 1.25 serialize_multiple => sub {
2490     my $self = shift;
2491    
2492     my $x = $self->_manakai_border_spacing_x;
2493     my $y = $self->_manakai_border_spacing_y;
2494     my $xi = $self->get_property_priority ('-manakai-border-spacing-x');
2495     my $yi = $self->get_property_priority ('-manakai-border-spacing-y');
2496 wakaba 1.34 if (length $x) {
2497     if (length $y) {
2498 wakaba 1.26 if ($xi eq $yi) {
2499 wakaba 1.25 if ($x eq $y) {
2500 wakaba 1.43 return {'border-spacing' => [$x, $xi]};
2501 wakaba 1.25 } else {
2502 wakaba 1.53 if ($x eq 'inherit' or $y eq 'inherit') {
2503     return {'-manakai-border-spacing-x' => [$x, $xi],
2504     '-manakai-border-spacing-y' => [$y, $yi]};
2505     } else {
2506     return {'border-spacing' => [$x . ' ' . $y, $xi]};
2507     }
2508 wakaba 1.25 }
2509     } else {
2510 wakaba 1.43 return {'-manakai-border-spacing-x' => [$x, $xi],
2511     '-manakai-border-spacing-y' => [$y, $yi]};
2512 wakaba 1.25 }
2513     } else {
2514 wakaba 1.43 return {'-manakai-border-spacing-x' => [$x, $xi]};
2515 wakaba 1.25 }
2516     } else {
2517 wakaba 1.34 if (length $y) {
2518 wakaba 1.43 return {'-manakai-border-spacing-y' => [$y, $yi]};
2519 wakaba 1.25 } else {
2520     return {};
2521     }
2522     }
2523     },
2524 wakaba 1.24 initial => ['DIMENSION', 0, 'px'],
2525     inherited => 1,
2526     compute => $compute_length,
2527     };
2528     $Attr->{_manakai_border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
2529     $Key->{border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
2530    
2531     $Prop->{'-manakai-border-spacing-y'} = {
2532     css => '-manakai-border-spacing-y',
2533     dom => '_manakai_border_spacing_y',
2534     key => 'border_spacing_y',
2535 wakaba 1.62 parse => $length_keyword_parser,
2536 wakaba 1.24 #allow_negative => 0,
2537     #keyword => {},
2538 wakaba 1.25 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
2539     ->{serialize_multiple},
2540 wakaba 1.24 initial => ['DIMENSION', 0, 'px'],
2541     inherited => 1,
2542     compute => $compute_length,
2543     };
2544     $Attr->{_manakai_border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
2545     $Key->{border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
2546    
2547 wakaba 1.62 $Attr->{marker_offset} =
2548     $Key->{marker_offset} =
2549     $Prop->{'marker-offset'} = {
2550     css => 'marker-offset',
2551     dom => 'marker_offset',
2552     key => 'marker_offset',
2553     parse => $length_keyword_parser,
2554     allow_negative => 1,
2555     keyword => {auto => 1},
2556     initial => ['KEYWORD', 'auto'],
2557     #inherited => 0,
2558     compute => $compute_length,
2559     };
2560    
2561 wakaba 1.19 $Prop->{'margin-top'} = {
2562     css => 'margin-top',
2563     dom => 'margin_top',
2564     key => 'margin_top',
2565 wakaba 1.62 parse => $length_percentage_keyword_parser,
2566 wakaba 1.22 allow_negative => 1,
2567     keyword => {auto => 1},
2568 wakaba 1.42 serialize_multiple => sub {
2569     my $self = shift;
2570    
2571 wakaba 1.43 ## NOTE: Same as |serialize_multiple| of 'padding-top'.
2572    
2573 wakaba 1.42 my $use_shorthand = 1;
2574     my $t = $self->margin_top;
2575     undef $use_shorthand unless length $t;
2576     my $t_i = $self->get_property_priority ('margin-top');
2577     my $r = $self->margin_right;
2578     undef $use_shorthand
2579     if not length $r or
2580     ($r eq 'inherit' and $t ne 'inherit') or
2581     ($t eq 'inherit' and $r ne 'inherit');
2582     my $r_i = $self->get_property_priority ('margin-right');
2583     undef $use_shorthand unless $r_i eq $t_i;
2584     my $b = $self->margin_bottom;
2585     undef $use_shorthand
2586     if not length $b or
2587     ($b eq 'inherit' and $t ne 'inherit') or
2588     ($t eq 'inherit' and $b ne 'inherit');
2589     my $b_i = $self->get_property_priority ('margin-bottom');
2590     undef $use_shorthand unless $b_i eq $t_i;
2591     my $l = $self->margin_left;
2592     undef $use_shorthand
2593     if not length $l or
2594     ($l eq 'inherit' and $t ne 'inherit') or
2595     ($t eq 'inherit' and $l ne 'inherit');
2596     my $l_i = $self->get_property_priority ('margin-left');
2597     undef $use_shorthand unless $l_i eq $t_i;
2598    
2599     if ($use_shorthand) {
2600     $b .= ' ' . $l if $r ne $l;
2601     $r .= ' ' . $b if $t ne $b;
2602     $t .= ' ' . $r if $t ne $r;
2603 wakaba 1.45 return {margin => [$t, $t_i]};
2604 wakaba 1.42 } else {
2605     my $v = {};
2606     if (length $t) {
2607 wakaba 1.45 $v->{'margin-top'} = [$t, $t_i];
2608 wakaba 1.42 }
2609     if (length $r) {
2610 wakaba 1.45 $v->{'margin-right'} = [$r, $r_i];
2611 wakaba 1.42 }
2612     if (length $b) {
2613 wakaba 1.45 $v->{'margin-bottom'} = [$b, $b_i];
2614 wakaba 1.42 }
2615     if (length $l) {
2616 wakaba 1.45 $v->{'margin-left'} = [$l, $l_i];
2617 wakaba 1.42 }
2618     return $v;
2619     }
2620     },
2621 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
2622     #inherited => 0,
2623     compute => $compute_length,
2624     };
2625     $Attr->{margin_top} = $Prop->{'margin-top'};
2626     $Key->{margin_top} = $Prop->{'margin-top'};
2627    
2628     $Prop->{'margin-bottom'} = {
2629     css => 'margin-bottom',
2630     dom => 'margin_bottom',
2631     key => 'margin_bottom',
2632     parse => $Prop->{'margin-top'}->{parse},
2633 wakaba 1.22 allow_negative => 1,
2634     keyword => {auto => 1},
2635 wakaba 1.42 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2636 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
2637     #inherited => 0,
2638     compute => $compute_length,
2639     };
2640     $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
2641     $Key->{margin_bottom} = $Prop->{'margin-bottom'};
2642    
2643     $Prop->{'margin-right'} = {
2644     css => 'margin-right',
2645     dom => 'margin_right',
2646     key => 'margin_right',
2647     parse => $Prop->{'margin-top'}->{parse},
2648 wakaba 1.22 allow_negative => 1,
2649     keyword => {auto => 1},
2650 wakaba 1.42 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2651 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
2652     #inherited => 0,
2653     compute => $compute_length,
2654     };
2655     $Attr->{margin_right} = $Prop->{'margin-right'};
2656     $Key->{margin_right} = $Prop->{'margin-right'};
2657    
2658     $Prop->{'margin-left'} = {
2659     css => 'margin-left',
2660     dom => 'margin_left',
2661     key => 'margin_left',
2662     parse => $Prop->{'margin-top'}->{parse},
2663 wakaba 1.22 allow_negative => 1,
2664     keyword => {auto => 1},
2665 wakaba 1.42 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2666 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
2667     #inherited => 0,
2668     compute => $compute_length,
2669     };
2670     $Attr->{margin_left} = $Prop->{'margin-left'};
2671     $Key->{margin_left} = $Prop->{'margin-left'};
2672    
2673 wakaba 1.21 $Prop->{top} = {
2674     css => 'top',
2675     dom => 'top',
2676     key => 'top',
2677     parse => $Prop->{'margin-top'}->{parse},
2678 wakaba 1.22 allow_negative => 1,
2679     keyword => {auto => 1},
2680 wakaba 1.21 initial => ['KEYWORD', 'auto'],
2681     #inherited => 0,
2682     compute_multiple => sub {
2683     my ($self, $element, $eid, $prop_name) = @_;
2684    
2685     my $pos_value = $self->get_computed_value ($element, 'position');
2686     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2687     if ($pos_value->[1] eq 'static') {
2688     $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
2689     $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
2690     return;
2691     } elsif ($pos_value->[1] eq 'relative') {
2692     my $top_specified = $self->get_specified_value_no_inherit
2693     ($element, 'top');
2694     if (defined $top_specified and
2695     ($top_specified->[0] eq 'DIMENSION' or
2696     $top_specified->[0] eq 'PERCENTAGE')) {
2697     my $tv = $self->{computed_value}->{$eid}->{top}
2698     = $compute_length->($self, $element, 'top', $top_specified);
2699     $self->{computed_value}->{$eid}->{bottom}
2700     = [$tv->[0], -$tv->[1], $tv->[2]];
2701     } else { # top: auto
2702     my $bottom_specified = $self->get_specified_value_no_inherit
2703     ($element, 'bottom');
2704     if (defined $bottom_specified and
2705     ($bottom_specified->[0] eq 'DIMENSION' or
2706     $bottom_specified->[0] eq 'PERCENTAGE')) {
2707     my $tv = $self->{computed_value}->{$eid}->{bottom}
2708     = $compute_length->($self, $element, 'bottom',
2709     $bottom_specified);
2710     $self->{computed_value}->{$eid}->{top}
2711     = [$tv->[0], -$tv->[1], $tv->[2]];
2712     } else { # bottom: auto
2713     $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
2714     $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
2715     }
2716     }
2717     return;
2718     }
2719     }
2720    
2721     my $top_specified = $self->get_specified_value_no_inherit
2722     ($element, 'top');
2723     $self->{computed_value}->{$eid}->{top}
2724     = $compute_length->($self, $element, 'top', $top_specified);
2725     my $bottom_specified = $self->get_specified_value_no_inherit
2726     ($element, 'bottom');
2727     $self->{computed_value}->{$eid}->{bottom}
2728     = $compute_length->($self, $element, 'bottom', $bottom_specified);
2729     },
2730     };
2731     $Attr->{top} = $Prop->{top};
2732     $Key->{top} = $Prop->{top};
2733    
2734     $Prop->{bottom} = {
2735     css => 'bottom',
2736     dom => 'bottom',
2737     key => 'bottom',
2738     parse => $Prop->{'margin-top'}->{parse},
2739 wakaba 1.22 allow_negative => 1,
2740     keyword => {auto => 1},
2741 wakaba 1.21 initial => ['KEYWORD', 'auto'],
2742     #inherited => 0,
2743     compute_multiple => $Prop->{top}->{compute_multiple},
2744     };
2745     $Attr->{bottom} = $Prop->{bottom};
2746     $Key->{bottom} = $Prop->{bottom};
2747    
2748     $Prop->{left} = {
2749     css => 'left',
2750     dom => 'left',
2751     key => 'left',
2752     parse => $Prop->{'margin-top'}->{parse},
2753 wakaba 1.22 allow_negative => 1,
2754     keyword => {auto => 1},
2755 wakaba 1.21 initial => ['KEYWORD', 'auto'],
2756     #inherited => 0,
2757     compute_multiple => sub {
2758     my ($self, $element, $eid, $prop_name) = @_;
2759    
2760     my $pos_value = $self->get_computed_value ($element, 'position');
2761     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2762     if ($pos_value->[1] eq 'static') {
2763     $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
2764     $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
2765     return;
2766     } elsif ($pos_value->[1] eq 'relative') {
2767     my $left_specified = $self->get_specified_value_no_inherit
2768     ($element, 'left');
2769     if (defined $left_specified and
2770     ($left_specified->[0] eq 'DIMENSION' or
2771     $left_specified->[0] eq 'PERCENTAGE')) {
2772     my $right_specified = $self->get_specified_value_no_inherit
2773     ($element, 'right');
2774     if (defined $right_specified and
2775     ($right_specified->[0] eq 'DIMENSION' or
2776     $right_specified->[0] eq 'PERCENTAGE')) {
2777     my $direction = $self->get_computed_value ($element, 'direction');
2778     if (defined $direction and $direction->[0] eq 'KEYWORD' and
2779     $direction->[0] eq 'ltr') {
2780     my $tv = $self->{computed_value}->{$eid}->{left}
2781     = $compute_length->($self, $element, 'left',
2782     $left_specified);
2783     $self->{computed_value}->{$eid}->{right}
2784     = [$tv->[0], -$tv->[1], $tv->[2]];
2785     } else {
2786     my $tv = $self->{computed_value}->{$eid}->{right}
2787     = $compute_length->($self, $element, 'right',
2788     $right_specified);
2789     $self->{computed_value}->{$eid}->{left}
2790     = [$tv->[0], -$tv->[1], $tv->[2]];
2791     }
2792     } else {
2793     my $tv = $self->{computed_value}->{$eid}->{left}
2794     = $compute_length->($self, $element, 'left', $left_specified);
2795     $self->{computed_value}->{$eid}->{right}
2796     = [$tv->[0], -$tv->[1], $tv->[2]];
2797     }
2798     } else { # left: auto
2799     my $right_specified = $self->get_specified_value_no_inherit
2800     ($element, 'right');
2801     if (defined $right_specified and
2802     ($right_specified->[0] eq 'DIMENSION' or
2803     $right_specified->[0] eq 'PERCENTAGE')) {
2804     my $tv = $self->{computed_value}->{$eid}->{right}
2805     = $compute_length->($self, $element, 'right',
2806     $right_specified);
2807     $self->{computed_value}->{$eid}->{left}
2808     = [$tv->[0], -$tv->[1], $tv->[2]];
2809     } else { # right: auto
2810     $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
2811     $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
2812     }
2813     }
2814     return;
2815     }
2816     }
2817    
2818     my $left_specified = $self->get_specified_value_no_inherit
2819     ($element, 'left');
2820     $self->{computed_value}->{$eid}->{left}
2821     = $compute_length->($self, $element, 'left', $left_specified);
2822     my $right_specified = $self->get_specified_value_no_inherit
2823     ($element, 'right');
2824     $self->{computed_value}->{$eid}->{right}
2825     = $compute_length->($self, $element, 'right', $right_specified);
2826     },
2827     };
2828     $Attr->{left} = $Prop->{left};
2829     $Key->{left} = $Prop->{left};
2830    
2831     $Prop->{right} = {
2832     css => 'right',
2833     dom => 'right',
2834     key => 'right',
2835     parse => $Prop->{'margin-top'}->{parse},
2836 wakaba 1.51 allow_negative => 1,
2837     keyword => {auto => 1},
2838 wakaba 1.21 initial => ['KEYWORD', 'auto'],
2839     #inherited => 0,
2840     compute_multiple => $Prop->{left}->{compute_multiple},
2841     };
2842     $Attr->{right} = $Prop->{right};
2843     $Key->{right} = $Prop->{right};
2844    
2845 wakaba 1.22 $Prop->{width} = {
2846     css => 'width',
2847     dom => 'width',
2848     key => 'width',
2849     parse => $Prop->{'margin-top'}->{parse},
2850     #allow_negative => 0,
2851 wakaba 1.71 keyword => {
2852     auto => 1,
2853    
2854     ## Firefox 3
2855     '-moz-max-content' => 2, '-moz-min-content' => 2,
2856     '-moz-available' => 2, '-moz-fit-content' => 2,
2857     ## NOTE: By "2", it represents that the parser must be configured
2858     ## to allow these values.
2859     },
2860 wakaba 1.22 initial => ['KEYWORD', 'auto'],
2861     #inherited => 0,
2862     compute => $compute_length,
2863     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/width> for
2864     ## browser compatibility issues.
2865     };
2866     $Attr->{width} = $Prop->{width};
2867     $Key->{width} = $Prop->{width};
2868    
2869     $Prop->{'min-width'} = {
2870     css => 'min-width',
2871     dom => 'min_width',
2872     key => 'min_width',
2873     parse => $Prop->{'margin-top'}->{parse},
2874     #allow_negative => 0,
2875 wakaba 1.71 keyword => {
2876     ## Firefox 3
2877     '-moz-max-content' => 2, '-moz-min-content' => 2,
2878     '-moz-available' => 2, '-moz-fit-content' => 2,
2879     },
2880 wakaba 1.22 initial => ['DIMENSION', 0, 'px'],
2881     #inherited => 0,
2882     compute => $compute_length,
2883     };
2884     $Attr->{min_width} = $Prop->{'min-width'};
2885     $Key->{min_width} = $Prop->{'min-width'};
2886    
2887     $Prop->{'max-width'} = {
2888     css => 'max-width',
2889     dom => 'max_width',
2890     key => 'max_width',
2891     parse => $Prop->{'margin-top'}->{parse},
2892     #allow_negative => 0,
2893 wakaba 1.71 keyword => {
2894     none => 1,
2895    
2896     ## Firefox 3
2897     '-moz-max-content' => 2, '-moz-min-content' => 2,
2898     '-moz-available' => 2, '-moz-fit-content' => 2,
2899     },
2900 wakaba 1.22 initial => ['KEYWORD', 'none'],
2901     #inherited => 0,
2902     compute => $compute_length,
2903     };
2904     $Attr->{max_width} = $Prop->{'max-width'};
2905     $Key->{max_width} = $Prop->{'max-width'};
2906    
2907     $Prop->{height} = {
2908     css => 'height',
2909     dom => 'height',
2910     key => 'height',
2911     parse => $Prop->{'margin-top'}->{parse},
2912     #allow_negative => 0,
2913     keyword => {auto => 1},
2914     initial => ['KEYWORD', 'auto'],
2915     #inherited => 0,
2916     compute => $compute_length,
2917     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/height> for
2918     ## browser compatibility issues.
2919     };
2920     $Attr->{height} = $Prop->{height};
2921     $Key->{height} = $Prop->{height};
2922    
2923     $Prop->{'min-height'} = {
2924     css => 'min-height',
2925     dom => 'min_height',
2926     key => 'min_height',
2927     parse => $Prop->{'margin-top'}->{parse},
2928     #allow_negative => 0,
2929     #keyword => {},
2930     initial => ['DIMENSION', 0, 'px'],
2931     #inherited => 0,
2932     compute => $compute_length,
2933     };
2934     $Attr->{min_height} = $Prop->{'min-height'};
2935     $Key->{min_height} = $Prop->{'min-height'};
2936    
2937     $Prop->{'max-height'} = {
2938     css => 'max-height',
2939     dom => 'max_height',
2940     key => 'max_height',
2941     parse => $Prop->{'margin-top'}->{parse},
2942     #allow_negative => 0,
2943     keyword => {none => 1},
2944     initial => ['KEYWORD', 'none'],
2945     #inherited => 0,
2946     compute => $compute_length,
2947     };
2948     $Attr->{max_height} = $Prop->{'max-height'};
2949     $Key->{max_height} = $Prop->{'max-height'};
2950    
2951     $Prop->{'line-height'} = {
2952     css => 'line-height',
2953     dom => 'line_height',
2954     key => 'line_height',
2955 wakaba 1.19 parse => sub {
2956     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2957    
2958 wakaba 1.22 ## NOTE: Similar to 'margin-top', but different handling
2959     ## for unitless numbers.
2960    
2961 wakaba 1.51 my $has_sign;
2962 wakaba 1.19 my $sign = 1;
2963     if ($t->{type} == MINUS_TOKEN) {
2964     $t = $tt->get_next_token;
2965     $sign = -1;
2966 wakaba 1.51 $has_sign = 1;
2967     } elsif ($t->{type} == PLUS_TOKEN) {
2968     $t = $tt->get_next_token;
2969     $has_sign = 1;
2970 wakaba 1.19 }
2971    
2972     if ($t->{type} == DIMENSION_TOKEN) {
2973     my $value = $t->{number} * $sign;
2974     my $unit = lc $t->{value}; ## TODO: case
2975     if ($length_unit->{$unit} and $value >= 0) {
2976 wakaba 1.51 $t = $tt->get_next_token;
2977 wakaba 1.19 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2978     }
2979     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2980     my $value = $t->{number} * $sign;
2981 wakaba 1.51 if ($value >= 0) {
2982     $t = $tt->get_next_token;
2983     return ($t, {$prop_name => ['PERCENTAGE', $value]});
2984     }
2985 wakaba 1.22 } elsif ($t->{type} == NUMBER_TOKEN) {
2986 wakaba 1.19 my $value = $t->{number} * $sign;
2987 wakaba 1.51 if ($value >= 0) {
2988     $t = $tt->get_next_token;
2989     return ($t, {$prop_name => ['NUMBER', $value]});
2990     }
2991     } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2992 wakaba 1.19 my $value = lc $t->{value}; ## TODO: case
2993 wakaba 1.22 if ($value eq 'normal') {
2994 wakaba 1.51 $t = $tt->get_next_token;
2995 wakaba 1.22 return ($t, {$prop_name => ['KEYWORD', $value]});
2996     } elsif ($value eq 'inherit') {
2997 wakaba 1.51 $t = $tt->get_next_token;
2998 wakaba 1.19 return ($t, {$prop_name => ['INHERIT']});
2999     }
3000     }
3001    
3002 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3003     level => $self->{level}->{must},
3004 wakaba 1.38 uri => \$self->{href},
3005 wakaba 1.19 token => $t);
3006     return ($t, undef);
3007     },
3008 wakaba 1.22 initial => ['KEYWORD', 'normal'],
3009     inherited => 1,
3010     compute => $compute_length,
3011     };
3012     $Attr->{line_height} = $Prop->{'line-height'};
3013     $Key->{line_height} = $Prop->{'line-height'};
3014    
3015     $Prop->{'vertical-align'} = {
3016     css => 'vertical-align',
3017     dom => 'vertical_align',
3018     key => 'vertical_align',
3019     parse => $Prop->{'margin-top'}->{parse},
3020     allow_negative => 1,
3021     keyword => {
3022     baseline => 1, sub => 1, super => 1, top => 1, 'text-top' => 1,
3023     middle => 1, bottom => 1, 'text-bottom' => 1,
3024     },
3025     ## NOTE: Currently, we don't support option to select subset of keywords
3026     ## supported by application (i.e.
3027     ## $parser->{prop_value}->{'line-height'->{$keyword}). Should we support
3028     ## it?
3029     initial => ['KEYWORD', 'baseline'],
3030     #inherited => 0,
3031     compute => $compute_length,
3032     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/vertical-align> for
3033     ## browser compatibility issues.
3034     };
3035     $Attr->{vertical_align} = $Prop->{'vertical-align'};
3036     $Key->{vertical_align} = $Prop->{'vertical-align'};
3037    
3038 wakaba 1.23 $Prop->{'text-indent'} = {
3039     css => 'text-indent',
3040     dom => 'text_indent',
3041     key => 'text_indent',
3042     parse => $Prop->{'margin-top'}->{parse},
3043     allow_negative => 1,
3044     keyword => {},
3045     initial => ['DIMENSION', 0, 'px'],
3046     inherited => 1,
3047     compute => $compute_length,
3048     };
3049     $Attr->{text_indent} = $Prop->{'text-indent'};
3050     $Key->{text_indent} = $Prop->{'text-indent'};
3051    
3052 wakaba 1.27 $Prop->{'background-position-x'} = {
3053     css => 'background-position-x',
3054     dom => 'background_position_x',
3055     key => 'background_position_x',
3056     parse => $Prop->{'margin-top'}->{parse},
3057     allow_negative => 1,
3058     keyword => {left => 1, center => 1, right => 1},
3059     initial => ['PERCENTAGE', 0],
3060     #inherited => 0,
3061     compute => sub {
3062     my ($self, $element, $prop_name, $specified_value) = @_;
3063    
3064     if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3065     my $v = {
3066     left => 0, center => 50, right => 100, top => 0, bottom => 100,
3067     }->{$specified_value->[1]};
3068     if (defined $v) {
3069     return ['PERCENTAGE', $v];
3070     } else {
3071     return $specified_value;
3072     }
3073     } else {
3074     return $compute_length->(@_);
3075     }
3076     },
3077 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
3078 wakaba 1.27 };
3079     $Attr->{background_position_x} = $Prop->{'background-position-x'};
3080     $Key->{background_position_x} = $Prop->{'background-position-x'};
3081    
3082     $Prop->{'background-position-y'} = {
3083     css => 'background-position-y',
3084     dom => 'background_position_y',
3085     key => 'background_position_y',
3086     parse => $Prop->{'margin-top'}->{parse},
3087     allow_negative => 1,
3088     keyword => {top => 1, center => 1, bottom => 1},
3089 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
3090 wakaba 1.27 initial => ['PERCENTAGE', 0],
3091     #inherited => 0,
3092     compute => $Prop->{'background-position-x'}->{compute},
3093     };
3094     $Attr->{background_position_y} = $Prop->{'background-position-y'};
3095     $Key->{background_position_y} = $Prop->{'background-position-y'};
3096    
3097 wakaba 1.22 $Prop->{'padding-top'} = {
3098     css => 'padding-top',
3099     dom => 'padding_top',
3100     key => 'padding_top',
3101     parse => $Prop->{'margin-top'}->{parse},
3102     #allow_negative => 0,
3103     #keyword => {},
3104 wakaba 1.43 serialize_multiple => sub {
3105     my $self = shift;
3106    
3107     ## NOTE: Same as |serialize_multiple| of 'margin-top'.
3108    
3109     my $use_shorthand = 1;
3110     my $t = $self->padding_top;
3111     undef $use_shorthand unless length $t;
3112     my $t_i = $self->get_property_priority ('padding-top');
3113     my $r = $self->padding_right;
3114     undef $use_shorthand
3115     if not length $r or
3116     ($r eq 'inherit' and $t ne 'inherit') or
3117     ($t eq 'inherit' and $r ne 'inherit');
3118     my $r_i = $self->get_property_priority ('padding-right');
3119     undef $use_shorthand unless $r_i eq $t_i;
3120     my $b = $self->padding_bottom;
3121     undef $use_shorthand
3122     if not length $b or
3123     ($b eq 'inherit' and $t ne 'inherit') or
3124     ($t eq 'inherit' and $b ne 'inherit');
3125     my $b_i = $self->get_property_priority ('padding-bottom');
3126     undef $use_shorthand unless $b_i eq $t_i;
3127     my $l = $self->padding_left;
3128     undef $use_shorthand
3129     if not length $l or
3130     ($l eq 'inherit' and $t ne 'inherit') or
3131     ($t eq 'inherit' and $l ne 'inherit');
3132     my $l_i = $self->get_property_priority ('padding-left');
3133     undef $use_shorthand unless $l_i eq $t_i;
3134    
3135     if ($use_shorthand) {
3136     $b .= ' ' . $l if $r ne $l;
3137     $r .= ' ' . $b if $t ne $b;
3138     $t .= ' ' . $r if $t ne $r;
3139 wakaba 1.45 return {padding => [$t, $t_i]};
3140 wakaba 1.43 } else {
3141     my $v = {};
3142     if (length $t) {
3143 wakaba 1.45 $v->{'padding-top'} = [$t, $t_i];
3144 wakaba 1.43 }
3145     if (length $r) {
3146 wakaba 1.45 $v->{'padding-right'} = [$r, $r_i];
3147 wakaba 1.43 }
3148     if (length $b) {
3149 wakaba 1.45 $v->{'padding-bottom'} = [$b, $b_i];
3150 wakaba 1.43 }
3151     if (length $l) {
3152 wakaba 1.45 $v->{'padding-left'} = [$l, $l_i];
3153 wakaba 1.43 }
3154     return $v;
3155     }
3156     },
3157 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
3158     #inherited => 0,
3159     compute => $compute_length,
3160     };
3161     $Attr->{padding_top} = $Prop->{'padding-top'};
3162     $Key->{padding_top} = $Prop->{'padding-top'};
3163    
3164     $Prop->{'padding-bottom'} = {
3165     css => 'padding-bottom',
3166     dom => 'padding_bottom',
3167     key => 'padding_bottom',
3168     parse => $Prop->{'padding-top'}->{parse},
3169 wakaba 1.22 #allow_negative => 0,
3170     #keyword => {},
3171 wakaba 1.43 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3172 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
3173     #inherited => 0,
3174     compute => $compute_length,
3175     };
3176     $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
3177     $Key->{padding_bottom} = $Prop->{'padding-bottom'};
3178    
3179     $Prop->{'padding-right'} = {
3180     css => 'padding-right',
3181     dom => 'padding_right',
3182     key => 'padding_right',
3183     parse => $Prop->{'padding-top'}->{parse},
3184 wakaba 1.22 #allow_negative => 0,
3185     #keyword => {},
3186 wakaba 1.43 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3187 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
3188     #inherited => 0,
3189     compute => $compute_length,
3190     };
3191     $Attr->{padding_right} = $Prop->{'padding-right'};
3192     $Key->{padding_right} = $Prop->{'padding-right'};
3193    
3194     $Prop->{'padding-left'} = {
3195     css => 'padding-left',
3196     dom => 'padding_left',
3197     key => 'padding_left',
3198     parse => $Prop->{'padding-top'}->{parse},
3199 wakaba 1.22 #allow_negative => 0,
3200     #keyword => {},
3201 wakaba 1.43 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3202 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
3203     #inherited => 0,
3204     compute => $compute_length,
3205     };
3206     $Attr->{padding_left} = $Prop->{'padding-left'};
3207     $Key->{padding_left} = $Prop->{'padding-left'};
3208    
3209 wakaba 1.20 $Prop->{'border-top-width'} = {
3210     css => 'border-top-width',
3211     dom => 'border_top_width',
3212     key => 'border_top_width',
3213 wakaba 1.22 parse => $Prop->{'margin-top'}->{parse},
3214     #allow_negative => 0,
3215     keyword => {thin => 1, medium => 1, thick => 1},
3216 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3217 wakaba 1.20 initial => ['KEYWORD', 'medium'],
3218     #inherited => 0,
3219     compute => sub {
3220     my ($self, $element, $prop_name, $specified_value) = @_;
3221    
3222 wakaba 1.23 ## NOTE: Used for 'border-top-width', 'border-right-width',
3223     ## 'border-bottom-width', 'border-right-width', and
3224     ## 'outline-width'.
3225    
3226 wakaba 1.20 my $style_prop = $prop_name;
3227     $style_prop =~ s/width/style/;
3228     my $style = $self->get_computed_value ($element, $style_prop);
3229     if (defined $style and $style->[0] eq 'KEYWORD' and
3230     ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
3231     return ['DIMENSION', 0, 'px'];
3232     }
3233    
3234     my $value = $compute_length->(@_);
3235     if (defined $value and $value->[0] eq 'KEYWORD') {
3236     if ($value->[1] eq 'thin') {
3237     return ['DIMENSION', 1, 'px']; ## Firefox/Opera
3238     } elsif ($value->[1] eq 'medium') {
3239     return ['DIMENSION', 3, 'px']; ## Firefox/Opera
3240     } elsif ($value->[1] eq 'thick') {
3241     return ['DIMENSION', 5, 'px']; ## Firefox
3242     }
3243     }
3244     return $value;
3245     },
3246 wakaba 1.23 ## NOTE: CSS3 will allow <percentage> as an option in <border-width>.
3247     ## Opera 9 has already implemented it.
3248 wakaba 1.20 };
3249     $Attr->{border_top_width} = $Prop->{'border-top-width'};
3250     $Key->{border_top_width} = $Prop->{'border-top-width'};
3251    
3252     $Prop->{'border-right-width'} = {
3253     css => 'border-right-width',
3254     dom => 'border_right_width',
3255     key => 'border_right_width',
3256     parse => $Prop->{'border-top-width'}->{parse},
3257 wakaba 1.22 #allow_negative => 0,
3258     keyword => {thin => 1, medium => 1, thick => 1},
3259 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3260 wakaba 1.20 initial => ['KEYWORD', 'medium'],
3261     #inherited => 0,
3262     compute => $Prop->{'border-top-width'}->{compute},
3263     };
3264     $Attr->{border_right_width} = $Prop->{'border-right-width'};
3265     $Key->{border_right_width} = $Prop->{'border-right-width'};
3266    
3267     $Prop->{'border-bottom-width'} = {
3268     css => 'border-bottom-width',
3269     dom => 'border_bottom_width',
3270     key => 'border_bottom_width',
3271     parse => $Prop->{'border-top-width'}->{parse},
3272 wakaba 1.22 #allow_negative => 0,
3273     keyword => {thin => 1, medium => 1, thick => 1},
3274 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3275 wakaba 1.20 initial => ['KEYWORD', 'medium'],
3276     #inherited => 0,
3277     compute => $Prop->{'border-top-width'}->{compute},
3278     };
3279     $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
3280     $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
3281    
3282     $Prop->{'border-left-width'} = {
3283     css => 'border-left-width',
3284     dom => 'border_left_width',
3285     key => 'border_left_width',
3286     parse => $Prop->{'border-top-width'}->{parse},
3287 wakaba 1.22 #allow_negative => 0,
3288     keyword => {thin => 1, medium => 1, thick => 1},
3289 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3290 wakaba 1.20 initial => ['KEYWORD', 'medium'],
3291     #inherited => 0,
3292     compute => $Prop->{'border-top-width'}->{compute},
3293     };
3294     $Attr->{border_left_width} = $Prop->{'border-left-width'};
3295     $Key->{border_left_width} = $Prop->{'border-left-width'};
3296    
3297 wakaba 1.23 $Prop->{'outline-width'} = {
3298     css => 'outline-width',
3299     dom => 'outline_width',
3300     key => 'outline_width',
3301     parse => $Prop->{'border-top-width'}->{parse},
3302     #allow_negative => 0,
3303     keyword => {thin => 1, medium => 1, thick => 1},
3304 wakaba 1.29 serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
3305 wakaba 1.23 initial => ['KEYWORD', 'medium'],
3306     #inherited => 0,
3307     compute => $Prop->{'border-top-width'}->{compute},
3308     };
3309     $Attr->{outline_width} = $Prop->{'outline-width'};
3310     $Key->{outline_width} = $Prop->{'outline-width'};
3311    
3312 wakaba 1.15 $Prop->{'font-weight'} = {
3313     css => 'font-weight',
3314     dom => 'font_weight',
3315     key => 'font_weight',
3316     parse => sub {
3317     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3318    
3319 wakaba 1.50 my $has_sign;
3320     if ($t->{type} == PLUS_TOKEN) {
3321     $has_sign = 1;
3322     $t = $tt->get_next_token;
3323     }
3324    
3325 wakaba 1.15 if ($t->{type} == NUMBER_TOKEN) {
3326     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
3327     ## browser compatibility issue.
3328     my $value = $t->{number};
3329     $t = $tt->get_next_token;
3330     if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
3331     return ($t, {$prop_name => ['WEIGHT', $value, 0]});
3332     }
3333 wakaba 1.50 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
3334 wakaba 1.15 my $value = lc $t->{value}; ## TODO: case
3335     if ({
3336     normal => 1, bold => 1, bolder => 1, lighter => 1,
3337     }->{$value}) {
3338 wakaba 1.50 $t = $tt->get_next_token;
3339 wakaba 1.15 return ($t, {$prop_name => ['KEYWORD', $value]});
3340     } elsif ($value eq 'inherit') {
3341 wakaba 1.50 $t = $tt->get_next_token;
3342 wakaba 1.15 return ($t, {$prop_name => ['INHERIT']});
3343     }
3344     }
3345    
3346 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3347     level => $self->{level}->{must},
3348 wakaba 1.38 uri => \$self->{href},
3349 wakaba 1.15 token => $t);
3350     return ($t, undef);
3351     },
3352     initial => ['KEYWORD', 'normal'],
3353     inherited => 1,
3354     compute => sub {
3355     my ($self, $element, $prop_name, $specified_value) = @_;
3356    
3357 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3358 wakaba 1.15 if ($specified_value->[1] eq 'normal') {
3359     return ['WEIGHT', 400, 0];
3360     } elsif ($specified_value->[1] eq 'bold') {
3361     return ['WEIGHT', 700, 0];
3362     } elsif ($specified_value->[1] eq 'bolder') {
3363     my $parent_element = $element->manakai_parent_element;
3364     if (defined $parent_element) {
3365     my $parent_value = $self->get_cascaded_value
3366     ($parent_element, $prop_name); ## NOTE: What Firefox does.
3367     return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
3368     } else {
3369     return ['WEIGHT', 400, 1];
3370     }
3371     } elsif ($specified_value->[1] eq 'lighter') {
3372     my $parent_element = $element->manakai_parent_element;
3373     if (defined $parent_element) {
3374     my $parent_value = $self->get_cascaded_value
3375     ($parent_element, $prop_name); ## NOTE: What Firefox does.
3376     return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
3377     } else {
3378     return ['WEIGHT', 400, 1];
3379     }
3380     }
3381 wakaba 1.17 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
3382 wakaba 1.15 #
3383     }
3384    
3385     return $specified_value;
3386     },
3387     };
3388     $Attr->{font_weight} = $Prop->{'font-weight'};
3389     $Key->{font_weight} = $Prop->{'font-weight'};
3390    
3391 wakaba 1.13 my $uri_or_none_parser = sub {
3392 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3393    
3394 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
3395 wakaba 1.11 my $value = $t->{value};
3396     $t = $tt->get_next_token;
3397     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
3398     } elsif ($t->{type} == IDENT_TOKEN) {
3399     my $value = lc $t->{value}; ## TODO: case
3400     $t = $tt->get_next_token;
3401     if ($value eq 'none') {
3402     ## NOTE: |none| is the default value and therefore it must be
3403     ## supported anyway.
3404     return ($t, {$prop_name => ["KEYWORD", 'none']});
3405     } elsif ($value eq 'inherit') {
3406     return ($t, {$prop_name => ['INHERIT']});
3407     }
3408     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
3409     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
3410     # my $value = $t->{value};
3411     # $t = $tt->get_next_token;
3412     # if ($t->{type} == EOF_TOKEN) {
3413 wakaba 1.39 # $onerror->(type => 'uri not closed',
3414 wakaba 1.69 # level => $self->{level}->{must},
3415 wakaba 1.38 # uri => \$self->{href},
3416 wakaba 1.11 # token => $t);
3417     #
3418     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
3419     # }
3420     }
3421    
3422 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3423     level => $self->{level}->{must},
3424 wakaba 1.38 uri => \$self->{href},
3425 wakaba 1.11 token => $t);
3426     return ($t, undef);
3427 wakaba 1.13 }; # $uri_or_none_parser
3428    
3429 wakaba 1.14 my $compute_uri_or_none = sub {
3430 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
3431    
3432     if (defined $specified_value and
3433     $specified_value->[0] eq 'URI' and
3434     defined $specified_value->[2]) {
3435     require Message::DOM::DOMImplementation;
3436     return ['URI',
3437     Message::DOM::DOMImplementation->create_uri_reference
3438     ($specified_value->[1])
3439     ->get_absolute_reference (${$specified_value->[2]})
3440     ->get_uri_reference,
3441     $specified_value->[2]];
3442     }
3443    
3444     return $specified_value;
3445 wakaba 1.14 }; # $compute_uri_or_none
3446    
3447     $Prop->{'list-style-image'} = {
3448     css => 'list-style-image',
3449     dom => 'list_style_image',
3450     key => 'list_style_image',
3451     parse => $uri_or_none_parser,
3452     initial => ['KEYWORD', 'none'],
3453     inherited => 1,
3454     compute => $compute_uri_or_none,
3455 wakaba 1.11 };
3456     $Attr->{list_style_image} = $Prop->{'list-style-image'};
3457     $Key->{list_style_image} = $Prop->{'list-style-image'};
3458    
3459 wakaba 1.15 $Prop->{'background-image'} = {
3460     css => 'background-image',
3461     dom => 'background_image',
3462     key => 'background_image',
3463     parse => $uri_or_none_parser,
3464 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
3465 wakaba 1.15 initial => ['KEYWORD', 'none'],
3466     #inherited => 0,
3467     compute => $compute_uri_or_none,
3468     };
3469     $Attr->{background_image} = $Prop->{'background-image'};
3470     $Key->{background_image} = $Prop->{'background-image'};
3471    
3472 wakaba 1.62 $Attr->{font_stretch} =
3473     $Key->{font_stretch} =
3474     $Prop->{'font-stretch'} = {
3475     css => 'font-stretch',
3476     dom => 'font_stretch',
3477     key => 'font_stretch',
3478     parse => $one_keyword_parser,
3479     keyword => {
3480     qw/normal 1 wider 1 narrower 1 ultra-condensed 1 extra-condensed 1
3481     condensed 1 semi-condensed 1 semi-expanded 1 expanded 1
3482     extra-expanded 1 ultra-expanded 1/,
3483     },
3484     initial => ["KEYWORD", 'normal'],
3485     inherited => 1,
3486     compute => sub {
3487     my ($self, $element, $prop_name, $specified_value) = @_;
3488    
3489     if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3490     if ($specified_value->[1] eq 'wider') {
3491     my $parent = $element->manakai_parent_element;
3492     if ($parent) {
3493     my $computed = $self->get_computed_value ($parent, $prop_name);
3494     if (defined $computed and $computed->[0] eq 'KEYWORD') {
3495     return ['KEYWORD', {
3496     'ultra-condensed' => 'extra-condensed',
3497     'extra-condensed' => 'condensed',
3498     'condensed' => 'semi-condensed',
3499     'semi-condensed' => 'normal',
3500     'normal' => 'semi-expanded',
3501     'semi-expanded' => 'expanded',
3502     'expanded' => 'extra-expanded',
3503     'extra-expanded' => 'ultra-expanded',
3504     'ultra-expanded' => 'ultra-expanded',
3505     }->{$computed->[1]} || $computed->[1]];
3506     } else { ## This is an implementation error.
3507     #
3508     }
3509     } else {
3510     return ['KEYWORD', 'semi-expanded'];
3511     }
3512     } elsif ($specified_value->[1] eq 'narrower') {
3513     my $parent = $element->manakai_parent_element;
3514     if ($parent) {
3515     my $computed = $self->get_computed_value ($parent, $prop_name);
3516     if (defined $computed and $computed->[0] eq 'KEYWORD') {
3517     return ['KEYWORD', {
3518     'ultra-condensed' => 'ultra-condensed',
3519     'extra-condensed' => 'ultra-condensed',
3520     'condensed' => 'extra-condensed',
3521     'semi-condensed' => 'condensed',
3522     'normal' => 'semi-condensed',
3523     'semi-expanded' => 'normal',
3524     'expanded' => 'semi-expanded',
3525     'extra-expanded' => 'expanded',
3526     'ultra-expanded' => 'extra-expanded',
3527     }->{$computed->[1]} || $computed->[1]];
3528     } else { ## This is an implementation error.
3529     #
3530     }
3531     } else {
3532     return ['KEYWORD', 'semi-condensed'];
3533     }
3534     }
3535     }
3536    
3537     return $specified_value;
3538     },
3539     };
3540    
3541 wakaba 1.68 $Attr->{writing_mode} =
3542     $Key->{writing_mode} =
3543     $Prop->{'writing-mode'} = {
3544     css => 'writing-mode',
3545     dom => 'writing_mode',
3546     key => 'writing_mode',
3547     parse => $one_keyword_parser,
3548     keyword => {
3549     'lr' => 1, 'lr-tb' => 1,
3550     'rl' => 1, 'rl-tb' => 1,
3551     'tb' => 1, 'tb-rl' => 1,
3552     },
3553     initial => ['KEYWORD', 'lr-tb'],
3554     inherited => 1,
3555     compute => sub {
3556     my ($self, $element, $prop_name, $specified_value) = @_;
3557    
3558     ## ISSUE: Not defined by any standard.
3559    
3560     if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3561     if ($specified_value->[1] eq 'lr') {
3562     return ['KEYWORD', 'lr-tb'];
3563     } elsif ($specified_value->[1] eq 'rl') {
3564     return ['KEYWORD', 'rl-tb'];
3565     } elsif ($specified_value->[1] eq 'tb') {
3566     return ['KEYWORD', 'tb-rl'];
3567     }
3568     }
3569    
3570     return $specified_value;
3571     },
3572     };
3573    
3574     $Attr->{text_anchor} =
3575     $Key->{text_anchor} =
3576     $Prop->{'text-anchor'} = {
3577     css => 'text-anchor',
3578     dom => 'text_anchor', ## TODO: manakai extension. Documentation.
3579     key => 'text_anchor',
3580     parse => $one_keyword_parser,
3581     keyword => {
3582     start => 1, middle => 1, end => 1,
3583     },
3584     initial => ['KEYWORD', 'start'],
3585     inherited => 1,
3586     compute => $compute_as_specified,
3587     };
3588    
3589     $Attr->{dominant_baseline} =
3590     $Key->{dominant_baseline} =
3591     $Prop->{'dominant-baseline'} = {
3592     css => 'dominant-baseline',
3593     dom => 'dominant_baseline', ## TODO: manakai extension. Documentation.
3594     key => 'dominant_baseline',
3595     parse => $one_keyword_parser,
3596     keyword => {
3597     qw/auto 1 use-script 1 no-change 1 reset-size 1 ideographic 1 alphabetic 1
3598     hanging 1 mathematical 1 central 1 middle 1 text-after-edge 1
3599     text-before-edge 1/
3600     },
3601     initial => ['KEYWORD', 'auto'],
3602     inherited => 0,
3603     compute => $compute_as_specified,
3604     };
3605    
3606     $Attr->{alignment_baseline} =
3607     $Key->{alignment_baseline} =
3608     $Prop->{'alignment-baseline'} = {
3609     css => 'alignment-baseline',
3610     dom => 'alignment_baseline', ## TODO: manakai extension. Documentation.
3611     key => 'alignment_baseline',
3612     parse => $one_keyword_parser,
3613     keyword => {
3614     qw/auto 1 baseline 1 before-edge 1 text-before-edge 1 middle 1 central 1
3615     after-edge 1 text-after-edge 1 ideographic 1 alphabetic 1 hanging 1
3616     mathematical 1/
3617     },
3618     initial => ['KEYWORD', 'auto'],
3619     inherited => 0,
3620     compute => $compute_as_specified,
3621     };
3622    
3623 wakaba 1.7 my $border_style_keyword = {
3624     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
3625     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
3626     };
3627    
3628     $Prop->{'border-top-style'} = {
3629     css => 'border-top-style',
3630     dom => 'border_top_style',
3631     key => 'border_top_style',
3632     parse => $one_keyword_parser,
3633 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3634 wakaba 1.7 keyword => $border_style_keyword,
3635 wakaba 1.9 initial => ["KEYWORD", "none"],
3636     #inherited => 0,
3637     compute => $compute_as_specified,
3638 wakaba 1.7 };
3639     $Attr->{border_top_style} = $Prop->{'border-top-style'};
3640     $Key->{border_top_style} = $Prop->{'border-top-style'};
3641    
3642     $Prop->{'border-right-style'} = {
3643     css => 'border-right-style',
3644     dom => 'border_right_style',
3645     key => 'border_right_style',
3646     parse => $one_keyword_parser,
3647 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3648 wakaba 1.7 keyword => $border_style_keyword,
3649 wakaba 1.9 initial => ["KEYWORD", "none"],
3650     #inherited => 0,
3651     compute => $compute_as_specified,
3652 wakaba 1.7 };
3653     $Attr->{border_right_style} = $Prop->{'border-right-style'};
3654     $Key->{border_right_style} = $Prop->{'border-right-style'};
3655    
3656     $Prop->{'border-bottom-style'} = {
3657     css => 'border-bottom-style',
3658     dom => 'border_bottom_style',
3659     key => 'border_bottom_style',
3660     parse => $one_keyword_parser,
3661 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3662 wakaba 1.7 keyword => $border_style_keyword,
3663 wakaba 1.9 initial => ["KEYWORD", "none"],
3664     #inherited => 0,
3665     compute => $compute_as_specified,
3666 wakaba 1.7 };
3667     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
3668     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
3669    
3670     $Prop->{'border-left-style'} = {
3671     css => 'border-left-style',
3672     dom => 'border_left_style',
3673     key => 'border_left_style',
3674     parse => $one_keyword_parser,
3675 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3676 wakaba 1.7 keyword => $border_style_keyword,
3677 wakaba 1.9 initial => ["KEYWORD", "none"],
3678     #inherited => 0,
3679     compute => $compute_as_specified,
3680 wakaba 1.7 };
3681     $Attr->{border_left_style} = $Prop->{'border-left-style'};
3682     $Key->{border_left_style} = $Prop->{'border-left-style'};
3683    
3684 wakaba 1.16 $Prop->{'outline-style'} = {
3685     css => 'outline-style',
3686     dom => 'outline_style',
3687     key => 'outline_style',
3688     parse => $one_keyword_parser,
3689 wakaba 1.29 serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
3690 wakaba 1.23 keyword => {%$border_style_keyword},
3691 wakaba 1.16 initial => ['KEYWORD', 'none'],
3692     #inherited => 0,
3693     compute => $compute_as_specified,
3694     };
3695     $Attr->{outline_style} = $Prop->{'outline-style'};
3696     $Key->{outline_style} = $Prop->{'outline-style'};
3697 wakaba 1.23 delete $Prop->{'outline-style'}->{keyword}->{hidden};
3698 wakaba 1.16
3699 wakaba 1.44 my $generic_font_keywords = {
3700 wakaba 1.31 serif => 1, 'sans-serif' => 1, cursive => 1,
3701     fantasy => 1, monospace => 1, '-manakai-default' => 1,
3702     '-manakai-caption' => 1, '-manakai-icon' => 1,
3703     '-manakai-menu' => 1, '-manakai-message-box' => 1,
3704     '-manakai-small-caption' => 1, '-manakai-status-bar' => 1,
3705     };
3706     ## NOTE: "All five generic font families are defined to exist in all CSS
3707     ## implementations (they need not necessarily map to five distinct actual
3708     ## fonts)." [CSS 2.1].
3709     ## NOTE: "If no font with the indicated characteristics exists on a given
3710     ## platform, the user agent should either intelligently substitute (e.g., a
3711     ## smaller version of the 'caption' font might be used for the 'small-caption'
3712     ## font), or substitute a user agent default font." [CSS 2.1].
3713    
3714 wakaba 1.15 $Prop->{'font-family'} = {
3715     css => 'font-family',
3716     dom => 'font_family',
3717     key => 'font_family',
3718     parse => sub {
3719     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3720    
3721     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
3722     ## how chaotic browsers are!
3723    
3724 wakaba 1.53 ## NOTE: Opera 9 allows NUMBER and DIMENSION as part of
3725     ## <font-family>, while Firefox 2 does not.
3726    
3727 wakaba 1.15 my @prop_value;
3728    
3729     my $font_name = '';
3730     my $may_be_generic = 1;
3731 wakaba 1.31 my $may_be_inherit = ($prop_name ne 'font');
3732 wakaba 1.15 my $has_s = 0;
3733     F: {
3734     if ($t->{type} == IDENT_TOKEN) {
3735     undef $may_be_inherit if $has_s or length $font_name;
3736     undef $may_be_generic if $has_s or length $font_name;
3737     $font_name .= ' ' if $has_s;
3738     $font_name .= $t->{value};
3739     undef $has_s;
3740     $t = $tt->get_next_token;
3741     } elsif ($t->{type} == STRING_TOKEN) {
3742     $font_name .= ' ' if $has_s;
3743     $font_name .= $t->{value};
3744     undef $may_be_inherit;
3745     undef $may_be_generic;
3746     undef $has_s;
3747     $t = $tt->get_next_token;
3748 wakaba 1.31 } elsif ($t->{type} == COMMA_TOKEN) { ## TODO: case
3749     if ($may_be_generic and $generic_font_keywords->{lc $font_name}) {
3750 wakaba 1.15 push @prop_value, ['KEYWORD', $font_name];
3751     } elsif (not $may_be_generic or length $font_name) {
3752     push @prop_value, ["STRING", $font_name];
3753     }
3754     undef $may_be_inherit;
3755     $may_be_generic = 1;
3756     undef $has_s;
3757     $font_name = '';
3758     $t = $tt->get_next_token;
3759     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3760     } elsif ($t->{type} == S_TOKEN) {
3761     $has_s = 1;
3762     $t = $tt->get_next_token;
3763     } else {
3764 wakaba 1.31 if ($may_be_generic and $generic_font_keywords->{lc $font_name}) {
3765     push @prop_value, ['KEYWORD', $font_name]; ## TODO: case
3766 wakaba 1.15 } elsif (not $may_be_generic or length $font_name) {
3767     push @prop_value, ['STRING', $font_name];
3768     } else {
3769 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3770     level => $self->{level}->{must},
3771 wakaba 1.38 uri => \$self->{href},
3772 wakaba 1.15 token => $t);
3773     return ($t, undef);
3774     }
3775     last F;
3776     }
3777     redo F;
3778     } # F
3779    
3780     if ($may_be_inherit and
3781     @prop_value == 1 and
3782     $prop_value[0]->[0] eq 'STRING' and
3783     lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
3784     return ($t, {$prop_name => ['INHERIT']});
3785     } else {
3786     unshift @prop_value, 'FONT';
3787     return ($t, {$prop_name => \@prop_value});
3788     }
3789     },
3790     initial => ['FONT', ['KEYWORD', '-manakai-default']],
3791     inherited => 1,
3792     compute => $compute_as_specified,
3793     };
3794     $Attr->{font_family} = $Prop->{'font-family'};
3795     $Key->{font_family} = $Prop->{'font-family'};
3796    
3797 wakaba 1.17 $Prop->{cursor} = {
3798     css => 'cursor',
3799     dom => 'cursor',
3800     key => 'cursor',
3801     parse => sub {
3802     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3803    
3804     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
3805     ## compatibility issues.
3806    
3807     my @prop_value = ('CURSOR');
3808    
3809     F: {
3810     if ($t->{type} == IDENT_TOKEN) {
3811     my $v = lc $t->{value}; ## TODO: case
3812     if ($Prop->{$prop_name}->{keyword}->{$v}) {
3813     push @prop_value, ['KEYWORD', $v];
3814 wakaba 1.70 $t = $tt->get_next_token;
3815     last F;
3816     } elsif ($v eq 'hand' and
3817     $Prop->{$prop_name}->{keyword}->{pointer}) {
3818     ## TODO: add test
3819     $onerror->(type => 'CSS cursor hand',
3820     level => $self->{level}->{must}, # not valid <'cursor'>
3821     uri => \$self->{href},
3822     token => $t);
3823     push @prop_value, ['KEYWORD', 'pointer'];
3824     $t = $tt->get_next_token;
3825 wakaba 1.17 last F;
3826     } elsif ($v eq 'inherit' and @prop_value == 1) {
3827 wakaba 1.70 $t = $tt->get_next_token;
3828 wakaba 1.17 return ($t, {$prop_name => ['INHERIT']});
3829     } else {
3830 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3831     level => $self->{level}->{must},
3832 wakaba 1.38 uri => \$self->{href},
3833 wakaba 1.17 token => $t);
3834     return ($t, undef);
3835     }
3836     } elsif ($t->{type} == URI_TOKEN) {
3837     push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
3838     $t = $tt->get_next_token;
3839     } else {
3840 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3841     level => $self->{level}->{must},
3842 wakaba 1.38 uri => \$self->{href},
3843 wakaba 1.17 token => $t);
3844     return ($t, undef);
3845     }
3846    
3847     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3848     if ($t->{type} == COMMA_TOKEN) {
3849     $t = $tt->get_next_token;
3850     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3851     redo F;
3852     }
3853     } # F
3854    
3855     return ($t, {$prop_name => \@prop_value});
3856     },
3857     keyword => {
3858     auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
3859     'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
3860     'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
3861     'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
3862     },
3863     initial => ['CURSOR', ['KEYWORD', 'auto']],
3864     inherited => 1,
3865     compute => sub {
3866     my ($self, $element, $prop_name, $specified_value) = @_;
3867    
3868     if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
3869     my @new_value = ('CURSOR');
3870     for my $value (@$specified_value[1..$#$specified_value]) {
3871     if ($value->[0] eq 'URI') {
3872     if (defined $value->[2]) {
3873     require Message::DOM::DOMImplementation;
3874     push @new_value, ['URI',
3875     Message::DOM::DOMImplementation
3876     ->create_uri_reference ($value->[1])
3877     ->get_absolute_reference (${$value->[2]})
3878     ->get_uri_reference,
3879     $value->[2]];
3880     } else {
3881     push @new_value, $value;
3882     }
3883     } else {
3884     push @new_value, $value;
3885     }
3886     }
3887     return \@new_value;
3888     }
3889    
3890     return $specified_value;
3891     },
3892     };
3893     $Attr->{cursor} = $Prop->{cursor};
3894     $Key->{cursor} = $Prop->{cursor};
3895    
3896 wakaba 1.7 $Prop->{'border-style'} = {
3897     css => 'border-style',
3898     dom => 'border_style',
3899     parse => sub {
3900     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3901    
3902     my %prop_value;
3903     if ($t->{type} == IDENT_TOKEN) {
3904     my $prop_value = lc $t->{value}; ## TODO: case folding
3905     $t = $tt->get_next_token;
3906     if ($border_style_keyword->{$prop_value} and
3907     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
3908     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
3909     } elsif ($prop_value eq 'inherit') {
3910 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
3911 wakaba 1.19 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
3912     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
3913     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3914     return ($t, \%prop_value);
3915 wakaba 1.7 } else {
3916 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3917     level => $self->{level}->{must},
3918 wakaba 1.38 uri => \$self->{href},
3919 wakaba 1.7 token => $t);
3920     return ($t, undef);
3921     }
3922     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
3923     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
3924     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3925     } else {
3926 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3927     level => $self->{level}->{must},
3928 wakaba 1.38 uri => \$self->{href},
3929 wakaba 1.7 token => $t);
3930     return ($t, undef);
3931     }
3932    
3933     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3934     if ($t->{type} == IDENT_TOKEN) {
3935     my $prop_value = lc $t->{value}; ## TODO: case folding
3936     $t = $tt->get_next_token;
3937 wakaba 1.19 if ($border_style_keyword->{$prop_value} and
3938 wakaba 1.7 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
3939     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
3940     } else {
3941 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3942     level => $self->{level}->{must},
3943 wakaba 1.38 uri => \$self->{href},
3944 wakaba 1.7 token => $t);
3945     return ($t, undef);
3946     }
3947     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3948    
3949     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3950     if ($t->{type} == IDENT_TOKEN) {
3951     my $prop_value = lc $t->{value}; ## TODO: case folding
3952     $t = $tt->get_next_token;
3953     if ($border_style_keyword->{$prop_value} and
3954     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
3955     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
3956     } else {
3957 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3958     level => $self->{level}->{must},
3959 wakaba 1.38 uri => \$self->{href},
3960 wakaba 1.7 token => $t);
3961     return ($t, undef);
3962     }
3963    
3964     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3965     if ($t->{type} == IDENT_TOKEN) {
3966     my $prop_value = lc $t->{value}; ## TODO: case folding
3967     $t = $tt->get_next_token;
3968     if ($border_style_keyword->{$prop_value} and
3969     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
3970     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
3971     } else {
3972 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3973     level => $self->{level}->{must},
3974 wakaba 1.38 uri => \$self->{href},
3975 wakaba 1.7 token => $t);
3976     return ($t, undef);
3977     }
3978     }
3979     }
3980     }
3981    
3982     return ($t, \%prop_value);
3983     },
3984 wakaba 1.43 serialize_shorthand => sub {
3985     my $self = shift;
3986    
3987 wakaba 1.7 my @v;
3988     push @v, $self->border_top_style;
3989 wakaba 1.43 my $i = $self->get_property_priority ('border-top-style');
3990 wakaba 1.45 return {} unless length $v[-1];
3991 wakaba 1.7 push @v, $self->border_right_style;
3992 wakaba 1.45 return {} unless length $v[-1];
3993     return {} unless $i eq $self->get_property_priority ('border-right-style');
3994 wakaba 1.7 push @v, $self->border_bottom_style;
3995 wakaba 1.45 return {} unless length $v[-1];
3996     return {} unless $i eq $self->get_property_priority ('border-bottom-style');
3997 wakaba 1.19 push @v, $self->border_left_style;
3998 wakaba 1.45 return {} unless length $v[-1];
3999     return {} unless $i eq $self->get_property_priority ('border-left-style');
4000 wakaba 1.43
4001     my $v = 0;
4002     for (0..3) {
4003     $v++ if $v[$_] eq 'inherit';
4004     }
4005     if ($v == 4) {
4006 wakaba 1.45 return {'border-style' => ['inherit', $i]};
4007 wakaba 1.43 } elsif ($v) {
4008     return {};
4009     }
4010 wakaba 1.7
4011     pop @v if $v[1] eq $v[3];
4012     pop @v if $v[0] eq $v[2];
4013     pop @v if $v[0] eq $v[1];
4014 wakaba 1.45 return {'border-style' => [(join ' ', @v), $i]};
4015 wakaba 1.7 },
4016 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4017 wakaba 1.7 };
4018     $Attr->{border_style} = $Prop->{'border-style'};
4019    
4020 wakaba 1.29 $Prop->{'border-color'} = {
4021     css => 'border-color',
4022     dom => 'border_color',
4023     parse => sub {
4024     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4025    
4026     my %prop_value;
4027     ($t, my $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4028     if (not defined $pv) {
4029     return ($t, undef);
4030     }
4031     $prop_value{'border-top-color'} = $pv->{'border-color'};
4032     $prop_value{'border-bottom-color'} = $prop_value{'border-top-color'};
4033     $prop_value{'border-right-color'} = $prop_value{'border-top-color'};
4034     $prop_value{'border-left-color'}= $prop_value{'border-right-color'};
4035     if ($prop_value{'border-top-color'}->[0] eq 'INHERIT') {
4036     return ($t, \%prop_value);
4037     }
4038    
4039     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4040     if ({
4041     IDENT_TOKEN, 1,
4042     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4043     FUNCTION_TOKEN, 1,
4044     }->{$t->{type}}) {
4045     ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4046     if (not defined $pv) {
4047     return ($t, undef);
4048     } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
4049 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4050     level => $self->{level}->{must},
4051 wakaba 1.38 uri => \$self->{href},
4052 wakaba 1.29 token => $t);
4053     return ($t, undef);
4054     }
4055     $prop_value{'border-right-color'} = $pv->{'border-color'};
4056     $prop_value{'border-left-color'}= $prop_value{'border-right-color'};
4057    
4058     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4059     if ({
4060     IDENT_TOKEN, 1,
4061     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4062     FUNCTION_TOKEN, 1,
4063     }->{$t->{type}}) {
4064     ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4065     if (not defined $pv) {
4066     return ($t, undef);
4067     } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
4068 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4069     level => $self->{level}->{must},
4070 wakaba 1.38 uri => \$self->{href},
4071 wakaba 1.29 token => $t);
4072     return ($t, undef);
4073     }
4074     $prop_value{'border-bottom-color'} = $pv->{'border-color'};
4075    
4076     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4077     if ({
4078     IDENT_TOKEN, 1,
4079     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4080     FUNCTION_TOKEN, 1,
4081     }->{$t->{type}}) {
4082     ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4083     if (not defined $pv) {
4084     return ($t, undef);
4085     } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
4086 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4087     level => $self->{level}->{must},
4088 wakaba 1.38 uri => \$self->{href},
4089 wakaba 1.29 token => $t);
4090     return ($t, undef);
4091     }
4092     $prop_value{'border-left-color'} = $pv->{'border-color'};
4093     }
4094     }
4095     }
4096    
4097     return ($t, \%prop_value);
4098     },
4099 wakaba 1.43 serialize_shorthand => sub {
4100     my $self = shift;
4101    
4102 wakaba 1.29 my @v;
4103     push @v, $self->border_top_color;
4104 wakaba 1.43 my $i = $self->get_property_priority ('border-top-color');
4105 wakaba 1.45 return {} unless length $v[-1];
4106 wakaba 1.29 push @v, $self->border_right_color;
4107 wakaba 1.45 return {} unless length $v[-1];
4108     return {} unless $i eq $self->get_property_priority ('border-right-color');
4109 wakaba 1.29 push @v, $self->border_bottom_color;
4110 wakaba 1.45 return {} unless length $v[-1];
4111     return {} unless $i eq $self->get_property_priority ('border-bottom-color');
4112 wakaba 1.29 push @v, $self->border_left_color;
4113 wakaba 1.45 return {} unless length $v[-1];
4114     return {} unless $i eq $self->get_property_priority ('border-left-color');
4115 wakaba 1.43
4116     my $v = 0;
4117     for (0..3) {
4118     $v++ if $v[$_] eq 'inherit';
4119     }
4120     if ($v == 4) {
4121 wakaba 1.45 return {'border-color' => ['inherit', $i]};
4122 wakaba 1.43 } elsif ($v) {
4123     return {};
4124     }
4125 wakaba 1.29
4126     pop @v if $v[1] eq $v[3];
4127     pop @v if $v[0] eq $v[2];
4128     pop @v if $v[0] eq $v[1];
4129 wakaba 1.45 return {'border-color' => [(join ' ', @v), $i]};
4130 wakaba 1.29 },
4131     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4132     };
4133     $Attr->{border_color} = $Prop->{'border-color'};
4134    
4135     $Prop->{'border-top'} = {
4136     css => 'border-top',
4137     dom => 'border_top',
4138     parse => sub {
4139     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4140    
4141 wakaba 1.65 ## TODO: Need to be rewritten.
4142    
4143 wakaba 1.29 my %prop_value;
4144     my $pv;
4145     ## NOTE: Since $onerror is disabled for three invocations below,
4146     ## some informative warning messages (if they are added someday) will not
4147     ## be reported.
4148     ($t, $pv) = $parse_color->($self, $prop_name.'-color', $tt, $t, sub {});
4149     if (defined $pv) {
4150     if ($pv->{$prop_name.'-color'}->[0] eq 'INHERIT') {
4151     return ($t, {$prop_name.'-color' => ['INHERIT'],
4152     $prop_name.'-style' => ['INHERIT'],
4153     $prop_name.'-width' => ['INHERIT']});
4154     } else {
4155     $prop_value{$prop_name.'-color'} = $pv->{$prop_name.'-color'};
4156     }
4157     } else {
4158     ($t, $pv) = $Prop->{'border-top-width'}->{parse}
4159     ->($self, $prop_name.'-width', $tt, $t, sub {});
4160     if (defined $pv) {
4161     $prop_value{$prop_name.'-width'} = $pv->{$prop_name.'-width'};
4162     } else {
4163     ($t, $pv) = $Prop->{'border-top-style'}->{parse}
4164     ->($self, $prop_name.'-style', $tt, $t, sub {});
4165     if (defined $pv) {
4166     $prop_value{$prop_name.'-style'} = $pv->{$prop_name.'-style'};
4167     } else {
4168 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4169     level => $self->{level}->{must},
4170 wakaba 1.38 uri => \$self->{href},
4171 wakaba 1.29 token => $t);
4172     return ($t, undef);
4173     }
4174     }
4175     }
4176    
4177     for (1..2) {
4178     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4179     if ($t->{type} == IDENT_TOKEN) {
4180     my $prop_value = lc $t->{value}; ## TODO: case
4181     if ($border_style_keyword->{$prop_value} and
4182     $self->{prop_value}->{'border-top-style'}->{$prop_value} and
4183     not defined $prop_value{$prop_name.'-style'}) {
4184     $prop_value{$prop_name.'-style'} = ['KEYWORD', $prop_value];
4185     $t = $tt->get_next_token;
4186     next;
4187     } elsif ({thin => 1, medium => 1, thick => 1}->{$prop_value} and
4188     not defined $prop_value{$prop_name.'-width'}) {
4189     $prop_value{$prop_name.'-width'} = ['KEYWORD', $prop_value];
4190     $t = $tt->get_next_token;
4191     next;
4192     }
4193     }
4194    
4195     undef $pv;
4196     ($t, $pv) = $parse_color->($self, $prop_name.'-color', $tt, $t, $onerror)
4197     if not defined $prop_value{$prop_name.'-color'} and
4198     {
4199     IDENT_TOKEN, 1,
4200     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4201     FUNCTION_TOKEN, 1,
4202     }->{$t->{type}};
4203     if (defined $pv) {
4204     if ($pv->{$prop_name.'-color'}->[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.'-color'} = $pv->{$prop_name.'-color'};
4211     }
4212     } else {
4213     undef $pv;
4214     ($t, $pv) = $Prop->{'border-top-width'}->{parse}
4215     ->($self, $prop_name.'-width',
4216     $tt, $t, $onerror)
4217     if not defined $prop_value{$prop_name.'-width'} and
4218     {
4219     DIMENSION_TOKEN, 1,
4220     NUMBER_TOKEN, 1,
4221     IDENT_TOKEN, 1,
4222     MINUS_TOKEN, 1,
4223     }->{$t->{type}};
4224     if (defined $pv) {
4225     if ($pv->{$prop_name.'-width'}->[0] eq 'INHERIT') {
4226 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4227     level => $self->{level}->{must},
4228 wakaba 1.38 uri => \$self->{href},
4229 wakaba 1.29 token => $t);
4230     } else {
4231     $prop_value{$prop_name.'-width'} = $pv->{$prop_name.'-width'};
4232     }
4233     } else {
4234     last;
4235     }
4236     }
4237     }
4238    
4239     $prop_value{$prop_name.'-color'}
4240     ||= $Prop->{$prop_name.'-color'}->{initial};
4241     $prop_value{$prop_name.'-width'}
4242     ||= $Prop->{$prop_name.'-width'}->{initial};
4243     $prop_value{$prop_name.'-style'}
4244     ||= $Prop->{$prop_name.'-style'}->{initial};
4245    
4246     return ($t, \%prop_value);
4247     },
4248 wakaba 1.43 serialize_shorthand => sub {
4249     my $self = shift;
4250    
4251     my $w = $self->border_top_width;
4252     return {} unless length $w;
4253     my $i = $self->get_property_priority ('border-top-width');
4254     my $s = $self->border_top_style;
4255     return {} unless length $s;
4256     return {} unless $i eq $self->get_property_priority ('border-top-style');
4257     my $c = $self->border_top_color;
4258     return {} unless length $c;
4259     return {} unless $i eq $self->get_property_priority ('border-top-color');
4260    
4261     my $v = 0;
4262     $v++ if $w eq 'inherit';
4263     $v++ if $s eq 'inherit';
4264     $v++ if $c eq 'inherit';
4265     if ($v == 3) {
4266     return {'border-top' => ['inherit', $i]};
4267     } elsif ($v) {
4268     return {};
4269     }
4270 wakaba 1.29
4271 wakaba 1.43 return {'border-top' => [$w . ' ' . $s . ' ' . $c, $i]};
4272 wakaba 1.29 },
4273     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4274     };
4275     $Attr->{border_top} = $Prop->{'border-top'};
4276    
4277     $Prop->{'border-right'} = {
4278     css => 'border-right',
4279     dom => 'border_right',
4280     parse => $Prop->{'border-top'}->{parse},
4281 wakaba 1.43 serialize_shorthand => sub {
4282     my $self = shift;
4283    
4284     my $w = $self->border_right_width;
4285     return {} unless length $w;
4286     my $i = $self->get_property_priority ('border-right-width');
4287     my $s = $self->border_right_style;
4288     return {} unless length $s;
4289     return {} unless $i eq $self->get_property_priority ('border-right-style');
4290     my $c = $self->border_right_color;
4291     return {} unless length $c;
4292     return {} unless $i eq $self->get_property_priority ('border-right-color');
4293    
4294     my $v = 0;
4295     $v++ if $w eq 'inherit';
4296     $v++ if $s eq 'inherit';
4297     $v++ if $c eq 'inherit';
4298     if ($v == 3) {
4299     return {'border-right' => ['inherit', $i]};
4300     } elsif ($v) {
4301     return {};
4302     }
4303    
4304     return {'border-right' => [$w . ' ' . $s . ' ' . $c, $i]};
4305     },
4306 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4307     };
4308     $Attr->{border_right} = $Prop->{'border-right'};
4309    
4310     $Prop->{'border-bottom'} = {
4311     css => 'border-bottom',
4312     dom => 'border_bottom',
4313     parse => $Prop->{'border-top'}->{parse},
4314 wakaba 1.43 serialize_shorthand => sub {
4315     my $self = shift;
4316    
4317     my $w = $self->border_bottom_width;
4318     return {} unless length $w;
4319     my $i = $self->get_property_priority ('border-bottom-width');
4320     my $s = $self->border_bottom_style;
4321     return {} unless length $s;
4322     return {} unless $i eq $self->get_property_priority ('border-bottom-style');
4323     my $c = $self->border_bottom_color;
4324     return {} unless length $c;
4325     return {} unless $i eq $self->get_property_priority ('border-bottom-color');
4326    
4327     my $v = 0;
4328     $v++ if $w eq 'inherit';
4329     $v++ if $s eq 'inherit';
4330     $v++ if $c eq 'inherit';
4331     if ($v == 3) {
4332     return {'border-bottom' => ['inherit', $i]};
4333     } elsif ($v) {
4334     return {};
4335     }
4336    
4337     return {'border-bottom' => [$w . ' ' . $s . ' ' . $c, $i]};
4338     },
4339 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4340     };
4341     $Attr->{border_bottom} = $Prop->{'border-bottom'};
4342    
4343     $Prop->{'border-left'} = {
4344     css => 'border-left',
4345     dom => 'border_left',
4346     parse => $Prop->{'border-top'}->{parse},
4347 wakaba 1.43 serialize_shorthand => sub {
4348     my $self = shift;
4349    
4350     my $w = $self->border_left_width;
4351     return {} unless length $w;
4352     my $i = $self->get_property_priority ('border-left-width');
4353     my $s = $self->border_left_style;
4354     return {} unless length $s;
4355     return {} unless $i eq $self->get_property_priority ('border-left-style');
4356     my $c = $self->border_left_color;
4357     return {} unless length $c;
4358     return {} unless $i eq $self->get_property_priority ('border-left-color');
4359    
4360     my $v = 0;
4361     $v++ if $w eq 'inherit';
4362     $v++ if $s eq 'inherit';
4363     $v++ if $c eq 'inherit';
4364     if ($v == 3) {
4365     return {'border-left' => ['inherit', $i]};
4366     } elsif ($v) {
4367     return {};
4368     }
4369    
4370     return {'border-left' => [$w . ' ' . $s . ' ' . $c, $i]};
4371     },
4372 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4373     };
4374     $Attr->{border_left} = $Prop->{'border-left'};
4375    
4376 wakaba 1.65 ## TODO: -moz-outline -> outline
4377    
4378 wakaba 1.29 $Prop->{outline} = {
4379     css => 'outline',
4380     dom => 'outline',
4381     parse => $Prop->{'border-top'}->{parse},
4382     serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
4383     };
4384     $Attr->{outline} = $Prop->{outline};
4385    
4386     $Prop->{border} = {
4387     css => 'border',
4388     dom => 'border',
4389     parse => sub {
4390     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4391     my $prop_value;
4392     ($t, $prop_value) = $Prop->{'border-top'}->{parse}
4393     ->($self, 'border-top', $tt, $t, $onerror);
4394     return ($t, undef) unless defined $prop_value;
4395    
4396     for (qw/border-right border-bottom border-left/) {
4397     $prop_value->{$_.'-color'} = $prop_value->{'border-top-color'}
4398     if defined $prop_value->{'border-top-color'};
4399     $prop_value->{$_.'-style'} = $prop_value->{'border-top-style'}
4400     if defined $prop_value->{'border-top-style'};
4401     $prop_value->{$_.'-width'} = $prop_value->{'border-top-width'}
4402     if defined $prop_value->{'border-top-width'};
4403     }
4404     return ($t, $prop_value);
4405     },
4406     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4407     };
4408     $Attr->{border} = $Prop->{border};
4409    
4410 wakaba 1.19 $Prop->{margin} = {
4411     css => 'margin',
4412     dom => 'margin',
4413     parse => sub {
4414     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4415    
4416     my %prop_value;
4417    
4418     my $sign = 1;
4419 wakaba 1.42 my $has_sign;
4420 wakaba 1.19 if ($t->{type} == MINUS_TOKEN) {
4421     $t = $tt->get_next_token;
4422 wakaba 1.42 $has_sign = 1;
4423 wakaba 1.19 $sign = -1;
4424 wakaba 1.42 } elsif ($t->{type} == PLUS_TOKEN) {
4425     $t = $tt->get_next_token;
4426     $has_sign = 1;
4427 wakaba 1.19 }
4428    
4429     if ($t->{type} == DIMENSION_TOKEN) {
4430     my $value = $t->{number} * $sign;
4431     my $unit = lc $t->{value}; ## TODO: case
4432     $t = $tt->get_next_token;
4433     if ($length_unit->{$unit}) {
4434     $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
4435     } else {
4436 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4437     level => $self->{level}->{must},
4438 wakaba 1.38 uri => \$self->{href},
4439 wakaba 1.19 token => $t);
4440     return ($t, undef);
4441     }
4442     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4443     my $value = $t->{number} * $sign;
4444     $t = $tt->get_next_token;
4445     $prop_value{'margin-top'} = ['PERCENTAGE', $value];
4446 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4447 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4448     my $value = $t->{number} * $sign;
4449     $t = $tt->get_next_token;
4450     $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
4451 wakaba 1.42 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4452 wakaba 1.19 my $prop_value = lc $t->{value}; ## TODO: case folding
4453     $t = $tt->get_next_token;
4454     if ($prop_value eq 'auto') {
4455     $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
4456     } elsif ($prop_value eq 'inherit') {
4457     $prop_value{'margin-top'} = ['INHERIT'];
4458     $prop_value{'margin-right'} = $prop_value{'margin-top'};
4459     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
4460     $prop_value{'margin-left'} = $prop_value{'margin-right'};
4461     return ($t, \%prop_value);
4462     } else {
4463 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4464     level => $self->{level}->{must},
4465 wakaba 1.38 uri => \$self->{href},
4466 wakaba 1.19 token => $t);
4467     return ($t, undef);
4468     }
4469     } else {
4470 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4471     level => $self->{level}->{must},
4472 wakaba 1.38 uri => \$self->{href},
4473 wakaba 1.19 token => $t);
4474     return ($t, undef);
4475     }
4476     $prop_value{'margin-right'} = $prop_value{'margin-top'};
4477     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
4478     $prop_value{'margin-left'} = $prop_value{'margin-right'};
4479    
4480     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4481 wakaba 1.42 undef $has_sign;
4482 wakaba 1.19 $sign = 1;
4483     if ($t->{type} == MINUS_TOKEN) {
4484     $t = $tt->get_next_token;
4485 wakaba 1.42 $has_sign = 1;
4486 wakaba 1.19 $sign = -1;
4487 wakaba 1.42 } elsif ($t->{type} == PLUS_TOKEN) {
4488     $t = $tt->get_next_token;
4489     $has_sign = 1;
4490 wakaba 1.19 }
4491    
4492     if ($t->{type} == DIMENSION_TOKEN) {
4493     my $value = $t->{number} * $sign;
4494     my $unit = lc $t->{value}; ## TODO: case
4495     $t = $tt->get_next_token;
4496     if ($length_unit->{$unit}) {
4497     $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
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     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4506     my $value = $t->{number} * $sign;
4507     $t = $tt->get_next_token;
4508     $prop_value{'margin-right'} = ['PERCENTAGE', $value];
4509 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4510 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4511     my $value = $t->{number} * $sign;
4512     $t = $tt->get_next_token;
4513     $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
4514 wakaba 1.42 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4515 wakaba 1.19 my $prop_value = lc $t->{value}; ## TODO: case folding
4516     $t = $tt->get_next_token;
4517     if ($prop_value eq 'auto') {
4518     $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
4519     } else {
4520 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4521     level => $self->{level}->{must},
4522 wakaba 1.38 uri => \$self->{href},
4523 wakaba 1.19 token => $t);
4524     return ($t, undef);
4525     }
4526     } else {
4527 wakaba 1.42 if ($has_sign) {
4528 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4529     level => $self->{level}->{must},
4530 wakaba 1.38 uri => \$self->{href},
4531 wakaba 1.24 token => $t);
4532     return ($t, undef);
4533     }
4534 wakaba 1.19 return ($t, \%prop_value);
4535     }
4536     $prop_value{'margin-left'} = $prop_value{'margin-right'};
4537    
4538     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4539 wakaba 1.42 undef $has_sign;
4540 wakaba 1.19 $sign = 1;
4541     if ($t->{type} == MINUS_TOKEN) {
4542     $t = $tt->get_next_token;
4543 wakaba 1.42 $has_sign = 1;
4544 wakaba 1.19 $sign = -1;
4545 wakaba 1.42 } elsif ($t->{type} == PLUS_TOKEN) {
4546     $t = $tt->get_next_token;
4547     $has_sign = 1;
4548 wakaba 1.19 }
4549    
4550     if ($t->{type} == DIMENSION_TOKEN) {
4551     my $value = $t->{number} * $sign;
4552     my $unit = lc $t->{value}; ## TODO: case
4553     $t = $tt->get_next_token;
4554     if ($length_unit->{$unit}) {
4555     $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
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     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4564     my $value = $t->{number} * $sign;
4565     $t = $tt->get_next_token;
4566     $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
4567 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4568 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4569     my $value = $t->{number} * $sign;
4570     $t = $tt->get_next_token;
4571     $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
4572 wakaba 1.42 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4573 wakaba 1.19 my $prop_value = lc $t->{value}; ## TODO: case folding
4574     $t = $tt->get_next_token;
4575     if ($prop_value eq 'auto') {
4576     $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
4577     } else {
4578 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4579     level => $self->{level}->{must},
4580 wakaba 1.38 uri => \$self->{href},
4581 wakaba 1.19 token => $t);
4582     return ($t, undef);
4583     }
4584     } else {
4585 wakaba 1.42 if ($has_sign) {
4586 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4587     level => $self->{level}->{must},
4588 wakaba 1.38 uri => \$self->{href},
4589 wakaba 1.24 token => $t);
4590     return ($t, undef);
4591     }
4592 wakaba 1.19 return ($t, \%prop_value);
4593     }
4594    
4595     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4596 wakaba 1.42 undef $has_sign;
4597 wakaba 1.19 $sign = 1;
4598     if ($t->{type} == MINUS_TOKEN) {
4599     $t = $tt->get_next_token;
4600 wakaba 1.42 $has_sign = 1;
4601 wakaba 1.19 $sign = -1;
4602 wakaba 1.42 } elsif ($t->{type} == PLUS_TOKEN) {
4603     $t = $tt->get_next_token;
4604     $has_sign = 1;
4605 wakaba 1.19 }
4606    
4607     if ($t->{type} == DIMENSION_TOKEN) {
4608     my $value = $t->{number} * $sign;
4609     my $unit = lc $t->{value}; ## TODO: case
4610     $t = $tt->get_next_token;
4611     if ($length_unit->{$unit}) {
4612     $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
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     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4621     my $value = $t->{number} * $sign;
4622     $t = $tt->get_next_token;
4623     $prop_value{'margin-left'} = ['PERCENTAGE', $value];
4624 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4625 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4626     my $value = $t->{number} * $sign;
4627     $t = $tt->get_next_token;
4628     $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
4629 wakaba 1.42 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4630 wakaba 1.19 my $prop_value = lc $t->{value}; ## TODO: case folding
4631     $t = $tt->get_next_token;
4632     if ($prop_value eq 'auto') {
4633     $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
4634     } else {
4635 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4636     level => $self->{level}->{must},
4637 wakaba 1.38 uri => \$self->{href},
4638 wakaba 1.19 token => $t);
4639     return ($t, undef);
4640     }
4641     } else {
4642 wakaba 1.42 if ($has_sign) {
4643 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4644     level => $self->{level}->{must},
4645 wakaba 1.38 uri => \$self->{href},
4646 wakaba 1.24 token => $t);
4647     return ($t, undef);
4648     }
4649 wakaba 1.19 return ($t, \%prop_value);
4650     }
4651    
4652     return ($t, \%prop_value);
4653     },
4654 wakaba 1.42 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
4655 wakaba 1.19 };
4656     $Attr->{margin} = $Prop->{margin};
4657    
4658     $Prop->{padding} = {
4659     css => 'padding',
4660     dom => 'padding',
4661     parse => sub {
4662     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4663    
4664     my %prop_value;
4665    
4666     my $sign = 1;
4667     if ($t->{type} == MINUS_TOKEN) {
4668     $t = $tt->get_next_token;
4669     $sign = -1;
4670     }
4671    
4672     if ($t->{type} == DIMENSION_TOKEN) {
4673     my $value = $t->{number} * $sign;
4674     my $unit = lc $t->{value}; ## TODO: case
4675     $t = $tt->get_next_token;
4676     if ($length_unit->{$unit} and $value >= 0) {
4677     $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
4678     } else {
4679 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4680     level => $self->{level}->{must},
4681 wakaba 1.38 uri => \$self->{href},
4682 wakaba 1.19 token => $t);
4683     return ($t, undef);
4684     }
4685     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4686     my $value = $t->{number} * $sign;
4687     $t = $tt->get_next_token;
4688     $prop_value{'padding-top'} = ['PERCENTAGE', $value];
4689     unless ($value >= 0) {
4690 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4691     level => $self->{level}->{must},
4692 wakaba 1.38 uri => \$self->{href},
4693 wakaba 1.19 token => $t);
4694     return ($t, undef);
4695     }
4696 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4697 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4698     my $value = $t->{number} * $sign;
4699     $t = $tt->get_next_token;
4700     $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
4701     unless ($value >= 0) {
4702 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4703     level => $self->{level}->{must},
4704 wakaba 1.38 uri => \$self->{href},
4705 wakaba 1.19 token => $t);
4706     return ($t, undef);
4707     }
4708     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4709     my $prop_value = lc $t->{value}; ## TODO: case folding
4710     $t = $tt->get_next_token;
4711     if ($prop_value eq 'inherit') {
4712     $prop_value{'padding-top'} = ['INHERIT'];
4713     $prop_value{'padding-right'} = $prop_value{'padding-top'};
4714     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
4715     $prop_value{'padding-left'} = $prop_value{'padding-right'};
4716     return ($t, \%prop_value);
4717     } else {
4718 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4719     level => $self->{level}->{must},
4720 wakaba 1.38 uri => \$self->{href},
4721 wakaba 1.19 token => $t);
4722     return ($t, undef);
4723     }
4724     } else {
4725 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4726     level => $self->{level}->{must},
4727 wakaba 1.38 uri => \$self->{href},
4728 wakaba 1.19 token => $t);
4729     return ($t, undef);
4730     }
4731     $prop_value{'padding-right'} = $prop_value{'padding-top'};
4732     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
4733     $prop_value{'padding-left'} = $prop_value{'padding-right'};
4734    
4735     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4736     $sign = 1;
4737     if ($t->{type} == MINUS_TOKEN) {
4738     $t = $tt->get_next_token;
4739     $sign = -1;
4740     }
4741    
4742     if ($t->{type} == DIMENSION_TOKEN) {
4743     my $value = $t->{number} * $sign;
4744     my $unit = lc $t->{value}; ## TODO: case
4745     $t = $tt->get_next_token;
4746     if ($length_unit->{$unit} and $value >= 0) {
4747     $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
4748     } else {
4749 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4750     level => $self->{level}->{must},
4751 wakaba 1.38 uri => \$self->{href},
4752 wakaba 1.19 token => $t);
4753     return ($t, undef);
4754     }
4755     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4756     my $value = $t->{number} * $sign;
4757     $t = $tt->get_next_token;
4758     $prop_value{'padding-right'} = ['PERCENTAGE', $value];
4759     unless ($value >= 0) {
4760 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4761     level => $self->{level}->{must},
4762 wakaba 1.38 uri => \$self->{href},
4763 wakaba 1.19 token => $t);
4764     return ($t, undef);
4765     }
4766 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4767 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4768     my $value = $t->{number} * $sign;
4769     $t = $tt->get_next_token;
4770     $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
4771     unless ($value >= 0) {
4772 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4773     level => $self->{level}->{must},
4774 wakaba 1.38 uri => \$self->{href},
4775 wakaba 1.19 token => $t);
4776     return ($t, undef);
4777     }
4778     } else {
4779 wakaba 1.24 if ($sign < 0) {
4780 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4781     level => $self->{level}->{must},
4782 wakaba 1.38 uri => \$self->{href},
4783 wakaba 1.24 token => $t);
4784     return ($t, undef);
4785     }
4786 wakaba 1.19 return ($t, \%prop_value);
4787     }
4788     $prop_value{'padding-left'} = $prop_value{'padding-right'};
4789    
4790     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4791     $sign = 1;
4792     if ($t->{type} == MINUS_TOKEN) {
4793     $t = $tt->get_next_token;
4794     $sign = -1;
4795     }
4796    
4797     if ($t->{type} == DIMENSION_TOKEN) {
4798     my $value = $t->{number} * $sign;
4799     my $unit = lc $t->{value}; ## TODO: case
4800     $t = $tt->get_next_token;
4801     if ($length_unit->{$unit} and $value >= 0) {
4802     $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
4803     } else {
4804 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4805     level => $self->{level}->{must},
4806 wakaba 1.38 uri => \$self->{href},
4807 wakaba 1.19 token => $t);
4808     return ($t, undef);
4809     }
4810     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4811     my $value = $t->{number} * $sign;
4812     $t = $tt->get_next_token;
4813     $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
4814     unless ($value >= 0) {
4815 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4816     level => $self->{level}->{must},
4817 wakaba 1.38 uri => \$self->{href},
4818 wakaba 1.19 token => $t);
4819     return ($t, undef);
4820     }
4821 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4822 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4823     my $value = $t->{number} * $sign;
4824     $t = $tt->get_next_token;
4825     $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
4826     unless ($value >= 0) {
4827 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4828     level => $self->{level}->{must},
4829 wakaba 1.38 uri => \$self->{href},
4830 wakaba 1.19 token => $t);
4831     return ($t, undef);
4832     }
4833     } else {
4834 wakaba 1.24 if ($sign < 0) {
4835 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4836     level => $self->{level}->{must},
4837 wakaba 1.38 uri => \$self->{href},
4838 wakaba 1.24 token => $t);
4839     return ($t, undef);
4840     }
4841 wakaba 1.19 return ($t, \%prop_value);
4842     }
4843    
4844     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4845     $sign = 1;
4846     if ($t->{type} == MINUS_TOKEN) {
4847     $t = $tt->get_next_token;
4848     $sign = -1;
4849     }
4850    
4851     if ($t->{type} == DIMENSION_TOKEN) {
4852     my $value = $t->{number} * $sign;
4853     my $unit = lc $t->{value}; ## TODO: case
4854     $t = $tt->get_next_token;
4855     if ($length_unit->{$unit} and $value >= 0) {
4856     $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
4857     } else {
4858 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4859     level => $self->{level}->{must},
4860 wakaba 1.38 uri => \$self->{href},
4861 wakaba 1.19 token => $t);
4862     return ($t, undef);
4863     }
4864     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4865     my $value = $t->{number} * $sign;
4866     $t = $tt->get_next_token;
4867     $prop_value{'padding-left'} = ['PERCENTAGE', $value];
4868     unless ($value >= 0) {
4869 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4870     level => $self->{level}->{must},
4871 wakaba 1.38 uri => \$self->{href},
4872 wakaba 1.19 token => $t);
4873     return ($t, undef);
4874     }
4875 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4876 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4877     my $value = $t->{number} * $sign;
4878     $t = $tt->get_next_token;
4879     $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
4880     unless ($value >= 0) {
4881 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4882     level => $self->{level}->{must},
4883 wakaba 1.38 uri => \$self->{href},
4884 wakaba 1.19 token => $t);
4885     return ($t, undef);
4886     }
4887     } else {
4888 wakaba 1.24 if ($sign < 0) {
4889 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4890     level => $self->{level}->{must},
4891 wakaba 1.38 uri => \$self->{href},
4892 wakaba 1.24 token => $t);
4893     return ($t, undef);
4894     }
4895 wakaba 1.19 return ($t, \%prop_value);
4896     }
4897    
4898     return ($t, \%prop_value);
4899     },
4900 wakaba 1.43 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
4901 wakaba 1.19 };
4902     $Attr->{padding} = $Prop->{padding};
4903    
4904 wakaba 1.24 $Prop->{'border-spacing'} = {
4905     css => 'border-spacing',
4906     dom => 'border_spacing',
4907     parse => sub {
4908     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4909    
4910     my %prop_value;
4911 wakaba 1.53 my $has_sign;
4912 wakaba 1.24 my $sign = 1;
4913     if ($t->{type} == MINUS_TOKEN) {
4914     $t = $tt->get_next_token;
4915 wakaba 1.53 $has_sign = 1;
4916 wakaba 1.24 $sign = -1;
4917 wakaba 1.53 } elsif ($t->{type} == PLUS_TOKEN) {
4918     $t = $tt->get_next_token;
4919     $has_sign = 1;
4920 wakaba 1.24 }
4921    
4922     if ($t->{type} == DIMENSION_TOKEN) {
4923     my $value = $t->{number} * $sign;
4924     my $unit = lc $t->{value}; ## TODO: case
4925     if ($length_unit->{$unit} and $value >= 0) {
4926 wakaba 1.53 $t = $tt->get_next_token;
4927 wakaba 1.24 $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, $unit];
4928     } else {
4929 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4930     level => $self->{level}->{must},
4931 wakaba 1.38 uri => \$self->{href},
4932 wakaba 1.24 token => $t);
4933     return ($t, undef);
4934     }
4935     } elsif ($t->{type} == NUMBER_TOKEN and
4936     ($self->{unitless_px} or $t->{number} == 0)) {
4937     my $value = $t->{number} * $sign;
4938     $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, 'px'];
4939 wakaba 1.53 if ($value >= 0) {
4940     $t = $tt->get_next_token;
4941     } else {
4942 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4943     level => $self->{level}->{must},
4944 wakaba 1.38 uri => \$self->{href},
4945 wakaba 1.24 token => $t);
4946     return ($t, undef);
4947     }
4948 wakaba 1.53 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4949 wakaba 1.24 my $prop_value = lc $t->{value}; ## TODO: case folding
4950     if ($prop_value eq 'inherit') {
4951 wakaba 1.53 $t = $tt->get_next_token;
4952 wakaba 1.24 $prop_value{'-manakai-border-spacing-x'} = ['INHERIT'];
4953     $prop_value{'-manakai-border-spacing-y'}
4954     = $prop_value{'-manakai-border-spacing-x'};
4955     return ($t, \%prop_value);
4956     } else {
4957 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4958     level => $self->{level}->{must},
4959 wakaba 1.38 uri => \$self->{href},
4960 wakaba 1.24 token => $t);
4961     return ($t, undef);
4962     }
4963     } else {
4964 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4965     level => $self->{level}->{must},
4966 wakaba 1.38 uri => \$self->{href},
4967 wakaba 1.24 token => $t);
4968     return ($t, undef);
4969     }
4970     $prop_value{'-manakai-border-spacing-y'}
4971     = $prop_value{'-manakai-border-spacing-x'};
4972    
4973     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4974 wakaba 1.53 undef $has_sign;
4975 wakaba 1.24 $sign = 1;
4976     if ($t->{type} == MINUS_TOKEN) {
4977     $t = $tt->get_next_token;
4978 wakaba 1.53 $has_sign = 1;
4979 wakaba 1.24 $sign = -1;
4980 wakaba 1.53 } elsif ($t->{type} == PLUS_TOKEN) {
4981     $t = $tt->get_next_token;
4982     $has_sign = 1;
4983 wakaba 1.24 }
4984    
4985     if ($t->{type} == DIMENSION_TOKEN) {
4986     my $value = $t->{number} * $sign;
4987     my $unit = lc $t->{value}; ## TODO: case
4988     if ($length_unit->{$unit} and $value >= 0) {
4989 wakaba 1.53 $t = $tt->get_next_token;
4990 wakaba 1.24 $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, $unit];
4991     } else {
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     } elsif ($t->{type} == NUMBER_TOKEN and
4999     ($self->{unitless_px} or $t->{number} == 0)) {
5000     my $value = $t->{number} * $sign;
5001     $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, 'px'];
5002 wakaba 1.53 if ($value >= 0) {
5003     $t = $tt->get_next_token;
5004     } else {
5005 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5006     level => $self->{level}->{must},
5007 wakaba 1.38 uri => \$self->{href},
5008 wakaba 1.24 token => $t);
5009     return ($t, undef);
5010     }
5011     } else {
5012 wakaba 1.53 if ($has_sign) {
5013 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5014     level => $self->{level}->{must},
5015 wakaba 1.38 uri => \$self->{href},
5016 wakaba 1.24 token => $t);
5017     return ($t, undef);
5018     }
5019     return ($t, \%prop_value);
5020     }
5021    
5022     return ($t, \%prop_value);
5023     },
5024 wakaba 1.25 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
5025     ->{serialize_multiple},
5026 wakaba 1.24 };
5027     $Attr->{border_spacing} = $Prop->{'border-spacing'};
5028    
5029 wakaba 1.27 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/background-position> for
5030     ## browser compatibility problems.
5031     $Prop->{'background-position'} = {
5032     css => 'background-position',
5033     dom => 'background_position',
5034     parse => sub {
5035     my ($self, $prop_name, $tt, $t, $onerror) = @_;
5036    
5037     my %prop_value;
5038    
5039     my $sign = 1;
5040 wakaba 1.52 my $has_sign;
5041 wakaba 1.27 if ($t->{type} == MINUS_TOKEN) {
5042     $t = $tt->get_next_token;
5043 wakaba 1.52 $has_sign = 1;
5044 wakaba 1.27 $sign = -1;
5045 wakaba 1.52 } elsif ($t->{type} == PLUS_TOKEN) {
5046     $t = $tt->get_next_token;
5047     $has_sign = 1;
5048 wakaba 1.27 }
5049    
5050     if ($t->{type} == DIMENSION_TOKEN) {
5051     my $value = $t->{number} * $sign;
5052     my $unit = lc $t->{value}; ## TODO: case
5053     if ($length_unit->{$unit}) {
5054 wakaba 1.52 $t = $tt->get_next_token;
5055 wakaba 1.27 $prop_value{'background-position-x'} = ['DIMENSION', $value, $unit];
5056     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5057     } else {
5058 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5059     level => $self->{level}->{must},
5060 wakaba 1.38 uri => \$self->{href},
5061 wakaba 1.27 token => $t);
5062     return ($t, undef);
5063     }
5064     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5065     my $value = $t->{number} * $sign;
5066     $t = $tt->get_next_token;
5067     $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
5068     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5069     } elsif ($t->{type} == NUMBER_TOKEN and
5070     ($self->{unitless_px} or $t->{number} == 0)) {
5071     my $value = $t->{number} * $sign;
5072     $t = $tt->get_next_token;
5073     $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
5074     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5075 wakaba 1.52 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5076 wakaba 1.27 my $prop_value = lc $t->{value}; ## TODO: case folding
5077 wakaba 1.65 if ($prop_value eq 'left' or $prop_value eq 'right') {
5078 wakaba 1.52 $t = $tt->get_next_token;
5079 wakaba 1.27 $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5080     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5081 wakaba 1.65 } elsif ($prop_value eq 'center') {
5082     $t = $tt->get_next_token;
5083     $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5084    
5085     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5086     if ($t->{type} == IDENT_TOKEN) {
5087     my $prop_value = lc $t->{value}; ## TODO: case folding
5088     if ($prop_value eq 'left' or $prop_value eq 'right') {
5089     $prop_value{'background-position-y'}
5090     = $prop_value{'background-position-x'};
5091     $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5092     $t = $tt->get_next_token;
5093     return ($t, \%prop_value);
5094     }
5095     } else {
5096     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5097     }
5098 wakaba 1.27 } elsif ($prop_value eq 'top' or $prop_value eq 'bottom') {
5099 wakaba 1.52 $t = $tt->get_next_token;
5100 wakaba 1.27 $prop_value{'background-position-y'} = ['KEYWORD', $prop_value];
5101    
5102     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5103     if ($t->{type} == IDENT_TOKEN) {
5104     my $prop_value = lc $t->{value}; ## TODO: case folding
5105     if ({left => 1, center => 1, right => 1}->{$prop_value}) {
5106     $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5107     $t = $tt->get_next_token;
5108     return ($t, \%prop_value);
5109     }
5110     }
5111     $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
5112     return ($t, \%prop_value);
5113     } elsif ($prop_value eq 'inherit') {
5114 wakaba 1.52 $t = $tt->get_next_token;
5115 wakaba 1.27 $prop_value{'background-position-x'} = ['INHERIT'];
5116     $prop_value{'background-position-y'} = ['INHERIT'];
5117     return ($t, \%prop_value);
5118     } else {
5119 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5120     level => $self->{level}->{must},
5121 wakaba 1.38 uri => \$self->{href},
5122 wakaba 1.27 token => $t);
5123     return ($t, undef);
5124     }
5125     } else {
5126 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5127     level => $self->{level}->{must},
5128 wakaba 1.38 uri => \$self->{href},
5129 wakaba 1.27 token => $t);
5130     return ($t, undef);
5131     }
5132    
5133     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5134 wakaba 1.52 undef $has_sign;
5135 wakaba 1.27 $sign = 1;
5136     if ($t->{type} == MINUS_TOKEN) {
5137     $t = $tt->get_next_token;
5138 wakaba 1.52 $has_sign = 1;
5139 wakaba 1.27 $sign = -1;
5140 wakaba 1.52 } elsif ($t->{type} == PLUS_TOKEN) {
5141     $t = $tt->get_next_token;
5142     $has_sign = 1;
5143 wakaba 1.27 }
5144    
5145     if ($t->{type} == DIMENSION_TOKEN) {
5146     my $value = $t->{number} * $sign;
5147     my $unit = lc $t->{value}; ## TODO: case
5148     if ($length_unit->{$unit}) {
5149 wakaba 1.52 $t = $tt->get_next_token;
5150 wakaba 1.27 $prop_value{'background-position-y'} = ['DIMENSION', $value, $unit];
5151     } else {
5152 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5153     level => $self->{level}->{must},
5154 wakaba 1.38 uri => \$self->{href},
5155 wakaba 1.27 token => $t);
5156     return ($t, undef);
5157     }
5158     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5159     my $value = $t->{number} * $sign;
5160     $t = $tt->get_next_token;
5161     $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5162 wakaba 1.30 } elsif ($t->{type} == NUMBER_TOKEN and
5163     ($self->{unitless_px} or $t->{number} == 0)) {
5164 wakaba 1.27 my $value = $t->{number} * $sign;
5165     $t = $tt->get_next_token;
5166     $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5167 wakaba 1.52 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5168 wakaba 1.27 my $value = lc $t->{value}; ## TODO: case
5169     if ({top => 1, center => 1, bottom => 1}->{$value}) {
5170     $prop_value{'background-position-y'} = ['KEYWORD', $value];
5171     $t = $tt->get_next_token;
5172     }
5173     } else {
5174 wakaba 1.52 if ($has_sign) {
5175 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5176     level => $self->{level}->{must},
5177 wakaba 1.38 uri => \$self->{href},
5178 wakaba 1.27 token => $t);
5179     return ($t, undef);
5180     }
5181     return ($t, \%prop_value);
5182     }
5183    
5184     return ($t, \%prop_value);
5185     },
5186 wakaba 1.48 serialize_shorthand => sub {
5187     my $self = shift;
5188    
5189     my $r = {};
5190    
5191 wakaba 1.27 my $x = $self->background_position_x;
5192     my $y = $self->background_position_y;
5193 wakaba 1.48 my $xi = $self->get_property_priority ('background-position-x');
5194     my $yi = $self->get_property_priority ('background-position-y');
5195     if (length $x) {
5196     if (length $y) {
5197     if ($xi eq $yi) {
5198     if ($x eq 'inherit') {
5199     if ($y eq 'inherit') {
5200     $r->{'background-position'} = ['inherit', $xi];
5201     } else {
5202     $r->{'background-position-x'} = [$x, $xi];
5203     $r->{'background-position-y'} = [$y, $yi];
5204     }
5205     } elsif ($y eq 'inherit') {
5206     $r->{'background-position-x'} = [$x, $xi];
5207     $r->{'background-position-y'} = [$y, $yi];
5208     } else {
5209     $r->{'background-position'} = [$x . ' ' . $y, $xi];
5210     }
5211     } else {
5212     $r->{'background-position-x'} = [$x, $xi];
5213     $r->{'background-position-y'} = [$y, $yi];
5214     }
5215     } else {
5216     $r->{'background-position-x'} = [$x, $xi];
5217     }
5218     } else {
5219     if (length $y) {
5220     $r->{'background-position-y'} = [$y, $yi];
5221     } else {
5222     #
5223     }
5224     }
5225    
5226     return $r;
5227 wakaba 1.27 },
5228 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
5229 wakaba 1.27 };
5230     $Attr->{background_position} = $Prop->{'background-position'};
5231    
5232 wakaba 1.30 $Prop->{background} = {
5233     css => 'background',
5234     dom => 'background',
5235     parse => sub {
5236     my ($self, $prop_name, $tt, $t, $onerror) = @_;
5237     my %prop_value;
5238     B: for (1..5) {
5239 wakaba 1.48 my $has_sign;
5240 wakaba 1.30 my $sign = 1;
5241     if ($t->{type} == MINUS_TOKEN) {
5242     $sign = -1;
5243 wakaba 1.48 $has_sign = 1;
5244     $t = $tt->get_next_token;
5245     } elsif ($t->{type} == PLUS_TOKEN) {
5246     $has_sign = 1;
5247 wakaba 1.30 $t = $tt->get_next_token;
5248     }
5249    
5250 wakaba 1.48 if (not $has_sign and $t->{type} == IDENT_TOKEN) {
5251 wakaba 1.30 my $value = lc $t->{value}; ## TODO: case
5252     if ($Prop->{'background-repeat'}->{keyword}->{$value} and
5253     $self->{prop_value}->{'background-repeat'}->{$value} and
5254     not defined $prop_value{'background-repeat'}) {
5255     $prop_value{'background-repeat'} = ['KEYWORD', $value];
5256     $t = $tt->get_next_token;
5257     } elsif ($Prop->{'background-attachment'}->{keyword}->{$value} and
5258     $self->{prop_value}->{'background-attachment'}->{$value} and
5259     not defined $prop_value{'background-attachment'}) {
5260     $prop_value{'background-attachment'} = ['KEYWORD', $value];
5261     $t = $tt->get_next_token;
5262     } elsif ($value eq 'none' and
5263     not defined $prop_value{'background-image'}) {
5264     $prop_value{'background-image'} = ['KEYWORD', $value];
5265     $t = $tt->get_next_token;
5266     } elsif ({left => 1, center => 1, right => 1}->{$value} and
5267     not defined $prop_value{'background-position-x'}) {
5268     $prop_value{'background-position-x'} = ['KEYWORD', $value];
5269     $t = $tt->get_next_token;
5270     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5271     my $sign = 1;
5272 wakaba 1.48 my $has_sign;
5273 wakaba 1.30 if ($t->{type} == MINUS_TOKEN) {
5274     $sign = -1;
5275 wakaba 1.48 $has_sign = 1;
5276     $t = $tt->get_next_token;
5277     } elsif ($t->{type} == PLUS_TOKEN) {
5278     $has_sign = 1;
5279 wakaba 1.30 $t = $tt->get_next_token;
5280     }
5281 wakaba 1.48 if (not $has_sign and $t->{type} == IDENT_TOKEN) {
5282 wakaba 1.30 my $value = lc $t->{value}; ## TODO: case
5283     if ({top => 1, bottom => 1, center => 1}->{$value}) {
5284     $prop_value{'background-position-y'} = ['KEYWORD', $value];
5285     $t = $tt->get_next_token;
5286     } elsif ($prop_value{'background-position-x'}->[1] eq 'center' and
5287     $value eq 'left' or $value eq 'right') {
5288     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5289     $prop_value{'background-position-x'} = ['KEYWORD', $value];
5290     $t = $tt->get_next_token;
5291     } else {
5292     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5293     }
5294     } elsif ($t->{type} == DIMENSION_TOKEN) {
5295     my $value = $t->{number} * $sign;
5296     my $unit = lc $t->{value}; ## TODO: case
5297     $t = $tt->get_next_token;
5298     if ($length_unit->{$unit}) {
5299     $prop_value{'background-position-y'}
5300     = ['DIMENSION', $value, $unit];
5301     } else {
5302 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5303     level => $self->{level}->{must},
5304 wakaba 1.38 uri => \$self->{href},
5305 wakaba 1.30 token => $t);
5306 wakaba 1.48 return ($t, undef);
5307 wakaba 1.30 }
5308     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5309     my $value = $t->{number} * $sign;
5310     $t = $tt->get_next_token;
5311     $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5312     } elsif ($t->{type} == NUMBER_TOKEN and
5313     ($self->{unitless_px} or $t->{number} == 0)) {
5314     my $value = $t->{number} * $sign;
5315     $t = $tt->get_next_token;
5316     $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5317 wakaba 1.48 } elsif ($has_sign) {
5318 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5319     level => $self->{level}->{must},
5320 wakaba 1.38 uri => \$self->{href},
5321 wakaba 1.30 token => $t);
5322 wakaba 1.48 return ($t, undef);
5323     } else {
5324     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5325 wakaba 1.30 }
5326     } elsif (($value eq 'top' or $value eq 'bottom') and
5327     not defined $prop_value{'background-position-y'}) {
5328     $prop_value{'background-position-y'} = ['KEYWORD', $value];
5329     $t = $tt->get_next_token;
5330     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5331     if ($t->{type} == IDENT_TOKEN and ## TODO: case
5332 wakaba 1.48 {
5333     left => 1, center => 1, right => 1,
5334     }->{my $value = lc $t->{value}}) {
5335 wakaba 1.30 $prop_value{'background-position-x'} = ['KEYWORD', $value];
5336     $t = $tt->get_next_token;
5337     } else {
5338     $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
5339     }
5340     } elsif ($value eq 'inherit' and not keys %prop_value) {
5341     $prop_value{'background-color'} =
5342     $prop_value{'background-image'} =
5343     $prop_value{'background-repeat'} =
5344     $prop_value{'background-attachment'} =
5345     $prop_value{'background-position-x'} =
5346     $prop_value{'background-position-y'} = ['INHERIT'];
5347     $t = $tt->get_next_token;
5348     return ($t, \%prop_value);
5349     } elsif (not defined $prop_value{'background-color'} or
5350     not keys %prop_value) {
5351     ($t, my $pv) = $parse_color->($self, 'background', $tt, $t,
5352     $onerror);
5353     if (defined $pv) {
5354     $prop_value{'background-color'} = $pv->{background};
5355     } else {
5356     ## NOTE: An error should already be raiased.
5357     return ($t, undef);
5358     }
5359     }
5360     } elsif (($t->{type} == DIMENSION_TOKEN or
5361     $t->{type} == PERCENTAGE_TOKEN or
5362     ($t->{type} == NUMBER_TOKEN and
5363 wakaba 1.48 ($t->{unitless_px} or $t->{number} == 0))) and
5364 wakaba 1.30 not defined $prop_value{'background-position-x'}) {
5365     if ($t->{type} == DIMENSION_TOKEN) {
5366     my $value = $t->{number} * $sign;
5367     my $unit = lc $t->{value}; ## TODO: case
5368     $t = $tt->get_next_token;
5369     if ($length_unit->{$unit}) {
5370     $prop_value{'background-position-x'}
5371     = ['DIMENSION', $value, $unit];
5372     } else {
5373 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5374     level => $self->{level}->{must},
5375 wakaba 1.38 uri => \$self->{href},
5376 wakaba 1.30 token => $t);
5377 wakaba 1.48 return ($t, undef);
5378 wakaba 1.30 }
5379     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5380     my $value = $t->{number} * $sign;
5381     $t = $tt->get_next_token;
5382     $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
5383     } elsif ($t->{type} == NUMBER_TOKEN and
5384     ($self->{unitless_px} or $t->{number} == 0)) {
5385     my $value = $t->{number} * $sign;
5386     $t = $tt->get_next_token;
5387     $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
5388     } else {
5389     ## NOTE: Should not be happened.
5390     last B;
5391     }
5392    
5393     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5394     if ($t->{type} == MINUS_TOKEN) {
5395     $sign = -1;
5396 wakaba 1.48 $has_sign = 1;
5397     $t = $tt->get_next_token;
5398     } elsif ($t->{type} == PLUS_TOKEN) {
5399     $has_sign = 1;
5400 wakaba 1.30 $t = $tt->get_next_token;
5401     } else {
5402 wakaba 1.48 undef $has_sign;
5403 wakaba 1.30 $sign = 1;
5404     }
5405    
5406     if ($t->{type} == DIMENSION_TOKEN) {
5407     my $value = $t->{number} * $sign;
5408     my $unit = lc $t->{value}; ## TODO: case
5409     $t = $tt->get_next_token;
5410     if ($length_unit->{$unit}) {
5411     $prop_value{'background-position-y'}
5412     = ['DIMENSION', $value, $unit];
5413     } else {
5414 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5415     level => $self->{level}->{must},
5416 wakaba 1.38 uri => \$self->{href},
5417 wakaba 1.30 token => $t);
5418 wakaba 1.48 return ($t, undef);
5419 wakaba 1.30 }
5420     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5421     my $value = $t->{number} * $sign;
5422     $t = $tt->get_next_token;
5423     $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5424     } elsif ($t->{type} == NUMBER_TOKEN and
5425     ($self->{unitless_px} or $t->{number} == 0)) {
5426     my $value = $t->{number} * $sign;
5427     $t = $tt->get_next_token;
5428     $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5429     } elsif ($t->{type} == IDENT_TOKEN) {
5430     my $value = lc $t->{value}; ## TODO: case
5431     if ({top => 1, center => 1, bottom => 1}->{$value}) {
5432     $prop_value{'background-position-y'} = ['KEYWORD', $value];
5433     $t = $tt->get_next_token;
5434     } else {
5435     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5436     }
5437     } else {
5438 wakaba 1.48 $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5439     if ($has_sign) {
5440 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5441     level => $self->{level}->{must},
5442 wakaba 1.38 uri => \$self->{href},
5443 wakaba 1.30 token => $t);
5444 wakaba 1.48 return ($t, undef);
5445 wakaba 1.30 }
5446     }
5447 wakaba 1.48 } elsif (not $has_sign and
5448     $t->{type} == URI_TOKEN and
5449 wakaba 1.30 not defined $prop_value{'background-image'}) {
5450 wakaba 1.48 $prop_value{'background-image'}
5451     = ['URI', $t->{value}, \($self->{base_uri})];
5452 wakaba 1.30 $t = $tt->get_next_token;
5453     } else {
5454 wakaba 1.48 if (keys %prop_value and not $has_sign) {
5455 wakaba 1.30 last B;
5456     } else {
5457 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5458     level => $self->{level}->{must},
5459 wakaba 1.38 uri => \$self->{href},
5460 wakaba 1.30 token => $t);
5461 wakaba 1.48 return ($t, undef);
5462 wakaba 1.30 }
5463     }
5464    
5465     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5466     } # B
5467    
5468     $prop_value{$_} ||= $Prop->{$_}->{initial}
5469     for qw/background-image background-attachment background-repeat
5470     background-color background-position-x background-position-y/;
5471    
5472     return ($t, \%prop_value);
5473     },
5474 wakaba 1.68 ## TODO: background: #fff does not work.
5475 wakaba 1.30 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
5476     };
5477     $Attr->{background} = $Prop->{background};
5478    
5479 wakaba 1.31 $Prop->{font} = {
5480     css => 'font',
5481     dom => 'font',
5482     parse => sub {
5483     my ($self, $prop_name, $tt, $t, $onerror) = @_;
5484    
5485     my %prop_value;
5486    
5487     A: for (1..3) {
5488     if ($t->{type} == IDENT_TOKEN) {
5489     my $value = lc $t->{value}; ## TODO: case
5490     if ($value eq 'normal') {
5491     $t = $tt->get_next_token;
5492     } elsif ($Prop->{'font-style'}->{keyword}->{$value} and
5493     $self->{prop_value}->{'font-style'}->{$value} and
5494     not defined $prop_value{'font-style'}) {
5495     $prop_value{'font-style'} = ['KEYWORD', $value];
5496     $t = $tt->get_next_token;
5497     } elsif ($Prop->{'font-variant'}->{keyword}->{$value} and
5498     $self->{prop_value}->{'font-variant'}->{$value} and
5499     not defined $prop_value{'font-variant'}) {
5500     $prop_value{'font-variant'} = ['KEYWORD', $value];
5501     $t = $tt->get_next_token;
5502     } elsif ({normal => 1, bold => 1,
5503     bolder => 1, lighter => 1}->{$value} and
5504     not defined $prop_value{'font-weight'}) {
5505     $prop_value{'font-weight'} = ['KEYWORD', $value];
5506     $t = $tt->get_next_token;
5507     } elsif ($value eq 'inherit' and 0 == keys %prop_value) {
5508     $t = $tt->get_next_token;
5509     return ($t, {'font-style' => ['INHERIT'],
5510     'font-variant' => ['INHERIT'],
5511     'font-weight' => ['INHERIT'],
5512     'font-size' => ['INHERIT'],
5513 wakaba 1.62 'font-size-adjust' => ['INHERIT'],
5514     'font-stretch' => ['INHERIT'],
5515 wakaba 1.31 'line-height' => ['INHERIT'],
5516     'font-family' => ['INHERIT']});
5517     } elsif ({
5518     caption => 1, icon => 1, menu => 1,
5519     'message-box' => 1, 'small-caption' => 1, 'status-bar' => 1,
5520     }->{$value} and 0 == keys %prop_value) {
5521     $t = $tt->get_next_token;
5522     return ($t, $self->{get_system_font}->($self, $value, {
5523     'font-style' => $Prop->{'font-style'}->{initial},
5524     'font-variant' => $Prop->{'font-variant'}->{initial},
5525     'font-weight' => $Prop->{'font-weight'}->{initial},
5526     'font-size' => $Prop->{'font-size'}->{initial},
5527 wakaba 1.62 'font-size-adjust' => $Prop->{'font-size-adjust'}->{initial},
5528     'font-stretch' => $Prop->{'font-stretch'}->{initial},
5529 wakaba 1.31 'line-height' => $Prop->{'line-height'}->{initial},
5530     'font-family' => ['FONT', ['KEYWORD', '-manakai-'.$value]],
5531     }));
5532     } else {
5533     if (keys %prop_value) {
5534     last A;
5535     } else {
5536 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5537     level => $self->{level}->{must},
5538 wakaba 1.38 uri => \$self->{href},
5539 wakaba 1.31 token => $t);
5540     return ($t, undef);
5541     }
5542     }
5543     } elsif ($t->{type} == NUMBER_TOKEN) {
5544     if ({100 => 1, 200 => 1, 300 => 1, 400 => 1, 500 => 1,
5545 wakaba 1.49 600 => 1, 700 => 1, 800 => 1, 900 => 1}->{$t->{number}}) {
5546     $prop_value{'font-weight'} = ['WEIGHT', $t->{number}, 0];
5547 wakaba 1.31 $t = $tt->get_next_token;
5548     } else {
5549     last A;
5550     }
5551 wakaba 1.49 } elsif ($t->{type} == PLUS_TOKEN) {
5552     $t = $tt->get_next_token;
5553     if ($t->{type} == NUMBER_TOKEN) {
5554     if ({100 => 1, 200 => 1, 300 => 1, 400 => 1, 500 => 1,
5555     600 => 1, 700 => 1, 800 => 1, 900 => 1}->{$t->{number}}) {
5556     $prop_value{'font-weight'} = ['WEIGHT', $t->{number}, 0];
5557     $t = $tt->get_next_token;
5558     } else {
5559     ## NOTE: <'font-size'> or invalid
5560     last A;
5561     }
5562     } elsif ($t->{type} == DIMENSION_TOKEN or
5563     $t->{type} == PERCENTAGE_TOKEN) {
5564     ## NOTE: <'font-size'> or invalid
5565     last A;
5566     } else {
5567 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5568     level => $self->{level}->{must},
5569 wakaba 1.49 uri => \$self->{href},
5570     token => $t);
5571     return ($t, undef);
5572     }
5573     } else {
5574     last A;
5575 wakaba 1.31 }
5576    
5577     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5578     } # A
5579    
5580     for (qw/font-style font-variant font-weight/) {
5581     $prop_value{$_} = $Prop->{$_}->{initial} unless defined $prop_value{$_};
5582     }
5583    
5584     ($t, my $pv) = $Prop->{'font-size'}->{parse}
5585     ->($self, 'font', $tt, $t, $onerror);
5586     return ($t, undef) unless defined $pv;
5587     if ($pv->{font}->[0] eq 'INHERIT') {
5588 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5589     level => $self->{level}->{must},
5590 wakaba 1.38 uri => \$self->{href},
5591 wakaba 1.31 token => $t);
5592     return ($t, undef);
5593     }
5594     $prop_value{'font-size'} = $pv->{font};
5595    
5596     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5597     if ($t->{type} == DELIM_TOKEN and $t->{value} eq '/') {
5598     $t = $tt->get_next_token;
5599     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5600     ($t, my $pv) = $Prop->{'line-height'}->{parse}
5601     ->($self, 'font', $tt, $t, $onerror);
5602     return ($t, undef) unless defined $pv;
5603     if ($pv->{font}->[0] eq 'INHERIT') {
5604 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5605     level => $self->{level}->{must},
5606 wakaba 1.38 uri => \$self->{href},
5607 wakaba 1.31 token => $t);
5608     return ($t, undef);
5609     }
5610     $prop_value{'line-height'} = $pv->{font};
5611     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5612     } else {
5613     $prop_value{'line-height'} = $Prop->{'line-height'}->{initial};
5614     }
5615    
5616     undef $pv;
5617     ($t, $pv) = $Prop->{'font-family'}->{parse}
5618     ->($self, 'font', $tt, $t, $onerror);
5619     return ($t, undef) unless defined $pv;
5620     $prop_value{'font-family'} = $pv->{font};
5621    
5622 wakaba 1.62 $prop_value{'font-size-adjust'} = $Prop->{'font-size-adjust'}->{initial};
5623     $prop_value{'font-stretch'} = $Prop->{'font-stretch'}->{initial};
5624    
5625 wakaba 1.31 return ($t, \%prop_value);
5626     },
5627 wakaba 1.44 serialize_shorthand => sub {
5628     my $self = shift;
5629 wakaba 1.31
5630     local $Error::Depth = $Error::Depth + 1;
5631     my $style = $self->font_style;
5632     my $i = $self->get_property_priority ('font-style');
5633 wakaba 1.44 return {} unless length $style;
5634 wakaba 1.31 my $variant = $self->font_variant;
5635 wakaba 1.44 return {} unless length $variant;
5636     return {} if $i ne $self->get_property_priority ('font-variant');
5637 wakaba 1.31 my $weight = $self->font_weight;
5638 wakaba 1.44 return {} unless length $weight;
5639     return {} if $i ne $self->get_property_priority ('font-weight');
5640 wakaba 1.31 my $size = $self->font_size;
5641 wakaba 1.44 return {} unless length $size;
5642     return {} if $i ne $self->get_property_priority ('font-size');
5643 wakaba 1.31 my $height = $self->line_height;
5644 wakaba 1.44 return {} unless length $height;
5645     return {} if $i ne $self->get_property_priority ('line-height');
5646 wakaba 1.31 my $family = $self->font_family;
5647 wakaba 1.44 return {} unless length $family;
5648     return {} if $i ne $self->get_property_priority ('font-family');
5649    
5650     my $v = 0;
5651     for ($style, $variant, $weight, $size, $height, $family) {
5652     $v++ if $_ eq 'inherit';
5653     }
5654     if ($v == 6) {
5655 wakaba 1.45 return {font => ['inherit', $i]};
5656 wakaba 1.44 } elsif ($v) {
5657     return {};
5658     }
5659 wakaba 1.31
5660     my @v;
5661     push @v, $style unless $style eq 'normal';
5662     push @v, $variant unless $variant eq 'normal';
5663     push @v, $weight unless $weight eq 'normal';
5664     push @v, $size.($height eq 'normal' ? '' : '/'.$height);
5665     push @v, $family;
5666 wakaba 1.45 return {font => [(join ' ', @v), $i]};
5667 wakaba 1.31 },
5668     };
5669     $Attr->{font} = $Prop->{font};
5670    
5671 wakaba 1.20 $Prop->{'border-width'} = {
5672     css => 'border-width',
5673     dom => 'border_width',
5674     parse => sub {
5675     my ($self, $prop_name, $tt, $t, $onerror) = @_;
5676    
5677     my %prop_value;
5678    
5679 wakaba 1.51 my $has_sign;
5680 wakaba 1.20 my $sign = 1;
5681     if ($t->{type} == MINUS_TOKEN) {
5682     $t = $tt->get_next_token;
5683 wakaba 1.51 $has_sign = 1;
5684 wakaba 1.20 $sign = -1;
5685 wakaba 1.51 } elsif ($t->{type} == PLUS_TOKEN) {
5686     $t = $tt->get_next_token;
5687     $has_sign = 1;
5688 wakaba 1.20 }
5689    
5690     if ($t->{type} == DIMENSION_TOKEN) {
5691     my $value = $t->{number} * $sign;
5692     my $unit = lc $t->{value}; ## TODO: case
5693     if ($length_unit->{$unit} and $value >= 0) {
5694     $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
5695 wakaba 1.51 $t = $tt->get_next_token;
5696 wakaba 1.20 } else {
5697 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5698     level => $self->{level}->{must},
5699 wakaba 1.38 uri => \$self->{href},
5700 wakaba 1.20 token => $t);
5701     return ($t, undef);
5702     }
5703     } elsif ($t->{type} == NUMBER_TOKEN and
5704     ($self->{unitless_px} or $t->{number} == 0)) {
5705     my $value = $t->{number} * $sign;
5706 wakaba 1.51 if ($value >= 0) {
5707     $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
5708     $t = $tt->get_next_token;
5709     } else {
5710 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5711     level => $self->{level}->{must},
5712 wakaba 1.38 uri => \$self->{href},
5713 wakaba 1.20 token => $t);
5714     return ($t, undef);
5715     }
5716 wakaba 1.51 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5717 wakaba 1.20 my $prop_value = lc $t->{value}; ## TODO: case folding
5718     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5719 wakaba 1.51 $t = $tt->get_next_token;
5720 wakaba 1.20 $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
5721     } elsif ($prop_value eq 'inherit') {
5722 wakaba 1.51 $t = $tt->get_next_token;
5723 wakaba 1.20 $prop_value{'border-top-width'} = ['INHERIT'];
5724     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
5725     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
5726     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
5727     return ($t, \%prop_value);
5728     } else {
5729 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5730     level => $self->{level}->{must},
5731 wakaba 1.38 uri => \$self->{href},
5732 wakaba 1.20 token => $t);
5733     return ($t, undef);
5734     }
5735     } else {
5736 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5737     level => $self->{level}->{must},
5738 wakaba 1.38 uri => \$self->{href},
5739 wakaba 1.20 token => $t);
5740     return ($t, undef);
5741     }
5742     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
5743     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
5744     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
5745    
5746     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5747     if ($t->{type} == MINUS_TOKEN) {
5748     $t = $tt->get_next_token;
5749 wakaba 1.51 $has_sign = 1;
5750 wakaba 1.20 $sign = -1;
5751 wakaba 1.51 } elsif ($t->{type} == PLUS_TOKEN) {
5752     $t = $tt->get_next_token;
5753     $has_sign = 1;
5754     $sign = 1;
5755     } else {
5756     undef $has_sign;
5757     $sign = 1;
5758 wakaba 1.20 }
5759    
5760     if ($t->{type} == DIMENSION_TOKEN) {
5761     my $value = $t->{number} * $sign;
5762     my $unit = lc $t->{value}; ## TODO: case
5763     if ($length_unit->{$unit} and $value >= 0) {
5764 wakaba 1.51 $t = $tt->get_next_token;
5765 wakaba 1.20 $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
5766     } else {
5767 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5768     level => $self->{level}->{must},
5769 wakaba 1.38 uri => \$self->{href},
5770 wakaba 1.20 token => $t);
5771     return ($t, undef);
5772     }
5773     } elsif ($t->{type} == NUMBER_TOKEN and
5774     ($self->{unitless_px} or $t->{number} == 0)) {
5775     my $value = $t->{number} * $sign;
5776 wakaba 1.51 if ($value >= 0) {
5777     $t = $tt->get_next_token;
5778     $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
5779     } else {
5780 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5781     level => $self->{level}->{must},
5782 wakaba 1.38 uri => \$self->{href},
5783 wakaba 1.20 token => $t);
5784     return ($t, undef);
5785     }
5786 wakaba 1.51 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5787 wakaba 1.20 my $prop_value = lc $t->{value}; ## TODO: case
5788     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5789     $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
5790     $t = $tt->get_next_token;
5791     }
5792     } else {
5793 wakaba 1.51 if ($has_sign) {
5794 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5795     level => $self->{level}->{must},
5796 wakaba 1.38 uri => \$self->{href},
5797 wakaba 1.24 token => $t);
5798     return ($t, undef);
5799     }
5800 wakaba 1.20 return ($t, \%prop_value);
5801     }
5802     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
5803    
5804     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5805     if ($t->{type} == MINUS_TOKEN) {
5806     $t = $tt->get_next_token;
5807 wakaba 1.51 $has_sign = 1;
5808 wakaba 1.20 $sign = -1;
5809 wakaba 1.51 } elsif ($t->{type} == PLUS_TOKEN) {
5810     $t = $tt->get_next_token;
5811     $has_sign = 1;
5812     $sign = 1;
5813     } else {
5814     undef $has_sign;
5815     $sign = 1;
5816 wakaba 1.20 }
5817    
5818     if ($t->{type} == DIMENSION_TOKEN) {
5819     my $value = $t->{number} * $sign;
5820     my $unit = lc $t->{value}; ## TODO: case
5821     if ($length_unit->{$unit} and $value >= 0) {
5822 wakaba 1.51 $t = $tt->get_next_token;
5823 wakaba 1.20 $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
5824     } else {
5825 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5826     level => $self->{level}->{must},
5827 wakaba 1.38 uri => \$self->{href},
5828 wakaba 1.20 token => $t);
5829     return ($t, undef);
5830     }
5831     } elsif ($t->{type} == NUMBER_TOKEN and
5832     ($self->{unitless_px} or $t->{number} == 0)) {
5833     my $value = $t->{number} * $sign;
5834 wakaba 1.51 if ($value >= 0) {
5835     $t = $tt->get_next_token;
5836     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
5837     } else {
5838 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5839     level => $self->{level}->{must},
5840 wakaba 1.38 uri => \$self->{href},
5841 wakaba 1.20 token => $t);
5842     return ($t, undef);
5843     }
5844 wakaba 1.51 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5845 wakaba 1.20 my $prop_value = lc $t->{value}; ## TODO: case
5846     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5847     $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
5848     $t = $tt->get_next_token;
5849     }
5850     } else {
5851 wakaba 1.51 if ($has_sign) {
5852 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5853     level => $self->{level}->{must},
5854 wakaba 1.38 uri => \$self->{href},
5855 wakaba 1.24 token => $t);
5856     return ($t, undef);
5857     }
5858 wakaba 1.20 return ($t, \%prop_value);
5859     }
5860    
5861     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5862     if ($t->{type} == MINUS_TOKEN) {
5863     $t = $tt->get_next_token;
5864 wakaba 1.51 $has_sign = 1;
5865 wakaba 1.20 $sign = -1;
5866 wakaba 1.51 } elsif ($t->{type} == PLUS_TOKEN) {
5867     $t = $tt->get_next_token;
5868     $has_sign = 1;
5869     $sign = 1;
5870     } else {
5871     undef $has_sign;
5872     $sign = 1;
5873 wakaba 1.20 }
5874    
5875     if ($t->{type} == DIMENSION_TOKEN) {
5876     my $value = $t->{number} * $sign;
5877     my $unit = lc $t->{value}; ## TODO: case
5878     if ($length_unit->{$unit} and $value >= 0) {
5879 wakaba 1.51 $t = $tt->get_next_token;
5880 wakaba 1.20 $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
5881     } else {
5882 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5883     level => $self->{level}->{must},
5884 wakaba 1.38 uri => \$self->{href},
5885 wakaba 1.20 token => $t);
5886     return ($t, undef);
5887     }
5888     } elsif ($t->{type} == NUMBER_TOKEN and
5889     ($self->{unitless_px} or $t->{number} == 0)) {
5890     my $value = $t->{number} * $sign;
5891 wakaba 1.51 if ($value >= 0) {
5892     $t = $tt->get_next_token;
5893     $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
5894     } else {
5895 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5896     level => $self->{level}->{must},
5897 wakaba 1.38 uri => \$self->{href},
5898 wakaba 1.20 token => $t);
5899     return ($t, undef);
5900     }
5901 wakaba 1.51 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5902 wakaba 1.20 my $prop_value = lc $t->{value}; ## TODO: case
5903     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5904     $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
5905     $t = $tt->get_next_token;
5906     }
5907     } else {
5908 wakaba 1.51 if ($has_sign) {
5909 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5910     level => $self->{level}->{must},
5911 wakaba 1.38 uri => \$self->{href},
5912 wakaba 1.24 token => $t);
5913     return ($t, undef);
5914     }
5915 wakaba 1.20 return ($t, \%prop_value);
5916     }
5917    
5918     return ($t, \%prop_value);
5919     },
5920 wakaba 1.43 serialize_shorthand => sub {
5921     my $self = shift;
5922    
5923 wakaba 1.20 my @v;
5924 wakaba 1.24 push @v, $self->border_top_width;
5925 wakaba 1.43 my $i = $self->get_property_priority ('border-top-width');
5926 wakaba 1.45 return {} unless length $v[-1];
5927 wakaba 1.24 push @v, $self->border_right_width;
5928 wakaba 1.45 return {} unless length $v[-1];
5929     return {} unless $i eq $self->get_property_priority ('border-right-width');
5930 wakaba 1.24 push @v, $self->border_bottom_width;
5931 wakaba 1.45 return {} unless length $v[-1];
5932     return {} unless $i eq $self->get_property_priority ('border-bottom-width');
5933 wakaba 1.24 push @v, $self->border_left_width;
5934 wakaba 1.45 return {} unless length $v[-1];
5935     return {} unless $i eq $self->get_property_priority ('border-left-width');
5936 wakaba 1.43
5937     my $v = 0;
5938     for (0..3) {
5939     $v++ if $v[$_] eq 'inherit';
5940     }
5941     if ($v == 4) {
5942 wakaba 1.45 return {'border-width' => ['inherit', $i]};
5943 wakaba 1.43 } elsif ($v) {
5944     return {};
5945     }
5946 wakaba 1.20
5947     pop @v if $v[1] eq $v[3];
5948     pop @v if $v[0] eq $v[2];
5949     pop @v if $v[0] eq $v[1];
5950 wakaba 1.45 return {'border-width' => [(join ' ', @v), $i]};
5951 wakaba 1.20 },
5952 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
5953 wakaba 1.20 };
5954 wakaba 1.24 $Attr->{border_width} = $Prop->{'border-width'};
5955 wakaba 1.20
5956 wakaba 1.12 $Prop->{'list-style'} = {
5957     css => 'list-style',
5958     dom => 'list_style',
5959     parse => sub {
5960     my ($self, $prop_name, $tt, $t, $onerror) = @_;
5961    
5962     my %prop_value;
5963     my $none = 0;
5964    
5965     F: for my $f (1..3) {
5966     if ($t->{type} == IDENT_TOKEN) {
5967     my $prop_value = lc $t->{value}; ## TODO: case folding
5968     $t = $tt->get_next_token;
5969    
5970     if ($prop_value eq 'none') {
5971     $none++;
5972     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
5973     if (exists $prop_value{'list-style-type'}) {
5974 wakaba 1.69 $onerror->(type => 'CSS duplication', text => "'list-style-type'",
5975     level => $self->{level}->{must},
5976 wakaba 1.38 uri => \$self->{href},
5977 wakaba 1.12 token => $t);
5978     return ($t, undef);
5979     } else {
5980     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
5981     }
5982     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
5983     if (exists $prop_value{'list-style-position'}) {
5984 wakaba 1.69 $onerror->(type => 'CSS duplication',
5985     text => "'list-style-position'",
5986     level => $self->{level}->{must},
5987 wakaba 1.38 uri => \$self->{href},
5988 wakaba 1.12 token => $t);
5989     return ($t, undef);
5990     }
5991    
5992     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
5993     } elsif ($f == 1 and $prop_value eq 'inherit') {
5994     $prop_value{'list-style-type'} = ["INHERIT"];
5995     $prop_value{'list-style-position'} = ["INHERIT"];
5996     $prop_value{'list-style-image'} = ["INHERIT"];
5997     last F;
5998     } else {
5999     if ($f == 1) {
6000 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6001     level => $self->{level}->{must},
6002 wakaba 1.38 uri => \$self->{href},
6003 wakaba 1.12 token => $t);
6004     return ($t, undef);
6005     } else {
6006     last F;
6007     }
6008     }
6009     } elsif ($t->{type} == URI_TOKEN) {
6010     if (exists $prop_value{'list-style-image'}) {
6011 wakaba 1.69 $onerror->(type => 'CSS duplication', text => "'list-style-image'",
6012 wakaba 1.38 uri => \$self->{href},
6013 wakaba 1.69 level => $self->{level}->{must},
6014 wakaba 1.12 token => $t);
6015     return ($t, undef);
6016     }
6017    
6018     $prop_value{'list-style-image'}
6019 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
6020 wakaba 1.12 $t = $tt->get_next_token;
6021     } else {
6022     if ($f == 1) {
6023 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6024     level => $self->{level}->{must},
6025 wakaba 1.38 uri => \$self->{href},
6026 wakaba 1.12 token => $t);
6027     return ($t, undef);
6028     } else {
6029     last F;
6030     }
6031     }
6032    
6033     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6034     } # F
6035     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
6036    
6037     if ($none == 1) {
6038     if (exists $prop_value{'list-style-type'}) {
6039     if (exists $prop_value{'list-style-image'}) {
6040 wakaba 1.69 $onerror->(type => 'CSS duplication', text => "'list-style-image'",
6041 wakaba 1.38 uri => \$self->{href},
6042 wakaba 1.69 level => $self->{level}->{must},
6043 wakaba 1.12 token => $t);
6044     return ($t, undef);
6045     } else {
6046     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
6047     }
6048     } else {
6049     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
6050     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
6051     unless exists $prop_value{'list-style-image'};
6052     }
6053     } elsif ($none == 2) {
6054     if (exists $prop_value{'list-style-type'}) {
6055 wakaba 1.69 $onerror->(type => 'CSS duplication', text => "'list-style-type'",
6056 wakaba 1.38 uri => \$self->{href},
6057 wakaba 1.69 level => $self->{level}->{must},
6058 wakaba 1.12 token => $t);
6059     return ($t, undef);
6060     }
6061     if (exists $prop_value{'list-style-image'}) {
6062 wakaba 1.69 $onerror->(type => 'CSS duplication', text => "'list-style-image'",
6063 wakaba 1.38 uri => \$self->{href},
6064 wakaba 1.69 level => $self->{level}->{must},
6065 wakaba 1.12 token => $t);
6066     return ($t, undef);
6067     }
6068    
6069     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
6070     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
6071     } elsif ($none == 3) {
6072 wakaba 1.69 $onerror->(type => 'CSS duplication', text => "'list-style-type'",
6073 wakaba 1.38 uri => \$self->{href},
6074 wakaba 1.69 level => $self->{level}->{must},
6075 wakaba 1.12 token => $t);
6076     return ($t, undef);
6077     }
6078    
6079     for (qw/list-style-type list-style-position list-style-image/) {
6080     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
6081     }
6082    
6083     return ($t, \%prop_value);
6084     },
6085 wakaba 1.43 ## NOTE: We don't merge longhands in |css_text| serialization,
6086     ## since no browser does.
6087     serialize_shorthand => sub {
6088     my $self = shift;
6089    
6090     ## NOTE: Don't omit any value even if it is the initial value,
6091     ## since WinIE is buggy.
6092 wakaba 1.12
6093 wakaba 1.43 my $type = $self->list_style_type;
6094     return {} unless length $type;
6095     my $type_i = $self->get_property_priority ('list-style-type');
6096     my $image = $self->list_style_image;
6097     return {} unless length $image;
6098     my $image_i = $self->get_property_priority ('list-style-image');
6099     return {} unless $type_i eq $image_i;
6100     my $position = $self->list_style_position;
6101     return {} unless length $position;
6102     my $position_i = $self->get_property_priority ('list-style-position');
6103     return {} unless $type_i eq $position_i;
6104    
6105 wakaba 1.45 return {'list-style' => [$type . ' ' . $image . ' ' . $position, $type_i]};
6106 wakaba 1.12 },
6107     };
6108     $Attr->{list_style} = $Prop->{'list-style'};
6109    
6110 wakaba 1.16 ## NOTE: Future version of the implementation will change the way to
6111     ## store the parsed value to support CSS 3 properties.
6112     $Prop->{'text-decoration'} = {
6113     css => 'text-decoration',
6114     dom => 'text_decoration',
6115     key => 'text_decoration',
6116     parse => sub {
6117     my ($self, $prop_name, $tt, $t, $onerror) = @_;
6118    
6119     my $value = ['DECORATION']; # , underline, overline, line-through, blink
6120    
6121     if ($t->{type} == IDENT_TOKEN) {
6122     my $v = lc $t->{value}; ## TODO: case
6123     $t = $tt->get_next_token;
6124     if ($v eq 'inherit') {
6125     return ($t, {$prop_name => ['INHERIT']});
6126     } elsif ($v eq 'none') {
6127     return ($t, {$prop_name => $value});
6128     } elsif ($v eq 'underline' and
6129     $self->{prop_value}->{$prop_name}->{$v}) {
6130     $value->[1] = 1;
6131     } elsif ($v eq 'overline' and
6132     $self->{prop_value}->{$prop_name}->{$v}) {
6133     $value->[2] = 1;
6134     } elsif ($v eq 'line-through' and
6135     $self->{prop_value}->{$prop_name}->{$v}) {
6136     $value->[3] = 1;
6137     } elsif ($v eq 'blink' and
6138     $self->{prop_value}->{$prop_name}->{$v}) {
6139     $value->[4] = 1;
6140     } else {
6141 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6142     level => $self->{level}->{must},
6143 wakaba 1.38 uri => \$self->{href},
6144 wakaba 1.16 token => $t);
6145     return ($t, undef);
6146     }
6147     }
6148    
6149     F: {
6150     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6151     last F unless $t->{type} == IDENT_TOKEN;
6152    
6153     my $v = lc $t->{value}; ## TODO: case
6154     $t = $tt->get_next_token;
6155     if ($v eq 'underline' and
6156     $self->{prop_value}->{$prop_name}->{$v}) {
6157     $value->[1] = 1;
6158     } elsif ($v eq 'overline' and
6159     $self->{prop_value}->{$prop_name}->{$v}) {
6160     $value->[1] = 2;
6161     } elsif ($v eq 'line-through' and
6162     $self->{prop_value}->{$prop_name}->{$v}) {
6163     $value->[1] = 3;
6164     } elsif ($v eq 'blink' and
6165     $self->{prop_value}->{$prop_name}->{$v}) {
6166     $value->[1] = 4;
6167     } else {
6168     last F;
6169     }
6170    
6171     redo F;
6172     } # F
6173    
6174     return ($t, {$prop_name => $value});
6175     },
6176     initial => ["KEYWORD", "none"],
6177     #inherited => 0,
6178     compute => $compute_as_specified,
6179     };
6180     $Attr->{text_decoration} = $Prop->{'text-decoration'};
6181     $Key->{text_decoration} = $Prop->{'text-decoration'};
6182    
6183 wakaba 1.56 $Attr->{quotes} =
6184     $Key->{quotes} =
6185     $Prop->{quotes} = {
6186     css => 'quotes',
6187     dom => 'quotes',
6188     key => 'quotes',
6189     parse => sub {
6190     my ($self, $prop_name, $tt, $t, $onerror) = @_;
6191    
6192     my @v;
6193     A: {
6194     if ($t->{type} == STRING_TOKEN) {
6195     my $open = $t->{value};
6196     $t = $tt->get_next_token;
6197    
6198     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6199     if ($t->{type} == STRING_TOKEN) {
6200     push @v, [$open, $t->{value}];
6201     $t = $tt->get_next_token;
6202     } else {
6203     last A;
6204     }
6205     } elsif (not @v and $t->{type} == IDENT_TOKEN) {
6206     my $value = lc $t->{value}; ## TODO: case
6207     if ($value eq 'none' or $value eq '-manakai-default') {
6208     $t = $tt->get_next_token;
6209     return ($t, {$prop_name => ['KEYWORD', $value]});
6210     } elsif ($value eq 'inherit') {
6211     $t = $tt->get_next_token;
6212     return ($t, {$prop_name => ['INHERIT']});
6213     } else {
6214     last A;
6215     }
6216     } else {
6217     if (@v) {
6218     return ($t, {$prop_name => ['QUOTES', \@v]});
6219     } else {
6220     last A;
6221     }
6222     }
6223    
6224     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6225     redo A;
6226     }
6227    
6228 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6229     level => $self->{level}->{must},
6230 wakaba 1.56 uri => \$self->{href},
6231     token => $t);
6232     return ($t, undef);
6233     },
6234     initial => ['KEYWORD', '-manakai-default'],
6235     inherited => 1,
6236     compute => $compute_as_specified,
6237     };
6238    
6239 wakaba 1.57 $Attr->{content} =
6240     $Key->{content} =
6241     $Prop->{content} = {
6242     css => 'content',
6243     dom => 'content',
6244     key => 'content',
6245     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/content>.
6246     parse => sub {
6247     my ($self, $prop_name, $tt, $t, $onerror) = @_;
6248    
6249     if ($t->{type} == IDENT_TOKEN) {
6250     my $value = lc $t->{value}; ## TODO: case
6251     if ($value eq 'normal' or $value eq 'none') {
6252     $t = $tt->get_next_token;
6253     return ($t, {$prop_name => ['KEYWORD', $value]});
6254     } elsif ($value eq 'inherit') {
6255     $t = $tt->get_next_token;
6256     return ($t, {$prop_name => ['INHERIT']});
6257     }
6258     }
6259    
6260     my @v;
6261     A: {
6262     if ($t->{type} == IDENT_TOKEN) {
6263     my $value = lc $t->{value}; ## TODO: case
6264     if ({qw/open-quote 1 close-quote 1
6265     no-open-quote 1 no-close-quote 1/}->{$value} and
6266     $self->{prop}->{quotes}) {
6267     push @v, ['KEYWORD', $value];
6268     $t = $tt->get_next_token;
6269     } else {
6270     last A;
6271     }
6272     } elsif ($t->{type} == STRING_TOKEN) {
6273     push @v, ['STRING', $t->{value}];
6274     $t = $tt->get_next_token;
6275     } elsif ($t->{type} == URI_TOKEN) {
6276     push @v, ['URI', $t->{value}, \($self->{base_uri})];
6277     $t = $tt->get_next_token;
6278     } elsif ($t->{type} == FUNCTION_TOKEN) {
6279     my $name = lc $t->{value}; ## TODO: case
6280     if ($name eq 'attr') {
6281     $t = $tt->get_next_token;
6282     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6283     if ($t->{type} == IDENT_TOKEN) {
6284     my $t_pfx;
6285     my $t_ln = $t;
6286     $t = $tt->get_next_token;
6287     if ($t->{type} == VBAR_TOKEN) {
6288     $t = $tt->get_next_token;
6289     if ($t->{type} == IDENT_TOKEN) {
6290     $t_pfx = $t_ln;
6291     $t_ln = $t;
6292     $t = $tt->get_next_token;
6293     } else {
6294     last A;
6295     }
6296     }
6297    
6298     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6299     if ($t->{type} == RPAREN_TOKEN) {
6300     if (defined $t_pfx) {
6301 wakaba 1.65 my $pfx = $t_pfx->{value};
6302     my $uri = $self->{lookup_namespace_uri}->($pfx);
6303 wakaba 1.57 unless (defined $uri) {
6304     $self->{onerror}->(type => 'namespace prefix:not declared',
6305 wakaba 1.69 level => $self->{level}->{must},
6306 wakaba 1.57 uri => \$self->{href},
6307 wakaba 1.65 token => $t_pfx,
6308     value => $pfx);
6309 wakaba 1.57 return ($t, undef);
6310     }
6311     push @v, ['ATTR', $uri, $t_ln->{value}];
6312     } else {
6313     push @v, ['ATTR', undef, $t_ln->{value}];
6314     }
6315     $t = $tt->get_next_token;
6316     } else {
6317     last A;
6318     }
6319     } elsif ($t->{type} == VBAR_TOKEN) {
6320     $t = $tt->get_next_token;
6321     my $t_ln;
6322     if ($t->{type} == IDENT_TOKEN) {
6323     $t_ln = $t;
6324     $t = $tt->get_next_token;
6325     } else {
6326     last A;
6327     }
6328    
6329     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6330     if ($t->{type} == RPAREN_TOKEN) {
6331     push @v, ['ATTR', undef, $t_ln->{value}];
6332     $t = $tt->get_next_token;
6333     } else {
6334     last A;
6335     }
6336     } else {
6337     last A;
6338     }
6339 wakaba 1.58 } elsif (($name eq 'counter' or $name eq 'counters') and
6340     $self->{prop}->{'counter-reset'}) {
6341 wakaba 1.57 $t = $tt->get_next_token;
6342     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6343     if ($t->{type} == IDENT_TOKEN) {
6344     my $t_id = $t;
6345     my $t_str;
6346     my $type;
6347     $t = $tt->get_next_token;
6348     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6349     if ($t->{type} == COMMA_TOKEN) {
6350     $t = $tt->get_next_token;
6351     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6352     if ($name eq 'counters' and $t->{type} == STRING_TOKEN) {
6353     $t_str = $t;
6354     $t = $tt->get_next_token;
6355     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6356     if ($t->{type} == COMMA_TOKEN) {
6357     $t = $tt->get_next_token;
6358     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6359     if ($t->{type} == IDENT_TOKEN) {
6360     $type = lc $t->{value}; ## TODO: value
6361     if ($Prop->{'list-style-type'}->{keyword}->{$type}) {
6362     $t = $tt->get_next_token;
6363     } else {
6364     last A;
6365     }
6366     } else {
6367     last A;
6368     }
6369     }
6370     } elsif ($name eq 'counter' and $t->{type} == IDENT_TOKEN) {
6371     $type = lc $t->{value}; ## TODO: value
6372     if ($Prop->{'list-style-type'}->{keyword}->{$type}) {
6373     $t = $tt->get_next_token;
6374     } else {
6375     last A;
6376     }
6377     } else {
6378     last A;
6379     }
6380     } elsif ($name eq 'counters') {
6381     last A;
6382     }
6383     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6384     if ($t->{type} == RPAREN_TOKEN) {
6385     push @v, [uc $name, ## |COUNTER| or |COUNTERS|
6386     $t_id->{value},
6387     defined $t_str ? $t_str->{value} : undef,
6388     defined $type ? $type : 'decimal'];
6389     $t = $tt->get_next_token;
6390     } else {
6391     last A;
6392     }
6393     } else {
6394     last A;
6395     }
6396     } else {
6397     last A;
6398     }
6399     } else {
6400     unshift @v, 'CONTENT';
6401     return ($t, {$prop_name => \@v});
6402     }
6403    
6404     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6405     redo A;
6406     } # A
6407    
6408 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6409     level => $self->{level}->{must},
6410 wakaba 1.57 uri => \$self->{href},
6411     token => $t);
6412     return ($t, undef);
6413     },
6414     initial => ['KEYWORD', 'normal'],
6415     #inherited => 0,
6416     compute => $compute_as_specified,
6417     ## NOTE: This is what Opera 9 does, except for 'normal' -> 'none'.
6418     ## TODO: 'normal' -> 'none' for ::before and ::after [CSS 2.1]
6419     };
6420    
6421 wakaba 1.58 $Attr->{counter_reset} =
6422     $Key->{counter_reset} =
6423     $Prop->{'counter-reset'} = {
6424     css => 'counter-reset',
6425     dom => 'counter_reset',
6426     key => 'counter_reset',
6427     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/counter-reset>.
6428     parse => sub {
6429     my ($self, $prop_name, $tt, $t, $onerror) = @_;
6430    
6431     ## NOTE: For 'counter-increment' and 'counter-reset'.
6432    
6433     my @v = ($prop_name eq 'counter-increment' ? 'ADDCOUNTER' : 'SETCOUNTER');
6434     B: {
6435     if ($t->{type} == IDENT_TOKEN) {
6436     my $value = $t->{value};
6437     my $lcvalue = lc $value; ## TODO: case
6438     last B if $lcvalue ne 'inherit' and $lcvalue ne 'none';
6439    
6440     $t = $tt->get_next_token;
6441     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6442     if ($t->{type} == IDENT_TOKEN) {
6443     push @v, [$value, $prop_name eq 'counter-increment' ? 1 : 0];
6444     } elsif ($t->{type} == NUMBER_TOKEN) {
6445     push @v, [$value, int $t->{number}];
6446     $t = $tt->get_next_token;
6447     } elsif ($t->{type} == PLUS_TOKEN) {
6448     $t = $tt->get_next_token;
6449     if ($t->{type} == NUMBER_TOKEN) {
6450     push @v, [$value, int $t->{number}];
6451     $t = $tt->get_next_token;
6452     } else {
6453 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6454     level => $self->{level}->{must},
6455 wakaba 1.58 uri => \$self->{href},
6456     token => $t);
6457     return ($t, undef);
6458     }
6459     } elsif ($t->{type} == MINUS_TOKEN) {
6460     $t = $tt->get_next_token;
6461     if ($t->{type} == NUMBER_TOKEN) {
6462     push @v, [$value, -int $t->{number}];
6463     $t = $tt->get_next_token;
6464     } else {
6465 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6466     level => $self->{level}->{must},
6467 wakaba 1.58 uri => \$self->{href},
6468     token => $t);
6469     return ($t, undef);
6470     }
6471     } else {
6472     if ($lcvalue eq 'none') {
6473     return ($t, {$prop_name => ['KEYWORD', $lcvalue]});
6474     } elsif ($lcvalue eq 'inherit') {
6475     return ($t, {$prop_name => ['INHERIT']});
6476     } else {
6477     last B;
6478     }
6479     }
6480     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6481     } else {
6482 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6483     level => $self->{level}->{must},
6484 wakaba 1.58 uri => \$self->{href},
6485     token => $t);
6486     return ($t, undef);
6487     }
6488     } # B
6489    
6490     A: {
6491     if ($t->{type} == IDENT_TOKEN) {
6492     my $value = $t->{value};
6493     $t = $tt->get_next_token;
6494     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6495     if ($t->{type} == NUMBER_TOKEN) {
6496     push @v, [$value, int $t->{number}];
6497     $t = $tt->get_next_token;
6498     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6499     } elsif ($t->{type} == MINUS_TOKEN) {
6500     $t = $tt->get_next_token;
6501     if ($t->{type} == NUMBER_TOKEN) {
6502     push @v, [$value, -int $t->{number}];
6503     $t = $tt->get_next_token;
6504     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6505     } else {
6506     last A;
6507     }
6508     } elsif ($t->{type} == PLUS_TOKEN) {
6509     $t = $tt->get_next_token;
6510     if ($t->{type} == NUMBER_TOKEN) {
6511     push @v, [$value, int $t->{number}];
6512     $t = $tt->get_next_token;
6513     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6514     } else {
6515     last A;
6516     }
6517     } else {
6518     push @v, [$value, $prop_name eq 'counter-increment' ? 1 : 0];
6519     }
6520     redo A;
6521     } else {
6522     return ($t, {$prop_name => \@v});
6523     }
6524     } # A
6525    
6526 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6527     level => $self->{level}->{must},
6528 wakaba 1.58 uri => \$self->{href},
6529     token => $t);
6530     return ($t, undef);
6531     },
6532     initial => ['KEYWORD', 'none'],
6533     #inherited => 0,
6534     compute => $compute_as_specified,
6535     };
6536    
6537     $Attr->{counter_increment} =
6538     $Key->{counter_increment} =
6539     $Prop->{'counter-increment'} = {
6540     css => 'counter-increment',
6541     dom => 'counter_increment',
6542     key => 'counter_increment',
6543     parse => $Prop->{'counter-reset'}->{parse},
6544     initial => ['KEYWORD', 'none'],
6545     #inherited => 0,
6546     compute => $compute_as_specified,
6547     };
6548    
6549 wakaba 1.59 $Attr->{clip} =
6550     $Key->{clip} =
6551     $Prop->{clip} = {
6552     css => 'clip',
6553     dom => 'clip',
6554     key => 'clip',
6555     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/clip>.
6556     parse => sub {
6557     my ($self, $prop_name, $tt, $t, $onerror) = @_;
6558    
6559     if ($t->{type} == FUNCTION_TOKEN) {
6560     my $value = lc $t->{value}; ## TODO: case
6561     if ($value eq 'rect') {
6562     $t = $tt->get_next_token;
6563     my $prop_value = ['RECT'];
6564    
6565     A: {
6566     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6567    
6568     my $has_sign;
6569     my $sign = 1;
6570     if ($t->{type} == MINUS_TOKEN) {
6571     $sign = -1;
6572     $has_sign = 1;
6573     $t = $tt->get_next_token;
6574     } elsif ($t->{type} == PLUS_TOKEN) {
6575     $has_sign = 1;
6576     $t = $tt->get_next_token;
6577     }
6578     if ($t->{type} == DIMENSION_TOKEN) {
6579     my $value = $t->{number} * $sign;
6580     my $unit = lc $t->{value}; ## TODO: case
6581     if ($length_unit->{$unit}) {
6582     $t = $tt->get_next_token;
6583     push @$prop_value, ['DIMENSION', $value, $unit];
6584     } else {
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     }
6591     } elsif ($t->{type} == NUMBER_TOKEN and
6592     ($self->{unitless_px} or $t->{number} == 0)) {
6593     my $value = $t->{number} * $sign;
6594     $t = $tt->get_next_token;
6595     push @$prop_value, ['DIMENSION', $value, 'px'];
6596     } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
6597     my $value = lc $t->{value}; ## TODO: case
6598     if ($value eq 'auto') {
6599     push @$prop_value, ['KEYWORD', 'auto'];
6600     $t = $tt->get_next_token;
6601     } else {
6602     last A;
6603     }
6604     } else {
6605     if ($has_sign) {
6606 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6607     level => $self->{level}->{must},
6608 wakaba 1.59 uri => \$self->{href},
6609     token => $t);
6610     return ($t, undef);
6611     } else {
6612     last A;
6613     }
6614     }
6615    
6616     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6617     if ($#$prop_value == 4) {
6618     if ($t->{type} == RPAREN_TOKEN) {
6619     $t = $tt->get_next_token;
6620     return ($t, {$prop_name => $prop_value});
6621     } else {
6622     last A;
6623     }
6624     } else {
6625     $t = $tt->get_next_token if $t->{type} == COMMA_TOKEN;
6626     redo A;
6627     }
6628     } # A
6629     }
6630     } elsif ($t->{type} == IDENT_TOKEN) {
6631     my $value = lc $t->{value}; ## TODO: case
6632     if ($value eq 'auto') {
6633     $t = $tt->get_next_token;
6634     return ($t, {$prop_name => ['KEYWORD', 'auto']});
6635     } elsif ($value eq 'inherit') {
6636     $t = $tt->get_next_token;
6637     return ($t, {$prop_name => ['INHERIT']});
6638     }
6639     }
6640    
6641 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6642     level => $self->{level}->{must},
6643 wakaba 1.59 uri => \$self->{href},
6644     token => $t);
6645     return ($t, undef);
6646     },
6647     initial => ['KEYWORD', 'auto'],
6648     #inherited => 0,
6649     compute => sub {
6650     my ($self, $element, $prop_name, $specified_value) = @_;
6651    
6652     if (defined $specified_value and $specified_value->[0] eq 'RECT') {
6653     my $v = ['RECT'];
6654     for (@$specified_value[1..4]) {
6655     push @$v, $compute_length->($self, $element, $prop_name, $_);
6656     }
6657     return $v;
6658     }
6659    
6660     return $specified_value;
6661     },
6662     };
6663    
6664 wakaba 1.62 $Attr->{marks} =
6665     $Key->{marks} =
6666     $Prop->{marks} = {
6667     css => 'marks',
6668     dom => 'marks',
6669     key => 'marks',
6670     parse => sub {
6671     my ($self, $prop_name, $tt, $t, $onerror) = @_;
6672    
6673     if ($t->{type} == IDENT_TOKEN) {
6674     my $value = lc $t->{value}; ## TODO: case
6675     if ($value eq 'crop' and $self->{prop_value}->{$prop_name}->{$value}) {
6676     $t = $tt->get_next_token;
6677     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6678     if ($t->{type} == IDENT_TOKEN) {
6679     my $value = lc $t->{value}; ## TODO: case
6680     if ($value eq 'cross' and
6681     $self->{prop_value}->{$prop_name}->{$value}) {
6682     $t = $tt->get_next_token;
6683     return ($t, {$prop_name => ['MARKS', 1, 1]});
6684     }
6685     }
6686     return ($t, {$prop_name => ['MARKS', 1, 0]});
6687     } elsif ($value eq 'cross' and
6688     $self->{prop_value}->{$prop_name}->{$value}) {
6689     $t = $tt->get_next_token;
6690     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6691     if ($t->{type} == IDENT_TOKEN) {
6692     my $value = lc $t->{value}; ## TODO: case
6693     if ($value eq 'crop' and
6694     $self->{prop_value}->{$prop_name}->{$value}) {
6695     $t = $tt->get_next_token;
6696     return ($t, {$prop_name => ['MARKS', 1, 1]});
6697     }
6698     }
6699     return ($t, {$prop_name => ['MARKS', 0, 1]});
6700     } elsif ($value eq 'none') {
6701     $t = $tt->get_next_token;
6702     return ($t, {$prop_name => ['MARKS']});
6703     } elsif ($value eq 'inherit') {
6704     $t = $tt->get_next_token;
6705     return ($t, {$prop_name => ['INHERIT']});
6706     }
6707     }
6708    
6709 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6710     level => $self->{level}->{must},
6711 wakaba 1.62 uri => \$self->{href},
6712     token => $t);
6713     return ($t, undef);
6714     },
6715     initial => ['MARKS', 0, 0],
6716     #inherited => 0,
6717     compute => $compute_as_specified,
6718     };
6719    
6720     $Attr->{size} =
6721     $Key->{size} =
6722     $Prop->{size} = {
6723     css => 'size',
6724     dom => 'size',
6725     key => 'size',
6726     parse => sub {
6727     my ($self, $prop_name, $tt, $t, $onerror) = @_;
6728    
6729     if ($t->{type} == IDENT_TOKEN) {
6730     my $value = lc $t->{value}; ## TODO: case
6731     if ({
6732     auto => 1, portrait => 1, landscape => 1,
6733     }->{$value}) {
6734     $t = $tt->get_next_token;
6735     return ($t, {$prop_name => ['KEYWORD', $value]});
6736     } elsif ($value eq 'inherit') {
6737     $t = $tt->get_next_token;
6738     return ($t, {$prop_name => ['INHERIT']});
6739     }
6740     }
6741    
6742     my $prop_value = ['SIZE'];
6743     A: {
6744     my $has_sign;
6745     my $sign = 1;
6746     if ($t->{type} == MINUS_TOKEN) {
6747     $has_sign = 1;
6748     $sign = -1;
6749     $t = $tt->get_next_token;
6750     } elsif ($t->{type} == PLUS_TOKEN) {
6751     $has_sign = 1;
6752     $t = $tt->get_next_token;
6753     }
6754    
6755     if ($t->{type} == DIMENSION_TOKEN) {
6756     my $value = $t->{number} * $sign;
6757     my $unit = lc $t->{value}; ## TODO: case
6758     if ($length_unit->{$unit}) {
6759     $t = $tt->get_next_token;
6760     push @$prop_value, ['DIMENSION', $value, $unit];
6761     } else {
6762 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6763     level => $self->{level}->{must},
6764 wakaba 1.62 uri => \$self->{href},
6765     token => $t);
6766     return ($t, undef);
6767     }
6768     } elsif ($t->{type} == NUMBER_TOKEN and
6769     ($self->{unitless_px} or $t->{number} == 0)) {
6770     my $value = $t->{number} * $sign;
6771     $t = $tt->get_next_token;
6772     push @$prop_value, ['DIMENSION', $value, 'px'];
6773     } else {
6774     if (@$prop_value == 2) {
6775     $prop_value->[2] = $prop_value->[1];
6776     return ($t, {$prop_name => $prop_value});
6777     } else {
6778     last A;
6779     }
6780     }
6781    
6782     if (@$prop_value == 3) {
6783     return ($t, {$prop_name => $prop_value});
6784     } else {
6785     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6786     redo A;
6787     }
6788     } # A
6789    
6790 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6791     level => $self->{level}->{must},
6792 wakaba 1.62 uri => \$self->{href},
6793     token => $t);
6794     return ($t, undef);
6795     },
6796     initial => ['KEYWORD', 'auto'],
6797     #inherited => 0,
6798     compute => sub {
6799     my ($self, $element, $prop_name, $specified_value) = @_;
6800    
6801     if (defined $specified_value and $specified_value->[0] eq 'SIZE') {
6802     my $v = ['SIZE'];
6803     for (@$specified_value[1..2]) {
6804     push @$v, $compute_length->($self, $element, $prop_name, $_);
6805     }
6806     return $v;
6807     }
6808    
6809     return $specified_value;
6810     },
6811     };
6812    
6813     $Attr->{page} =
6814     $Key->{page} =
6815     $Prop->{page} = {
6816     css => 'page',
6817     dom => 'page',
6818     key => 'page',
6819     parse => sub {
6820     my ($self, $prop_name, $tt, $t, $onerror) = @_;
6821    
6822     if ($t->{type} == IDENT_TOKEN) {
6823     my $value = lc $t->{value}; ## TODO: case
6824     if ($value eq 'auto') {
6825     $t = $tt->get_next_token;
6826     return ($t, {$prop_name => ['KEYWORD', 'auto']});
6827     } else {
6828     $value = $t->{value};
6829     $t = $tt->get_next_token;
6830     return ($t, {$prop_name => ['PAGE', $value]});
6831     }
6832     }
6833    
6834 wakaba 1.69 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6835     level => $self->{level}->{must},
6836 wakaba 1.62 uri => \$self->{href},
6837     token => $t);
6838     return ($t, undef);
6839     },
6840     initial => ['KEYWORD', 'auto'],
6841     inherited => 1,
6842     compute => $compute_as_specified,
6843     };
6844    
6845 wakaba 1.1 1;
6846 wakaba 1.71 ## $Date: 2008/08/16 08:37:40 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24