/[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.72 - (hide annotations) (download)
Mon Sep 15 23:45:34 2008 UTC (16 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.71: +8 -1 lines
++ whatpm/t/ChangeLog	15 Sep 2008 23:44:55 -0000
2008-09-16  Wakaba  <wakaba@suika.fam.cx>

	* css-text.dat: Test data for pre-wrap updated.

++ whatpm/Whatpm/CSS/ChangeLog	15 Sep 2008 23:45:19 -0000
2008-09-16  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm: Support for '-o-pre-wrap'.  '-moz-pre-wrap'
	is now replaced by 'pre-wrap'.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24