/[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.29 - (hide annotations) (download)
Sat Jan 12 07:20:22 2008 UTC (17 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.28: +412 -6 lines
++ whatpm/Whatpm/CSS/ChangeLog	12 Jan 2008 07:19:24 -0000
2008-01-12  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm: 'border', 'border-top', 'border-right', 'border-bottom',
	'border-left', and 'outline' are implemented.  White space characters
	were not allowed between property name and COLON.

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     initial => ['KEYWORD', 'transparent'],
955     #inherited => 0,
956     compute => $Prop->{color}->{compute},
957     };
958     $Attr->{background_color} = $Prop->{'background-color'};
959     $Key->{background_color} = $Prop->{'background-color'};
960    
961     $Prop->{'border-top-color'} = {
962     css => 'border-top-color',
963     dom => 'border_top_color',
964     key => 'border_top_color',
965     parse => $parse_color,
966     serialize => $default_serializer,
967 wakaba 1.29 serialize_multiple => sub {
968     my $self = shift;
969     ## NOTE: This algorithm returns the same result as that of Firefox 2
970     ## in many case, but not always.
971     my $r = {
972     'border-top-color' => $self->border_top_color,
973     'border-top-style' => $self->border_top_style,
974     'border-top-width' => $self->border_top_width,
975     'border-right-color' => $self->border_right_color,
976     'border-right-style' => $self->border_right_style,
977     'border-right-width' => $self->border_right_width,
978     'border-bottom-color' => $self->border_bottom_color,
979     'border-bottom-style' => $self->border_bottom_style,
980     'border-bottom-width' => $self->border_bottom_width,
981     'border-left-color' => $self->border_left_color,
982     'border-left-style' => $self->border_left_style,
983     'border-left-width' => $self->border_left_width,
984     };
985     my $i = 0;
986     for my $prop (qw/border-top border-right border-bottom border-left/) {
987     if (defined $r->{$prop.'-color'} and
988     defined $r->{$prop.'-style'} and
989     defined $r->{$prop.'-width'}) {
990     $r->{$prop} = $r->{$prop.'-width'} . ' ' .
991     $r->{$prop.'-style'} . ' ' .
992     $r->{$prop.'-color'};
993     delete $r->{$prop.'-width'};
994     delete $r->{$prop.'-style'};
995     delete $r->{$prop.'-color'};
996     $i++;
997     }
998     }
999     if ($i == 4 and $r->{'border-top'} eq $r->{'border-right'} and
1000     $r->{'border-right'} eq $r->{'border-bottom'} and
1001     $r->{'border-bottom'} eq $r->{'border-left'}) {
1002     return {border => $r->{'border-top'}};
1003     }
1004    
1005     unless ($i) {
1006     for my $prop (qw/color style width/) {
1007     if (defined $r->{'border-top-'.$prop} and
1008     defined $r->{'border-bottom-'.$prop} and
1009     defined $r->{'border-right-'.$prop} and
1010     defined $r->{'border-left-'.$prop}) {
1011     my @v = ($r->{'border-top-'.$prop},
1012     $r->{'border-right-'.$prop},
1013     $r->{'border-bottom-'.$prop},
1014     $r->{'border-left-'.$prop});
1015     pop @v if $r->{'border-right-'.$prop} eq $r->{'border-left-'.$prop};
1016     pop @v if $r->{'border-bottom-'.$prop} eq $r->{'border-top-'.$prop};
1017     pop @v if $r->{'border-right-'.$prop} eq $r->{'border-top-'.$prop};
1018     $r->{'border-'.$prop} = join ' ', @v;
1019     delete $r->{'border-top-'.$prop};
1020     delete $r->{'border-bottom-'.$prop};
1021     delete $r->{'border-right-'.$prop};
1022     delete $r->{'border-left-'.$prop};
1023     }
1024     }
1025     }
1026    
1027     delete $r->{$_} for grep {not defined $r->{$_}} keys %$r;
1028     return $r;
1029     },
1030 wakaba 1.28 initial => ['KEYWORD', 'currentcolor'],
1031     #inherited => 0,
1032     compute => $Prop->{color}->{compute},
1033     };
1034     $Attr->{border_top_color} = $Prop->{'border-top-color'};
1035     $Key->{border_top_color} = $Prop->{'border-top-color'};
1036    
1037     $Prop->{'border-right-color'} = {
1038     css => 'border-right-color',
1039     dom => 'border_right_color',
1040     key => 'border_right_color',
1041     parse => $parse_color,
1042     serialize => $default_serializer,
1043 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
1044 wakaba 1.28 initial => ['KEYWORD', 'currentcolor'],
1045     #inherited => 0,
1046     compute => $Prop->{color}->{compute},
1047     };
1048 wakaba 1.29 $Attr->{border_right_color} = $Prop->{'border-right-color'};
1049     $Key->{border_right_color} = $Prop->{'border-right-color'};
1050 wakaba 1.28
1051     $Prop->{'border-bottom-color'} = {
1052     css => 'border-bottom-color',
1053     dom => 'border_bottom_color',
1054     key => 'border_bottom_color',
1055     parse => $parse_color,
1056     serialize => $default_serializer,
1057 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
1058 wakaba 1.28 initial => ['KEYWORD', 'currentcolor'],
1059     #inherited => 0,
1060     compute => $Prop->{color}->{compute},
1061     };
1062     $Attr->{border_bottom_color} = $Prop->{'border-bottom-color'};
1063     $Key->{border_bottom_color} = $Prop->{'border-bottom-color'};
1064    
1065     $Prop->{'border-left-color'} = {
1066     css => 'border-left-color',
1067     dom => 'border_left_color',
1068     key => 'border_left_color',
1069     parse => $parse_color,
1070     serialize => $default_serializer,
1071 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
1072 wakaba 1.28 initial => ['KEYWORD', 'currentcolor'],
1073     #inherited => 0,
1074     compute => $Prop->{color}->{compute},
1075     };
1076     $Attr->{border_left_color} = $Prop->{'border-left-color'};
1077     $Key->{border_left_color} = $Prop->{'border-left-color'};
1078    
1079     $Prop->{'outline-color'} = {
1080     css => 'outline-color',
1081     dom => 'outline_color',
1082     key => 'outline_color',
1083     parse => $parse_color,
1084     serialize => $default_serializer,
1085 wakaba 1.29 serialize_multiple => sub {
1086     my $self = shift;
1087     my $oc = $self->outline_color;
1088     my $os = $self->outline_style;
1089     my $ow = $self->outline_width;
1090     my $r = {};
1091     if (defined $oc and defined $os and defined $ow) {
1092     $r->{outline} = $ow . ' ' . $os . ' ' . $oc;
1093     } else {
1094     $r->{'outline-color'} = $oc if defined $oc;
1095     $r->{'outline-style'} = $os if defined $os;
1096     $r->{'outline-width'} = $ow if defined $ow;
1097     }
1098     return $r;
1099     },
1100 wakaba 1.28 initial => ['KEYWORD', '-manakai-invert-or-currentcolor'],
1101     #inherited => 0,
1102     compute => $Prop->{color}->{compute},
1103     };
1104     $Attr->{outline_color} = $Prop->{'outline-color'};
1105     $Key->{outline_color} = $Prop->{'outline-color'};
1106    
1107 wakaba 1.6 my $one_keyword_parser = sub {
1108     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1109    
1110     if ($t->{type} == IDENT_TOKEN) {
1111     my $prop_value = lc $t->{value}; ## TODO: case folding
1112     $t = $tt->get_next_token;
1113     if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
1114     $self->{prop_value}->{$prop_name}->{$prop_value}) {
1115 wakaba 1.7 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
1116 wakaba 1.6 } elsif ($prop_value eq 'inherit') {
1117 wakaba 1.10 return ($t, {$prop_name => ['INHERIT']});
1118 wakaba 1.6 }
1119     }
1120    
1121 wakaba 1.7 $onerror->(type => 'syntax error:keyword:'.$prop_name,
1122 wakaba 1.6 level => $self->{must_level},
1123     token => $t);
1124     return ($t, undef);
1125     };
1126    
1127     $Prop->{display} = {
1128     css => 'display',
1129     dom => 'display',
1130     key => 'display',
1131     parse => $one_keyword_parser,
1132 wakaba 1.11 serialize => $default_serializer,
1133 wakaba 1.6 keyword => {
1134     block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
1135     'list-item' => 1, none => 1,
1136     table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
1137     'table-column-group' => 1, 'table-header-group' => 1,
1138     'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
1139     },
1140 wakaba 1.9 initial => ["KEYWORD", "inline"],
1141     #inherited => 0,
1142     compute => sub {
1143     my ($self, $element, $prop_name, $specified_value) = @_;
1144     ## NOTE: CSS 2.1 Section 9.7.
1145    
1146     ## WARNING: |compute| for 'float' property invoke this CODE
1147     ## in some case. Careless modification might cause a infinite loop.
1148    
1149 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1150 wakaba 1.9 if ($specified_value->[1] eq 'none') {
1151     ## Case 1 [CSS 2.1]
1152     return $specified_value;
1153     } else {
1154     my $position = $self->get_computed_value ($element, 'position');
1155     if ($position->[0] eq 'KEYWORD' and
1156     ($position->[1] eq 'absolute' or
1157     $position->[1] eq 'fixed')) {
1158     ## Case 2 [CSS 2.1]
1159     #
1160     } else {
1161     my $float = $self->get_computed_value ($element, 'float');
1162     if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
1163     ## Caes 3 [CSS 2.1]
1164     #
1165     } elsif (not defined $element->manakai_parent_element) {
1166     ## Case 4 [CSS 2.1]
1167     #
1168     } else {
1169     ## Case 5 [CSS 2.1]
1170     return $specified_value;
1171     }
1172     }
1173    
1174     return ["KEYWORD",
1175     {
1176     'inline-table' => 'table',
1177     inline => 'block',
1178     'run-in' => 'block',
1179     'table-row-group' => 'block',
1180     'table-column' => 'block',
1181     'table-column-group' => 'block',
1182     'table-header-group' => 'block',
1183     'table-footer-group' => 'block',
1184     'table-row' => 'block',
1185     'table-cell' => 'block',
1186     'table-caption' => 'block',
1187     'inline-block' => 'block',
1188     }->{$specified_value->[1]} || $specified_value->[1]];
1189     }
1190     } else {
1191     return $specified_value; ## Maybe an error of the implementation.
1192     }
1193     },
1194 wakaba 1.6 };
1195     $Attr->{display} = $Prop->{display};
1196     $Key->{display} = $Prop->{display};
1197    
1198     $Prop->{position} = {
1199     css => 'position',
1200     dom => 'position',
1201     key => 'position',
1202     parse => $one_keyword_parser,
1203 wakaba 1.11 serialize => $default_serializer,
1204 wakaba 1.6 keyword => {
1205     static => 1, relative => 1, absolute => 1, fixed => 1,
1206     },
1207 wakaba 1.10 initial => ["KEYWORD", "static"],
1208 wakaba 1.9 #inherited => 0,
1209     compute => $compute_as_specified,
1210 wakaba 1.6 };
1211     $Attr->{position} = $Prop->{position};
1212     $Key->{position} = $Prop->{position};
1213    
1214     $Prop->{float} = {
1215     css => 'float',
1216     dom => 'css_float',
1217     key => 'float',
1218     parse => $one_keyword_parser,
1219 wakaba 1.11 serialize => $default_serializer,
1220 wakaba 1.6 keyword => {
1221     left => 1, right => 1, none => 1,
1222     },
1223 wakaba 1.9 initial => ["KEYWORD", "none"],
1224     #inherited => 0,
1225     compute => sub {
1226     my ($self, $element, $prop_name, $specified_value) = @_;
1227     ## NOTE: CSS 2.1 Section 9.7.
1228    
1229     ## WARNING: |compute| for 'display' property invoke this CODE
1230     ## in some case. Careless modification might cause a infinite loop.
1231    
1232 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1233 wakaba 1.9 if ($specified_value->[1] eq 'none') {
1234     ## Case 1 [CSS 2.1]
1235     return $specified_value;
1236     } else {
1237     my $position = $self->get_computed_value ($element, 'position');
1238     if ($position->[0] eq 'KEYWORD' and
1239     ($position->[1] eq 'absolute' or
1240     $position->[1] eq 'fixed')) {
1241     ## Case 2 [CSS 2.1]
1242     return ["KEYWORD", "none"];
1243     }
1244     }
1245     }
1246    
1247     ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
1248     ## on computed value of 'float' property.
1249    
1250     ## Case 3, 4, and 5 [CSS 2.1]
1251     return $specified_value;
1252     },
1253 wakaba 1.6 };
1254     $Attr->{css_float} = $Prop->{float};
1255     $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
1256     $Key->{float} = $Prop->{float};
1257    
1258     $Prop->{clear} = {
1259     css => 'clear',
1260     dom => 'clear',
1261     key => 'clear',
1262     parse => $one_keyword_parser,
1263 wakaba 1.11 serialize => $default_serializer,
1264 wakaba 1.6 keyword => {
1265     left => 1, right => 1, none => 1, both => 1,
1266     },
1267 wakaba 1.9 initial => ["KEYWORD", "none"],
1268     #inherited => 0,
1269     compute => $compute_as_specified,
1270 wakaba 1.6 };
1271     $Attr->{clear} = $Prop->{clear};
1272     $Key->{clear} = $Prop->{clear};
1273    
1274     $Prop->{direction} = {
1275     css => 'direction',
1276     dom => 'direction',
1277     key => 'direction',
1278     parse => $one_keyword_parser,
1279 wakaba 1.11 serialize => $default_serializer,
1280 wakaba 1.6 keyword => {
1281     ltr => 1, rtl => 1,
1282     },
1283 wakaba 1.9 initial => ["KEYWORD", "ltr"],
1284     inherited => 1,
1285     compute => $compute_as_specified,
1286 wakaba 1.6 };
1287     $Attr->{direction} = $Prop->{direction};
1288     $Key->{direction} = $Prop->{direction};
1289    
1290     $Prop->{'unicode-bidi'} = {
1291     css => 'unicode-bidi',
1292     dom => 'unicode_bidi',
1293     key => 'unicode_bidi',
1294     parse => $one_keyword_parser,
1295 wakaba 1.11 serialize => $default_serializer,
1296 wakaba 1.6 keyword => {
1297     normal => 1, embed => 1, 'bidi-override' => 1,
1298     },
1299 wakaba 1.9 initial => ["KEYWORD", "normal"],
1300     #inherited => 0,
1301     compute => $compute_as_specified,
1302 wakaba 1.6 };
1303     $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
1304     $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
1305    
1306 wakaba 1.11 $Prop->{overflow} = {
1307     css => 'overflow',
1308     dom => 'overflow',
1309     key => 'overflow',
1310     parse => $one_keyword_parser,
1311     serialize => $default_serializer,
1312     keyword => {
1313     visible => 1, hidden => 1, scroll => 1, auto => 1,
1314     },
1315     initial => ["KEYWORD", "visible"],
1316     #inherited => 0,
1317     compute => $compute_as_specified,
1318     };
1319     $Attr->{overflow} = $Prop->{overflow};
1320     $Key->{overflow} = $Prop->{overflow};
1321    
1322     $Prop->{visibility} = {
1323     css => 'visibility',
1324     dom => 'visibility',
1325     key => 'visibility',
1326     parse => $one_keyword_parser,
1327     serialize => $default_serializer,
1328     keyword => {
1329     visible => 1, hidden => 1, collapse => 1,
1330     },
1331     initial => ["KEYWORD", "visible"],
1332     #inherited => 0,
1333     compute => $compute_as_specified,
1334     };
1335     $Attr->{visibility} = $Prop->{visibility};
1336     $Key->{visibility} = $Prop->{visibility};
1337    
1338     $Prop->{'list-style-type'} = {
1339     css => 'list-style-type',
1340     dom => 'list_style_type',
1341     key => 'list_style_type',
1342     parse => $one_keyword_parser,
1343     serialize => $default_serializer,
1344     keyword => {
1345     qw/
1346     disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
1347     lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
1348     upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
1349     none 1
1350     /,
1351     },
1352     initial => ["KEYWORD", 'disc'],
1353     inherited => 1,
1354     compute => $compute_as_specified,
1355     };
1356     $Attr->{list_style_type} = $Prop->{'list-style-type'};
1357     $Key->{list_style_type} = $Prop->{'list-style-type'};
1358    
1359     $Prop->{'list-style-position'} = {
1360     css => 'list-style-position',
1361     dom => 'list_style_position',
1362     key => 'list_style_position',
1363     parse => $one_keyword_parser,
1364     serialize => $default_serializer,
1365     keyword => {
1366     inside => 1, outside => 1,
1367     },
1368     initial => ["KEYWORD", 'outside'],
1369     inherited => 1,
1370     compute => $compute_as_specified,
1371     };
1372     $Attr->{list_style_position} = $Prop->{'list-style-position'};
1373     $Key->{list_style_position} = $Prop->{'list-style-position'};
1374    
1375 wakaba 1.12 $Prop->{'page-break-before'} = {
1376     css => 'page-break-before',
1377     dom => 'page_break_before',
1378     key => 'page_break_before',
1379     parse => $one_keyword_parser,
1380     serialize => $default_serializer,
1381     keyword => {
1382     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
1383     },
1384     initial => ["KEYWORD", 'auto'],
1385 wakaba 1.15 #inherited => 0,
1386 wakaba 1.12 compute => $compute_as_specified,
1387     };
1388     $Attr->{page_break_before} = $Prop->{'page-break-before'};
1389     $Key->{page_break_before} = $Prop->{'page-break-before'};
1390    
1391     $Prop->{'page-break-after'} = {
1392     css => 'page-break-after',
1393     dom => 'page_break_after',
1394     key => 'page_break_after',
1395     parse => $one_keyword_parser,
1396     serialize => $default_serializer,
1397     keyword => {
1398     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
1399     },
1400     initial => ["KEYWORD", 'auto'],
1401 wakaba 1.15 #inherited => 0,
1402 wakaba 1.12 compute => $compute_as_specified,
1403     };
1404     $Attr->{page_break_after} = $Prop->{'page-break-after'};
1405     $Key->{page_break_after} = $Prop->{'page-break-after'};
1406    
1407     $Prop->{'page-break-inside'} = {
1408     css => 'page-break-inside',
1409     dom => 'page_break_inside',
1410     key => 'page_break_inside',
1411     parse => $one_keyword_parser,
1412     serialize => $default_serializer,
1413     keyword => {
1414     auto => 1, avoid => 1,
1415     },
1416     initial => ["KEYWORD", 'auto'],
1417     inherited => 1,
1418     compute => $compute_as_specified,
1419     };
1420     $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
1421     $Key->{page_break_inside} = $Prop->{'page-break-inside'};
1422    
1423 wakaba 1.15 $Prop->{'background-repeat'} = {
1424     css => 'background-repeat',
1425     dom => 'background_repeat',
1426     key => 'background_repeat',
1427     parse => $one_keyword_parser,
1428     serialize => $default_serializer,
1429     keyword => {
1430     repeat => 1, 'repeat-x' => 1, 'repeat-y' => 1, 'no-repeat' => 1,
1431     },
1432     initial => ["KEYWORD", 'repeat'],
1433     #inherited => 0,
1434     compute => $compute_as_specified,
1435     };
1436     $Attr->{background_repeat} = $Prop->{'background-repeat'};
1437     $Key->{backgroud_repeat} = $Prop->{'background-repeat'};
1438    
1439     $Prop->{'background-attachment'} = {
1440     css => 'background-attachment',
1441     dom => 'background_attachment',
1442     key => 'background_attachment',
1443     parse => $one_keyword_parser,
1444     serialize => $default_serializer,
1445     keyword => {
1446     scroll => 1, fixed => 1,
1447     },
1448     initial => ["KEYWORD", 'scroll'],
1449     #inherited => 0,
1450     compute => $compute_as_specified,
1451     };
1452     $Attr->{background_attachment} = $Prop->{'background-attachment'};
1453     $Key->{backgroud_attachment} = $Prop->{'background-attachment'};
1454    
1455     $Prop->{'font-style'} = {
1456     css => 'font-style',
1457 wakaba 1.18 dom => 'font_style',
1458     key => 'font_style',
1459 wakaba 1.15 parse => $one_keyword_parser,
1460     serialize => $default_serializer,
1461     keyword => {
1462     normal => 1, italic => 1, oblique => 1,
1463     },
1464     initial => ["KEYWORD", 'normal'],
1465     inherited => 1,
1466     compute => $compute_as_specified,
1467     };
1468     $Attr->{font_style} = $Prop->{'font-style'};
1469     $Key->{font_style} = $Prop->{'font-style'};
1470    
1471     $Prop->{'font-variant'} = {
1472     css => 'font-variant',
1473     dom => 'font_variant',
1474     key => 'font_variant',
1475     parse => $one_keyword_parser,
1476     serialize => $default_serializer,
1477     keyword => {
1478     normal => 1, 'small-caps' => 1,
1479     },
1480     initial => ["KEYWORD", 'normal'],
1481     inherited => 1,
1482     compute => $compute_as_specified,
1483     };
1484     $Attr->{font_variant} = $Prop->{'font-variant'};
1485     $Key->{font_variant} = $Prop->{'font-variant'};
1486    
1487 wakaba 1.16 $Prop->{'text-align'} = {
1488     css => 'text-align',
1489     dom => 'text_align',
1490     key => 'text_align',
1491     parse => $one_keyword_parser,
1492     serialize => $default_serializer,
1493     keyword => {
1494     left => 1, right => 1, center => 1, justify => 1, ## CSS 2
1495     begin => 1, end => 1, ## CSS 3
1496     },
1497     initial => ["KEYWORD", 'begin'],
1498     inherited => 1,
1499     compute => $compute_as_specified,
1500     };
1501     $Attr->{text_align} = $Prop->{'text-align'};
1502     $Key->{text_align} = $Prop->{'text-align'};
1503    
1504     $Prop->{'text-transform'} = {
1505     css => 'text-transform',
1506     dom => 'text_transform',
1507     key => 'text_transform',
1508     parse => $one_keyword_parser,
1509     serialize => $default_serializer,
1510     keyword => {
1511     capitalize => 1, uppercase => 1, lowercase => 1, none => 1,
1512     },
1513     initial => ["KEYWORD", 'none'],
1514     inherited => 1,
1515     compute => $compute_as_specified,
1516     };
1517     $Attr->{text_transform} = $Prop->{'text-transform'};
1518     $Key->{text_transform} = $Prop->{'text-transform'};
1519    
1520     $Prop->{'white-space'} = {
1521     css => 'white-space',
1522     dom => 'white_space',
1523     key => 'white_space',
1524     parse => $one_keyword_parser,
1525     serialize => $default_serializer,
1526     keyword => {
1527     normal => 1, pre => 1, nowrap => 1, 'pre-wrap' => 1, 'pre-line' => 1,
1528     },
1529     initial => ["KEYWORD", 'normal'],
1530     inherited => 1,
1531     compute => $compute_as_specified,
1532     };
1533     $Attr->{white_space} = $Prop->{'white-space'};
1534     $Key->{white_space} = $Prop->{'white-space'};
1535    
1536     $Prop->{'caption-side'} = {
1537     css => 'caption-side',
1538     dom => 'caption_side',
1539     key => 'caption_side',
1540     parse => $one_keyword_parser,
1541     serialize => $default_serializer,
1542     keyword => {
1543     top => 1, bottom => 1,
1544     },
1545     initial => ['KEYWORD', 'top'],
1546     inherited => 1,
1547     compute => $compute_as_specified,
1548     };
1549     $Attr->{caption_side} = $Prop->{'caption-side'};
1550     $Key->{caption_side} = $Prop->{'caption-side'};
1551    
1552     $Prop->{'table-layout'} = {
1553     css => 'table-layout',
1554     dom => 'table_layout',
1555     key => 'table_layout',
1556     parse => $one_keyword_parser,
1557     serialize => $default_serializer,
1558     keyword => {
1559     auto => 1, fixed => 1,
1560     },
1561     initial => ['KEYWORD', 'auto'],
1562     #inherited => 0,
1563     compute => $compute_as_specified,
1564     };
1565     $Attr->{table_layout} = $Prop->{'table-layout'};
1566     $Key->{table_layout} = $Prop->{'table-layout'};
1567    
1568     $Prop->{'border-collapse'} = {
1569     css => 'border-collapse',
1570     dom => 'border_collapse',
1571     key => 'border_collapse',
1572     parse => $one_keyword_parser,
1573     serialize => $default_serializer,
1574     keyword => {
1575     collapse => 1, separate => 1,
1576     },
1577     initial => ['KEYWORD', 'separate'],
1578     inherited => 1,
1579     compute => $compute_as_specified,
1580     };
1581     $Attr->{border_collapse} = $Prop->{'border-collapse'};
1582     $Key->{border_collapse} = $Prop->{'border-collapse'};
1583    
1584     $Prop->{'empty-cells'} = {
1585     css => 'empty-cells',
1586     dom => 'empty_cells',
1587     key => 'empty_cells',
1588     parse => $one_keyword_parser,
1589     serialize => $default_serializer,
1590     keyword => {
1591     show => 1, hide => 1,
1592     },
1593     initial => ['KEYWORD', 'show'],
1594     inherited => 1,
1595     compute => $compute_as_specified,
1596     };
1597     $Attr->{empty_cells} = $Prop->{'empty-cells'};
1598     $Key->{empty_cells} = $Prop->{'empty-cells'};
1599    
1600 wakaba 1.11 $Prop->{'z-index'} = {
1601     css => 'z-index',
1602     dom => 'z_index',
1603     key => 'z_index',
1604     parse => sub {
1605     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1606    
1607 wakaba 1.12 my $sign = 1;
1608     if ($t->{type} == MINUS_TOKEN) {
1609     $sign = -1;
1610     $t = $tt->get_next_token;
1611     }
1612    
1613 wakaba 1.11 if ($t->{type} == NUMBER_TOKEN) {
1614     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
1615     ## browser compatibility issue.
1616     my $value = $t->{number};
1617     $t = $tt->get_next_token;
1618 wakaba 1.12 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1619     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1620 wakaba 1.11 my $value = lc $t->{value}; ## TODO: case
1621     $t = $tt->get_next_token;
1622     if ($value eq 'auto') {
1623     ## NOTE: |z-index| is the default value and therefore it must be
1624     ## supported anyway.
1625     return ($t, {$prop_name => ["KEYWORD", 'auto']});
1626     } elsif ($value eq 'inherit') {
1627     return ($t, {$prop_name => ['INHERIT']});
1628     }
1629     }
1630    
1631     $onerror->(type => 'syntax error:'.$prop_name,
1632     level => $self->{must_level},
1633     token => $t);
1634     return ($t, undef);
1635     },
1636     serialize => $default_serializer,
1637     initial => ['KEYWORD', 'auto'],
1638     #inherited => 0,
1639     compute => $compute_as_specified,
1640     };
1641     $Attr->{z_index} = $Prop->{'z-index'};
1642     $Key->{z_index} = $Prop->{'z-index'};
1643    
1644 wakaba 1.12 $Prop->{orphans} = {
1645     css => 'orphans',
1646     dom => 'orphans',
1647     key => 'orphans',
1648     parse => sub {
1649     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1650    
1651     my $sign = 1;
1652     if ($t->{type} == MINUS_TOKEN) {
1653     $t = $tt->get_next_token;
1654     $sign = -1;
1655     }
1656    
1657     if ($t->{type} == NUMBER_TOKEN) {
1658     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
1659     ## <http://suika.fam.cx/gate/2005/sw/widows> for
1660     ## browser compatibility issue.
1661     my $value = $t->{number};
1662     $t = $tt->get_next_token;
1663     return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1664     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1665     my $value = lc $t->{value}; ## TODO: case
1666     $t = $tt->get_next_token;
1667     if ($value eq 'inherit') {
1668     return ($t, {$prop_name => ['INHERIT']});
1669     }
1670     }
1671    
1672     $onerror->(type => 'syntax error:'.$prop_name,
1673     level => $self->{must_level},
1674     token => $t);
1675     return ($t, undef);
1676     },
1677     serialize => $default_serializer,
1678     initial => ['NUMBER', 2],
1679     inherited => 1,
1680     compute => $compute_as_specified,
1681     };
1682     $Attr->{orphans} = $Prop->{orphans};
1683     $Key->{orphans} = $Prop->{orphans};
1684    
1685     $Prop->{widows} = {
1686     css => 'widows',
1687     dom => 'widows',
1688     key => 'widows',
1689     parse => $Prop->{orphans}->{parse},
1690     serialize => $default_serializer,
1691     initial => ['NUMBER', 2],
1692     inherited => 1,
1693     compute => $compute_as_specified,
1694     };
1695     $Attr->{widows} = $Prop->{widows};
1696     $Key->{widows} = $Prop->{widows};
1697    
1698 wakaba 1.19 my $length_unit = {
1699     em => 1, ex => 1, px => 1,
1700     in => 1, cm => 1, mm => 1, pt => 1, pc => 1,
1701     };
1702    
1703 wakaba 1.18 $Prop->{'font-size'} = {
1704     css => 'font-size',
1705     dom => 'font_size',
1706     key => 'font_size',
1707     parse => sub {
1708     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1709    
1710     my $sign = 1;
1711     if ($t->{type} == MINUS_TOKEN) {
1712     $t = $tt->get_next_token;
1713     $sign = -1;
1714     }
1715    
1716     if ($t->{type} == DIMENSION_TOKEN) {
1717     my $value = $t->{number} * $sign;
1718     my $unit = lc $t->{value}; ## TODO: case
1719     $t = $tt->get_next_token;
1720 wakaba 1.19 if ($length_unit->{$unit} and $value >= 0) {
1721 wakaba 1.18 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1722     }
1723     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1724     my $value = $t->{number} * $sign;
1725     $t = $tt->get_next_token;
1726     return ($t, {$prop_name => ['PERCENTAGE', $value]}) if $value >= 0;
1727 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
1728 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
1729 wakaba 1.18 my $value = $t->{number} * $sign;
1730     $t = $tt->get_next_token;
1731     return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1732     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1733     my $value = lc $t->{value}; ## TODO: case
1734     $t = $tt->get_next_token;
1735     if ({
1736     'xx-small' => 1, 'x-small' => 1, small => 1, medium => 1,
1737     large => 1, 'x-large' => 1, 'xx-large' => 1,
1738     '-manakai-xxx-large' => 1, # -webkit-xxx-large
1739     larger => 1, smaller => 1,
1740     }->{$value}) {
1741     return ($t, {$prop_name => ['KEYWORD', $value]});
1742     } elsif ($value eq 'inherit') {
1743     return ($t, {$prop_name => ['INHERIT']});
1744     }
1745     }
1746    
1747     $onerror->(type => 'syntax error:'.$prop_name,
1748     level => $self->{must_level},
1749     token => $t);
1750     return ($t, undef);
1751     },
1752     serialize => $default_serializer,
1753     initial => ['KEYWORD', 'medium'],
1754     inherited => 1,
1755     compute => sub {
1756     my ($self, $element, $prop_name, $specified_value) = @_;
1757    
1758     if (defined $specified_value) {
1759     if ($specified_value->[0] eq 'DIMENSION') {
1760     my $unit = $specified_value->[2];
1761     my $value = $specified_value->[1];
1762    
1763     if ($unit eq 'em' or $unit eq 'ex') {
1764     $value *= 0.5 if $unit eq 'ex';
1765     ## TODO: Preferred way to determine the |ex| size is defined
1766     ## in CSS 2.1.
1767    
1768     my $parent_element = $element->manakai_parent_element;
1769     if (defined $parent_element) {
1770     $value *= $self->get_computed_value ($parent_element, $prop_name)
1771     ->[1];
1772     } else {
1773     $value *= $self->{font_size}->[3]; # medium
1774     }
1775     $unit = 'px';
1776     } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
1777     ($value *= 12, $unit = 'pc') if $unit eq 'pc';
1778     ($value /= 72, $unit = 'in') if $unit eq 'pt';
1779     ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
1780     ($value *= 10, $unit = 'mm') if $unit eq 'cm';
1781     ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
1782     }
1783 wakaba 1.19 ## else: consistency error
1784 wakaba 1.18
1785     return ['DIMENSION', $value, $unit];
1786     } elsif ($specified_value->[0] eq 'PERCENTAGE') {
1787     my $parent_element = $element->manakai_parent_element;
1788     my $parent_cv;
1789     if (defined $parent_element) {
1790     $parent_cv = $self->get_computed_value
1791     ($parent_element, $prop_name);
1792     } else {
1793     $parent_cv = [undef, $self->{font_size}->[3]];
1794     }
1795     return ['DIMENSION', $parent_cv->[1] * $specified_value->[1] / 100,
1796     'px'];
1797     } elsif ($specified_value->[0] eq 'KEYWORD') {
1798     if ($specified_value->[1] eq 'larger') {
1799     my $parent_element = $element->manakai_parent_element;
1800     if (defined $parent_element) {
1801     my $parent_cv = $self->get_computed_value
1802     ($parent_element, $prop_name);
1803     return ['DIMENSION',
1804     $self->{get_larger_font_size}->($self, $parent_cv->[1]),
1805     'px'];
1806     } else { ## 'larger' relative to 'medium', initial of 'font-size'
1807     return ['DIMENSION', $self->{font_size}->[4], 'px'];
1808     }
1809     } elsif ($specified_value->[1] eq 'smaller') {
1810     my $parent_element = $element->manakai_parent_element;
1811     if (defined $parent_element) {
1812     my $parent_cv = $self->get_computed_value
1813     ($parent_element, $prop_name);
1814     return ['DIMENSION',
1815     $self->{get_smaller_font_size}->($self, $parent_cv->[1]),
1816     'px'];
1817     } else { ## 'smaller' relative to 'medium', initial of 'font-size'
1818     return ['DIMENSION', $self->{font_size}->[2], 'px'];
1819     }
1820     } else {
1821     return ['DIMENSION', $self->{font_size}->[{
1822     'xx-small' => 0,
1823     'x-small' => 1,
1824     small => 2,
1825     medium => 3,
1826     large => 4,
1827     'x-large' => 5,
1828     'xx-large' => 6,
1829     '-manakai-xxx-large' => 7,
1830     }->{$specified_value->[1]}], 'px'];
1831     }
1832     }
1833     }
1834    
1835     return $specified_value;
1836     },
1837     };
1838     $Attr->{font_size} = $Prop->{'font-size'};
1839     $Key->{font_size} = $Prop->{'font-size'};
1840    
1841 wakaba 1.19 my $compute_length = sub {
1842     my ($self, $element, $prop_name, $specified_value) = @_;
1843    
1844     if (defined $specified_value) {
1845     if ($specified_value->[0] eq 'DIMENSION') {
1846     my $unit = $specified_value->[2];
1847     my $value = $specified_value->[1];
1848    
1849     if ($unit eq 'em' or $unit eq 'ex') {
1850     $value *= 0.5 if $unit eq 'ex';
1851     ## TODO: Preferred way to determine the |ex| size is defined
1852     ## in CSS 2.1.
1853    
1854     $value *= $self->get_computed_value ($element, 'font-size')->[1];
1855     $unit = 'px';
1856     } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
1857     ($value *= 12, $unit = 'pc') if $unit eq 'pc';
1858     ($value /= 72, $unit = 'in') if $unit eq 'pt';
1859     ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
1860     ($value *= 10, $unit = 'mm') if $unit eq 'cm';
1861     ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
1862     }
1863    
1864     return ['DIMENSION', $value, $unit];
1865     }
1866     }
1867    
1868     return $specified_value;
1869     }; # $compute_length
1870    
1871 wakaba 1.23 $Prop->{'letter-spacing'} = {
1872     css => 'letter-spacing',
1873     dom => 'letter_spacing',
1874     key => 'letter_spacing',
1875     parse => sub {
1876     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1877    
1878 wakaba 1.24 ## NOTE: Used also for 'word-spacing', '-manakai-border-spacing-x',
1879     ## and '-manakai-border-spacing-y'.
1880 wakaba 1.23
1881     my $sign = 1;
1882     if ($t->{type} == MINUS_TOKEN) {
1883     $t = $tt->get_next_token;
1884     $sign = -1;
1885     }
1886     my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1887    
1888     if ($t->{type} == DIMENSION_TOKEN) {
1889     my $value = $t->{number} * $sign;
1890     my $unit = lc $t->{value}; ## TODO: case
1891     $t = $tt->get_next_token;
1892 wakaba 1.24 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
1893 wakaba 1.23 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1894     }
1895     } elsif ($t->{type} == NUMBER_TOKEN and
1896     ($self->{unitless_px} or $t->{number} == 0)) {
1897     my $value = $t->{number} * $sign;
1898     $t = $tt->get_next_token;
1899 wakaba 1.24 return ($t, {$prop_name => ['DIMENSION', $value, 'px']})
1900     if $allow_negative or $value >= 0;
1901 wakaba 1.23 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1902     my $value = lc $t->{value}; ## TODO: case
1903     $t = $tt->get_next_token;
1904 wakaba 1.24 if ($Prop->{$prop_name}->{keyword}->{$value}) {
1905 wakaba 1.23 return ($t, {$prop_name => ['KEYWORD', $value]});
1906     } elsif ($value eq 'inherit') {
1907     return ($t, {$prop_name => ['INHERIT']});
1908     }
1909     }
1910    
1911     $onerror->(type => 'syntax error:'.$prop_name,
1912     level => $self->{must_level},
1913     token => $t);
1914     return ($t, undef);
1915     },
1916 wakaba 1.24 allow_negative => 1,
1917     keyword => {normal => 1},
1918 wakaba 1.23 serialize => $default_serializer,
1919     initial => ['KEYWORD', 'normal'],
1920     inherited => 1,
1921     compute => $compute_length,
1922     };
1923     $Attr->{letter_spacing} = $Prop->{'letter-spacing'};
1924     $Key->{letter_spacing} = $Prop->{'letter-spacing'};
1925    
1926     $Prop->{'word-spacing'} = {
1927     css => 'word-spacing',
1928     dom => 'word_spacing',
1929     key => 'word_spacing',
1930     parse => $Prop->{'letter-spacing'}->{parse},
1931 wakaba 1.24 allow_negative => 1,
1932     keyword => {normal => 1},
1933 wakaba 1.23 serialize => $default_serializer,
1934     initial => ['KEYWORD', 'normal'],
1935     inherited => 1,
1936     compute => $compute_length,
1937     };
1938     $Attr->{word_spacing} = $Prop->{'word-spacing'};
1939     $Key->{word_spacing} = $Prop->{'word-spacing'};
1940    
1941 wakaba 1.24 $Prop->{'-manakai-border-spacing-x'} = {
1942     css => '-manakai-border-spacing-x',
1943     dom => '_manakai_border_spacing_x',
1944     key => 'border_spacing_x',
1945     parse => $Prop->{'letter-spacing'}->{parse},
1946     #allow_negative => 0,
1947     #keyword => {},
1948     serialize => $default_serializer,
1949 wakaba 1.25 serialize_multiple => sub {
1950     my $self = shift;
1951    
1952     local $Error::Depth = $Error::Depth + 1;
1953     my $x = $self->_manakai_border_spacing_x;
1954     my $y = $self->_manakai_border_spacing_y;
1955     my $xi = $self->get_property_priority ('-manakai-border-spacing-x');
1956     my $yi = $self->get_property_priority ('-manakai-border-spacing-y');
1957     $xi = ' !' . $xi if length $xi;
1958     $yi = ' !' . $yi if length $yi;
1959     if (defined $x) {
1960     if (defined $y) {
1961 wakaba 1.26 if ($xi eq $yi) {
1962 wakaba 1.25 if ($x eq $y) {
1963     return {'border-spacing' => $x . $xi};
1964     } else {
1965     return {'border-spacing' => $x . ' ' . $y . $xi};
1966     }
1967     } else {
1968     return {'-manakai-border-spacing-x' => $x . $xi,
1969     '-manakai-border-spacing-y' => $y . $yi};
1970     }
1971     } else {
1972     return {'-manakai-border-spacing-x' => $x . $xi};
1973     }
1974     } else {
1975     if (defined $y) {
1976     return {'-manakai-border-spacing-y' => $y . $yi};
1977     } else {
1978     return {};
1979     }
1980     }
1981     },
1982 wakaba 1.24 initial => ['DIMENSION', 0, 'px'],
1983     inherited => 1,
1984     compute => $compute_length,
1985     };
1986     $Attr->{_manakai_border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
1987     $Key->{border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
1988    
1989     $Prop->{'-manakai-border-spacing-y'} = {
1990     css => '-manakai-border-spacing-y',
1991     dom => '_manakai_border_spacing_y',
1992     key => 'border_spacing_y',
1993     parse => $Prop->{'letter-spacing'}->{parse},
1994     #allow_negative => 0,
1995     #keyword => {},
1996     serialize => $default_serializer,
1997 wakaba 1.25 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
1998     ->{serialize_multiple},
1999 wakaba 1.24 initial => ['DIMENSION', 0, 'px'],
2000     inherited => 1,
2001     compute => $compute_length,
2002     };
2003     $Attr->{_manakai_border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
2004     $Key->{border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
2005    
2006 wakaba 1.19 $Prop->{'margin-top'} = {
2007     css => 'margin-top',
2008     dom => 'margin_top',
2009     key => 'margin_top',
2010     parse => sub {
2011     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2012    
2013 wakaba 1.21 ## NOTE: Used for 'margin-top', 'margin-right', 'margin-bottom',
2014 wakaba 1.22 ## 'margin-left', 'top', 'right', 'bottom', 'left', 'padding-top',
2015     ## 'padding-right', 'padding-bottom', 'padding-left',
2016     ## 'border-top-width', 'border-right-width', 'border-bottom-width',
2017 wakaba 1.27 ## 'border-left-width', 'text-indent', 'background-position-x',
2018     ## and 'background-position-y'.
2019 wakaba 1.21
2020 wakaba 1.19 my $sign = 1;
2021     if ($t->{type} == MINUS_TOKEN) {
2022     $t = $tt->get_next_token;
2023     $sign = -1;
2024     }
2025 wakaba 1.22 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
2026 wakaba 1.19
2027     if ($t->{type} == DIMENSION_TOKEN) {
2028     my $value = $t->{number} * $sign;
2029     my $unit = lc $t->{value}; ## TODO: case
2030     $t = $tt->get_next_token;
2031 wakaba 1.22 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
2032 wakaba 1.19 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2033     }
2034     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2035     my $value = $t->{number} * $sign;
2036     $t = $tt->get_next_token;
2037 wakaba 1.22 return ($t, {$prop_name => ['PERCENTAGE', $value]})
2038     if $allow_negative or $value >= 0;
2039 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2040 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2041     my $value = $t->{number} * $sign;
2042     $t = $tt->get_next_token;
2043 wakaba 1.22 return ($t, {$prop_name => ['DIMENSION', $value, 'px']})
2044     if $allow_negative or $value >= 0;
2045 wakaba 1.19 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2046     my $value = lc $t->{value}; ## TODO: case
2047 wakaba 1.22 if ($Prop->{$prop_name}->{keyword}->{$value}) {
2048 wakaba 1.29 $t = $tt->get_next_token;
2049 wakaba 1.19 return ($t, {$prop_name => ['KEYWORD', $value]});
2050     } elsif ($value eq 'inherit') {
2051 wakaba 1.29 $t = $tt->get_next_token;
2052 wakaba 1.19 return ($t, {$prop_name => ['INHERIT']});
2053     }
2054 wakaba 1.29 ## NOTE: In the "else" case, don't procede the |$t| pointer
2055     ## for the support of 'border-top' property (and similar ones).
2056 wakaba 1.19 }
2057    
2058     $onerror->(type => 'syntax error:'.$prop_name,
2059     level => $self->{must_level},
2060     token => $t);
2061     return ($t, undef);
2062     },
2063 wakaba 1.22 allow_negative => 1,
2064     keyword => {auto => 1},
2065 wakaba 1.19 serialize => $default_serializer,
2066     initial => ['DIMENSION', 0, 'px'],
2067     #inherited => 0,
2068     compute => $compute_length,
2069     };
2070     $Attr->{margin_top} = $Prop->{'margin-top'};
2071     $Key->{margin_top} = $Prop->{'margin-top'};
2072    
2073     $Prop->{'margin-bottom'} = {
2074     css => 'margin-bottom',
2075     dom => 'margin_bottom',
2076     key => 'margin_bottom',
2077     parse => $Prop->{'margin-top'}->{parse},
2078 wakaba 1.22 allow_negative => 1,
2079     keyword => {auto => 1},
2080 wakaba 1.19 serialize => $default_serializer,
2081     initial => ['DIMENSION', 0, 'px'],
2082     #inherited => 0,
2083     compute => $compute_length,
2084     };
2085     $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
2086     $Key->{margin_bottom} = $Prop->{'margin-bottom'};
2087    
2088     $Prop->{'margin-right'} = {
2089     css => 'margin-right',
2090     dom => 'margin_right',
2091     key => 'margin_right',
2092     parse => $Prop->{'margin-top'}->{parse},
2093 wakaba 1.22 allow_negative => 1,
2094     keyword => {auto => 1},
2095 wakaba 1.19 serialize => $default_serializer,
2096     initial => ['DIMENSION', 0, 'px'],
2097     #inherited => 0,
2098     compute => $compute_length,
2099     };
2100     $Attr->{margin_right} = $Prop->{'margin-right'};
2101     $Key->{margin_right} = $Prop->{'margin-right'};
2102    
2103     $Prop->{'margin-left'} = {
2104     css => 'margin-left',
2105     dom => 'margin_left',
2106     key => 'margin_left',
2107     parse => $Prop->{'margin-top'}->{parse},
2108 wakaba 1.22 allow_negative => 1,
2109     keyword => {auto => 1},
2110 wakaba 1.19 serialize => $default_serializer,
2111     initial => ['DIMENSION', 0, 'px'],
2112     #inherited => 0,
2113     compute => $compute_length,
2114     };
2115     $Attr->{margin_left} = $Prop->{'margin-left'};
2116     $Key->{margin_left} = $Prop->{'margin-left'};
2117    
2118 wakaba 1.21 $Prop->{top} = {
2119     css => 'top',
2120     dom => 'top',
2121     key => 'top',
2122     parse => $Prop->{'margin-top'}->{parse},
2123 wakaba 1.22 allow_negative => 1,
2124     keyword => {auto => 1},
2125 wakaba 1.21 serialize => $default_serializer,
2126     initial => ['KEYWORD', 'auto'],
2127     #inherited => 0,
2128     compute_multiple => sub {
2129     my ($self, $element, $eid, $prop_name) = @_;
2130    
2131     my $pos_value = $self->get_computed_value ($element, 'position');
2132     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2133     if ($pos_value->[1] eq 'static') {
2134     $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
2135     $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
2136     return;
2137     } elsif ($pos_value->[1] eq 'relative') {
2138     my $top_specified = $self->get_specified_value_no_inherit
2139     ($element, 'top');
2140     if (defined $top_specified and
2141     ($top_specified->[0] eq 'DIMENSION' or
2142     $top_specified->[0] eq 'PERCENTAGE')) {
2143     my $tv = $self->{computed_value}->{$eid}->{top}
2144     = $compute_length->($self, $element, 'top', $top_specified);
2145     $self->{computed_value}->{$eid}->{bottom}
2146     = [$tv->[0], -$tv->[1], $tv->[2]];
2147     } else { # top: auto
2148     my $bottom_specified = $self->get_specified_value_no_inherit
2149     ($element, 'bottom');
2150     if (defined $bottom_specified and
2151     ($bottom_specified->[0] eq 'DIMENSION' or
2152     $bottom_specified->[0] eq 'PERCENTAGE')) {
2153     my $tv = $self->{computed_value}->{$eid}->{bottom}
2154     = $compute_length->($self, $element, 'bottom',
2155     $bottom_specified);
2156     $self->{computed_value}->{$eid}->{top}
2157     = [$tv->[0], -$tv->[1], $tv->[2]];
2158     } else { # bottom: auto
2159     $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
2160     $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
2161     }
2162     }
2163     return;
2164     }
2165     }
2166    
2167     my $top_specified = $self->get_specified_value_no_inherit
2168     ($element, 'top');
2169     $self->{computed_value}->{$eid}->{top}
2170     = $compute_length->($self, $element, 'top', $top_specified);
2171     my $bottom_specified = $self->get_specified_value_no_inherit
2172     ($element, 'bottom');
2173     $self->{computed_value}->{$eid}->{bottom}
2174     = $compute_length->($self, $element, 'bottom', $bottom_specified);
2175     },
2176     };
2177     $Attr->{top} = $Prop->{top};
2178     $Key->{top} = $Prop->{top};
2179    
2180     $Prop->{bottom} = {
2181     css => 'bottom',
2182     dom => 'bottom',
2183     key => 'bottom',
2184     parse => $Prop->{'margin-top'}->{parse},
2185 wakaba 1.22 allow_negative => 1,
2186     keyword => {auto => 1},
2187 wakaba 1.21 serialize => $default_serializer,
2188     initial => ['KEYWORD', 'auto'],
2189     #inherited => 0,
2190     compute_multiple => $Prop->{top}->{compute_multiple},
2191     };
2192     $Attr->{bottom} = $Prop->{bottom};
2193     $Key->{bottom} = $Prop->{bottom};
2194    
2195     $Prop->{left} = {
2196     css => 'left',
2197     dom => 'left',
2198     key => 'left',
2199     parse => $Prop->{'margin-top'}->{parse},
2200 wakaba 1.22 allow_negative => 1,
2201     keyword => {auto => 1},
2202 wakaba 1.21 serialize => $default_serializer,
2203     initial => ['KEYWORD', 'auto'],
2204     #inherited => 0,
2205     compute_multiple => sub {
2206     my ($self, $element, $eid, $prop_name) = @_;
2207    
2208     my $pos_value = $self->get_computed_value ($element, 'position');
2209     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2210     if ($pos_value->[1] eq 'static') {
2211     $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
2212     $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
2213     return;
2214     } elsif ($pos_value->[1] eq 'relative') {
2215     my $left_specified = $self->get_specified_value_no_inherit
2216     ($element, 'left');
2217     if (defined $left_specified and
2218     ($left_specified->[0] eq 'DIMENSION' or
2219     $left_specified->[0] eq 'PERCENTAGE')) {
2220     my $right_specified = $self->get_specified_value_no_inherit
2221     ($element, 'right');
2222     if (defined $right_specified and
2223     ($right_specified->[0] eq 'DIMENSION' or
2224     $right_specified->[0] eq 'PERCENTAGE')) {
2225     my $direction = $self->get_computed_value ($element, 'direction');
2226     if (defined $direction and $direction->[0] eq 'KEYWORD' and
2227     $direction->[0] eq 'ltr') {
2228     my $tv = $self->{computed_value}->{$eid}->{left}
2229     = $compute_length->($self, $element, 'left',
2230     $left_specified);
2231     $self->{computed_value}->{$eid}->{right}
2232     = [$tv->[0], -$tv->[1], $tv->[2]];
2233     } else {
2234     my $tv = $self->{computed_value}->{$eid}->{right}
2235     = $compute_length->($self, $element, 'right',
2236     $right_specified);
2237     $self->{computed_value}->{$eid}->{left}
2238     = [$tv->[0], -$tv->[1], $tv->[2]];
2239     }
2240     } else {
2241     my $tv = $self->{computed_value}->{$eid}->{left}
2242     = $compute_length->($self, $element, 'left', $left_specified);
2243     $self->{computed_value}->{$eid}->{right}
2244     = [$tv->[0], -$tv->[1], $tv->[2]];
2245     }
2246     } else { # left: auto
2247     my $right_specified = $self->get_specified_value_no_inherit
2248     ($element, 'right');
2249     if (defined $right_specified and
2250     ($right_specified->[0] eq 'DIMENSION' or
2251     $right_specified->[0] eq 'PERCENTAGE')) {
2252     my $tv = $self->{computed_value}->{$eid}->{right}
2253     = $compute_length->($self, $element, 'right',
2254     $right_specified);
2255     $self->{computed_value}->{$eid}->{left}
2256     = [$tv->[0], -$tv->[1], $tv->[2]];
2257     } else { # right: auto
2258     $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
2259     $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
2260     }
2261     }
2262     return;
2263     }
2264     }
2265    
2266     my $left_specified = $self->get_specified_value_no_inherit
2267     ($element, 'left');
2268     $self->{computed_value}->{$eid}->{left}
2269     = $compute_length->($self, $element, 'left', $left_specified);
2270     my $right_specified = $self->get_specified_value_no_inherit
2271     ($element, 'right');
2272     $self->{computed_value}->{$eid}->{right}
2273     = $compute_length->($self, $element, 'right', $right_specified);
2274     },
2275     };
2276     $Attr->{left} = $Prop->{left};
2277     $Key->{left} = $Prop->{left};
2278    
2279     $Prop->{right} = {
2280     css => 'right',
2281     dom => 'right',
2282     key => 'right',
2283     parse => $Prop->{'margin-top'}->{parse},
2284     serialize => $default_serializer,
2285     initial => ['KEYWORD', 'auto'],
2286     #inherited => 0,
2287     compute_multiple => $Prop->{left}->{compute_multiple},
2288     };
2289     $Attr->{right} = $Prop->{right};
2290     $Key->{right} = $Prop->{right};
2291    
2292 wakaba 1.22 $Prop->{width} = {
2293     css => 'width',
2294     dom => 'width',
2295     key => 'width',
2296     parse => $Prop->{'margin-top'}->{parse},
2297     #allow_negative => 0,
2298     keyword => {auto => 1},
2299     serialize => $default_serializer,
2300     initial => ['KEYWORD', 'auto'],
2301     #inherited => 0,
2302     compute => $compute_length,
2303     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/width> for
2304     ## browser compatibility issues.
2305     };
2306     $Attr->{width} = $Prop->{width};
2307     $Key->{width} = $Prop->{width};
2308    
2309     $Prop->{'min-width'} = {
2310     css => 'min-width',
2311     dom => 'min_width',
2312     key => 'min_width',
2313     parse => $Prop->{'margin-top'}->{parse},
2314     #allow_negative => 0,
2315     #keyword => {},
2316     serialize => $default_serializer,
2317     initial => ['DIMENSION', 0, 'px'],
2318     #inherited => 0,
2319     compute => $compute_length,
2320     };
2321     $Attr->{min_width} = $Prop->{'min-width'};
2322     $Key->{min_width} = $Prop->{'min-width'};
2323    
2324     $Prop->{'max-width'} = {
2325     css => 'max-width',
2326     dom => 'max_width',
2327     key => 'max_width',
2328     parse => $Prop->{'margin-top'}->{parse},
2329     #allow_negative => 0,
2330     keyword => {none => 1},
2331     serialize => $default_serializer,
2332     initial => ['KEYWORD', 'none'],
2333     #inherited => 0,
2334     compute => $compute_length,
2335     };
2336     $Attr->{max_width} = $Prop->{'max-width'};
2337     $Key->{max_width} = $Prop->{'max-width'};
2338    
2339     $Prop->{height} = {
2340     css => 'height',
2341     dom => 'height',
2342     key => 'height',
2343     parse => $Prop->{'margin-top'}->{parse},
2344     #allow_negative => 0,
2345     keyword => {auto => 1},
2346     serialize => $default_serializer,
2347     initial => ['KEYWORD', 'auto'],
2348     #inherited => 0,
2349     compute => $compute_length,
2350     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/height> for
2351     ## browser compatibility issues.
2352     };
2353     $Attr->{height} = $Prop->{height};
2354     $Key->{height} = $Prop->{height};
2355    
2356     $Prop->{'min-height'} = {
2357     css => 'min-height',
2358     dom => 'min_height',
2359     key => 'min_height',
2360     parse => $Prop->{'margin-top'}->{parse},
2361     #allow_negative => 0,
2362     #keyword => {},
2363     serialize => $default_serializer,
2364     initial => ['DIMENSION', 0, 'px'],
2365     #inherited => 0,
2366     compute => $compute_length,
2367     };
2368     $Attr->{min_height} = $Prop->{'min-height'};
2369     $Key->{min_height} = $Prop->{'min-height'};
2370    
2371     $Prop->{'max-height'} = {
2372     css => 'max-height',
2373     dom => 'max_height',
2374     key => 'max_height',
2375     parse => $Prop->{'margin-top'}->{parse},
2376     #allow_negative => 0,
2377     keyword => {none => 1},
2378     serialize => $default_serializer,
2379     initial => ['KEYWORD', 'none'],
2380     #inherited => 0,
2381     compute => $compute_length,
2382     };
2383     $Attr->{max_height} = $Prop->{'max-height'};
2384     $Key->{max_height} = $Prop->{'max-height'};
2385    
2386     $Prop->{'line-height'} = {
2387     css => 'line-height',
2388     dom => 'line_height',
2389     key => 'line_height',
2390 wakaba 1.19 parse => sub {
2391     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2392    
2393 wakaba 1.22 ## NOTE: Similar to 'margin-top', but different handling
2394     ## for unitless numbers.
2395    
2396 wakaba 1.19 my $sign = 1;
2397     if ($t->{type} == MINUS_TOKEN) {
2398     $t = $tt->get_next_token;
2399     $sign = -1;
2400     }
2401 wakaba 1.22 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
2402 wakaba 1.19
2403     if ($t->{type} == DIMENSION_TOKEN) {
2404     my $value = $t->{number} * $sign;
2405     my $unit = lc $t->{value}; ## TODO: case
2406     $t = $tt->get_next_token;
2407     if ($length_unit->{$unit} and $value >= 0) {
2408     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2409     }
2410     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2411     my $value = $t->{number} * $sign;
2412     $t = $tt->get_next_token;
2413 wakaba 1.22 return ($t, {$prop_name => ['PERCENTAGE', $value]})
2414     if $value >= 0;
2415     } elsif ($t->{type} == NUMBER_TOKEN) {
2416 wakaba 1.19 my $value = $t->{number} * $sign;
2417     $t = $tt->get_next_token;
2418 wakaba 1.22 return ($t, {$prop_name => ['NUMBER', $value]}) if $value >= 0;
2419 wakaba 1.19 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2420     my $value = lc $t->{value}; ## TODO: case
2421     $t = $tt->get_next_token;
2422 wakaba 1.22 if ($value eq 'normal') {
2423     return ($t, {$prop_name => ['KEYWORD', $value]});
2424     } elsif ($value eq 'inherit') {
2425 wakaba 1.19 return ($t, {$prop_name => ['INHERIT']});
2426     }
2427     }
2428    
2429     $onerror->(type => 'syntax error:'.$prop_name,
2430     level => $self->{must_level},
2431     token => $t);
2432     return ($t, undef);
2433     },
2434     serialize => $default_serializer,
2435 wakaba 1.22 initial => ['KEYWORD', 'normal'],
2436     inherited => 1,
2437     compute => $compute_length,
2438     };
2439     $Attr->{line_height} = $Prop->{'line-height'};
2440     $Key->{line_height} = $Prop->{'line-height'};
2441    
2442     $Prop->{'vertical-align'} = {
2443     css => 'vertical-align',
2444     dom => 'vertical_align',
2445     key => 'vertical_align',
2446     parse => $Prop->{'margin-top'}->{parse},
2447     allow_negative => 1,
2448     keyword => {
2449     baseline => 1, sub => 1, super => 1, top => 1, 'text-top' => 1,
2450     middle => 1, bottom => 1, 'text-bottom' => 1,
2451     },
2452     ## NOTE: Currently, we don't support option to select subset of keywords
2453     ## supported by application (i.e.
2454     ## $parser->{prop_value}->{'line-height'->{$keyword}). Should we support
2455     ## it?
2456     serialize => $default_serializer,
2457     initial => ['KEYWORD', 'baseline'],
2458     #inherited => 0,
2459     compute => $compute_length,
2460     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/vertical-align> for
2461     ## browser compatibility issues.
2462     };
2463     $Attr->{vertical_align} = $Prop->{'vertical-align'};
2464     $Key->{vertical_align} = $Prop->{'vertical-align'};
2465    
2466 wakaba 1.23 $Prop->{'text-indent'} = {
2467     css => 'text-indent',
2468     dom => 'text_indent',
2469     key => 'text_indent',
2470     parse => $Prop->{'margin-top'}->{parse},
2471     allow_negative => 1,
2472     keyword => {},
2473     serialize => $default_serializer,
2474     initial => ['DIMENSION', 0, 'px'],
2475     inherited => 1,
2476     compute => $compute_length,
2477     };
2478     $Attr->{text_indent} = $Prop->{'text-indent'};
2479     $Key->{text_indent} = $Prop->{'text-indent'};
2480    
2481 wakaba 1.27 $Prop->{'background-position-x'} = {
2482     css => 'background-position-x',
2483     dom => 'background_position_x',
2484     key => 'background_position_x',
2485     parse => $Prop->{'margin-top'}->{parse},
2486     allow_negative => 1,
2487     keyword => {left => 1, center => 1, right => 1},
2488     serialize => $default_serializer,
2489     initial => ['PERCENTAGE', 0],
2490     #inherited => 0,
2491     compute => sub {
2492     my ($self, $element, $prop_name, $specified_value) = @_;
2493    
2494     if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
2495     my $v = {
2496     left => 0, center => 50, right => 100, top => 0, bottom => 100,
2497     }->{$specified_value->[1]};
2498     if (defined $v) {
2499     return ['PERCENTAGE', $v];
2500     } else {
2501     return $specified_value;
2502     }
2503     } else {
2504     return $compute_length->(@_);
2505     }
2506     },
2507     serialize_multiple => sub {
2508     my $self = shift;
2509    
2510     local $Error::Depth = $Error::Depth + 1;
2511     my $x = $self->background_position_x;
2512     my $y = $self->background_position_y;
2513     my $xi = $self->get_property_priority ('background-position-x');
2514     my $yi = $self->get_property_priority ('background-position-y');
2515     $xi = ' !' . $xi if length $xi;
2516     $yi = ' !' . $yi if length $yi;
2517     if (defined $x) {
2518     if (defined $y) {
2519     if ($xi eq $yi) {
2520     return {'background-position' => $x . ' ' . $y . $xi};
2521     } else {
2522     return {'background-position-x' => $x . $xi,
2523     'background-position-y' => $y . $yi};
2524     }
2525     } else {
2526     return {'background-position-x' => $x . $xi};
2527     }
2528     } else {
2529     if (defined $y) {
2530     return {'background-position-y' => $y . $yi};
2531     } else {
2532     return {};
2533     }
2534     }
2535     },
2536     };
2537     $Attr->{background_position_x} = $Prop->{'background-position-x'};
2538     $Key->{background_position_x} = $Prop->{'background-position-x'};
2539    
2540     $Prop->{'background-position-y'} = {
2541     css => 'background-position-y',
2542     dom => 'background_position_y',
2543     key => 'background_position_y',
2544     parse => $Prop->{'margin-top'}->{parse},
2545     allow_negative => 1,
2546     keyword => {top => 1, center => 1, bottom => 1},
2547     serialize => $default_serializer,
2548     serialize_multiple => $Prop->{'background-position-x'}->{serialize_multiple},
2549     initial => ['PERCENTAGE', 0],
2550     #inherited => 0,
2551     compute => $Prop->{'background-position-x'}->{compute},
2552     };
2553     $Attr->{background_position_y} = $Prop->{'background-position-y'};
2554     $Key->{background_position_y} = $Prop->{'background-position-y'};
2555    
2556 wakaba 1.22 $Prop->{'padding-top'} = {
2557     css => 'padding-top',
2558     dom => 'padding_top',
2559     key => 'padding_top',
2560     parse => $Prop->{'margin-top'}->{parse},
2561     #allow_negative => 0,
2562     #keyword => {},
2563     serialize => $default_serializer,
2564 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
2565     #inherited => 0,
2566     compute => $compute_length,
2567     };
2568     $Attr->{padding_top} = $Prop->{'padding-top'};
2569     $Key->{padding_top} = $Prop->{'padding-top'};
2570    
2571     $Prop->{'padding-bottom'} = {
2572     css => 'padding-bottom',
2573     dom => 'padding_bottom',
2574     key => 'padding_bottom',
2575     parse => $Prop->{'padding-top'}->{parse},
2576 wakaba 1.22 #allow_negative => 0,
2577     #keyword => {},
2578 wakaba 1.19 serialize => $default_serializer,
2579     initial => ['DIMENSION', 0, 'px'],
2580     #inherited => 0,
2581     compute => $compute_length,
2582     };
2583     $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
2584     $Key->{padding_bottom} = $Prop->{'padding-bottom'};
2585    
2586     $Prop->{'padding-right'} = {
2587     css => 'padding-right',
2588     dom => 'padding_right',
2589     key => 'padding_right',
2590     parse => $Prop->{'padding-top'}->{parse},
2591 wakaba 1.22 #allow_negative => 0,
2592     #keyword => {},
2593 wakaba 1.19 serialize => $default_serializer,
2594     initial => ['DIMENSION', 0, 'px'],
2595     #inherited => 0,
2596     compute => $compute_length,
2597     };
2598     $Attr->{padding_right} = $Prop->{'padding-right'};
2599     $Key->{padding_right} = $Prop->{'padding-right'};
2600    
2601     $Prop->{'padding-left'} = {
2602     css => 'padding-left',
2603     dom => 'padding_left',
2604     key => 'padding_left',
2605     parse => $Prop->{'padding-top'}->{parse},
2606 wakaba 1.22 #allow_negative => 0,
2607     #keyword => {},
2608 wakaba 1.19 serialize => $default_serializer,
2609     initial => ['DIMENSION', 0, 'px'],
2610     #inherited => 0,
2611     compute => $compute_length,
2612     };
2613     $Attr->{padding_left} = $Prop->{'padding-left'};
2614     $Key->{padding_left} = $Prop->{'padding-left'};
2615    
2616 wakaba 1.20 $Prop->{'border-top-width'} = {
2617     css => 'border-top-width',
2618     dom => 'border_top_width',
2619     key => 'border_top_width',
2620 wakaba 1.22 parse => $Prop->{'margin-top'}->{parse},
2621     #allow_negative => 0,
2622     keyword => {thin => 1, medium => 1, thick => 1},
2623 wakaba 1.20 serialize => $default_serializer,
2624 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
2625 wakaba 1.20 initial => ['KEYWORD', 'medium'],
2626     #inherited => 0,
2627     compute => sub {
2628     my ($self, $element, $prop_name, $specified_value) = @_;
2629    
2630 wakaba 1.23 ## NOTE: Used for 'border-top-width', 'border-right-width',
2631     ## 'border-bottom-width', 'border-right-width', and
2632     ## 'outline-width'.
2633    
2634 wakaba 1.20 my $style_prop = $prop_name;
2635     $style_prop =~ s/width/style/;
2636     my $style = $self->get_computed_value ($element, $style_prop);
2637     if (defined $style and $style->[0] eq 'KEYWORD' and
2638     ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
2639     return ['DIMENSION', 0, 'px'];
2640     }
2641    
2642     my $value = $compute_length->(@_);
2643     if (defined $value and $value->[0] eq 'KEYWORD') {
2644     if ($value->[1] eq 'thin') {
2645     return ['DIMENSION', 1, 'px']; ## Firefox/Opera
2646     } elsif ($value->[1] eq 'medium') {
2647     return ['DIMENSION', 3, 'px']; ## Firefox/Opera
2648     } elsif ($value->[1] eq 'thick') {
2649     return ['DIMENSION', 5, 'px']; ## Firefox
2650     }
2651     }
2652     return $value;
2653     },
2654 wakaba 1.23 ## NOTE: CSS3 will allow <percentage> as an option in <border-width>.
2655     ## Opera 9 has already implemented it.
2656 wakaba 1.20 };
2657     $Attr->{border_top_width} = $Prop->{'border-top-width'};
2658     $Key->{border_top_width} = $Prop->{'border-top-width'};
2659    
2660     $Prop->{'border-right-width'} = {
2661     css => 'border-right-width',
2662     dom => 'border_right_width',
2663     key => 'border_right_width',
2664     parse => $Prop->{'border-top-width'}->{parse},
2665 wakaba 1.22 #allow_negative => 0,
2666     keyword => {thin => 1, medium => 1, thick => 1},
2667 wakaba 1.20 serialize => $default_serializer,
2668 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
2669 wakaba 1.20 initial => ['KEYWORD', 'medium'],
2670     #inherited => 0,
2671     compute => $Prop->{'border-top-width'}->{compute},
2672     };
2673     $Attr->{border_right_width} = $Prop->{'border-right-width'};
2674     $Key->{border_right_width} = $Prop->{'border-right-width'};
2675    
2676     $Prop->{'border-bottom-width'} = {
2677     css => 'border-bottom-width',
2678     dom => 'border_bottom_width',
2679     key => 'border_bottom_width',
2680     parse => $Prop->{'border-top-width'}->{parse},
2681 wakaba 1.22 #allow_negative => 0,
2682     keyword => {thin => 1, medium => 1, thick => 1},
2683 wakaba 1.20 serialize => $default_serializer,
2684 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
2685 wakaba 1.20 initial => ['KEYWORD', 'medium'],
2686     #inherited => 0,
2687     compute => $Prop->{'border-top-width'}->{compute},
2688     };
2689     $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
2690     $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
2691    
2692     $Prop->{'border-left-width'} = {
2693     css => 'border-left-width',
2694     dom => 'border_left_width',
2695     key => 'border_left_width',
2696     parse => $Prop->{'border-top-width'}->{parse},
2697 wakaba 1.22 #allow_negative => 0,
2698     keyword => {thin => 1, medium => 1, thick => 1},
2699 wakaba 1.20 serialize => $default_serializer,
2700 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
2701 wakaba 1.20 initial => ['KEYWORD', 'medium'],
2702     #inherited => 0,
2703     compute => $Prop->{'border-top-width'}->{compute},
2704     };
2705     $Attr->{border_left_width} = $Prop->{'border-left-width'};
2706     $Key->{border_left_width} = $Prop->{'border-left-width'};
2707    
2708 wakaba 1.23 $Prop->{'outline-width'} = {
2709     css => 'outline-width',
2710     dom => 'outline_width',
2711     key => 'outline_width',
2712     parse => $Prop->{'border-top-width'}->{parse},
2713     #allow_negative => 0,
2714     keyword => {thin => 1, medium => 1, thick => 1},
2715     serialize => $default_serializer,
2716 wakaba 1.29 serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
2717 wakaba 1.23 initial => ['KEYWORD', 'medium'],
2718     #inherited => 0,
2719     compute => $Prop->{'border-top-width'}->{compute},
2720     };
2721     $Attr->{outline_width} = $Prop->{'outline-width'};
2722     $Key->{outline_width} = $Prop->{'outline-width'};
2723    
2724 wakaba 1.15 $Prop->{'font-weight'} = {
2725     css => 'font-weight',
2726     dom => 'font_weight',
2727     key => 'font_weight',
2728     parse => sub {
2729     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2730    
2731     if ($t->{type} == NUMBER_TOKEN) {
2732     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
2733     ## browser compatibility issue.
2734     my $value = $t->{number};
2735     $t = $tt->get_next_token;
2736     if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
2737     return ($t, {$prop_name => ['WEIGHT', $value, 0]});
2738     }
2739     } elsif ($t->{type} == IDENT_TOKEN) {
2740     my $value = lc $t->{value}; ## TODO: case
2741     $t = $tt->get_next_token;
2742     if ({
2743     normal => 1, bold => 1, bolder => 1, lighter => 1,
2744     }->{$value}) {
2745     return ($t, {$prop_name => ['KEYWORD', $value]});
2746     } elsif ($value eq 'inherit') {
2747     return ($t, {$prop_name => ['INHERIT']});
2748     }
2749     }
2750    
2751     $onerror->(type => 'syntax error:'.$prop_name,
2752     level => $self->{must_level},
2753     token => $t);
2754     return ($t, undef);
2755     },
2756     serialize => $default_serializer,
2757     initial => ['KEYWORD', 'normal'],
2758     inherited => 1,
2759     compute => sub {
2760     my ($self, $element, $prop_name, $specified_value) = @_;
2761    
2762 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
2763 wakaba 1.15 if ($specified_value->[1] eq 'normal') {
2764     return ['WEIGHT', 400, 0];
2765     } elsif ($specified_value->[1] eq 'bold') {
2766     return ['WEIGHT', 700, 0];
2767     } elsif ($specified_value->[1] eq 'bolder') {
2768     my $parent_element = $element->manakai_parent_element;
2769     if (defined $parent_element) {
2770     my $parent_value = $self->get_cascaded_value
2771     ($parent_element, $prop_name); ## NOTE: What Firefox does.
2772     return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
2773     } else {
2774     return ['WEIGHT', 400, 1];
2775     }
2776     } elsif ($specified_value->[1] eq 'lighter') {
2777     my $parent_element = $element->manakai_parent_element;
2778     if (defined $parent_element) {
2779     my $parent_value = $self->get_cascaded_value
2780     ($parent_element, $prop_name); ## NOTE: What Firefox does.
2781     return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
2782     } else {
2783     return ['WEIGHT', 400, 1];
2784     }
2785     }
2786 wakaba 1.17 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
2787 wakaba 1.15 #
2788     }
2789    
2790     return $specified_value;
2791     },
2792     };
2793     $Attr->{font_weight} = $Prop->{'font-weight'};
2794     $Key->{font_weight} = $Prop->{'font-weight'};
2795    
2796 wakaba 1.13 my $uri_or_none_parser = sub {
2797 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2798    
2799 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
2800 wakaba 1.11 my $value = $t->{value};
2801     $t = $tt->get_next_token;
2802     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2803     } elsif ($t->{type} == IDENT_TOKEN) {
2804     my $value = lc $t->{value}; ## TODO: case
2805     $t = $tt->get_next_token;
2806     if ($value eq 'none') {
2807     ## NOTE: |none| is the default value and therefore it must be
2808     ## supported anyway.
2809     return ($t, {$prop_name => ["KEYWORD", 'none']});
2810     } elsif ($value eq 'inherit') {
2811     return ($t, {$prop_name => ['INHERIT']});
2812     }
2813     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
2814     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
2815     # my $value = $t->{value};
2816     # $t = $tt->get_next_token;
2817     # if ($t->{type} == EOF_TOKEN) {
2818     # $onerror->(type => 'syntax error:eof:'.$prop_name,
2819     # level => $self->{must_level},
2820     # token => $t);
2821     #
2822     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2823     # }
2824     }
2825    
2826     $onerror->(type => 'syntax error:'.$prop_name,
2827     level => $self->{must_level},
2828     token => $t);
2829     return ($t, undef);
2830 wakaba 1.13 }; # $uri_or_none_parser
2831    
2832 wakaba 1.14 my $compute_uri_or_none = sub {
2833 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
2834    
2835     if (defined $specified_value and
2836     $specified_value->[0] eq 'URI' and
2837     defined $specified_value->[2]) {
2838     require Message::DOM::DOMImplementation;
2839     return ['URI',
2840     Message::DOM::DOMImplementation->create_uri_reference
2841     ($specified_value->[1])
2842     ->get_absolute_reference (${$specified_value->[2]})
2843     ->get_uri_reference,
2844     $specified_value->[2]];
2845     }
2846    
2847     return $specified_value;
2848 wakaba 1.14 }; # $compute_uri_or_none
2849    
2850     $Prop->{'list-style-image'} = {
2851     css => 'list-style-image',
2852     dom => 'list_style_image',
2853     key => 'list_style_image',
2854     parse => $uri_or_none_parser,
2855     serialize => $default_serializer,
2856     initial => ['KEYWORD', 'none'],
2857     inherited => 1,
2858     compute => $compute_uri_or_none,
2859 wakaba 1.11 };
2860     $Attr->{list_style_image} = $Prop->{'list-style-image'};
2861     $Key->{list_style_image} = $Prop->{'list-style-image'};
2862    
2863 wakaba 1.15 $Prop->{'background-image'} = {
2864     css => 'background-image',
2865     dom => 'background_image',
2866     key => 'background_image',
2867     parse => $uri_or_none_parser,
2868     serialize => $default_serializer,
2869     initial => ['KEYWORD', 'none'],
2870     #inherited => 0,
2871     compute => $compute_uri_or_none,
2872     };
2873     $Attr->{background_image} = $Prop->{'background-image'};
2874     $Key->{background_image} = $Prop->{'background-image'};
2875    
2876 wakaba 1.7 my $border_style_keyword = {
2877     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
2878     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
2879     };
2880    
2881     $Prop->{'border-top-style'} = {
2882     css => 'border-top-style',
2883     dom => 'border_top_style',
2884     key => 'border_top_style',
2885     parse => $one_keyword_parser,
2886 wakaba 1.11 serialize => $default_serializer,
2887 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
2888 wakaba 1.7 keyword => $border_style_keyword,
2889 wakaba 1.9 initial => ["KEYWORD", "none"],
2890     #inherited => 0,
2891     compute => $compute_as_specified,
2892 wakaba 1.7 };
2893     $Attr->{border_top_style} = $Prop->{'border-top-style'};
2894     $Key->{border_top_style} = $Prop->{'border-top-style'};
2895    
2896     $Prop->{'border-right-style'} = {
2897     css => 'border-right-style',
2898     dom => 'border_right_style',
2899     key => 'border_right_style',
2900     parse => $one_keyword_parser,
2901 wakaba 1.11 serialize => $default_serializer,
2902 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
2903 wakaba 1.7 keyword => $border_style_keyword,
2904 wakaba 1.9 initial => ["KEYWORD", "none"],
2905     #inherited => 0,
2906     compute => $compute_as_specified,
2907 wakaba 1.7 };
2908     $Attr->{border_right_style} = $Prop->{'border-right-style'};
2909     $Key->{border_right_style} = $Prop->{'border-right-style'};
2910    
2911     $Prop->{'border-bottom-style'} = {
2912     css => 'border-bottom-style',
2913     dom => 'border_bottom_style',
2914     key => 'border_bottom_style',
2915     parse => $one_keyword_parser,
2916 wakaba 1.11 serialize => $default_serializer,
2917 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
2918 wakaba 1.7 keyword => $border_style_keyword,
2919 wakaba 1.9 initial => ["KEYWORD", "none"],
2920     #inherited => 0,
2921     compute => $compute_as_specified,
2922 wakaba 1.7 };
2923     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
2924     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
2925    
2926     $Prop->{'border-left-style'} = {
2927     css => 'border-left-style',
2928     dom => 'border_left_style',
2929     key => 'border_left_style',
2930     parse => $one_keyword_parser,
2931 wakaba 1.11 serialize => $default_serializer,
2932 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
2933 wakaba 1.7 keyword => $border_style_keyword,
2934 wakaba 1.9 initial => ["KEYWORD", "none"],
2935     #inherited => 0,
2936     compute => $compute_as_specified,
2937 wakaba 1.7 };
2938     $Attr->{border_left_style} = $Prop->{'border-left-style'};
2939     $Key->{border_left_style} = $Prop->{'border-left-style'};
2940    
2941 wakaba 1.16 $Prop->{'outline-style'} = {
2942     css => 'outline-style',
2943     dom => 'outline_style',
2944     key => 'outline_style',
2945     parse => $one_keyword_parser,
2946     serialize => $default_serializer,
2947 wakaba 1.29 serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
2948 wakaba 1.23 keyword => {%$border_style_keyword},
2949 wakaba 1.16 initial => ['KEYWORD', 'none'],
2950     #inherited => 0,
2951     compute => $compute_as_specified,
2952     };
2953     $Attr->{outline_style} = $Prop->{'outline-style'};
2954     $Key->{outline_style} = $Prop->{'outline-style'};
2955 wakaba 1.23 delete $Prop->{'outline-style'}->{keyword}->{hidden};
2956 wakaba 1.16
2957 wakaba 1.15 $Prop->{'font-family'} = {
2958     css => 'font-family',
2959     dom => 'font_family',
2960     key => 'font_family',
2961     parse => sub {
2962     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2963    
2964     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
2965     ## how chaotic browsers are!
2966    
2967     my @prop_value;
2968    
2969     my $font_name = '';
2970     my $may_be_generic = 1;
2971     my $may_be_inherit = 1;
2972     my $has_s = 0;
2973     F: {
2974     if ($t->{type} == IDENT_TOKEN) {
2975     undef $may_be_inherit if $has_s or length $font_name;
2976     undef $may_be_generic if $has_s or length $font_name;
2977     $font_name .= ' ' if $has_s;
2978     $font_name .= $t->{value};
2979     undef $has_s;
2980     $t = $tt->get_next_token;
2981     } elsif ($t->{type} == STRING_TOKEN) {
2982     $font_name .= ' ' if $has_s;
2983     $font_name .= $t->{value};
2984     undef $may_be_inherit;
2985     undef $may_be_generic;
2986     undef $has_s;
2987     $t = $tt->get_next_token;
2988     } elsif ($t->{type} == COMMA_TOKEN) {
2989     if ($may_be_generic and
2990     {
2991     serif => 1, 'sans-serif' => 1, cursive => 1,
2992     fantasy => 1, monospace => 1, '-manakai-default' => 1,
2993     }->{lc $font_name}) { ## TODO: case
2994     push @prop_value, ['KEYWORD', $font_name];
2995     } elsif (not $may_be_generic or length $font_name) {
2996     push @prop_value, ["STRING", $font_name];
2997     }
2998     undef $may_be_inherit;
2999     $may_be_generic = 1;
3000     undef $has_s;
3001     $font_name = '';
3002     $t = $tt->get_next_token;
3003     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3004     } elsif ($t->{type} == S_TOKEN) {
3005     $has_s = 1;
3006     $t = $tt->get_next_token;
3007     } else {
3008     if ($may_be_generic and
3009     {
3010     serif => 1, 'sans-serif' => 1, cursive => 1,
3011     fantasy => 1, monospace => 1, '-manakai-default' => 1,
3012     }->{lc $font_name}) { ## TODO: case
3013     push @prop_value, ['KEYWORD', $font_name];
3014     } elsif (not $may_be_generic or length $font_name) {
3015     push @prop_value, ['STRING', $font_name];
3016     } else {
3017     $onerror->(type => 'syntax error:'.$prop_name,
3018     level => $self->{must_level},
3019     token => $t);
3020     return ($t, undef);
3021     }
3022     last F;
3023     }
3024     redo F;
3025     } # F
3026    
3027     if ($may_be_inherit and
3028     @prop_value == 1 and
3029     $prop_value[0]->[0] eq 'STRING' and
3030     lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
3031     return ($t, {$prop_name => ['INHERIT']});
3032     } else {
3033     unshift @prop_value, 'FONT';
3034     return ($t, {$prop_name => \@prop_value});
3035     }
3036     },
3037     serialize => sub {
3038     my ($self, $prop_name, $value) = @_;
3039    
3040     if ($value->[0] eq 'FONT') {
3041     return join ', ', map {
3042     if ($_->[0] eq 'STRING') {
3043     '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
3044     } elsif ($_->[0] eq 'KEYWORD') {
3045     $_->[1]; ## NOTE: This is what Firefox does.
3046     } else {
3047     ## NOTE: This should be an error.
3048     '""';
3049     }
3050     } @$value[1..$#$value];
3051     } elsif ($value->[0] eq 'INHERIT') {
3052     return 'inherit';
3053     } else {
3054     return undef;
3055     }
3056     },
3057     initial => ['FONT', ['KEYWORD', '-manakai-default']],
3058     inherited => 1,
3059     compute => $compute_as_specified,
3060     };
3061     $Attr->{font_family} = $Prop->{'font-family'};
3062     $Key->{font_family} = $Prop->{'font-family'};
3063    
3064 wakaba 1.17 $Prop->{cursor} = {
3065     css => 'cursor',
3066     dom => 'cursor',
3067     key => 'cursor',
3068     parse => sub {
3069     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3070    
3071     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
3072     ## compatibility issues.
3073    
3074     my @prop_value = ('CURSOR');
3075    
3076     F: {
3077     if ($t->{type} == IDENT_TOKEN) {
3078     my $v = lc $t->{value}; ## TODO: case
3079     $t = $tt->get_next_token;
3080     if ($Prop->{$prop_name}->{keyword}->{$v}) {
3081     push @prop_value, ['KEYWORD', $v];
3082     last F;
3083     } elsif ($v eq 'inherit' and @prop_value == 1) {
3084     return ($t, {$prop_name => ['INHERIT']});
3085     } else {
3086     $onerror->(type => 'syntax error:'.$prop_name,
3087     level => $self->{must_level},
3088     token => $t);
3089     return ($t, undef);
3090     }
3091     } elsif ($t->{type} == URI_TOKEN) {
3092     push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
3093     $t = $tt->get_next_token;
3094     } else {
3095     $onerror->(type => 'syntax error:'.$prop_name,
3096     level => $self->{must_level},
3097     token => $t);
3098     return ($t, undef);
3099     }
3100    
3101     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3102     if ($t->{type} == COMMA_TOKEN) {
3103     $t = $tt->get_next_token;
3104     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3105     redo F;
3106     }
3107     } # F
3108    
3109     return ($t, {$prop_name => \@prop_value});
3110     },
3111     serialize => sub {
3112     my ($self, $prop_name, $value) = @_;
3113    
3114     if ($value->[0] eq 'CURSOR') {
3115     return join ', ', map {
3116     if ($_->[0] eq 'URI') {
3117     'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
3118     } elsif ($_->[0] eq 'KEYWORD') {
3119     $_->[1];
3120     } else {
3121     ## NOTE: This should be an error.
3122     '""';
3123     }
3124     } @$value[1..$#$value];
3125     } elsif ($value->[0] eq 'INHERIT') {
3126     return 'inherit';
3127     } else {
3128     return undef;
3129     }
3130     },
3131     keyword => {
3132     auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
3133     'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
3134     'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
3135     'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
3136     },
3137     initial => ['CURSOR', ['KEYWORD', 'auto']],
3138     inherited => 1,
3139     compute => sub {
3140     my ($self, $element, $prop_name, $specified_value) = @_;
3141    
3142     if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
3143     my @new_value = ('CURSOR');
3144     for my $value (@$specified_value[1..$#$specified_value]) {
3145     if ($value->[0] eq 'URI') {
3146     if (defined $value->[2]) {
3147     require Message::DOM::DOMImplementation;
3148     push @new_value, ['URI',
3149     Message::DOM::DOMImplementation
3150     ->create_uri_reference ($value->[1])
3151     ->get_absolute_reference (${$value->[2]})
3152     ->get_uri_reference,
3153     $value->[2]];
3154     } else {
3155     push @new_value, $value;
3156     }
3157     } else {
3158     push @new_value, $value;
3159     }
3160     }
3161     return \@new_value;
3162     }
3163    
3164     return $specified_value;
3165     },
3166     };
3167     $Attr->{cursor} = $Prop->{cursor};
3168     $Key->{cursor} = $Prop->{cursor};
3169    
3170 wakaba 1.7 $Prop->{'border-style'} = {
3171     css => 'border-style',
3172     dom => 'border_style',
3173     parse => sub {
3174     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3175    
3176     my %prop_value;
3177     if ($t->{type} == IDENT_TOKEN) {
3178     my $prop_value = lc $t->{value}; ## TODO: case folding
3179     $t = $tt->get_next_token;
3180     if ($border_style_keyword->{$prop_value} and
3181     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
3182     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
3183     } elsif ($prop_value eq 'inherit') {
3184 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
3185 wakaba 1.19 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
3186     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
3187     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3188     return ($t, \%prop_value);
3189 wakaba 1.7 } else {
3190     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3191     level => $self->{must_level},
3192     token => $t);
3193     return ($t, undef);
3194     }
3195     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
3196     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
3197     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3198     } else {
3199     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3200     level => $self->{must_level},
3201     token => $t);
3202     return ($t, undef);
3203     }
3204    
3205     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3206     if ($t->{type} == IDENT_TOKEN) {
3207     my $prop_value = lc $t->{value}; ## TODO: case folding
3208     $t = $tt->get_next_token;
3209 wakaba 1.19 if ($border_style_keyword->{$prop_value} and
3210 wakaba 1.7 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
3211     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
3212     } else {
3213     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3214     level => $self->{must_level},
3215     token => $t);
3216     return ($t, undef);
3217     }
3218     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
3219    
3220     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3221     if ($t->{type} == IDENT_TOKEN) {
3222     my $prop_value = lc $t->{value}; ## TODO: case folding
3223     $t = $tt->get_next_token;
3224     if ($border_style_keyword->{$prop_value} and
3225     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
3226     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
3227     } else {
3228     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3229     level => $self->{must_level},
3230     token => $t);
3231     return ($t, undef);
3232     }
3233    
3234     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3235     if ($t->{type} == IDENT_TOKEN) {
3236     my $prop_value = lc $t->{value}; ## TODO: case folding
3237     $t = $tt->get_next_token;
3238     if ($border_style_keyword->{$prop_value} and
3239     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
3240     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
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     }
3249     }
3250    
3251     return ($t, \%prop_value);
3252     },
3253     serialize => sub {
3254     my ($self, $prop_name, $value) = @_;
3255    
3256     local $Error::Depth = $Error::Depth + 1;
3257     my @v;
3258     push @v, $self->border_top_style;
3259     return undef unless defined $v[-1];
3260     push @v, $self->border_right_style;
3261     return undef unless defined $v[-1];
3262     push @v, $self->border_bottom_style;
3263     return undef unless defined $v[-1];
3264 wakaba 1.19 push @v, $self->border_left_style;
3265 wakaba 1.7 return undef unless defined $v[-1];
3266    
3267     pop @v if $v[1] eq $v[3];
3268     pop @v if $v[0] eq $v[2];
3269     pop @v if $v[0] eq $v[1];
3270     return join ' ', @v;
3271     },
3272 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3273 wakaba 1.7 };
3274     $Attr->{border_style} = $Prop->{'border-style'};
3275    
3276 wakaba 1.29 $Prop->{'border-color'} = {
3277     css => 'border-color',
3278     dom => 'border_color',
3279     parse => sub {
3280     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3281    
3282     my %prop_value;
3283     ($t, my $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
3284     if (not defined $pv) {
3285     return ($t, undef);
3286     }
3287     $prop_value{'border-top-color'} = $pv->{'border-color'};
3288     $prop_value{'border-bottom-color'} = $prop_value{'border-top-color'};
3289     $prop_value{'border-right-color'} = $prop_value{'border-top-color'};
3290     $prop_value{'border-left-color'}= $prop_value{'border-right-color'};
3291     if ($prop_value{'border-top-color'}->[0] eq 'INHERIT') {
3292     return ($t, \%prop_value);
3293     }
3294    
3295     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3296     if ({
3297     IDENT_TOKEN, 1,
3298     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
3299     FUNCTION_TOKEN, 1,
3300     }->{$t->{type}}) {
3301     ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
3302     if (not defined $pv) {
3303     return ($t, undef);
3304     } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
3305     $onerror->(type => 'syntax error:'.$prop_name,
3306     level => $self->{must_level},
3307     token => $t);
3308     return ($t, undef);
3309     }
3310     $prop_value{'border-right-color'} = $pv->{'border-color'};
3311     $prop_value{'border-left-color'}= $prop_value{'border-right-color'};
3312    
3313     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3314     if ({
3315     IDENT_TOKEN, 1,
3316     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
3317     FUNCTION_TOKEN, 1,
3318     }->{$t->{type}}) {
3319     ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
3320     if (not defined $pv) {
3321     return ($t, undef);
3322     } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
3323     $onerror->(type => 'syntax error:'.$prop_name,
3324     level => $self->{must_level},
3325     token => $t);
3326     return ($t, undef);
3327     }
3328     $prop_value{'border-bottom-color'} = $pv->{'border-color'};
3329    
3330     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3331     if ({
3332     IDENT_TOKEN, 1,
3333     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
3334     FUNCTION_TOKEN, 1,
3335     }->{$t->{type}}) {
3336     ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
3337     if (not defined $pv) {
3338     return ($t, undef);
3339     } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
3340     $onerror->(type => 'syntax error:'.$prop_name,
3341     level => $self->{must_level},
3342     token => $t);
3343     return ($t, undef);
3344     }
3345     $prop_value{'border-left-color'} = $pv->{'border-color'};
3346     }
3347     }
3348     }
3349    
3350     return ($t, \%prop_value);
3351     },
3352     serialize => sub {
3353     my ($self, $prop_name, $value) = @_;
3354    
3355     local $Error::Depth = $Error::Depth + 1;
3356     my @v;
3357     push @v, $self->border_top_color;
3358     return undef unless defined $v[-1];
3359     push @v, $self->border_right_color;
3360     return undef unless defined $v[-1];
3361     push @v, $self->border_bottom_color;
3362     return undef unless defined $v[-1];
3363     push @v, $self->border_left_color;
3364     return undef unless defined $v[-1];
3365    
3366     pop @v if $v[1] eq $v[3];
3367     pop @v if $v[0] eq $v[2];
3368     pop @v if $v[0] eq $v[1];
3369     return join ' ', @v;
3370     },
3371     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3372     };
3373     $Attr->{border_color} = $Prop->{'border-color'};
3374    
3375     $Prop->{'border-top'} = {
3376     css => 'border-top',
3377     dom => 'border_top',
3378     parse => sub {
3379     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3380    
3381     my %prop_value;
3382     my $pv;
3383     ## NOTE: Since $onerror is disabled for three invocations below,
3384     ## some informative warning messages (if they are added someday) will not
3385     ## be reported.
3386     ($t, $pv) = $parse_color->($self, $prop_name.'-color', $tt, $t, sub {});
3387     if (defined $pv) {
3388     if ($pv->{$prop_name.'-color'}->[0] eq 'INHERIT') {
3389     return ($t, {$prop_name.'-color' => ['INHERIT'],
3390     $prop_name.'-style' => ['INHERIT'],
3391     $prop_name.'-width' => ['INHERIT']});
3392     } else {
3393     $prop_value{$prop_name.'-color'} = $pv->{$prop_name.'-color'};
3394     }
3395     } else {
3396     ($t, $pv) = $Prop->{'border-top-width'}->{parse}
3397     ->($self, $prop_name.'-width', $tt, $t, sub {});
3398     if (defined $pv) {
3399     $prop_value{$prop_name.'-width'} = $pv->{$prop_name.'-width'};
3400     } else {
3401     ($t, $pv) = $Prop->{'border-top-style'}->{parse}
3402     ->($self, $prop_name.'-style', $tt, $t, sub {});
3403     if (defined $pv) {
3404     $prop_value{$prop_name.'-style'} = $pv->{$prop_name.'-style'};
3405     } else {
3406     $onerror->(type => 'syntax error:'.$prop_name,
3407     level => $self->{must_level},
3408     token => $t);
3409     return ($t, undef);
3410     }
3411     }
3412     }
3413    
3414     for (1..2) {
3415     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3416     if ($t->{type} == IDENT_TOKEN) {
3417     my $prop_value = lc $t->{value}; ## TODO: case
3418     if ($border_style_keyword->{$prop_value} and
3419     $self->{prop_value}->{'border-top-style'}->{$prop_value} and
3420     not defined $prop_value{$prop_name.'-style'}) {
3421     $prop_value{$prop_name.'-style'} = ['KEYWORD', $prop_value];
3422     $t = $tt->get_next_token;
3423     next;
3424     } elsif ({thin => 1, medium => 1, thick => 1}->{$prop_value} and
3425     not defined $prop_value{$prop_name.'-width'}) {
3426     $prop_value{$prop_name.'-width'} = ['KEYWORD', $prop_value];
3427     $t = $tt->get_next_token;
3428     next;
3429     }
3430     }
3431    
3432     undef $pv;
3433     ($t, $pv) = $parse_color->($self, $prop_name.'-color', $tt, $t, $onerror)
3434     if not defined $prop_value{$prop_name.'-color'} and
3435     {
3436     IDENT_TOKEN, 1,
3437     HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
3438     FUNCTION_TOKEN, 1,
3439     }->{$t->{type}};
3440     if (defined $pv) {
3441     if ($pv->{$prop_name.'-color'}->[0] eq 'INHERIT') {
3442     $onerror->(type => 'syntax error:'.$prop_name,
3443     level => $self->{must_level},
3444     token => $t);
3445     } else {
3446     $prop_value{$prop_name.'-color'} = $pv->{$prop_name.'-color'};
3447     }
3448     } else {
3449     undef $pv;
3450     ($t, $pv) = $Prop->{'border-top-width'}->{parse}
3451     ->($self, $prop_name.'-width',
3452     $tt, $t, $onerror)
3453     if not defined $prop_value{$prop_name.'-width'} and
3454     {
3455     DIMENSION_TOKEN, 1,
3456     NUMBER_TOKEN, 1,
3457     IDENT_TOKEN, 1,
3458     MINUS_TOKEN, 1,
3459     }->{$t->{type}};
3460     if (defined $pv) {
3461     if ($pv->{$prop_name.'-width'}->[0] eq 'INHERIT') {
3462     $onerror->(type => 'syntax error:'.$prop_name,
3463     level => $self->{must_level},
3464     token => $t);
3465     } else {
3466     $prop_value{$prop_name.'-width'} = $pv->{$prop_name.'-width'};
3467     }
3468     } else {
3469     last;
3470     }
3471     }
3472     }
3473    
3474     $prop_value{$prop_name.'-color'}
3475     ||= $Prop->{$prop_name.'-color'}->{initial};
3476     $prop_value{$prop_name.'-width'}
3477     ||= $Prop->{$prop_name.'-width'}->{initial};
3478     $prop_value{$prop_name.'-style'}
3479     ||= $Prop->{$prop_name.'-style'}->{initial};
3480    
3481     return ($t, \%prop_value);
3482     },
3483     serialize => sub {
3484     my ($self, $prop_name, $value) = @_;
3485    
3486     local $Error::Depth = $Error::Depth + 1;
3487     my $width_prop = $prop_name . '_width'; $width_prop =~ tr/-/_/;
3488     my $width_value = $self->$width_prop;
3489     return undef unless defined $width_value;
3490     my $style_prop = $prop_name . '_style'; $style_prop =~ tr/-/_/;
3491     my $style_value = $self->$style_prop;
3492     return undef unless defined $style_value;
3493     my $color_prop = $prop_name . '_color'; $color_prop =~ tr/-/_/;
3494     my $color_value = $self->$color_prop;
3495     return undef unless defined $color_value;
3496    
3497     return $width_value . ' ' . $style_value . ' ' . $color_value;
3498     },
3499     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3500     };
3501     $Attr->{border_top} = $Prop->{'border-top'};
3502    
3503     $Prop->{'border-right'} = {
3504     css => 'border-right',
3505     dom => 'border_right',
3506     parse => $Prop->{'border-top'}->{parse},
3507     serialize => $Prop->{'border-top'}->{serialize},
3508     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3509     };
3510     $Attr->{border_right} = $Prop->{'border-right'};
3511    
3512     $Prop->{'border-bottom'} = {
3513     css => 'border-bottom',
3514     dom => 'border_bottom',
3515     parse => $Prop->{'border-top'}->{parse},
3516     serialize => $Prop->{'border-top'}->{serialize},
3517     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3518     };
3519     $Attr->{border_bottom} = $Prop->{'border-bottom'};
3520    
3521     $Prop->{'border-left'} = {
3522     css => 'border-left',
3523     dom => 'border_left',
3524     parse => $Prop->{'border-top'}->{parse},
3525     serialize => $Prop->{'border-top'}->{serialize},
3526     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3527     };
3528     $Attr->{border_left} = $Prop->{'border-left'};
3529    
3530     $Prop->{outline} = {
3531     css => 'outline',
3532     dom => 'outline',
3533     parse => $Prop->{'border-top'}->{parse},
3534     serialize => $Prop->{'border-top'}->{serialize},
3535     serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
3536     };
3537     $Attr->{outline} = $Prop->{outline};
3538    
3539     $Prop->{border} = {
3540     css => 'border',
3541     dom => 'border',
3542     parse => sub {
3543     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3544     my $prop_value;
3545     ($t, $prop_value) = $Prop->{'border-top'}->{parse}
3546     ->($self, 'border-top', $tt, $t, $onerror);
3547     return ($t, undef) unless defined $prop_value;
3548    
3549     for (qw/border-right border-bottom border-left/) {
3550     $prop_value->{$_.'-color'} = $prop_value->{'border-top-color'}
3551     if defined $prop_value->{'border-top-color'};
3552     $prop_value->{$_.'-style'} = $prop_value->{'border-top-style'}
3553     if defined $prop_value->{'border-top-style'};
3554     $prop_value->{$_.'-width'} = $prop_value->{'border-top-width'}
3555     if defined $prop_value->{'border-top-width'};
3556     }
3557     return ($t, $prop_value);
3558     },
3559     serialize => sub {
3560     my ($self, $prop_name, $value) = @_;
3561    
3562     local $Error::Depth = $Error::Depth + 1;
3563     my $bt = $self->border_top;
3564     return undef unless defined $bt;
3565     my $br = $self->border_right;
3566     return undef unless defined $br;
3567     return undef unless $bt eq $br;
3568     my $bb = $self->border_bottom;
3569     return undef unless defined $bb;
3570     return undef unless $bt eq $bb;
3571     my $bl = $self->border_left;
3572     return undef unless defined $bl;
3573     return undef unless $bt eq $bl;
3574    
3575     return $bt;
3576     },
3577     serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3578     };
3579     $Attr->{border} = $Prop->{border};
3580    
3581 wakaba 1.19 $Prop->{margin} = {
3582     css => 'margin',
3583     dom => 'margin',
3584     parse => sub {
3585     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3586    
3587     my %prop_value;
3588    
3589     my $sign = 1;
3590     if ($t->{type} == MINUS_TOKEN) {
3591     $t = $tt->get_next_token;
3592     $sign = -1;
3593     }
3594    
3595     if ($t->{type} == DIMENSION_TOKEN) {
3596     my $value = $t->{number} * $sign;
3597     my $unit = lc $t->{value}; ## TODO: case
3598     $t = $tt->get_next_token;
3599     if ($length_unit->{$unit}) {
3600     $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
3601     } else {
3602     $onerror->(type => 'syntax error:'.$prop_name,
3603     level => $self->{must_level},
3604     token => $t);
3605     return ($t, undef);
3606     }
3607     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3608     my $value = $t->{number} * $sign;
3609     $t = $tt->get_next_token;
3610     $prop_value{'margin-top'} = ['PERCENTAGE', $value];
3611 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3612 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3613     my $value = $t->{number} * $sign;
3614     $t = $tt->get_next_token;
3615     $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
3616     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3617     my $prop_value = lc $t->{value}; ## TODO: case folding
3618     $t = $tt->get_next_token;
3619     if ($prop_value eq 'auto') {
3620     $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
3621     } elsif ($prop_value eq 'inherit') {
3622     $prop_value{'margin-top'} = ['INHERIT'];
3623     $prop_value{'margin-right'} = $prop_value{'margin-top'};
3624     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
3625     $prop_value{'margin-left'} = $prop_value{'margin-right'};
3626     return ($t, \%prop_value);
3627     } else {
3628     $onerror->(type => 'syntax error:'.$prop_name,
3629     level => $self->{must_level},
3630     token => $t);
3631     return ($t, undef);
3632     }
3633     } else {
3634     $onerror->(type => 'syntax error:'.$prop_name,
3635     level => $self->{must_level},
3636     token => $t);
3637     return ($t, undef);
3638     }
3639     $prop_value{'margin-right'} = $prop_value{'margin-top'};
3640     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
3641     $prop_value{'margin-left'} = $prop_value{'margin-right'};
3642    
3643     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3644     $sign = 1;
3645     if ($t->{type} == MINUS_TOKEN) {
3646     $t = $tt->get_next_token;
3647     $sign = -1;
3648     }
3649    
3650     if ($t->{type} == DIMENSION_TOKEN) {
3651     my $value = $t->{number} * $sign;
3652     my $unit = lc $t->{value}; ## TODO: case
3653     $t = $tt->get_next_token;
3654     if ($length_unit->{$unit}) {
3655     $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
3656     } else {
3657     $onerror->(type => 'syntax error:'.$prop_name,
3658     level => $self->{must_level},
3659     token => $t);
3660     return ($t, undef);
3661     }
3662     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3663     my $value = $t->{number} * $sign;
3664     $t = $tt->get_next_token;
3665     $prop_value{'margin-right'} = ['PERCENTAGE', $value];
3666 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3667 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3668     my $value = $t->{number} * $sign;
3669     $t = $tt->get_next_token;
3670     $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
3671     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3672     my $prop_value = lc $t->{value}; ## TODO: case folding
3673     $t = $tt->get_next_token;
3674     if ($prop_value eq 'auto') {
3675     $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
3676     } else {
3677     $onerror->(type => 'syntax error:'.$prop_name,
3678     level => $self->{must_level},
3679     token => $t);
3680     return ($t, undef);
3681     }
3682     } else {
3683 wakaba 1.24 if ($sign < 0) {
3684     $onerror->(type => 'syntax error:'.$prop_name,
3685     level => $self->{must_level},
3686     token => $t);
3687     return ($t, undef);
3688     }
3689 wakaba 1.19 return ($t, \%prop_value);
3690     }
3691     $prop_value{'margin-left'} = $prop_value{'margin-right'};
3692    
3693     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3694     $sign = 1;
3695     if ($t->{type} == MINUS_TOKEN) {
3696     $t = $tt->get_next_token;
3697     $sign = -1;
3698     }
3699    
3700     if ($t->{type} == DIMENSION_TOKEN) {
3701     my $value = $t->{number} * $sign;
3702     my $unit = lc $t->{value}; ## TODO: case
3703     $t = $tt->get_next_token;
3704     if ($length_unit->{$unit}) {
3705     $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
3706     } else {
3707     $onerror->(type => 'syntax error:'.$prop_name,
3708     level => $self->{must_level},
3709     token => $t);
3710     return ($t, undef);
3711     }
3712     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3713     my $value = $t->{number} * $sign;
3714     $t = $tt->get_next_token;
3715     $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
3716 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3717 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3718     my $value = $t->{number} * $sign;
3719     $t = $tt->get_next_token;
3720     $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
3721     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3722     my $prop_value = lc $t->{value}; ## TODO: case folding
3723     $t = $tt->get_next_token;
3724     if ($prop_value eq 'auto') {
3725     $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
3726     } else {
3727     $onerror->(type => 'syntax error:'.$prop_name,
3728     level => $self->{must_level},
3729     token => $t);
3730     return ($t, undef);
3731     }
3732     } else {
3733 wakaba 1.24 if ($sign < 0) {
3734     $onerror->(type => 'syntax error:'.$prop_name,
3735     level => $self->{must_level},
3736     token => $t);
3737     return ($t, undef);
3738     }
3739 wakaba 1.19 return ($t, \%prop_value);
3740     }
3741    
3742     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3743     $sign = 1;
3744     if ($t->{type} == MINUS_TOKEN) {
3745     $t = $tt->get_next_token;
3746     $sign = -1;
3747     }
3748    
3749     if ($t->{type} == DIMENSION_TOKEN) {
3750     my $value = $t->{number} * $sign;
3751     my $unit = lc $t->{value}; ## TODO: case
3752     $t = $tt->get_next_token;
3753     if ($length_unit->{$unit}) {
3754     $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
3755     } else {
3756     $onerror->(type => 'syntax error:'.$prop_name,
3757     level => $self->{must_level},
3758     token => $t);
3759     return ($t, undef);
3760     }
3761     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3762     my $value = $t->{number} * $sign;
3763     $t = $tt->get_next_token;
3764     $prop_value{'margin-left'} = ['PERCENTAGE', $value];
3765 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3766 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3767     my $value = $t->{number} * $sign;
3768     $t = $tt->get_next_token;
3769     $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
3770     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3771     my $prop_value = lc $t->{value}; ## TODO: case folding
3772     $t = $tt->get_next_token;
3773     if ($prop_value eq 'auto') {
3774     $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
3775     } else {
3776     $onerror->(type => 'syntax error:'.$prop_name,
3777     level => $self->{must_level},
3778     token => $t);
3779     return ($t, undef);
3780     }
3781     } else {
3782 wakaba 1.24 if ($sign < 0) {
3783     $onerror->(type => 'syntax error:'.$prop_name,
3784     level => $self->{must_level},
3785     token => $t);
3786     return ($t, undef);
3787     }
3788 wakaba 1.19 return ($t, \%prop_value);
3789     }
3790    
3791     return ($t, \%prop_value);
3792     },
3793     serialize => sub {
3794     my ($self, $prop_name, $value) = @_;
3795    
3796     local $Error::Depth = $Error::Depth + 1;
3797     my @v;
3798     push @v, $self->margin_top;
3799     return undef unless defined $v[-1];
3800     push @v, $self->margin_right;
3801     return undef unless defined $v[-1];
3802     push @v, $self->margin_bottom;
3803     return undef unless defined $v[-1];
3804     push @v, $self->margin_left;
3805     return undef unless defined $v[-1];
3806    
3807     pop @v if $v[1] eq $v[3];
3808     pop @v if $v[0] eq $v[2];
3809     pop @v if $v[0] eq $v[1];
3810     return join ' ', @v;
3811     },
3812     };
3813     $Attr->{margin} = $Prop->{margin};
3814    
3815     $Prop->{padding} = {
3816     css => 'padding',
3817     dom => 'padding',
3818     parse => sub {
3819     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3820    
3821     my %prop_value;
3822    
3823     my $sign = 1;
3824     if ($t->{type} == MINUS_TOKEN) {
3825     $t = $tt->get_next_token;
3826     $sign = -1;
3827     }
3828    
3829     if ($t->{type} == DIMENSION_TOKEN) {
3830     my $value = $t->{number} * $sign;
3831     my $unit = lc $t->{value}; ## TODO: case
3832     $t = $tt->get_next_token;
3833     if ($length_unit->{$unit} and $value >= 0) {
3834     $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
3835     } else {
3836     $onerror->(type => 'syntax error:'.$prop_name,
3837     level => $self->{must_level},
3838     token => $t);
3839     return ($t, undef);
3840     }
3841     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3842     my $value = $t->{number} * $sign;
3843     $t = $tt->get_next_token;
3844     $prop_value{'padding-top'} = ['PERCENTAGE', $value];
3845     unless ($value >= 0) {
3846     $onerror->(type => 'syntax error:'.$prop_name,
3847     level => $self->{must_level},
3848     token => $t);
3849     return ($t, undef);
3850     }
3851 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3852 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3853     my $value = $t->{number} * $sign;
3854     $t = $tt->get_next_token;
3855     $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
3856     unless ($value >= 0) {
3857     $onerror->(type => 'syntax error:'.$prop_name,
3858     level => $self->{must_level},
3859     token => $t);
3860     return ($t, undef);
3861     }
3862     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3863     my $prop_value = lc $t->{value}; ## TODO: case folding
3864     $t = $tt->get_next_token;
3865     if ($prop_value eq 'inherit') {
3866     $prop_value{'padding-top'} = ['INHERIT'];
3867     $prop_value{'padding-right'} = $prop_value{'padding-top'};
3868     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
3869     $prop_value{'padding-left'} = $prop_value{'padding-right'};
3870     return ($t, \%prop_value);
3871     } else {
3872     $onerror->(type => 'syntax error:'.$prop_name,
3873     level => $self->{must_level},
3874     token => $t);
3875     return ($t, undef);
3876     }
3877     } else {
3878     $onerror->(type => 'syntax error:'.$prop_name,
3879     level => $self->{must_level},
3880     token => $t);
3881     return ($t, undef);
3882     }
3883     $prop_value{'padding-right'} = $prop_value{'padding-top'};
3884     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
3885     $prop_value{'padding-left'} = $prop_value{'padding-right'};
3886    
3887     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3888     $sign = 1;
3889     if ($t->{type} == MINUS_TOKEN) {
3890     $t = $tt->get_next_token;
3891     $sign = -1;
3892     }
3893    
3894     if ($t->{type} == DIMENSION_TOKEN) {
3895     my $value = $t->{number} * $sign;
3896     my $unit = lc $t->{value}; ## TODO: case
3897     $t = $tt->get_next_token;
3898     if ($length_unit->{$unit} and $value >= 0) {
3899     $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
3900     } else {
3901     $onerror->(type => 'syntax error:'.$prop_name,
3902     level => $self->{must_level},
3903     token => $t);
3904     return ($t, undef);
3905     }
3906     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3907     my $value = $t->{number} * $sign;
3908     $t = $tt->get_next_token;
3909     $prop_value{'padding-right'} = ['PERCENTAGE', $value];
3910     unless ($value >= 0) {
3911     $onerror->(type => 'syntax error:'.$prop_name,
3912     level => $self->{must_level},
3913     token => $t);
3914     return ($t, undef);
3915     }
3916 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3917 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3918     my $value = $t->{number} * $sign;
3919     $t = $tt->get_next_token;
3920     $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
3921     unless ($value >= 0) {
3922     $onerror->(type => 'syntax error:'.$prop_name,
3923     level => $self->{must_level},
3924     token => $t);
3925     return ($t, undef);
3926     }
3927     } else {
3928 wakaba 1.24 if ($sign < 0) {
3929     $onerror->(type => 'syntax error:'.$prop_name,
3930     level => $self->{must_level},
3931     token => $t);
3932     return ($t, undef);
3933     }
3934 wakaba 1.19 return ($t, \%prop_value);
3935     }
3936     $prop_value{'padding-left'} = $prop_value{'padding-right'};
3937    
3938     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3939     $sign = 1;
3940     if ($t->{type} == MINUS_TOKEN) {
3941     $t = $tt->get_next_token;
3942     $sign = -1;
3943     }
3944    
3945     if ($t->{type} == DIMENSION_TOKEN) {
3946     my $value = $t->{number} * $sign;
3947     my $unit = lc $t->{value}; ## TODO: case
3948     $t = $tt->get_next_token;
3949     if ($length_unit->{$unit} and $value >= 0) {
3950     $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
3951     } else {
3952     $onerror->(type => 'syntax error:'.$prop_name,
3953     level => $self->{must_level},
3954     token => $t);
3955     return ($t, undef);
3956     }
3957     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3958     my $value = $t->{number} * $sign;
3959     $t = $tt->get_next_token;
3960     $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
3961     unless ($value >= 0) {
3962     $onerror->(type => 'syntax error:'.$prop_name,
3963     level => $self->{must_level},
3964     token => $t);
3965     return ($t, undef);
3966     }
3967 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3968 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3969     my $value = $t->{number} * $sign;
3970     $t = $tt->get_next_token;
3971     $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
3972     unless ($value >= 0) {
3973     $onerror->(type => 'syntax error:'.$prop_name,
3974     level => $self->{must_level},
3975     token => $t);
3976     return ($t, undef);
3977     }
3978     } else {
3979 wakaba 1.24 if ($sign < 0) {
3980     $onerror->(type => 'syntax error:'.$prop_name,
3981     level => $self->{must_level},
3982     token => $t);
3983     return ($t, undef);
3984     }
3985 wakaba 1.19 return ($t, \%prop_value);
3986     }
3987    
3988     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3989     $sign = 1;
3990     if ($t->{type} == MINUS_TOKEN) {
3991     $t = $tt->get_next_token;
3992     $sign = -1;
3993     }
3994    
3995     if ($t->{type} == DIMENSION_TOKEN) {
3996     my $value = $t->{number} * $sign;
3997     my $unit = lc $t->{value}; ## TODO: case
3998     $t = $tt->get_next_token;
3999     if ($length_unit->{$unit} and $value >= 0) {
4000     $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
4001     } else {
4002     $onerror->(type => 'syntax error:'.$prop_name,
4003     level => $self->{must_level},
4004     token => $t);
4005     return ($t, undef);
4006     }
4007     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4008     my $value = $t->{number} * $sign;
4009     $t = $tt->get_next_token;
4010     $prop_value{'padding-left'} = ['PERCENTAGE', $value];
4011     unless ($value >= 0) {
4012     $onerror->(type => 'syntax error:'.$prop_name,
4013     level => $self->{must_level},
4014     token => $t);
4015     return ($t, undef);
4016     }
4017 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
4018 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
4019     my $value = $t->{number} * $sign;
4020     $t = $tt->get_next_token;
4021     $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
4022     unless ($value >= 0) {
4023     $onerror->(type => 'syntax error:'.$prop_name,
4024     level => $self->{must_level},
4025     token => $t);
4026     return ($t, undef);
4027     }
4028     } else {
4029 wakaba 1.24 if ($sign < 0) {
4030     $onerror->(type => 'syntax error:'.$prop_name,
4031     level => $self->{must_level},
4032     token => $t);
4033     return ($t, undef);
4034     }
4035 wakaba 1.19 return ($t, \%prop_value);
4036     }
4037    
4038     return ($t, \%prop_value);
4039     },
4040     serialize => sub {
4041     my ($self, $prop_name, $value) = @_;
4042    
4043     local $Error::Depth = $Error::Depth + 1;
4044     my @v;
4045     push @v, $self->padding_top;
4046     return undef unless defined $v[-1];
4047     push @v, $self->padding_right;
4048     return undef unless defined $v[-1];
4049     push @v, $self->padding_bottom;
4050     return undef unless defined $v[-1];
4051     push @v, $self->padding_left;
4052     return undef unless defined $v[-1];
4053    
4054     pop @v if $v[1] eq $v[3];
4055     pop @v if $v[0] eq $v[2];
4056     pop @v if $v[0] eq $v[1];
4057     return join ' ', @v;
4058     },
4059     };
4060     $Attr->{padding} = $Prop->{padding};
4061    
4062 wakaba 1.24 $Prop->{'border-spacing'} = {
4063     css => 'border-spacing',
4064     dom => 'border_spacing',
4065     parse => sub {
4066     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4067    
4068     my %prop_value;
4069    
4070     my $sign = 1;
4071     if ($t->{type} == MINUS_TOKEN) {
4072     $t = $tt->get_next_token;
4073     $sign = -1;
4074     }
4075    
4076     if ($t->{type} == DIMENSION_TOKEN) {
4077     my $value = $t->{number} * $sign;
4078     my $unit = lc $t->{value}; ## TODO: case
4079     $t = $tt->get_next_token;
4080     if ($length_unit->{$unit} and $value >= 0) {
4081     $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, $unit];
4082     } else {
4083     $onerror->(type => 'syntax error:'.$prop_name,
4084     level => $self->{must_level},
4085     token => $t);
4086     return ($t, undef);
4087     }
4088     } elsif ($t->{type} == NUMBER_TOKEN and
4089     ($self->{unitless_px} or $t->{number} == 0)) {
4090     my $value = $t->{number} * $sign;
4091     $t = $tt->get_next_token;
4092     $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, 'px'];
4093     unless ($value >= 0) {
4094     $onerror->(type => 'syntax error:'.$prop_name,
4095     level => $self->{must_level},
4096     token => $t);
4097     return ($t, undef);
4098     }
4099     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4100     my $prop_value = lc $t->{value}; ## TODO: case folding
4101     $t = $tt->get_next_token;
4102     if ($prop_value eq 'inherit') {
4103     $prop_value{'-manakai-border-spacing-x'} = ['INHERIT'];
4104     $prop_value{'-manakai-border-spacing-y'}
4105     = $prop_value{'-manakai-border-spacing-x'};
4106     return ($t, \%prop_value);
4107     } else {
4108     $onerror->(type => 'syntax error:'.$prop_name,
4109     level => $self->{must_level},
4110     token => $t);
4111     return ($t, undef);
4112     }
4113     } else {
4114     $onerror->(type => 'syntax error:'.$prop_name,
4115     level => $self->{must_level},
4116     token => $t);
4117     return ($t, undef);
4118     }
4119     $prop_value{'-manakai-border-spacing-y'}
4120     = $prop_value{'-manakai-border-spacing-x'};
4121    
4122     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4123     $sign = 1;
4124     if ($t->{type} == MINUS_TOKEN) {
4125     $t = $tt->get_next_token;
4126     $sign = -1;
4127     }
4128    
4129     if ($t->{type} == DIMENSION_TOKEN) {
4130     my $value = $t->{number} * $sign;
4131     my $unit = lc $t->{value}; ## TODO: case
4132     $t = $tt->get_next_token;
4133     if ($length_unit->{$unit} and $value >= 0) {
4134     $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, $unit];
4135     } else {
4136     $onerror->(type => 'syntax error:'.$prop_name,
4137     level => $self->{must_level},
4138     token => $t);
4139     return ($t, undef);
4140     }
4141     } elsif ($t->{type} == NUMBER_TOKEN and
4142     ($self->{unitless_px} or $t->{number} == 0)) {
4143     my $value = $t->{number} * $sign;
4144     $t = $tt->get_next_token;
4145     $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, 'px'];
4146     unless ($value >= 0) {
4147     $onerror->(type => 'syntax error:'.$prop_name,
4148     level => $self->{must_level},
4149     token => $t);
4150     return ($t, undef);
4151     }
4152     } else {
4153     if ($sign < 0) {
4154     $onerror->(type => 'syntax error:'.$prop_name,
4155     level => $self->{must_level},
4156     token => $t);
4157     return ($t, undef);
4158     }
4159     return ($t, \%prop_value);
4160     }
4161    
4162     return ($t, \%prop_value);
4163     },
4164     serialize => sub {
4165     my ($self, $prop_name, $value) = @_;
4166    
4167     local $Error::Depth = $Error::Depth + 1;
4168     my @v;
4169     push @v, $self->_manakai_border_spacing_x;
4170     return undef unless defined $v[-1];
4171     push @v, $self->_manakai_border_spacing_y;
4172     return undef unless defined $v[-1];
4173    
4174     pop @v if $v[0] eq $v[1];
4175     return join ' ', @v;
4176     },
4177 wakaba 1.25 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
4178     ->{serialize_multiple},
4179 wakaba 1.24 };
4180     $Attr->{border_spacing} = $Prop->{'border-spacing'};
4181    
4182 wakaba 1.27 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/background-position> for
4183     ## browser compatibility problems.
4184     $Prop->{'background-position'} = {
4185     css => 'background-position',
4186     dom => 'background_position',
4187     parse => sub {
4188     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4189    
4190     my %prop_value;
4191    
4192     my $sign = 1;
4193     if ($t->{type} == MINUS_TOKEN) {
4194     $t = $tt->get_next_token;
4195     $sign = -1;
4196     }
4197    
4198     if ($t->{type} == DIMENSION_TOKEN) {
4199     my $value = $t->{number} * $sign;
4200     my $unit = lc $t->{value}; ## TODO: case
4201     $t = $tt->get_next_token;
4202     if ($length_unit->{$unit}) {
4203     $prop_value{'background-position-x'} = ['DIMENSION', $value, $unit];
4204     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
4205     } else {
4206     $onerror->(type => 'syntax error:'.$prop_name,
4207     level => $self->{must_level},
4208     token => $t);
4209     return ($t, undef);
4210     }
4211     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4212     my $value = $t->{number} * $sign;
4213     $t = $tt->get_next_token;
4214     $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
4215     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
4216     } elsif ($t->{type} == NUMBER_TOKEN and
4217     ($self->{unitless_px} or $t->{number} == 0)) {
4218     my $value = $t->{number} * $sign;
4219     $t = $tt->get_next_token;
4220     $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
4221     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
4222     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4223     my $prop_value = lc $t->{value}; ## TODO: case folding
4224     $t = $tt->get_next_token;
4225     if ({left => 1, center => 1, right => 1}->{$prop_value}) {
4226     $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
4227     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
4228     } elsif ($prop_value eq 'top' or $prop_value eq 'bottom') {
4229     $prop_value{'background-position-y'} = ['KEYWORD', $prop_value];
4230    
4231     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4232     if ($t->{type} == IDENT_TOKEN) {
4233     my $prop_value = lc $t->{value}; ## TODO: case folding
4234     if ({left => 1, center => 1, right => 1}->{$prop_value}) {
4235     $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
4236     $t = $tt->get_next_token;
4237     return ($t, \%prop_value);
4238     }
4239     }
4240     $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
4241     return ($t, \%prop_value);
4242     } elsif ($prop_value eq 'inherit') {
4243     $prop_value{'background-position-x'} = ['INHERIT'];
4244     $prop_value{'background-position-y'} = ['INHERIT'];
4245     return ($t, \%prop_value);
4246     } else {
4247     $onerror->(type => 'syntax error:'.$prop_name,
4248     level => $self->{must_level},
4249     token => $t);
4250     return ($t, undef);
4251     }
4252     } else {
4253     $onerror->(type => 'syntax error:'.$prop_name,
4254     level => $self->{must_level},
4255     token => $t);
4256     return ($t, undef);
4257     }
4258    
4259     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4260     $sign = 1;
4261     if ($t->{type} == MINUS_TOKEN) {
4262     $t = $tt->get_next_token;
4263     $sign = -1;
4264     }
4265    
4266     if ($t->{type} == DIMENSION_TOKEN) {
4267     my $value = $t->{number} * $sign;
4268     my $unit = lc $t->{value}; ## TODO: case
4269     $t = $tt->get_next_token;
4270     if ($length_unit->{$unit}) {
4271     $prop_value{'background-position-y'} = ['DIMENSION', $value, $unit];
4272     } else {
4273     $onerror->(type => 'syntax error:'.$prop_name,
4274     level => $self->{must_level},
4275     token => $t);
4276     return ($t, undef);
4277     }
4278     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4279     my $value = $t->{number} * $sign;
4280     $t = $tt->get_next_token;
4281     $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
4282     } elsif ($t->{type} == NUMBER_TOKEN and $self->{unitless_px}) {
4283     my $value = $t->{number} * $sign;
4284     $t = $tt->get_next_token;
4285     $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
4286     } elsif ($t->{type} == IDENT_TOKEN) {
4287     my $value = lc $t->{value}; ## TODO: case
4288     if ({top => 1, center => 1, bottom => 1}->{$value}) {
4289     $prop_value{'background-position-y'} = ['KEYWORD', $value];
4290     $t = $tt->get_next_token;
4291     }
4292     } else {
4293     if ($sign < 0) {
4294     $onerror->(type => 'syntax error:'.$prop_name,
4295     level => $self->{must_level},
4296     token => $t);
4297     return ($t, undef);
4298     }
4299     return ($t, \%prop_value);
4300     }
4301    
4302     return ($t, \%prop_value);
4303     },
4304     serialize => sub {
4305     my ($self, $prop_name, $value) = @_;
4306    
4307     local $Error::Depth = $Error::Depth + 1;
4308     my $x = $self->background_position_x;
4309     my $y = $self->background_position_y;
4310     return $x . ' ' . $y if defined $x and defined $y;
4311     return undef;
4312     },
4313     serialize_multiple => $Prop->{'background-position-x'}
4314     ->{serialize_multiple},
4315     };
4316     $Attr->{background_position} = $Prop->{'background-position'};
4317    
4318 wakaba 1.20 $Prop->{'border-width'} = {
4319     css => 'border-width',
4320     dom => 'border_width',
4321     parse => sub {
4322     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4323    
4324     my %prop_value;
4325    
4326     my $sign = 1;
4327     if ($t->{type} == MINUS_TOKEN) {
4328     $t = $tt->get_next_token;
4329     $sign = -1;
4330     }
4331    
4332     if ($t->{type} == DIMENSION_TOKEN) {
4333     my $value = $t->{number} * $sign;
4334     my $unit = lc $t->{value}; ## TODO: case
4335     $t = $tt->get_next_token;
4336     if ($length_unit->{$unit} and $value >= 0) {
4337     $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
4338     } else {
4339     $onerror->(type => 'syntax error:'.$prop_name,
4340     level => $self->{must_level},
4341     token => $t);
4342     return ($t, undef);
4343     }
4344     } elsif ($t->{type} == NUMBER_TOKEN and
4345     ($self->{unitless_px} or $t->{number} == 0)) {
4346     my $value = $t->{number} * $sign;
4347     $t = $tt->get_next_token;
4348     $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
4349     unless ($value >= 0) {
4350     $onerror->(type => 'syntax error:'.$prop_name,
4351     level => $self->{must_level},
4352     token => $t);
4353     return ($t, undef);
4354     }
4355     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4356     my $prop_value = lc $t->{value}; ## TODO: case folding
4357     $t = $tt->get_next_token;
4358     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
4359     $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
4360     } elsif ($prop_value eq 'inherit') {
4361     $prop_value{'border-top-width'} = ['INHERIT'];
4362     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
4363     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
4364     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
4365     return ($t, \%prop_value);
4366     } else {
4367     $onerror->(type => 'syntax error:'.$prop_name,
4368     level => $self->{must_level},
4369     token => $t);
4370     return ($t, undef);
4371     }
4372     } else {
4373     $onerror->(type => 'syntax error:'.$prop_name,
4374     level => $self->{must_level},
4375     token => $t);
4376     return ($t, undef);
4377     }
4378     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
4379     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
4380     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
4381    
4382     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4383     $sign = 1;
4384     if ($t->{type} == MINUS_TOKEN) {
4385     $t = $tt->get_next_token;
4386     $sign = -1;
4387     }
4388    
4389     if ($t->{type} == DIMENSION_TOKEN) {
4390     my $value = $t->{number} * $sign;
4391     my $unit = lc $t->{value}; ## TODO: case
4392     $t = $tt->get_next_token;
4393     if ($length_unit->{$unit} and $value >= 0) {
4394     $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
4395     } else {
4396     $onerror->(type => 'syntax error:'.$prop_name,
4397     level => $self->{must_level},
4398     token => $t);
4399     return ($t, undef);
4400     }
4401     } elsif ($t->{type} == NUMBER_TOKEN and
4402     ($self->{unitless_px} or $t->{number} == 0)) {
4403     my $value = $t->{number} * $sign;
4404     $t = $tt->get_next_token;
4405     $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
4406     unless ($value >= 0) {
4407     $onerror->(type => 'syntax error:'.$prop_name,
4408     level => $self->{must_level},
4409     token => $t);
4410     return ($t, undef);
4411     }
4412     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4413     my $prop_value = lc $t->{value}; ## TODO: case
4414     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
4415     $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
4416     $t = $tt->get_next_token;
4417     }
4418     } else {
4419 wakaba 1.24 if ($sign < 0) {
4420     $onerror->(type => 'syntax error:'.$prop_name,
4421     level => $self->{must_level},
4422     token => $t);
4423     return ($t, undef);
4424     }
4425 wakaba 1.20 return ($t, \%prop_value);
4426     }
4427     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
4428    
4429     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4430     $sign = 1;
4431     if ($t->{type} == MINUS_TOKEN) {
4432     $t = $tt->get_next_token;
4433     $sign = -1;
4434     }
4435    
4436     if ($t->{type} == DIMENSION_TOKEN) {
4437     my $value = $t->{number} * $sign;
4438     my $unit = lc $t->{value}; ## TODO: case
4439     $t = $tt->get_next_token;
4440     if ($length_unit->{$unit} and $value >= 0) {
4441     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
4442     } else {
4443     $onerror->(type => 'syntax error:'.$prop_name,
4444     level => $self->{must_level},
4445     token => $t);
4446     return ($t, undef);
4447     }
4448     } elsif ($t->{type} == NUMBER_TOKEN and
4449     ($self->{unitless_px} or $t->{number} == 0)) {
4450     my $value = $t->{number} * $sign;
4451     $t = $tt->get_next_token;
4452     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
4453     unless ($value >= 0) {
4454     $onerror->(type => 'syntax error:'.$prop_name,
4455     level => $self->{must_level},
4456     token => $t);
4457     return ($t, undef);
4458     }
4459     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4460     my $prop_value = lc $t->{value}; ## TODO: case
4461     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
4462     $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
4463     $t = $tt->get_next_token;
4464     }
4465     } else {
4466 wakaba 1.24 if ($sign < 0) {
4467     $onerror->(type => 'syntax error:'.$prop_name,
4468     level => $self->{must_level},
4469     token => $t);
4470     return ($t, undef);
4471     }
4472 wakaba 1.20 return ($t, \%prop_value);
4473     }
4474    
4475     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4476     $sign = 1;
4477     if ($t->{type} == MINUS_TOKEN) {
4478     $t = $tt->get_next_token;
4479     $sign = -1;
4480     }
4481    
4482     if ($t->{type} == DIMENSION_TOKEN) {
4483     my $value = $t->{number} * $sign;
4484     my $unit = lc $t->{value}; ## TODO: case
4485     $t = $tt->get_next_token;
4486     if ($length_unit->{$unit} and $value >= 0) {
4487     $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
4488     } else {
4489     $onerror->(type => 'syntax error:'.$prop_name,
4490     level => $self->{must_level},
4491     token => $t);
4492     return ($t, undef);
4493     }
4494     } elsif ($t->{type} == NUMBER_TOKEN and
4495     ($self->{unitless_px} or $t->{number} == 0)) {
4496     my $value = $t->{number} * $sign;
4497     $t = $tt->get_next_token;
4498     $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
4499     unless ($value >= 0) {
4500     $onerror->(type => 'syntax error:'.$prop_name,
4501     level => $self->{must_level},
4502     token => $t);
4503     return ($t, undef);
4504     }
4505     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4506     my $prop_value = lc $t->{value}; ## TODO: case
4507     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
4508     $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
4509     $t = $tt->get_next_token;
4510     }
4511     } else {
4512 wakaba 1.24 if ($sign < 0) {
4513     $onerror->(type => 'syntax error:'.$prop_name,
4514     level => $self->{must_level},
4515     token => $t);
4516     return ($t, undef);
4517     }
4518 wakaba 1.20 return ($t, \%prop_value);
4519     }
4520    
4521     return ($t, \%prop_value);
4522     },
4523     serialize => sub {
4524     my ($self, $prop_name, $value) = @_;
4525    
4526     local $Error::Depth = $Error::Depth + 1;
4527     my @v;
4528 wakaba 1.24 push @v, $self->border_top_width;
4529 wakaba 1.20 return undef unless defined $v[-1];
4530 wakaba 1.24 push @v, $self->border_right_width;
4531 wakaba 1.20 return undef unless defined $v[-1];
4532 wakaba 1.24 push @v, $self->border_bottom_width;
4533 wakaba 1.20 return undef unless defined $v[-1];
4534 wakaba 1.24 push @v, $self->border_left_width;
4535 wakaba 1.20 return undef unless defined $v[-1];
4536    
4537     pop @v if $v[1] eq $v[3];
4538     pop @v if $v[0] eq $v[2];
4539     pop @v if $v[0] eq $v[1];
4540     return join ' ', @v;
4541     },
4542 wakaba 1.29 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4543 wakaba 1.20 };
4544 wakaba 1.24 $Attr->{border_width} = $Prop->{'border-width'};
4545 wakaba 1.20
4546 wakaba 1.12 $Prop->{'list-style'} = {
4547     css => 'list-style',
4548     dom => 'list_style',
4549     parse => sub {
4550     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4551    
4552     my %prop_value;
4553     my $none = 0;
4554    
4555     F: for my $f (1..3) {
4556     if ($t->{type} == IDENT_TOKEN) {
4557     my $prop_value = lc $t->{value}; ## TODO: case folding
4558     $t = $tt->get_next_token;
4559    
4560     if ($prop_value eq 'none') {
4561     $none++;
4562     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
4563     if (exists $prop_value{'list-style-type'}) {
4564     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
4565     $prop_name,
4566     level => $self->{must_level},
4567     token => $t);
4568     return ($t, undef);
4569     } else {
4570     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
4571     }
4572     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
4573     if (exists $prop_value{'list-style-position'}) {
4574     $onerror->(type => q[syntax error:duplicate:'list-style-position':].
4575     $prop_name,
4576     level => $self->{must_level},
4577     token => $t);
4578     return ($t, undef);
4579     }
4580    
4581     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
4582     } elsif ($f == 1 and $prop_value eq 'inherit') {
4583     $prop_value{'list-style-type'} = ["INHERIT"];
4584     $prop_value{'list-style-position'} = ["INHERIT"];
4585     $prop_value{'list-style-image'} = ["INHERIT"];
4586     last F;
4587     } else {
4588     if ($f == 1) {
4589     $onerror->(type => 'syntax error:'.$prop_name,
4590     level => $self->{must_level},
4591     token => $t);
4592     return ($t, undef);
4593     } else {
4594     last F;
4595     }
4596     }
4597     } elsif ($t->{type} == URI_TOKEN) {
4598     if (exists $prop_value{'list-style-image'}) {
4599     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
4600     $prop_name,
4601     level => $self->{must_level},
4602     token => $t);
4603     return ($t, undef);
4604     }
4605    
4606     $prop_value{'list-style-image'}
4607 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
4608 wakaba 1.12 $t = $tt->get_next_token;
4609     } else {
4610     if ($f == 1) {
4611     $onerror->(type => 'syntax error:keyword:'.$prop_name,
4612     level => $self->{must_level},
4613     token => $t);
4614     return ($t, undef);
4615     } else {
4616     last F;
4617     }
4618     }
4619    
4620     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4621     } # F
4622     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
4623    
4624     if ($none == 1) {
4625     if (exists $prop_value{'list-style-type'}) {
4626     if (exists $prop_value{'list-style-image'}) {
4627     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
4628     $prop_name,
4629     level => $self->{must_level},
4630     token => $t);
4631     return ($t, undef);
4632     } else {
4633     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
4634     }
4635     } else {
4636     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
4637     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
4638     unless exists $prop_value{'list-style-image'};
4639     }
4640     } elsif ($none == 2) {
4641     if (exists $prop_value{'list-style-type'}) {
4642     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
4643     $prop_name,
4644     level => $self->{must_level},
4645     token => $t);
4646     return ($t, undef);
4647     }
4648     if (exists $prop_value{'list-style-image'}) {
4649     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
4650     $prop_name,
4651     level => $self->{must_level},
4652     token => $t);
4653     return ($t, undef);
4654     }
4655    
4656     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
4657     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
4658     } elsif ($none == 3) {
4659     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
4660     $prop_name,
4661     level => $self->{must_level},
4662     token => $t);
4663     return ($t, undef);
4664     }
4665    
4666     for (qw/list-style-type list-style-position list-style-image/) {
4667     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
4668     }
4669    
4670     return ($t, \%prop_value);
4671     },
4672     serialize => sub {
4673     my ($self, $prop_name, $value) = @_;
4674    
4675     local $Error::Depth = $Error::Depth + 1;
4676     return $self->list_style_type . ' ' . $self->list_style_position .
4677     ' ' . $self->list_style_image;
4678     },
4679     };
4680     $Attr->{list_style} = $Prop->{'list-style'};
4681    
4682 wakaba 1.16 ## NOTE: Future version of the implementation will change the way to
4683     ## store the parsed value to support CSS 3 properties.
4684     $Prop->{'text-decoration'} = {
4685     css => 'text-decoration',
4686     dom => 'text_decoration',
4687     key => 'text_decoration',
4688     parse => sub {
4689     my ($self, $prop_name, $tt, $t, $onerror) = @_;
4690    
4691     my $value = ['DECORATION']; # , underline, overline, line-through, blink
4692    
4693     if ($t->{type} == IDENT_TOKEN) {
4694     my $v = lc $t->{value}; ## TODO: case
4695     $t = $tt->get_next_token;
4696     if ($v eq 'inherit') {
4697     return ($t, {$prop_name => ['INHERIT']});
4698     } elsif ($v eq 'none') {
4699     return ($t, {$prop_name => $value});
4700     } elsif ($v eq 'underline' and
4701     $self->{prop_value}->{$prop_name}->{$v}) {
4702     $value->[1] = 1;
4703     } elsif ($v eq 'overline' and
4704     $self->{prop_value}->{$prop_name}->{$v}) {
4705     $value->[2] = 1;
4706     } elsif ($v eq 'line-through' and
4707     $self->{prop_value}->{$prop_name}->{$v}) {
4708     $value->[3] = 1;
4709     } elsif ($v eq 'blink' and
4710     $self->{prop_value}->{$prop_name}->{$v}) {
4711     $value->[4] = 1;
4712     } else {
4713     $onerror->(type => 'syntax error:'.$prop_name,
4714     level => $self->{must_level},
4715     token => $t);
4716     return ($t, undef);
4717     }
4718     }
4719    
4720     F: {
4721     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4722     last F unless $t->{type} == IDENT_TOKEN;
4723    
4724     my $v = lc $t->{value}; ## TODO: case
4725     $t = $tt->get_next_token;
4726     if ($v eq 'underline' and
4727     $self->{prop_value}->{$prop_name}->{$v}) {
4728     $value->[1] = 1;
4729     } elsif ($v eq 'overline' and
4730     $self->{prop_value}->{$prop_name}->{$v}) {
4731     $value->[1] = 2;
4732     } elsif ($v eq 'line-through' and
4733     $self->{prop_value}->{$prop_name}->{$v}) {
4734     $value->[1] = 3;
4735     } elsif ($v eq 'blink' and
4736     $self->{prop_value}->{$prop_name}->{$v}) {
4737     $value->[1] = 4;
4738     } else {
4739     last F;
4740     }
4741    
4742     redo F;
4743     } # F
4744    
4745     return ($t, {$prop_name => $value});
4746     },
4747     serialize => $default_serializer,
4748     initial => ["KEYWORD", "none"],
4749     #inherited => 0,
4750     compute => $compute_as_specified,
4751     };
4752     $Attr->{text_decoration} = $Prop->{'text-decoration'};
4753     $Key->{text_decoration} = $Prop->{'text-decoration'};
4754    
4755 wakaba 1.1 1;
4756 wakaba 1.29 ## $Date: 2008/01/06 14:15:36 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24