/[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.37 - (hide annotations) (download)
Sun Jan 20 04:02:25 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.36: +9 -2 lines
++ whatpm/Whatpm/CSS/ChangeLog	20 Jan 2008 04:02:20 -0000
2008-01-20  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm (parse_char_string): Revise |$tt->{get_char}| callback
	so that it sets |$tt->{line}| and |$tt->{column}| options.  Some
	error handler calling codes are modified for the experimental
	support for more precious reporting of error location.

	* Tokenizer.pm (new): The |onerror| option has been removed, since
	it was never used.
	(get_next_token): Limited and experimental support for token
	emittion with the information on the position where it occurs.
	(serialize_token): New function.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24