/[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.32 - (hide annotations) (download)
Sat Jan 12 14:45:47 2008 UTC (17 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.31: +63 -1 lines
++ whatpm/Whatpm/CSS/ChangeLog	12 Jan 2008 14:45:15 -0000
	* Parser.pm: 'opacity' and '-moz-opacity' are implemented.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24