/[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.62 - (hide annotations) (download)
Sun Feb 3 06:00:40 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.61: +413 -131 lines
++ whatpm/t/ChangeLog	3 Feb 2008 06:00:37 -0000
2008-02-03  Wakaba  <wakaba@suika.fam.cx>

	* CSS-Parser-1.t: Typo fixed.  New properties added.

	* css-font.dat, css-table.dat, css-paged.dat, css-generated.dat,
	css-visual.dat: New test data for
	newly supported properties are added.

++ whatpm/Whatpm/CSS/ChangeLog	3 Feb 2008 05:59:12 -0000
2008-02-03  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm ('display', 'list-style-type',
	'caption-side'): CSS 2.0 values added.
	('font-size-adjust', 'font-stretch', 'marker-offset'): Implemented.
	($length_percentage_keyword_parser): Now this is in a variable.
	($length_keyword_parser): Now this is in a variable, too.
	('font'): Reset 'font-size-adjust' and 'font-stretch' as
	defined by CSS2.
	('marks', 'size', 'page'): Implemented.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24