/[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.61 - (hide annotations) (download)
Sat Feb 2 13:56:40 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.60: +1 -175 lines
++ whatpm/Whatpm/CSS/ChangeLog	2 Feb 2008 13:56:35 -0000
	* Parser.pm ($default_serializer, serialize parameters): Removed (now
	it is part of manakai Message::DOM::CSSStyleDeclaration).
	('overflow' key): Removed (since it is a shorthand property).

2008-02-02  Wakaba  <wakaba@suika.fam.cx>

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24