/[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.73 - (hide annotations) (download)
Wed Sep 17 03:55:15 2008 UTC (16 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.72: +257 -4 lines
++ whatpm/t/ChangeLog	17 Sep 2008 03:45:14 -0000
2008-09-17  Wakaba  <wakaba@suika.fam.cx>

	* content-model-1.dat: Test results are updated.

	* content-model-2.dat: Tests for style="" are added.

++ whatpm/Whatpm/CSS/ChangeLog	17 Sep 2008 03:45:24 -0000
2008-09-17  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm (parse_char_string_as_inline): New method.

++ whatpm/Whatpm/ContentChecker/ChangeLog	17 Sep 2008 03:55:06 -0000
2008-09-17  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm: Support for style="" attributes.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24