/[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.74 - (hide annotations) (download)
Wed Sep 17 07:19:30 2008 UTC (16 years, 9 months ago) by wakaba
Branch: MAIN
CVS Tags: HEAD
Changes since 1.73: +6 -1 lines
++ ChangeLog	17 Sep 2008 07:19:23 -0000
	* readme.en.html: Add link to the CSS extension document.

2008-09-17  Wakaba  <wakaba@suika.fam.cx>

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24