/[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.57 - (hide annotations) (download)
Mon Jan 28 13:13:24 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.56: +193 -1 lines
++ whatpm/t/ChangeLog	28 Jan 2008 13:13:16 -0000
2008-01-28  Wakaba  <wakaba@suika.fam.cx>

	* CSS-Parser-1.t: 'content' added to the list of supported
	property and the list of initial values.

	* css-generated.dat: Tests for 'content' are added.

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

	* CSS-Parser-1.t: Loads test files as UTF-8.

	* css-generated.dat: Semi-real-world test data for 'quotes' are added.

++ whatpm/Whatpm/CSS/ChangeLog	28 Jan 2008 13:11:50 -0000
2008-01-28  Wakaba  <wakaba@suika.fam.cx>

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24