/[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.30 - (hide annotations) (download)
Sat Jan 12 10:40:58 2008 UTC (16 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.29: +322 -34 lines
++ whatpm/Whatpm/CSS/ChangeLog	12 Jan 2008 10:40:50 -0000
	* Parser.pm: 'background' is implemented.

2008-01-12  Wakaba  <wakaba@suika.fam.cx>

	were not allowed between property name and COLON.  NUMBER and
	DIMENSION are not allowed as color in quirks mode.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24