/[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.69 - (hide annotations) (download)
Sat Aug 16 07:35:23 2008 UTC (16 years, 2 months ago) by wakaba
Branch: MAIN
Changes since 1.68: +272 -258 lines
++ whatpm/Whatpm/ChangeLog	16 Aug 2008 07:34:18 -0000
	* CacheManifest.pm: Support for new style of error
	reports.

	* HTML.pm.src: Set line=1, column=1 to the document node.

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

++ whatpm/Whatpm/CSS/ChangeLog	16 Aug 2008 07:34:47 -0000
2008-08-16  Wakaba  <wakaba@suika.fam.cx>

	* MediaQueryParser.pm, SelectorsParser.pm, Parser.pm: Support
	for new style of error reports.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24