/[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.33 - (hide annotations) (download)
Mon Jan 14 05:57:35 2008 UTC (16 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.32: +26 -8 lines
++ whatpm/Whatpm/CSS/ChangeLog	14 Jan 2008 05:57:32 -0000
	* Parser.pm (parse_char_string): Namespace support is revised so
	that more Gecko-like namespace serialization can be implemented.

	* SelectorsSerializer.pm (serialize_selector_text): Revised.
	Now it does almost same as what Gecko does for namespace
	tratements, what Gecko does for universal selector omittion, and what
	Opera does for ordering (i.e. no sorting).  Only one COLON
	for pseudo-elements since Gecko and Opera do so.

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     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.7 my $important = (defined $prop_flag and $prop_flag eq 'important');
386     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     return undef;
541     }
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     if (defined $x) {
998     if (defined $y) {
999     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     if (defined $y) {
1011     $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     if (defined $value) {
1021     $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     if (defined $oc and defined $os and defined $ow) {
1188     $r->{outline} = $ow . ' ' . $os . ' ' . $oc;
1189     } else {
1190     $r->{'outline-color'} = $oc if defined $oc;
1191     $r->{'outline-style'} = $os if defined $os;
1192     $r->{'outline-width'} = $ow if defined $ow;
1193     }
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     if (defined $x) {
2120     if (defined $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     if (defined $y) {
2136     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     return undef;
3195     }
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     return undef;
3269     }
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     return undef unless defined $v[-1];
3400     push @v, $self->border_right_style;
3401     return undef unless defined $v[-1];
3402     push @v, $self->border_bottom_style;
3403     return undef unless defined $v[-1];
3404 wakaba 1.19 push @v, $self->border_left_style;
3405 wakaba 1.7 return undef unless defined $v[-1];
3406    
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     return undef unless defined $v[-1];
3499     push @v, $self->border_right_color;
3500     return undef unless defined $v[-1];
3501     push @v, $self->border_bottom_color;
3502     return undef unless defined $v[-1];
3503     push @v, $self->border_left_color;
3504     return undef unless defined $v[-1];
3505    
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     return undef unless defined $width_value;
3630     my $style_prop = $prop_name . '_style'; $style_prop =~ tr/-/_/;
3631     my $style_value = $self->$style_prop;
3632     return undef unless defined $style_value;
3633     my $color_prop = $prop_name . '_color'; $color_prop =~ tr/-/_/;
3634     my $color_value = $self->$color_prop;
3635     return undef unless defined $color_value;
3636    
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     return undef unless defined $bt;
3705     my $br = $self->border_right;
3706     return undef unless defined $br;
3707     return undef unless $bt eq $br;
3708     my $bb = $self->border_bottom;
3709     return undef unless defined $bb;
3710     return undef unless $bt eq $bb;
3711     my $bl = $self->border_left;
3712     return undef unless defined $bl;
3713     return undef unless $bt eq $bl;
3714    
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     return undef unless defined $v[-1];
3940     push @v, $self->margin_right;
3941     return undef unless defined $v[-1];
3942     push @v, $self->margin_bottom;
3943     return undef unless defined $v[-1];
3944     push @v, $self->margin_left;
3945     return undef unless defined $v[-1];
3946    
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     return undef unless defined $v[-1];
4187     push @v, $self->padding_right;
4188     return undef unless defined $v[-1];
4189     push @v, $self->padding_bottom;
4190     return undef unless defined $v[-1];
4191     push @v, $self->padding_left;
4192     return undef unless defined $v[-1];
4193    
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     return undef unless defined $v[-1];
4311     push @v, $self->_manakai_border_spacing_y;
4312     return undef unless defined $v[-1];
4313    
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     return $x . ' ' . $y if defined $x and defined $y;
4452     return undef;
4453     },
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     return undef unless defined $color;
4678     my $image = $self->background_image;
4679     return undef unless defined $image;
4680     my $repeat = $self->background_repeat;
4681     return undef unless defined $repeat;
4682     my $attachment = $self->background_attachment;
4683     return undef unless defined $attachment;
4684     my $position = $self->background_position;
4685     return undef unless defined $position;
4686    
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     return undef unless defined $style;
4824     my $variant = $self->font_variant;
4825     return undef unless defined $variant;
4826     return undef if $i ne $self->get_property_priority ('font-variant');
4827     my $weight = $self->font_weight;
4828     return undef unless defined $weight;
4829     return undef if $i ne $self->get_property_priority ('font-weight');
4830     my $size = $self->font_size;
4831     return undef unless defined $size;
4832     return undef if $i ne $self->get_property_priority ('font-size');
4833     my $height = $self->line_height;
4834     return undef unless defined $height;
4835     return undef if $i ne $self->get_property_priority ('line-height');
4836     my $family = $self->font_family;
4837     return undef unless defined $family;
4838     return undef if $i ne $self->get_property_priority ('font-family');
4839    
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     push @v, '!'.$i if length $i;
4847     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.20 return undef unless defined $v[-1];
5064 wakaba 1.24 push @v, $self->border_right_width;
5065 wakaba 1.20 return undef unless defined $v[-1];
5066 wakaba 1.24 push @v, $self->border_bottom_width;
5067 wakaba 1.20 return undef unless defined $v[-1];
5068 wakaba 1.24 push @v, $self->border_left_width;
5069 wakaba 1.20 return undef unless defined $v[-1];
5070    
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.33 ## $Date: 2008/01/12 14:45:47 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24