/[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.60 - (hide annotations) (download)
Sat Feb 2 13:46:55 2008 UTC (16 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.59: +25 -41 lines
++ whatpm/Whatpm/CSS/ChangeLog	2 Feb 2008 13:46:50 -0000
	* Parser.pm ('font', 'cursor'): Serializer are
	merged with $default_serializer.

2008-02-02  Wakaba  <wakaba@suika.fam.cx>

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24