/[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.35 - (hide annotations) (download)
Mon Jan 14 11:21:22 2008 UTC (16 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.34: +3 -3 lines
++ whatpm/Whatpm/CSS/ChangeLog	14 Jan 2008 11:21:19 -0000
	* Cascade.pm (get_cascaded_value): No longer have to test
	whether priority is defined.

	* Parser.pm (parse_char_string): Set an empty string as the priority
	if no priority was specified.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24