/[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.64 - (hide annotations) (download)
Sat Feb 9 11:29:13 2008 UTC (16 years, 8 months ago) by wakaba
Branch: MAIN
Changes since 1.63: +2 -2 lines
++ whatpm/t/ChangeLog	9 Feb 2008 11:29:09 -0000
2008-02-09  Wakaba  <wakaba@suika.fam.cx>

	* css-2.dat, css-font.dat: s/unsupported/u/g;

++ whatpm/Whatpm/CSS/ChangeLog	9 Feb 2008 11:28:38 -0000
2008-02-09  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm (new): s/unsupported/u/;

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24