/[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.65 - (hide annotations) (download)
Sun Feb 10 07:34:10 2008 UTC (17 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.64: +35 -8 lines
++ whatpm/t/ChangeLog	10 Feb 2008 07:34:02 -0000
2008-02-10  Wakaba  <wakaba@suika.fam.cx>

	* css-3.dat: New test file.

	* CSS-Parser-1.t: |css-3.dat| is added.  '-moz-pre-wrap' added.
	Typo for 'collapse' value fixed.  Support for '@namespace'
	serialization.

	* css-1.dat: New test data for selectors parsing.

	* css-font.dat: Error locations for 'u' errors are corrected.

	* css-table.dat: Test data for 'border-collapse' are added.

	* css-text.dat: Test data for '-moz-pre-wrap' are added.

	* css-visual.dat: Test data for 'background-position' are added.

++ whatpm/Whatpm/Charset/ChangeLog	10 Feb 2008 07:31:32 -0000
2008-02-10  Wakaba  <wakaba@suika.fam.cx>

	* CharDet.pm (detect): Catch an error in detection function.

++ whatpm/Whatpm/CSS/ChangeLog	10 Feb 2008 07:30:34 -0000
2008-02-10  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm: |attr(prefix|localname)| did not work.  Support
	for uppercase namespace prefixes (namespace prefixes were
	not normalized into lowercase when they were looked up.).  Report
	the property name token when an unknown property error is raised.
	The '-moz-pre-wrap' value is supported for 'white-space' property.
	('background-position'): 'center left' and 'center right'
	were not supported.

	* SelectorsParser.pm: Report the namespace prefix token when
	an undeclared prefix error is raised.  S_TOKEN in
	COMBINATOR_STATE was not handled correctly.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24