/[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.59 - (hide annotations) (download)
Sat Feb 2 13:42:42 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.58: +123 -1 lines
++ whatpm/t/ChangeLog	2 Feb 2008 13:42:33 -0000
2008-02-02  Wakaba  <wakaba@suika.fam.cx>

	* CSS-Parser-1.t: 'clip' added.

	* css-visual.t: New test data for 'clip' are added.

++ whatpm/Whatpm/CSS/ChangeLog	2 Feb 2008 13:42:01 -0000
2008-02-02  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm ($default_serializer): New 'RECT' type supported.
	('clip'): Implemented.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24