/[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.21 - (hide annotations) (download)
Thu Jan 3 13:51:41 2008 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.20: +172 -1 lines
++ whatpm/Whatpm/CSS/ChangeLog	3 Jan 2008 13:51:32 -0000
	* Cascade.pm (get_specified_value_no_inherit): New function.
	(get_computed_value): New way to get computed value,
	the |{compute_multiple}| code, is now supported.

	* Parser.pm (top, bottom, left, right): Implemented.

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

1 wakaba 1.1 package Whatpm::CSS::Parser;
2     use strict;
3     use Whatpm::CSS::Tokenizer qw(:token);
4     require Whatpm::CSS::SelectorsParser;
5    
6     sub new ($) {
7 wakaba 1.3 my $self = bless {onerror => sub { }, must_level => 'm',
8 wakaba 1.5 message_level => 'w',
9 wakaba 1.3 unsupported_level => 'unsupported'}, shift;
10 wakaba 1.11 # $self->{base_uri}
11 wakaba 1.18 # $self->{unitless_px} = 1/0
12 wakaba 1.1
13     return $self;
14     } # new
15    
16     sub BEFORE_STATEMENT_STATE () { 0 }
17     sub BEFORE_DECLARATION_STATE () { 1 }
18     sub IGNORED_STATEMENT_STATE () { 2 }
19     sub IGNORED_DECLARATION_STATE () { 3 }
20    
21 wakaba 1.5 our $Prop; ## By CSS property name
22     our $Attr; ## By CSSOM attribute name
23     our $Key; ## By internal key
24    
25 wakaba 1.1 sub parse_char_string ($$) {
26     my $self = $_[0];
27    
28     my $s = $_[1];
29     pos ($s) = 0;
30 wakaba 1.2 my $line = 1;
31     my $column = 0;
32    
33     my $_onerror = $self->{onerror};
34     my $onerror = sub {
35     $_onerror->(@_, line => $line, column => $column);
36     };
37 wakaba 1.1
38     my $tt = Whatpm::CSS::Tokenizer->new;
39 wakaba 1.2 $tt->{onerror} = $onerror;
40 wakaba 1.1 $tt->{get_char} = sub {
41     if (pos $s < length $s) {
42 wakaba 1.2 my $c = ord substr $s, pos ($s)++, 1;
43     if ($c == 0x000A) {
44     $line++;
45     $column = 0;
46     } elsif ($c == 0x000D) {
47     unless (substr ($s, pos ($s), 1) eq "\x0A") {
48     $line++;
49     $column = 0;
50     } else {
51     $column++;
52     }
53     } else {
54     $column++;
55     }
56     return $c;
57 wakaba 1.1 } else {
58     return -1;
59     }
60     }; # $tt->{get_char}
61     $tt->init;
62    
63     my $sp = Whatpm::CSS::SelectorsParser->new;
64 wakaba 1.2 $sp->{onerror} = $onerror;
65 wakaba 1.1 $sp->{must_level} = $self->{must_level};
66 wakaba 1.2 $sp->{pseudo_element} = $self->{pseudo_element};
67     $sp->{pseudo_class} = $self->{pseudo_class};
68 wakaba 1.1
69 wakaba 1.4 my $nsmap = {};
70     $sp->{lookup_namespace_uri} = sub {
71     return $nsmap->{$_[0]}; # $_[0] is '' (default namespace) or prefix
72     }; # $sp->{lookup_namespace_uri}
73 wakaba 1.1
74     ## TODO: Supported pseudo classes and elements...
75    
76     require Message::DOM::CSSStyleSheet;
77     require Message::DOM::CSSRule;
78     require Message::DOM::CSSStyleDeclaration;
79    
80 wakaba 1.11 $self->{base_uri} = $self->{href} unless defined $self->{base_uri};
81    
82 wakaba 1.1 my $state = BEFORE_STATEMENT_STATE;
83     my $t = $tt->get_next_token;
84    
85     my $open_rules = [[]];
86     my $current_rules = $open_rules->[-1];
87     my $current_decls;
88     my $closing_tokens = [];
89 wakaba 1.3 my $charset_allowed = 1;
90 wakaba 1.4 my $namespace_allowed = 1;
91 wakaba 1.1
92     S: {
93     if ($state == BEFORE_STATEMENT_STATE) {
94     $t = $tt->get_next_token
95     while $t->{type} == S_TOKEN or
96     $t->{type} == CDO_TOKEN or
97     $t->{type} == CDC_TOKEN;
98    
99     if ($t->{type} == ATKEYWORD_TOKEN) {
100 wakaba 1.5 if (lc $t->{value} eq 'namespace') { ## TODO: case folding
101 wakaba 1.4 $t = $tt->get_next_token;
102     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
103    
104     my $prefix;
105     if ($t->{type} == IDENT_TOKEN) {
106     $prefix = lc $t->{value};
107     ## TODO: Unicode lowercase
108    
109     $t = $tt->get_next_token;
110     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
111     }
112    
113     if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
114     my $uri = $t->{value};
115    
116     $t = $tt->get_next_token;
117     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
118    
119     ## ISSUE: On handling of empty namespace URI, Firefox 2 and
120     ## Opera 9 work differently (See SuikaWiki:namespace).
121     ## TODO: We need to check what we do once it is specced.
122    
123     if ($t->{type} == SEMICOLON_TOKEN) {
124     if ($namespace_allowed) {
125     $nsmap->{defined $prefix ? $prefix : ''} = $uri;
126     push @$current_rules,
127     Message::DOM::CSSNamespaceRule->____new ($prefix, $uri);
128     undef $charset_allowed;
129     } else {
130     $onerror->(type => 'at:namespace:not allowed',
131     level => $self->{must_level},
132     token => $t);
133     }
134    
135     $t = $tt->get_next_token;
136     ## Stay in the state.
137     redo S;
138     } else {
139     #
140     }
141     } else {
142     #
143     }
144    
145     $onerror->(type => 'syntax error:at:namespace',
146     level => $self->{must_level},
147     token => $t);
148     #
149 wakaba 1.5 } elsif (lc $t->{value} eq 'charset') { ## TODO: case folding
150 wakaba 1.3 $t = $tt->get_next_token;
151     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
152    
153     if ($t->{type} == STRING_TOKEN) {
154     my $encoding = $t->{value};
155    
156     $t = $tt->get_next_token;
157     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
158    
159     if ($t->{type} == SEMICOLON_TOKEN) {
160     if ($charset_allowed) {
161     push @$current_rules,
162     Message::DOM::CSSCharsetRule->____new ($encoding);
163     undef $charset_allowed;
164     } else {
165     $onerror->(type => 'at:charset:not allowed',
166     level => $self->{must_level},
167     token => $t);
168     }
169    
170     ## TODO: Detect the conformance errors for @charset...
171    
172     $t = $tt->get_next_token;
173     ## Stay in the state.
174     redo S;
175     } else {
176     #
177     }
178     } else {
179     #
180     }
181    
182     $onerror->(type => 'syntax error:at:charset',
183     level => $self->{must_level},
184     token => $t);
185 wakaba 1.4 #
186 wakaba 1.3 ## NOTE: When adding support for new at-rule, insert code
187 wakaba 1.4 ## "undef $charset_allowed" and "undef $namespace_token" as
188     ## appropriate.
189 wakaba 1.3 } else {
190     $onerror->(type => 'not supported:at:'.$t->{value},
191     level => $self->{unsupported_level},
192     token => $t);
193     }
194 wakaba 1.1
195     $t = $tt->get_next_token;
196     $state = IGNORED_STATEMENT_STATE;
197     redo S;
198     } elsif (@$open_rules > 1 and $t->{type} == RBRACE_TOKEN) {
199     pop @$open_rules;
200     ## Stay in the state.
201     $t = $tt->get_next_token;
202     redo S;
203     } elsif ($t->{type} == EOF_TOKEN) {
204     if (@$open_rules > 1) {
205 wakaba 1.2 $onerror->(type => 'syntax error:block not closed',
206     level => $self->{must_level},
207     token => $t);
208 wakaba 1.1 }
209    
210     last S;
211     } else {
212 wakaba 1.3 undef $charset_allowed;
213 wakaba 1.4 undef $namespace_allowed;
214 wakaba 1.3
215 wakaba 1.1 ($t, my $selectors) = $sp->_parse_selectors_with_tokenizer
216     ($tt, LBRACE_TOKEN, $t);
217    
218     $t = $tt->get_next_token
219     while $t->{type} != LBRACE_TOKEN and $t->{type} != EOF_TOKEN;
220    
221     if ($t->{type} == LBRACE_TOKEN) {
222     $current_decls = Message::DOM::CSSStyleDeclaration->____new;
223     my $rs = Message::DOM::CSSStyleRule->____new
224     ($selectors, $current_decls);
225     push @{$current_rules}, $rs if defined $selectors;
226    
227     $state = BEFORE_DECLARATION_STATE;
228     $t = $tt->get_next_token;
229     redo S;
230     } else {
231 wakaba 1.2 $onerror->(type => 'syntax error:after selectors',
232     level => $self->{must_level},
233     token => $t);
234 wakaba 1.1
235     ## Stay in the state.
236     $t = $tt->get_next_token;
237     redo S;
238     }
239     }
240     } elsif ($state == BEFORE_DECLARATION_STATE) {
241     ## NOTE: DELIM? in declaration will be removed:
242     ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.
243    
244 wakaba 1.5 my $prop_def;
245     my $prop_value;
246     my $prop_flag;
247 wakaba 1.1 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
248     if ($t->{type} == IDENT_TOKEN) { # property
249 wakaba 1.5 my $prop_name = lc $t->{value}; ## TODO: case folding
250     $t = $tt->get_next_token;
251     if ($t->{type} == COLON_TOKEN) {
252     $t = $tt->get_next_token;
253     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
254    
255     $prop_def = $Prop->{$prop_name};
256 wakaba 1.6 if ($prop_def and $self->{prop}->{$prop_name}) {
257 wakaba 1.5 ($t, $prop_value)
258     = $prop_def->{parse}->($self, $prop_name, $tt, $t, $onerror);
259     if ($prop_value) {
260     ## NOTE: {parse} don't have to consume trailing spaces.
261     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
262    
263     if ($t->{type} == EXCLAMATION_TOKEN) {
264     $t = $tt->get_next_token;
265     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
266     if ($t->{type} == IDENT_TOKEN and
267     lc $t->{value} eq 'important') { ## TODO: case folding
268     $prop_flag = 'important';
269    
270     $t = $tt->get_next_token;
271     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
272    
273     #
274     } else {
275     $onerror->(type => 'syntax error:important',
276     level => $self->{must_level},
277     token => $t);
278    
279     ## Reprocess.
280     $state = IGNORED_DECLARATION_STATE;
281     redo S;
282     }
283     }
284    
285     #
286     } else {
287     ## Syntax error.
288    
289     ## Reprocess.
290     $state = IGNORED_DECLARATION_STATE;
291     redo S;
292     }
293     } else {
294     $onerror->(type => 'not supported:property',
295     level => $self->{unsupported_level},
296     token => $t, value => $prop_name);
297    
298     #
299     $state = IGNORED_DECLARATION_STATE;
300     redo S;
301     }
302     } else {
303     $onerror->(type => 'syntax error:property colon',
304     level => $self->{must_level},
305     token => $t);
306 wakaba 1.1
307 wakaba 1.5 #
308     $state = IGNORED_DECLARATION_STATE;
309     redo S;
310     }
311     }
312    
313     if ($t->{type} == RBRACE_TOKEN) {
314 wakaba 1.1 $t = $tt->get_next_token;
315 wakaba 1.5 $state = BEFORE_STATEMENT_STATE;
316     #redo S;
317     } elsif ($t->{type} == SEMICOLON_TOKEN) {
318 wakaba 1.1 $t = $tt->get_next_token;
319 wakaba 1.5 ## Stay in the state.
320     #redo S;
321 wakaba 1.1 } elsif ($t->{type} == EOF_TOKEN) {
322 wakaba 1.2 $onerror->(type => 'syntax error:ruleset not closed',
323     level => $self->{must_level},
324     token => $t);
325 wakaba 1.1 ## Reprocess.
326     $state = BEFORE_STATEMENT_STATE;
327 wakaba 1.5 #redo S;
328     } else {
329     if ($prop_value) {
330     $onerror->(type => 'syntax error:property semicolon',
331     level => $self->{must_level},
332     token => $t);
333     } else {
334     $onerror->(type => 'syntax error:property name',
335     level => $self->{must_level},
336     token => $t);
337     }
338    
339     #
340     $state = IGNORED_DECLARATION_STATE;
341 wakaba 1.1 redo S;
342     }
343    
344 wakaba 1.7 my $important = (defined $prop_flag and $prop_flag eq 'important');
345     for my $set_prop_name (keys %{$prop_value or {}}) {
346     my $set_prop_def = $Prop->{$set_prop_name};
347     $$current_decls->{$set_prop_def->{key}}
348     = [$prop_value->{$set_prop_name}, $prop_flag]
349     if $important or
350     not $$current_decls->{$set_prop_def->{key}} or
351     not defined $$current_decls->{$set_prop_def->{key}}->[1];
352 wakaba 1.5 }
353 wakaba 1.1 redo S;
354     } elsif ($state == IGNORED_STATEMENT_STATE or
355     $state == IGNORED_DECLARATION_STATE) {
356     if (@$closing_tokens) { ## Something is yet in opening state.
357     if ($t->{type} == EOF_TOKEN) {
358     @$closing_tokens = ();
359     ## Reprocess.
360     $state = $state == IGNORED_STATEMENT_STATE
361     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
362     redo S;
363     } elsif ($t->{type} == $closing_tokens->[-1]) {
364     pop @$closing_tokens;
365     if (@$closing_tokens == 0 and
366     $t->{type} == RBRACE_TOKEN and
367     $state == IGNORED_STATEMENT_STATE) {
368     $t = $tt->get_next_token;
369     $state = BEFORE_STATEMENT_STATE;
370     redo S;
371     } else {
372     $t = $tt->get_next_token;
373     ## Stay in the state.
374     redo S;
375     }
376     } else {
377     #
378     }
379     } else {
380     if ($t->{type} == SEMICOLON_TOKEN) {
381     $t = $tt->get_next_token;
382     $state = $state == IGNORED_STATEMENT_STATE
383     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
384     redo S;
385     } elsif ($state == IGNORED_DECLARATION_STATE and
386     $t->{type} == RBRACE_TOKEN) {
387     $t = $tt->get_next_token;
388     $state = BEFORE_STATEMENT_STATE;
389     redo S;
390     } elsif ($t->{type} == EOF_TOKEN) {
391     ## Reprocess.
392     $state = $state == IGNORED_STATEMENT_STATE
393     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
394     redo S;
395     } else {
396     #
397     }
398     }
399    
400     while (not {
401     EOF_TOKEN, 1,
402     RBRACE_TOKEN, 1,
403     RBRACKET_TOKEN, 1,
404     RPAREN_TOKEN, 1,
405     SEMICOLON_TOKEN, 1,
406     }->{$t->{type}}) {
407     if ($t->{type} == LBRACE_TOKEN) {
408     push @$closing_tokens, RBRACE_TOKEN;
409     } elsif ($t->{type} == LBRACKET_TOKEN) {
410     push @$closing_tokens, RBRACKET_TOKEN;
411     } elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {
412     push @$closing_tokens, RPAREN_TOKEN;
413     }
414    
415     $t = $tt->get_next_token;
416     }
417    
418     #
419     ## Stay in the state.
420     redo S;
421     } else {
422     die "$0: parse_char_string: Unknown state: $state";
423     }
424     } # S
425    
426     my $ss = Message::DOM::CSSStyleSheet->____new
427 wakaba 1.11 (manakai_base_uri => $self->{base_uri},
428     css_rules => $open_rules->[0],
429 wakaba 1.1 ## TODO: href
430     ## TODO: owner_node
431     ## TODO: media
432     type => 'text/css', ## TODO: OK?
433     _parser => $self);
434     return $ss;
435     } # parse_char_string
436    
437 wakaba 1.9 my $compute_as_specified = sub ($$$$) {
438     #my ($self, $element, $prop_name, $specified_value) = @_;
439     return $_[3];
440     }; # $compute_as_specified
441    
442 wakaba 1.11 my $default_serializer = sub {
443     my ($self, $prop_name, $value) = @_;
444 wakaba 1.15 if ($value->[0] eq 'NUMBER' or $value->[0] eq 'WEIGHT') {
445     ## TODO: What we currently do for 'font-weight' is different from
446     ## any browser for lighter/bolder cases. We need to fix this, but
447     ## how?
448 wakaba 1.11 return $value->[1]; ## TODO: big or small number cases?
449 wakaba 1.18 } elsif ($value->[0] eq 'DIMENSION') {
450     return $value->[1] . $value->[2]; ## NOTE: This is what browsers do.
451 wakaba 1.11 } elsif ($value->[0] eq 'KEYWORD') {
452     return $value->[1];
453     } elsif ($value->[0] eq 'URI') {
454     ## NOTE: This is what browsers do.
455     return 'url('.$value->[1].')';
456     } elsif ($value->[0] eq 'INHERIT') {
457     return 'inherit';
458 wakaba 1.16 } elsif ($value->[0] eq 'DECORATION') {
459     my @v = ();
460     push @v, 'underline' if $value->[1];
461     push @v, 'overline' if $value->[2];
462     push @v, 'line-through' if $value->[3];
463     push @v, 'blink' if $value->[4];
464     return 'none' unless @v;
465     return join ' ', @v;
466 wakaba 1.11 } else {
467     return undef;
468     }
469     }; # $default_serializer
470    
471 wakaba 1.5 $Prop->{color} = {
472     css => 'color',
473     dom => 'color',
474     key => 'color',
475     parse => sub {
476     my ($self, $prop_name, $tt, $t, $onerror) = @_;
477    
478     if ($t->{type} == IDENT_TOKEN) {
479     if (lc $t->{value} eq 'blue') { ## TODO: case folding
480     $t = $tt->get_next_token;
481 wakaba 1.7 return ($t, {$prop_name => ["RGBA", 0, 0, 255, 1]});
482 wakaba 1.5 } else {
483     #
484     }
485     } else {
486     #
487     }
488    
489     $onerror->(type => 'syntax error:color',
490     level => $self->{must_level},
491     token => $t);
492    
493     return ($t, undef);
494     },
495     serialize => sub {
496     my ($self, $prop_name, $value) = @_;
497     if ($value->[0] eq 'RGBA') { ## TODO: %d? %f?
498     return sprintf 'rgba(%d, %d, %d, %f)', @$value[1, 2, 3, 4];
499     } else {
500     return undef;
501     }
502     },
503 wakaba 1.9 initial => ["KEYWORD", "-manakai-initial-color"], ## NOTE: UA-dependent in CSS 2.1.
504     inherited => 1,
505     compute => $compute_as_specified,
506 wakaba 1.5 };
507     $Attr->{color} = $Prop->{color};
508     $Key->{color} = $Prop->{color};
509    
510 wakaba 1.6 my $one_keyword_parser = sub {
511     my ($self, $prop_name, $tt, $t, $onerror) = @_;
512    
513     if ($t->{type} == IDENT_TOKEN) {
514     my $prop_value = lc $t->{value}; ## TODO: case folding
515     $t = $tt->get_next_token;
516     if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
517     $self->{prop_value}->{$prop_name}->{$prop_value}) {
518 wakaba 1.7 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
519 wakaba 1.6 } elsif ($prop_value eq 'inherit') {
520 wakaba 1.10 return ($t, {$prop_name => ['INHERIT']});
521 wakaba 1.6 }
522     }
523    
524 wakaba 1.7 $onerror->(type => 'syntax error:keyword:'.$prop_name,
525 wakaba 1.6 level => $self->{must_level},
526     token => $t);
527     return ($t, undef);
528     };
529    
530     $Prop->{display} = {
531     css => 'display',
532     dom => 'display',
533     key => 'display',
534     parse => $one_keyword_parser,
535 wakaba 1.11 serialize => $default_serializer,
536 wakaba 1.6 keyword => {
537     block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
538     'list-item' => 1, none => 1,
539     table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
540     'table-column-group' => 1, 'table-header-group' => 1,
541     'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
542     },
543 wakaba 1.9 initial => ["KEYWORD", "inline"],
544     #inherited => 0,
545     compute => sub {
546     my ($self, $element, $prop_name, $specified_value) = @_;
547     ## NOTE: CSS 2.1 Section 9.7.
548    
549     ## WARNING: |compute| for 'float' property invoke this CODE
550     ## in some case. Careless modification might cause a infinite loop.
551    
552 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
553 wakaba 1.9 if ($specified_value->[1] eq 'none') {
554     ## Case 1 [CSS 2.1]
555     return $specified_value;
556     } else {
557     my $position = $self->get_computed_value ($element, 'position');
558     if ($position->[0] eq 'KEYWORD' and
559     ($position->[1] eq 'absolute' or
560     $position->[1] eq 'fixed')) {
561     ## Case 2 [CSS 2.1]
562     #
563     } else {
564     my $float = $self->get_computed_value ($element, 'float');
565     if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
566     ## Caes 3 [CSS 2.1]
567     #
568     } elsif (not defined $element->manakai_parent_element) {
569     ## Case 4 [CSS 2.1]
570     #
571     } else {
572     ## Case 5 [CSS 2.1]
573     return $specified_value;
574     }
575     }
576    
577     return ["KEYWORD",
578     {
579     'inline-table' => 'table',
580     inline => 'block',
581     'run-in' => 'block',
582     'table-row-group' => 'block',
583     'table-column' => 'block',
584     'table-column-group' => 'block',
585     'table-header-group' => 'block',
586     'table-footer-group' => 'block',
587     'table-row' => 'block',
588     'table-cell' => 'block',
589     'table-caption' => 'block',
590     'inline-block' => 'block',
591     }->{$specified_value->[1]} || $specified_value->[1]];
592     }
593     } else {
594     return $specified_value; ## Maybe an error of the implementation.
595     }
596     },
597 wakaba 1.6 };
598     $Attr->{display} = $Prop->{display};
599     $Key->{display} = $Prop->{display};
600    
601     $Prop->{position} = {
602     css => 'position',
603     dom => 'position',
604     key => 'position',
605     parse => $one_keyword_parser,
606 wakaba 1.11 serialize => $default_serializer,
607 wakaba 1.6 keyword => {
608     static => 1, relative => 1, absolute => 1, fixed => 1,
609     },
610 wakaba 1.10 initial => ["KEYWORD", "static"],
611 wakaba 1.9 #inherited => 0,
612     compute => $compute_as_specified,
613 wakaba 1.6 };
614     $Attr->{position} = $Prop->{position};
615     $Key->{position} = $Prop->{position};
616    
617     $Prop->{float} = {
618     css => 'float',
619     dom => 'css_float',
620     key => 'float',
621     parse => $one_keyword_parser,
622 wakaba 1.11 serialize => $default_serializer,
623 wakaba 1.6 keyword => {
624     left => 1, right => 1, none => 1,
625     },
626 wakaba 1.9 initial => ["KEYWORD", "none"],
627     #inherited => 0,
628     compute => sub {
629     my ($self, $element, $prop_name, $specified_value) = @_;
630     ## NOTE: CSS 2.1 Section 9.7.
631    
632     ## WARNING: |compute| for 'display' property invoke this CODE
633     ## in some case. Careless modification might cause a infinite loop.
634    
635 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
636 wakaba 1.9 if ($specified_value->[1] eq 'none') {
637     ## Case 1 [CSS 2.1]
638     return $specified_value;
639     } else {
640     my $position = $self->get_computed_value ($element, 'position');
641     if ($position->[0] eq 'KEYWORD' and
642     ($position->[1] eq 'absolute' or
643     $position->[1] eq 'fixed')) {
644     ## Case 2 [CSS 2.1]
645     return ["KEYWORD", "none"];
646     }
647     }
648     }
649    
650     ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
651     ## on computed value of 'float' property.
652    
653     ## Case 3, 4, and 5 [CSS 2.1]
654     return $specified_value;
655     },
656 wakaba 1.6 };
657     $Attr->{css_float} = $Prop->{float};
658     $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
659     $Key->{float} = $Prop->{float};
660    
661     $Prop->{clear} = {
662     css => 'clear',
663     dom => 'clear',
664     key => 'clear',
665     parse => $one_keyword_parser,
666 wakaba 1.11 serialize => $default_serializer,
667 wakaba 1.6 keyword => {
668     left => 1, right => 1, none => 1, both => 1,
669     },
670 wakaba 1.9 initial => ["KEYWORD", "none"],
671     #inherited => 0,
672     compute => $compute_as_specified,
673 wakaba 1.6 };
674     $Attr->{clear} = $Prop->{clear};
675     $Key->{clear} = $Prop->{clear};
676    
677     $Prop->{direction} = {
678     css => 'direction',
679     dom => 'direction',
680     key => 'direction',
681     parse => $one_keyword_parser,
682 wakaba 1.11 serialize => $default_serializer,
683 wakaba 1.6 keyword => {
684     ltr => 1, rtl => 1,
685     },
686 wakaba 1.9 initial => ["KEYWORD", "ltr"],
687     inherited => 1,
688     compute => $compute_as_specified,
689 wakaba 1.6 };
690     $Attr->{direction} = $Prop->{direction};
691     $Key->{direction} = $Prop->{direction};
692    
693     $Prop->{'unicode-bidi'} = {
694     css => 'unicode-bidi',
695     dom => 'unicode_bidi',
696     key => 'unicode_bidi',
697     parse => $one_keyword_parser,
698 wakaba 1.11 serialize => $default_serializer,
699 wakaba 1.6 keyword => {
700     normal => 1, embed => 1, 'bidi-override' => 1,
701     },
702 wakaba 1.9 initial => ["KEYWORD", "normal"],
703     #inherited => 0,
704     compute => $compute_as_specified,
705 wakaba 1.6 };
706     $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
707     $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
708    
709 wakaba 1.11 $Prop->{overflow} = {
710     css => 'overflow',
711     dom => 'overflow',
712     key => 'overflow',
713     parse => $one_keyword_parser,
714     serialize => $default_serializer,
715     keyword => {
716     visible => 1, hidden => 1, scroll => 1, auto => 1,
717     },
718     initial => ["KEYWORD", "visible"],
719     #inherited => 0,
720     compute => $compute_as_specified,
721     };
722     $Attr->{overflow} = $Prop->{overflow};
723     $Key->{overflow} = $Prop->{overflow};
724    
725     $Prop->{visibility} = {
726     css => 'visibility',
727     dom => 'visibility',
728     key => 'visibility',
729     parse => $one_keyword_parser,
730     serialize => $default_serializer,
731     keyword => {
732     visible => 1, hidden => 1, collapse => 1,
733     },
734     initial => ["KEYWORD", "visible"],
735     #inherited => 0,
736     compute => $compute_as_specified,
737     };
738     $Attr->{visibility} = $Prop->{visibility};
739     $Key->{visibility} = $Prop->{visibility};
740    
741     $Prop->{'list-style-type'} = {
742     css => 'list-style-type',
743     dom => 'list_style_type',
744     key => 'list_style_type',
745     parse => $one_keyword_parser,
746     serialize => $default_serializer,
747     keyword => {
748     qw/
749     disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
750     lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
751     upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
752     none 1
753     /,
754     },
755     initial => ["KEYWORD", 'disc'],
756     inherited => 1,
757     compute => $compute_as_specified,
758     };
759     $Attr->{list_style_type} = $Prop->{'list-style-type'};
760     $Key->{list_style_type} = $Prop->{'list-style-type'};
761    
762     $Prop->{'list-style-position'} = {
763     css => 'list-style-position',
764     dom => 'list_style_position',
765     key => 'list_style_position',
766     parse => $one_keyword_parser,
767     serialize => $default_serializer,
768     keyword => {
769     inside => 1, outside => 1,
770     },
771     initial => ["KEYWORD", 'outside'],
772     inherited => 1,
773     compute => $compute_as_specified,
774     };
775     $Attr->{list_style_position} = $Prop->{'list-style-position'};
776     $Key->{list_style_position} = $Prop->{'list-style-position'};
777    
778 wakaba 1.12 $Prop->{'page-break-before'} = {
779     css => 'page-break-before',
780     dom => 'page_break_before',
781     key => 'page_break_before',
782     parse => $one_keyword_parser,
783     serialize => $default_serializer,
784     keyword => {
785     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
786     },
787     initial => ["KEYWORD", 'auto'],
788 wakaba 1.15 #inherited => 0,
789 wakaba 1.12 compute => $compute_as_specified,
790     };
791     $Attr->{page_break_before} = $Prop->{'page-break-before'};
792     $Key->{page_break_before} = $Prop->{'page-break-before'};
793    
794     $Prop->{'page-break-after'} = {
795     css => 'page-break-after',
796     dom => 'page_break_after',
797     key => 'page_break_after',
798     parse => $one_keyword_parser,
799     serialize => $default_serializer,
800     keyword => {
801     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
802     },
803     initial => ["KEYWORD", 'auto'],
804 wakaba 1.15 #inherited => 0,
805 wakaba 1.12 compute => $compute_as_specified,
806     };
807     $Attr->{page_break_after} = $Prop->{'page-break-after'};
808     $Key->{page_break_after} = $Prop->{'page-break-after'};
809    
810     $Prop->{'page-break-inside'} = {
811     css => 'page-break-inside',
812     dom => 'page_break_inside',
813     key => 'page_break_inside',
814     parse => $one_keyword_parser,
815     serialize => $default_serializer,
816     keyword => {
817     auto => 1, avoid => 1,
818     },
819     initial => ["KEYWORD", 'auto'],
820     inherited => 1,
821     compute => $compute_as_specified,
822     };
823     $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
824     $Key->{page_break_inside} = $Prop->{'page-break-inside'};
825    
826 wakaba 1.15 $Prop->{'background-repeat'} = {
827     css => 'background-repeat',
828     dom => 'background_repeat',
829     key => 'background_repeat',
830     parse => $one_keyword_parser,
831     serialize => $default_serializer,
832     keyword => {
833     repeat => 1, 'repeat-x' => 1, 'repeat-y' => 1, 'no-repeat' => 1,
834     },
835     initial => ["KEYWORD", 'repeat'],
836     #inherited => 0,
837     compute => $compute_as_specified,
838     };
839     $Attr->{background_repeat} = $Prop->{'background-repeat'};
840     $Key->{backgroud_repeat} = $Prop->{'background-repeat'};
841    
842     $Prop->{'background-attachment'} = {
843     css => 'background-attachment',
844     dom => 'background_attachment',
845     key => 'background_attachment',
846     parse => $one_keyword_parser,
847     serialize => $default_serializer,
848     keyword => {
849     scroll => 1, fixed => 1,
850     },
851     initial => ["KEYWORD", 'scroll'],
852     #inherited => 0,
853     compute => $compute_as_specified,
854     };
855     $Attr->{background_attachment} = $Prop->{'background-attachment'};
856     $Key->{backgroud_attachment} = $Prop->{'background-attachment'};
857    
858     $Prop->{'font-style'} = {
859     css => 'font-style',
860 wakaba 1.18 dom => 'font_style',
861     key => 'font_style',
862 wakaba 1.15 parse => $one_keyword_parser,
863     serialize => $default_serializer,
864     keyword => {
865     normal => 1, italic => 1, oblique => 1,
866     },
867     initial => ["KEYWORD", 'normal'],
868     inherited => 1,
869     compute => $compute_as_specified,
870     };
871     $Attr->{font_style} = $Prop->{'font-style'};
872     $Key->{font_style} = $Prop->{'font-style'};
873    
874     $Prop->{'font-variant'} = {
875     css => 'font-variant',
876     dom => 'font_variant',
877     key => 'font_variant',
878     parse => $one_keyword_parser,
879     serialize => $default_serializer,
880     keyword => {
881     normal => 1, 'small-caps' => 1,
882     },
883     initial => ["KEYWORD", 'normal'],
884     inherited => 1,
885     compute => $compute_as_specified,
886     };
887     $Attr->{font_variant} = $Prop->{'font-variant'};
888     $Key->{font_variant} = $Prop->{'font-variant'};
889    
890 wakaba 1.16 $Prop->{'text-align'} = {
891     css => 'text-align',
892     dom => 'text_align',
893     key => 'text_align',
894     parse => $one_keyword_parser,
895     serialize => $default_serializer,
896     keyword => {
897     left => 1, right => 1, center => 1, justify => 1, ## CSS 2
898     begin => 1, end => 1, ## CSS 3
899     },
900     initial => ["KEYWORD", 'begin'],
901     inherited => 1,
902     compute => $compute_as_specified,
903     };
904     $Attr->{text_align} = $Prop->{'text-align'};
905     $Key->{text_align} = $Prop->{'text-align'};
906    
907     $Prop->{'text-transform'} = {
908     css => 'text-transform',
909     dom => 'text_transform',
910     key => 'text_transform',
911     parse => $one_keyword_parser,
912     serialize => $default_serializer,
913     keyword => {
914     capitalize => 1, uppercase => 1, lowercase => 1, none => 1,
915     },
916     initial => ["KEYWORD", 'none'],
917     inherited => 1,
918     compute => $compute_as_specified,
919     };
920     $Attr->{text_transform} = $Prop->{'text-transform'};
921     $Key->{text_transform} = $Prop->{'text-transform'};
922    
923     $Prop->{'white-space'} = {
924     css => 'white-space',
925     dom => 'white_space',
926     key => 'white_space',
927     parse => $one_keyword_parser,
928     serialize => $default_serializer,
929     keyword => {
930     normal => 1, pre => 1, nowrap => 1, 'pre-wrap' => 1, 'pre-line' => 1,
931     },
932     initial => ["KEYWORD", 'normal'],
933     inherited => 1,
934     compute => $compute_as_specified,
935     };
936     $Attr->{white_space} = $Prop->{'white-space'};
937     $Key->{white_space} = $Prop->{'white-space'};
938    
939     $Prop->{'caption-side'} = {
940     css => 'caption-side',
941     dom => 'caption_side',
942     key => 'caption_side',
943     parse => $one_keyword_parser,
944     serialize => $default_serializer,
945     keyword => {
946     top => 1, bottom => 1,
947     },
948     initial => ['KEYWORD', 'top'],
949     inherited => 1,
950     compute => $compute_as_specified,
951     };
952     $Attr->{caption_side} = $Prop->{'caption-side'};
953     $Key->{caption_side} = $Prop->{'caption-side'};
954    
955     $Prop->{'table-layout'} = {
956     css => 'table-layout',
957     dom => 'table_layout',
958     key => 'table_layout',
959     parse => $one_keyword_parser,
960     serialize => $default_serializer,
961     keyword => {
962     auto => 1, fixed => 1,
963     },
964     initial => ['KEYWORD', 'auto'],
965     #inherited => 0,
966     compute => $compute_as_specified,
967     };
968     $Attr->{table_layout} = $Prop->{'table-layout'};
969     $Key->{table_layout} = $Prop->{'table-layout'};
970    
971     $Prop->{'border-collapse'} = {
972     css => 'border-collapse',
973     dom => 'border_collapse',
974     key => 'border_collapse',
975     parse => $one_keyword_parser,
976     serialize => $default_serializer,
977     keyword => {
978     collapse => 1, separate => 1,
979     },
980     initial => ['KEYWORD', 'separate'],
981     inherited => 1,
982     compute => $compute_as_specified,
983     };
984     $Attr->{border_collapse} = $Prop->{'border-collapse'};
985     $Key->{border_collapse} = $Prop->{'border-collapse'};
986    
987     $Prop->{'empty-cells'} = {
988     css => 'empty-cells',
989     dom => 'empty_cells',
990     key => 'empty_cells',
991     parse => $one_keyword_parser,
992     serialize => $default_serializer,
993     keyword => {
994     show => 1, hide => 1,
995     },
996     initial => ['KEYWORD', 'show'],
997     inherited => 1,
998     compute => $compute_as_specified,
999     };
1000     $Attr->{empty_cells} = $Prop->{'empty-cells'};
1001     $Key->{empty_cells} = $Prop->{'empty-cells'};
1002    
1003 wakaba 1.11 $Prop->{'z-index'} = {
1004     css => 'z-index',
1005     dom => 'z_index',
1006     key => 'z_index',
1007     parse => sub {
1008     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1009    
1010 wakaba 1.12 my $sign = 1;
1011     if ($t->{type} == MINUS_TOKEN) {
1012     $sign = -1;
1013     $t = $tt->get_next_token;
1014     }
1015    
1016 wakaba 1.11 if ($t->{type} == NUMBER_TOKEN) {
1017     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
1018     ## browser compatibility issue.
1019     my $value = $t->{number};
1020     $t = $tt->get_next_token;
1021 wakaba 1.12 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1022     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1023 wakaba 1.11 my $value = lc $t->{value}; ## TODO: case
1024     $t = $tt->get_next_token;
1025     if ($value eq 'auto') {
1026     ## NOTE: |z-index| is the default value and therefore it must be
1027     ## supported anyway.
1028     return ($t, {$prop_name => ["KEYWORD", 'auto']});
1029     } elsif ($value eq 'inherit') {
1030     return ($t, {$prop_name => ['INHERIT']});
1031     }
1032     }
1033    
1034     $onerror->(type => 'syntax error:'.$prop_name,
1035     level => $self->{must_level},
1036     token => $t);
1037     return ($t, undef);
1038     },
1039     serialize => $default_serializer,
1040     initial => ['KEYWORD', 'auto'],
1041     #inherited => 0,
1042     compute => $compute_as_specified,
1043     };
1044     $Attr->{z_index} = $Prop->{'z-index'};
1045     $Key->{z_index} = $Prop->{'z-index'};
1046    
1047 wakaba 1.12 $Prop->{orphans} = {
1048     css => 'orphans',
1049     dom => 'orphans',
1050     key => 'orphans',
1051     parse => sub {
1052     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1053    
1054     my $sign = 1;
1055     if ($t->{type} == MINUS_TOKEN) {
1056     $t = $tt->get_next_token;
1057     $sign = -1;
1058     }
1059    
1060     if ($t->{type} == NUMBER_TOKEN) {
1061     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
1062     ## <http://suika.fam.cx/gate/2005/sw/widows> for
1063     ## browser compatibility issue.
1064     my $value = $t->{number};
1065     $t = $tt->get_next_token;
1066     return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1067     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1068     my $value = lc $t->{value}; ## TODO: case
1069     $t = $tt->get_next_token;
1070     if ($value eq 'inherit') {
1071     return ($t, {$prop_name => ['INHERIT']});
1072     }
1073     }
1074    
1075     $onerror->(type => 'syntax error:'.$prop_name,
1076     level => $self->{must_level},
1077     token => $t);
1078     return ($t, undef);
1079     },
1080     serialize => $default_serializer,
1081     initial => ['NUMBER', 2],
1082     inherited => 1,
1083     compute => $compute_as_specified,
1084     };
1085     $Attr->{orphans} = $Prop->{orphans};
1086     $Key->{orphans} = $Prop->{orphans};
1087    
1088     $Prop->{widows} = {
1089     css => 'widows',
1090     dom => 'widows',
1091     key => 'widows',
1092     parse => $Prop->{orphans}->{parse},
1093     serialize => $default_serializer,
1094     initial => ['NUMBER', 2],
1095     inherited => 1,
1096     compute => $compute_as_specified,
1097     };
1098     $Attr->{widows} = $Prop->{widows};
1099     $Key->{widows} = $Prop->{widows};
1100    
1101 wakaba 1.19 my $length_unit = {
1102     em => 1, ex => 1, px => 1,
1103     in => 1, cm => 1, mm => 1, pt => 1, pc => 1,
1104     };
1105    
1106 wakaba 1.18 $Prop->{'font-size'} = {
1107     css => 'font-size',
1108     dom => 'font_size',
1109     key => 'font_size',
1110     parse => sub {
1111     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1112    
1113     my $sign = 1;
1114     if ($t->{type} == MINUS_TOKEN) {
1115     $t = $tt->get_next_token;
1116     $sign = -1;
1117     }
1118    
1119     if ($t->{type} == DIMENSION_TOKEN) {
1120     my $value = $t->{number} * $sign;
1121     my $unit = lc $t->{value}; ## TODO: case
1122     $t = $tt->get_next_token;
1123 wakaba 1.19 if ($length_unit->{$unit} and $value >= 0) {
1124 wakaba 1.18 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1125     }
1126     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1127     my $value = $t->{number} * $sign;
1128     $t = $tt->get_next_token;
1129     return ($t, {$prop_name => ['PERCENTAGE', $value]}) if $value >= 0;
1130 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
1131 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
1132 wakaba 1.18 my $value = $t->{number} * $sign;
1133     $t = $tt->get_next_token;
1134     return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1135     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1136     my $value = lc $t->{value}; ## TODO: case
1137     $t = $tt->get_next_token;
1138     if ({
1139     'xx-small' => 1, 'x-small' => 1, small => 1, medium => 1,
1140     large => 1, 'x-large' => 1, 'xx-large' => 1,
1141     '-manakai-xxx-large' => 1, # -webkit-xxx-large
1142     larger => 1, smaller => 1,
1143     }->{$value}) {
1144     return ($t, {$prop_name => ['KEYWORD', $value]});
1145     } elsif ($value eq 'inherit') {
1146     return ($t, {$prop_name => ['INHERIT']});
1147     }
1148     }
1149    
1150     $onerror->(type => 'syntax error:'.$prop_name,
1151     level => $self->{must_level},
1152     token => $t);
1153     return ($t, undef);
1154     },
1155     serialize => $default_serializer,
1156     initial => ['KEYWORD', 'medium'],
1157     inherited => 1,
1158     compute => sub {
1159     my ($self, $element, $prop_name, $specified_value) = @_;
1160    
1161     if (defined $specified_value) {
1162     if ($specified_value->[0] eq 'DIMENSION') {
1163     my $unit = $specified_value->[2];
1164     my $value = $specified_value->[1];
1165    
1166     if ($unit eq 'em' or $unit eq 'ex') {
1167     $value *= 0.5 if $unit eq 'ex';
1168     ## TODO: Preferred way to determine the |ex| size is defined
1169     ## in CSS 2.1.
1170    
1171     my $parent_element = $element->manakai_parent_element;
1172     if (defined $parent_element) {
1173     $value *= $self->get_computed_value ($parent_element, $prop_name)
1174     ->[1];
1175     } else {
1176     $value *= $self->{font_size}->[3]; # medium
1177     }
1178     $unit = 'px';
1179     } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
1180     ($value *= 12, $unit = 'pc') if $unit eq 'pc';
1181     ($value /= 72, $unit = 'in') if $unit eq 'pt';
1182     ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
1183     ($value *= 10, $unit = 'mm') if $unit eq 'cm';
1184     ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
1185     }
1186 wakaba 1.19 ## else: consistency error
1187 wakaba 1.18
1188     return ['DIMENSION', $value, $unit];
1189     } elsif ($specified_value->[0] eq 'PERCENTAGE') {
1190     my $parent_element = $element->manakai_parent_element;
1191     my $parent_cv;
1192     if (defined $parent_element) {
1193     $parent_cv = $self->get_computed_value
1194     ($parent_element, $prop_name);
1195     } else {
1196     $parent_cv = [undef, $self->{font_size}->[3]];
1197     }
1198     return ['DIMENSION', $parent_cv->[1] * $specified_value->[1] / 100,
1199     'px'];
1200     } elsif ($specified_value->[0] eq 'KEYWORD') {
1201     if ($specified_value->[1] eq 'larger') {
1202     my $parent_element = $element->manakai_parent_element;
1203     if (defined $parent_element) {
1204     my $parent_cv = $self->get_computed_value
1205     ($parent_element, $prop_name);
1206     return ['DIMENSION',
1207     $self->{get_larger_font_size}->($self, $parent_cv->[1]),
1208     'px'];
1209     } else { ## 'larger' relative to 'medium', initial of 'font-size'
1210     return ['DIMENSION', $self->{font_size}->[4], 'px'];
1211     }
1212     } elsif ($specified_value->[1] eq 'smaller') {
1213     my $parent_element = $element->manakai_parent_element;
1214     if (defined $parent_element) {
1215     my $parent_cv = $self->get_computed_value
1216     ($parent_element, $prop_name);
1217     return ['DIMENSION',
1218     $self->{get_smaller_font_size}->($self, $parent_cv->[1]),
1219     'px'];
1220     } else { ## 'smaller' relative to 'medium', initial of 'font-size'
1221     return ['DIMENSION', $self->{font_size}->[2], 'px'];
1222     }
1223     } else {
1224     return ['DIMENSION', $self->{font_size}->[{
1225     'xx-small' => 0,
1226     'x-small' => 1,
1227     small => 2,
1228     medium => 3,
1229     large => 4,
1230     'x-large' => 5,
1231     'xx-large' => 6,
1232     '-manakai-xxx-large' => 7,
1233     }->{$specified_value->[1]}], 'px'];
1234     }
1235     }
1236     }
1237    
1238     return $specified_value;
1239     },
1240     };
1241     $Attr->{font_size} = $Prop->{'font-size'};
1242     $Key->{font_size} = $Prop->{'font-size'};
1243    
1244 wakaba 1.19 my $compute_length = sub {
1245     my ($self, $element, $prop_name, $specified_value) = @_;
1246    
1247     if (defined $specified_value) {
1248     if ($specified_value->[0] eq 'DIMENSION') {
1249     my $unit = $specified_value->[2];
1250     my $value = $specified_value->[1];
1251    
1252     if ($unit eq 'em' or $unit eq 'ex') {
1253     $value *= 0.5 if $unit eq 'ex';
1254     ## TODO: Preferred way to determine the |ex| size is defined
1255     ## in CSS 2.1.
1256    
1257     $value *= $self->get_computed_value ($element, 'font-size')->[1];
1258     $unit = 'px';
1259     } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
1260     ($value *= 12, $unit = 'pc') if $unit eq 'pc';
1261     ($value /= 72, $unit = 'in') if $unit eq 'pt';
1262     ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
1263     ($value *= 10, $unit = 'mm') if $unit eq 'cm';
1264     ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
1265     }
1266    
1267     return ['DIMENSION', $value, $unit];
1268     }
1269     }
1270    
1271     return $specified_value;
1272     }; # $compute_length
1273    
1274     $Prop->{'margin-top'} = {
1275     css => 'margin-top',
1276     dom => 'margin_top',
1277     key => 'margin_top',
1278     parse => sub {
1279     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1280    
1281 wakaba 1.21 ## NOTE: Used for 'margin-top', 'margin-right', 'margin-bottom',
1282     ## 'margin-left', 'top', 'right', 'bottom', and 'left'.
1283    
1284 wakaba 1.19 my $sign = 1;
1285     if ($t->{type} == MINUS_TOKEN) {
1286     $t = $tt->get_next_token;
1287     $sign = -1;
1288     }
1289    
1290     if ($t->{type} == DIMENSION_TOKEN) {
1291     my $value = $t->{number} * $sign;
1292     my $unit = lc $t->{value}; ## TODO: case
1293     $t = $tt->get_next_token;
1294     if ($length_unit->{$unit}) {
1295     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1296     }
1297     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1298     my $value = $t->{number} * $sign;
1299     $t = $tt->get_next_token;
1300     return ($t, {$prop_name => ['PERCENTAGE', $value]});
1301 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
1302 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
1303     my $value = $t->{number} * $sign;
1304     $t = $tt->get_next_token;
1305     return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
1306     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1307     my $value = lc $t->{value}; ## TODO: case
1308     $t = $tt->get_next_token;
1309     if ($value eq 'auto') {
1310     return ($t, {$prop_name => ['KEYWORD', $value]});
1311     } elsif ($value eq 'inherit') {
1312     return ($t, {$prop_name => ['INHERIT']});
1313     }
1314     }
1315    
1316     $onerror->(type => 'syntax error:'.$prop_name,
1317     level => $self->{must_level},
1318     token => $t);
1319     return ($t, undef);
1320     },
1321     serialize => $default_serializer,
1322     initial => ['DIMENSION', 0, 'px'],
1323     #inherited => 0,
1324     compute => $compute_length,
1325     };
1326     $Attr->{margin_top} = $Prop->{'margin-top'};
1327     $Key->{margin_top} = $Prop->{'margin-top'};
1328    
1329     $Prop->{'margin-bottom'} = {
1330     css => 'margin-bottom',
1331     dom => 'margin_bottom',
1332     key => 'margin_bottom',
1333     parse => $Prop->{'margin-top'}->{parse},
1334     serialize => $default_serializer,
1335     initial => ['DIMENSION', 0, 'px'],
1336     #inherited => 0,
1337     compute => $compute_length,
1338     };
1339     $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
1340     $Key->{margin_bottom} = $Prop->{'margin-bottom'};
1341    
1342     $Prop->{'margin-right'} = {
1343     css => 'margin-right',
1344     dom => 'margin_right',
1345     key => 'margin_right',
1346     parse => $Prop->{'margin-top'}->{parse},
1347     serialize => $default_serializer,
1348     initial => ['DIMENSION', 0, 'px'],
1349     #inherited => 0,
1350     compute => $compute_length,
1351     };
1352     $Attr->{margin_right} = $Prop->{'margin-right'};
1353     $Key->{margin_right} = $Prop->{'margin-right'};
1354    
1355     $Prop->{'margin-left'} = {
1356     css => 'margin-left',
1357     dom => 'margin_left',
1358     key => 'margin_left',
1359     parse => $Prop->{'margin-top'}->{parse},
1360     serialize => $default_serializer,
1361     initial => ['DIMENSION', 0, 'px'],
1362     #inherited => 0,
1363     compute => $compute_length,
1364     };
1365     $Attr->{margin_left} = $Prop->{'margin-left'};
1366     $Key->{margin_left} = $Prop->{'margin-left'};
1367    
1368 wakaba 1.21 $Prop->{top} = {
1369     css => 'top',
1370     dom => 'top',
1371     key => 'top',
1372     parse => $Prop->{'margin-top'}->{parse},
1373     serialize => $default_serializer,
1374     initial => ['KEYWORD', 'auto'],
1375     #inherited => 0,
1376     compute_multiple => sub {
1377     my ($self, $element, $eid, $prop_name) = @_;
1378    
1379     my $pos_value = $self->get_computed_value ($element, 'position');
1380     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1381     if ($pos_value->[1] eq 'static') {
1382     $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
1383     $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
1384     return;
1385     } elsif ($pos_value->[1] eq 'relative') {
1386     my $top_specified = $self->get_specified_value_no_inherit
1387     ($element, 'top');
1388     if (defined $top_specified and
1389     ($top_specified->[0] eq 'DIMENSION' or
1390     $top_specified->[0] eq 'PERCENTAGE')) {
1391     my $tv = $self->{computed_value}->{$eid}->{top}
1392     = $compute_length->($self, $element, 'top', $top_specified);
1393     $self->{computed_value}->{$eid}->{bottom}
1394     = [$tv->[0], -$tv->[1], $tv->[2]];
1395     } else { # top: auto
1396     my $bottom_specified = $self->get_specified_value_no_inherit
1397     ($element, 'bottom');
1398     if (defined $bottom_specified and
1399     ($bottom_specified->[0] eq 'DIMENSION' or
1400     $bottom_specified->[0] eq 'PERCENTAGE')) {
1401     my $tv = $self->{computed_value}->{$eid}->{bottom}
1402     = $compute_length->($self, $element, 'bottom',
1403     $bottom_specified);
1404     $self->{computed_value}->{$eid}->{top}
1405     = [$tv->[0], -$tv->[1], $tv->[2]];
1406     } else { # bottom: auto
1407     $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
1408     $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
1409     }
1410     }
1411     return;
1412     }
1413     }
1414    
1415     my $top_specified = $self->get_specified_value_no_inherit
1416     ($element, 'top');
1417     $self->{computed_value}->{$eid}->{top}
1418     = $compute_length->($self, $element, 'top', $top_specified);
1419     my $bottom_specified = $self->get_specified_value_no_inherit
1420     ($element, 'bottom');
1421     $self->{computed_value}->{$eid}->{bottom}
1422     = $compute_length->($self, $element, 'bottom', $bottom_specified);
1423     },
1424     };
1425     $Attr->{top} = $Prop->{top};
1426     $Key->{top} = $Prop->{top};
1427    
1428     $Prop->{bottom} = {
1429     css => 'bottom',
1430     dom => 'bottom',
1431     key => 'bottom',
1432     parse => $Prop->{'margin-top'}->{parse},
1433     serialize => $default_serializer,
1434     initial => ['KEYWORD', 'auto'],
1435     #inherited => 0,
1436     compute_multiple => $Prop->{top}->{compute_multiple},
1437     };
1438     $Attr->{bottom} = $Prop->{bottom};
1439     $Key->{bottom} = $Prop->{bottom};
1440    
1441     $Prop->{left} = {
1442     css => 'left',
1443     dom => 'left',
1444     key => 'left',
1445     parse => $Prop->{'margin-top'}->{parse},
1446     serialize => $default_serializer,
1447     initial => ['KEYWORD', 'auto'],
1448     #inherited => 0,
1449     compute_multiple => sub {
1450     my ($self, $element, $eid, $prop_name) = @_;
1451    
1452     my $pos_value = $self->get_computed_value ($element, 'position');
1453     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1454     if ($pos_value->[1] eq 'static') {
1455     $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
1456     $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
1457     return;
1458     } elsif ($pos_value->[1] eq 'relative') {
1459     my $left_specified = $self->get_specified_value_no_inherit
1460     ($element, 'left');
1461     if (defined $left_specified and
1462     ($left_specified->[0] eq 'DIMENSION' or
1463     $left_specified->[0] eq 'PERCENTAGE')) {
1464     my $right_specified = $self->get_specified_value_no_inherit
1465     ($element, 'right');
1466     if (defined $right_specified and
1467     ($right_specified->[0] eq 'DIMENSION' or
1468     $right_specified->[0] eq 'PERCENTAGE')) {
1469     my $direction = $self->get_computed_value ($element, 'direction');
1470     if (defined $direction and $direction->[0] eq 'KEYWORD' and
1471     $direction->[0] eq 'ltr') {
1472     my $tv = $self->{computed_value}->{$eid}->{left}
1473     = $compute_length->($self, $element, 'left',
1474     $left_specified);
1475     $self->{computed_value}->{$eid}->{right}
1476     = [$tv->[0], -$tv->[1], $tv->[2]];
1477     } else {
1478     my $tv = $self->{computed_value}->{$eid}->{right}
1479     = $compute_length->($self, $element, 'right',
1480     $right_specified);
1481     $self->{computed_value}->{$eid}->{left}
1482     = [$tv->[0], -$tv->[1], $tv->[2]];
1483     }
1484     } else {
1485     my $tv = $self->{computed_value}->{$eid}->{left}
1486     = $compute_length->($self, $element, 'left', $left_specified);
1487     $self->{computed_value}->{$eid}->{right}
1488     = [$tv->[0], -$tv->[1], $tv->[2]];
1489     }
1490     } else { # left: auto
1491     my $right_specified = $self->get_specified_value_no_inherit
1492     ($element, 'right');
1493     if (defined $right_specified and
1494     ($right_specified->[0] eq 'DIMENSION' or
1495     $right_specified->[0] eq 'PERCENTAGE')) {
1496     my $tv = $self->{computed_value}->{$eid}->{right}
1497     = $compute_length->($self, $element, 'right',
1498     $right_specified);
1499     $self->{computed_value}->{$eid}->{left}
1500     = [$tv->[0], -$tv->[1], $tv->[2]];
1501     } else { # right: auto
1502     $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
1503     $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
1504     }
1505     }
1506     return;
1507     }
1508     }
1509    
1510     my $left_specified = $self->get_specified_value_no_inherit
1511     ($element, 'left');
1512     $self->{computed_value}->{$eid}->{left}
1513     = $compute_length->($self, $element, 'left', $left_specified);
1514     my $right_specified = $self->get_specified_value_no_inherit
1515     ($element, 'right');
1516     $self->{computed_value}->{$eid}->{right}
1517     = $compute_length->($self, $element, 'right', $right_specified);
1518     },
1519     };
1520     $Attr->{left} = $Prop->{left};
1521     $Key->{left} = $Prop->{left};
1522    
1523     $Prop->{right} = {
1524     css => 'right',
1525     dom => 'right',
1526     key => 'right',
1527     parse => $Prop->{'margin-top'}->{parse},
1528     serialize => $default_serializer,
1529     initial => ['KEYWORD', 'auto'],
1530     #inherited => 0,
1531     compute_multiple => $Prop->{left}->{compute_multiple},
1532     };
1533     $Attr->{right} = $Prop->{right};
1534     $Key->{right} = $Prop->{right};
1535    
1536 wakaba 1.19 $Prop->{'padding-top'} = {
1537     css => 'padding-top',
1538     dom => 'padding_top',
1539     key => 'padding_top',
1540     parse => sub {
1541     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1542    
1543     my $sign = 1;
1544     if ($t->{type} == MINUS_TOKEN) {
1545     $t = $tt->get_next_token;
1546     $sign = -1;
1547     }
1548    
1549     if ($t->{type} == DIMENSION_TOKEN) {
1550     my $value = $t->{number} * $sign;
1551     my $unit = lc $t->{value}; ## TODO: case
1552     $t = $tt->get_next_token;
1553     if ($length_unit->{$unit} and $value >= 0) {
1554     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1555     }
1556     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1557     my $value = $t->{number} * $sign;
1558     $t = $tt->get_next_token;
1559     return ($t, {$prop_name => ['PERCENTAGE', $value]}) if $value >= 0;
1560 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
1561 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
1562     my $value = $t->{number} * $sign;
1563     $t = $tt->get_next_token;
1564     return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1565     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1566     my $value = lc $t->{value}; ## TODO: case
1567     $t = $tt->get_next_token;
1568     if ($value eq 'inherit') {
1569     return ($t, {$prop_name => ['INHERIT']});
1570     }
1571     }
1572    
1573     $onerror->(type => 'syntax error:'.$prop_name,
1574     level => $self->{must_level},
1575     token => $t);
1576     return ($t, undef);
1577     },
1578     serialize => $default_serializer,
1579     initial => ['DIMENSION', 0, 'px'],
1580     #inherited => 0,
1581     compute => $compute_length,
1582     };
1583     $Attr->{padding_top} = $Prop->{'padding-top'};
1584     $Key->{padding_top} = $Prop->{'padding-top'};
1585    
1586     $Prop->{'padding-bottom'} = {
1587     css => 'padding-bottom',
1588     dom => 'padding_bottom',
1589     key => 'padding_bottom',
1590     parse => $Prop->{'padding-top'}->{parse},
1591     serialize => $default_serializer,
1592     initial => ['DIMENSION', 0, 'px'],
1593     #inherited => 0,
1594     compute => $compute_length,
1595     };
1596     $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
1597     $Key->{padding_bottom} = $Prop->{'padding-bottom'};
1598    
1599     $Prop->{'padding-right'} = {
1600     css => 'padding-right',
1601     dom => 'padding_right',
1602     key => 'padding_right',
1603     parse => $Prop->{'padding-top'}->{parse},
1604     serialize => $default_serializer,
1605     initial => ['DIMENSION', 0, 'px'],
1606     #inherited => 0,
1607     compute => $compute_length,
1608     };
1609     $Attr->{padding_right} = $Prop->{'padding-right'};
1610     $Key->{padding_right} = $Prop->{'padding-right'};
1611    
1612     $Prop->{'padding-left'} = {
1613     css => 'padding-left',
1614     dom => 'padding_left',
1615     key => 'padding_left',
1616     parse => $Prop->{'padding-top'}->{parse},
1617     serialize => $default_serializer,
1618     initial => ['DIMENSION', 0, 'px'],
1619     #inherited => 0,
1620     compute => $compute_length,
1621     };
1622     $Attr->{padding_left} = $Prop->{'padding-left'};
1623     $Key->{padding_left} = $Prop->{'padding-left'};
1624    
1625 wakaba 1.20 $Prop->{'border-top-width'} = {
1626     css => 'border-top-width',
1627     dom => 'border_top_width',
1628     key => 'border_top_width',
1629     parse => sub {
1630     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1631    
1632     my $sign = 1;
1633     if ($t->{type} == MINUS_TOKEN) {
1634     $t = $tt->get_next_token;
1635     $sign = -1;
1636     }
1637    
1638     if ($t->{type} == DIMENSION_TOKEN) {
1639     my $value = $t->{number} * $sign;
1640     my $unit = lc $t->{value}; ## TODO: case
1641     $t = $tt->get_next_token;
1642     if ($length_unit->{$unit} and $value >= 0) {
1643     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1644     }
1645     } elsif ($t->{type} == NUMBER_TOKEN and
1646     ($self->{unitless_px} or $t->{number} == 0)) {
1647     my $value = $t->{number} * $sign;
1648     $t = $tt->get_next_token;
1649     return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1650     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1651     my $value = lc $t->{value}; ## TODO: case
1652     $t = $tt->get_next_token;
1653     if ({thin => 1, medium => 1, thick => 1}->{$value}) {
1654     return ($t, {$prop_name => ['KEYWORD', $value]});
1655     } elsif ($value eq 'inherit') {
1656     return ($t, {$prop_name => ['INHERIT']});
1657     }
1658     }
1659    
1660     $onerror->(type => 'syntax error:'.$prop_name,
1661     level => $self->{must_level},
1662     token => $t);
1663     return ($t, undef);
1664     },
1665     serialize => $default_serializer,
1666     initial => ['KEYWORD', 'medium'],
1667     #inherited => 0,
1668     compute => sub {
1669     my ($self, $element, $prop_name, $specified_value) = @_;
1670    
1671     my $style_prop = $prop_name;
1672     $style_prop =~ s/width/style/;
1673     my $style = $self->get_computed_value ($element, $style_prop);
1674     if (defined $style and $style->[0] eq 'KEYWORD' and
1675     ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
1676     return ['DIMENSION', 0, 'px'];
1677     }
1678    
1679     my $value = $compute_length->(@_);
1680     if (defined $value and $value->[0] eq 'KEYWORD') {
1681     if ($value->[1] eq 'thin') {
1682     return ['DIMENSION', 1, 'px']; ## Firefox/Opera
1683     } elsif ($value->[1] eq 'medium') {
1684     return ['DIMENSION', 3, 'px']; ## Firefox/Opera
1685     } elsif ($value->[1] eq 'thick') {
1686     return ['DIMENSION', 5, 'px']; ## Firefox
1687     }
1688     }
1689     return $value;
1690     },
1691     };
1692     $Attr->{border_top_width} = $Prop->{'border-top-width'};
1693     $Key->{border_top_width} = $Prop->{'border-top-width'};
1694    
1695     $Prop->{'border-right-width'} = {
1696     css => 'border-right-width',
1697     dom => 'border_right_width',
1698     key => 'border_right_width',
1699     parse => $Prop->{'border-top-width'}->{parse},
1700     serialize => $default_serializer,
1701     initial => ['KEYWORD', 'medium'],
1702     #inherited => 0,
1703     compute => $Prop->{'border-top-width'}->{compute},
1704     };
1705     $Attr->{border_right_width} = $Prop->{'border-right-width'};
1706     $Key->{border_right_width} = $Prop->{'border-right-width'};
1707    
1708     $Prop->{'border-bottom-width'} = {
1709     css => 'border-bottom-width',
1710     dom => 'border_bottom_width',
1711     key => 'border_bottom_width',
1712     parse => $Prop->{'border-top-width'}->{parse},
1713     serialize => $default_serializer,
1714     initial => ['KEYWORD', 'medium'],
1715     #inherited => 0,
1716     compute => $Prop->{'border-top-width'}->{compute},
1717     };
1718     $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
1719     $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
1720    
1721     $Prop->{'border-left-width'} = {
1722     css => 'border-left-width',
1723     dom => 'border_left_width',
1724     key => 'border_left_width',
1725     parse => $Prop->{'border-top-width'}->{parse},
1726     serialize => $default_serializer,
1727     initial => ['KEYWORD', 'medium'],
1728     #inherited => 0,
1729     compute => $Prop->{'border-top-width'}->{compute},
1730     };
1731     $Attr->{border_left_width} = $Prop->{'border-left-width'};
1732     $Key->{border_left_width} = $Prop->{'border-left-width'};
1733    
1734 wakaba 1.15 $Prop->{'font-weight'} = {
1735     css => 'font-weight',
1736     dom => 'font_weight',
1737     key => 'font_weight',
1738     parse => sub {
1739     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1740    
1741     if ($t->{type} == NUMBER_TOKEN) {
1742     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
1743     ## browser compatibility issue.
1744     my $value = $t->{number};
1745     $t = $tt->get_next_token;
1746     if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
1747     return ($t, {$prop_name => ['WEIGHT', $value, 0]});
1748     }
1749     } elsif ($t->{type} == IDENT_TOKEN) {
1750     my $value = lc $t->{value}; ## TODO: case
1751     $t = $tt->get_next_token;
1752     if ({
1753     normal => 1, bold => 1, bolder => 1, lighter => 1,
1754     }->{$value}) {
1755     return ($t, {$prop_name => ['KEYWORD', $value]});
1756     } elsif ($value eq 'inherit') {
1757     return ($t, {$prop_name => ['INHERIT']});
1758     }
1759     }
1760    
1761     $onerror->(type => 'syntax error:'.$prop_name,
1762     level => $self->{must_level},
1763     token => $t);
1764     return ($t, undef);
1765     },
1766     serialize => $default_serializer,
1767     initial => ['KEYWORD', 'normal'],
1768     inherited => 1,
1769     compute => sub {
1770     my ($self, $element, $prop_name, $specified_value) = @_;
1771    
1772 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1773 wakaba 1.15 if ($specified_value->[1] eq 'normal') {
1774     return ['WEIGHT', 400, 0];
1775     } elsif ($specified_value->[1] eq 'bold') {
1776     return ['WEIGHT', 700, 0];
1777     } elsif ($specified_value->[1] eq 'bolder') {
1778     my $parent_element = $element->manakai_parent_element;
1779     if (defined $parent_element) {
1780     my $parent_value = $self->get_cascaded_value
1781     ($parent_element, $prop_name); ## NOTE: What Firefox does.
1782     return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
1783     } else {
1784     return ['WEIGHT', 400, 1];
1785     }
1786     } elsif ($specified_value->[1] eq 'lighter') {
1787     my $parent_element = $element->manakai_parent_element;
1788     if (defined $parent_element) {
1789     my $parent_value = $self->get_cascaded_value
1790     ($parent_element, $prop_name); ## NOTE: What Firefox does.
1791     return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
1792     } else {
1793     return ['WEIGHT', 400, 1];
1794     }
1795     }
1796 wakaba 1.17 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
1797 wakaba 1.15 #
1798     }
1799    
1800     return $specified_value;
1801     },
1802     };
1803     $Attr->{font_weight} = $Prop->{'font-weight'};
1804     $Key->{font_weight} = $Prop->{'font-weight'};
1805    
1806 wakaba 1.13 my $uri_or_none_parser = sub {
1807 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1808    
1809 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
1810 wakaba 1.11 my $value = $t->{value};
1811     $t = $tt->get_next_token;
1812     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
1813     } elsif ($t->{type} == IDENT_TOKEN) {
1814     my $value = lc $t->{value}; ## TODO: case
1815     $t = $tt->get_next_token;
1816     if ($value eq 'none') {
1817     ## NOTE: |none| is the default value and therefore it must be
1818     ## supported anyway.
1819     return ($t, {$prop_name => ["KEYWORD", 'none']});
1820     } elsif ($value eq 'inherit') {
1821     return ($t, {$prop_name => ['INHERIT']});
1822     }
1823     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
1824     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
1825     # my $value = $t->{value};
1826     # $t = $tt->get_next_token;
1827     # if ($t->{type} == EOF_TOKEN) {
1828     # $onerror->(type => 'syntax error:eof:'.$prop_name,
1829     # level => $self->{must_level},
1830     # token => $t);
1831     #
1832     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
1833     # }
1834     }
1835    
1836     $onerror->(type => 'syntax error:'.$prop_name,
1837     level => $self->{must_level},
1838     token => $t);
1839     return ($t, undef);
1840 wakaba 1.13 }; # $uri_or_none_parser
1841    
1842 wakaba 1.14 my $compute_uri_or_none = sub {
1843 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
1844    
1845     if (defined $specified_value and
1846     $specified_value->[0] eq 'URI' and
1847     defined $specified_value->[2]) {
1848     require Message::DOM::DOMImplementation;
1849     return ['URI',
1850     Message::DOM::DOMImplementation->create_uri_reference
1851     ($specified_value->[1])
1852     ->get_absolute_reference (${$specified_value->[2]})
1853     ->get_uri_reference,
1854     $specified_value->[2]];
1855     }
1856    
1857     return $specified_value;
1858 wakaba 1.14 }; # $compute_uri_or_none
1859    
1860     $Prop->{'list-style-image'} = {
1861     css => 'list-style-image',
1862     dom => 'list_style_image',
1863     key => 'list_style_image',
1864     parse => $uri_or_none_parser,
1865     serialize => $default_serializer,
1866     initial => ['KEYWORD', 'none'],
1867     inherited => 1,
1868     compute => $compute_uri_or_none,
1869 wakaba 1.11 };
1870     $Attr->{list_style_image} = $Prop->{'list-style-image'};
1871     $Key->{list_style_image} = $Prop->{'list-style-image'};
1872    
1873 wakaba 1.15 $Prop->{'background-image'} = {
1874     css => 'background-image',
1875     dom => 'background_image',
1876     key => 'background_image',
1877     parse => $uri_or_none_parser,
1878     serialize => $default_serializer,
1879     initial => ['KEYWORD', 'none'],
1880     #inherited => 0,
1881     compute => $compute_uri_or_none,
1882     };
1883     $Attr->{background_image} = $Prop->{'background-image'};
1884     $Key->{background_image} = $Prop->{'background-image'};
1885    
1886 wakaba 1.7 my $border_style_keyword = {
1887     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
1888     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
1889     };
1890    
1891     $Prop->{'border-top-style'} = {
1892     css => 'border-top-style',
1893     dom => 'border_top_style',
1894     key => 'border_top_style',
1895     parse => $one_keyword_parser,
1896 wakaba 1.11 serialize => $default_serializer,
1897 wakaba 1.7 keyword => $border_style_keyword,
1898 wakaba 1.9 initial => ["KEYWORD", "none"],
1899     #inherited => 0,
1900     compute => $compute_as_specified,
1901 wakaba 1.7 };
1902     $Attr->{border_top_style} = $Prop->{'border-top-style'};
1903     $Key->{border_top_style} = $Prop->{'border-top-style'};
1904    
1905     $Prop->{'border-right-style'} = {
1906     css => 'border-right-style',
1907     dom => 'border_right_style',
1908     key => 'border_right_style',
1909     parse => $one_keyword_parser,
1910 wakaba 1.11 serialize => $default_serializer,
1911 wakaba 1.7 keyword => $border_style_keyword,
1912 wakaba 1.9 initial => ["KEYWORD", "none"],
1913     #inherited => 0,
1914     compute => $compute_as_specified,
1915 wakaba 1.7 };
1916     $Attr->{border_right_style} = $Prop->{'border-right-style'};
1917     $Key->{border_right_style} = $Prop->{'border-right-style'};
1918    
1919     $Prop->{'border-bottom-style'} = {
1920     css => 'border-bottom-style',
1921     dom => 'border_bottom_style',
1922     key => 'border_bottom_style',
1923     parse => $one_keyword_parser,
1924 wakaba 1.11 serialize => $default_serializer,
1925 wakaba 1.7 keyword => $border_style_keyword,
1926 wakaba 1.9 initial => ["KEYWORD", "none"],
1927     #inherited => 0,
1928     compute => $compute_as_specified,
1929 wakaba 1.7 };
1930     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
1931     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
1932    
1933     $Prop->{'border-left-style'} = {
1934     css => 'border-left-style',
1935     dom => 'border_left_style',
1936     key => 'border_left_style',
1937     parse => $one_keyword_parser,
1938 wakaba 1.11 serialize => $default_serializer,
1939 wakaba 1.7 keyword => $border_style_keyword,
1940 wakaba 1.9 initial => ["KEYWORD", "none"],
1941     #inherited => 0,
1942     compute => $compute_as_specified,
1943 wakaba 1.7 };
1944     $Attr->{border_left_style} = $Prop->{'border-left-style'};
1945     $Key->{border_left_style} = $Prop->{'border-left-style'};
1946    
1947 wakaba 1.16 $Prop->{'outline-style'} = {
1948     css => 'outline-style',
1949     dom => 'outline_style',
1950     key => 'outline_style',
1951     parse => $one_keyword_parser,
1952     serialize => $default_serializer,
1953     keyword => $border_style_keyword,
1954     initial => ['KEYWORD', 'none'],
1955     #inherited => 0,
1956     compute => $compute_as_specified,
1957     };
1958     $Attr->{outline_style} = $Prop->{'outline-style'};
1959     $Key->{outline_style} = $Prop->{'outline-style'};
1960    
1961 wakaba 1.15 $Prop->{'font-family'} = {
1962     css => 'font-family',
1963     dom => 'font_family',
1964     key => 'font_family',
1965     parse => sub {
1966     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1967    
1968     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
1969     ## how chaotic browsers are!
1970    
1971     my @prop_value;
1972    
1973     my $font_name = '';
1974     my $may_be_generic = 1;
1975     my $may_be_inherit = 1;
1976     my $has_s = 0;
1977     F: {
1978     if ($t->{type} == IDENT_TOKEN) {
1979     undef $may_be_inherit if $has_s or length $font_name;
1980     undef $may_be_generic if $has_s or length $font_name;
1981     $font_name .= ' ' if $has_s;
1982     $font_name .= $t->{value};
1983     undef $has_s;
1984     $t = $tt->get_next_token;
1985     } elsif ($t->{type} == STRING_TOKEN) {
1986     $font_name .= ' ' if $has_s;
1987     $font_name .= $t->{value};
1988     undef $may_be_inherit;
1989     undef $may_be_generic;
1990     undef $has_s;
1991     $t = $tt->get_next_token;
1992     } elsif ($t->{type} == COMMA_TOKEN) {
1993     if ($may_be_generic and
1994     {
1995     serif => 1, 'sans-serif' => 1, cursive => 1,
1996     fantasy => 1, monospace => 1, '-manakai-default' => 1,
1997     }->{lc $font_name}) { ## TODO: case
1998     push @prop_value, ['KEYWORD', $font_name];
1999     } elsif (not $may_be_generic or length $font_name) {
2000     push @prop_value, ["STRING", $font_name];
2001     }
2002     undef $may_be_inherit;
2003     $may_be_generic = 1;
2004     undef $has_s;
2005     $font_name = '';
2006     $t = $tt->get_next_token;
2007     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2008     } elsif ($t->{type} == S_TOKEN) {
2009     $has_s = 1;
2010     $t = $tt->get_next_token;
2011     } else {
2012     if ($may_be_generic and
2013     {
2014     serif => 1, 'sans-serif' => 1, cursive => 1,
2015     fantasy => 1, monospace => 1, '-manakai-default' => 1,
2016     }->{lc $font_name}) { ## TODO: case
2017     push @prop_value, ['KEYWORD', $font_name];
2018     } elsif (not $may_be_generic or length $font_name) {
2019     push @prop_value, ['STRING', $font_name];
2020     } else {
2021     $onerror->(type => 'syntax error:'.$prop_name,
2022     level => $self->{must_level},
2023     token => $t);
2024     return ($t, undef);
2025     }
2026     last F;
2027     }
2028     redo F;
2029     } # F
2030    
2031     if ($may_be_inherit and
2032     @prop_value == 1 and
2033     $prop_value[0]->[0] eq 'STRING' and
2034     lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
2035     return ($t, {$prop_name => ['INHERIT']});
2036     } else {
2037     unshift @prop_value, 'FONT';
2038     return ($t, {$prop_name => \@prop_value});
2039     }
2040     },
2041     serialize => sub {
2042     my ($self, $prop_name, $value) = @_;
2043    
2044     if ($value->[0] eq 'FONT') {
2045     return join ', ', map {
2046     if ($_->[0] eq 'STRING') {
2047     '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
2048     } elsif ($_->[0] eq 'KEYWORD') {
2049     $_->[1]; ## NOTE: This is what Firefox does.
2050     } else {
2051     ## NOTE: This should be an error.
2052     '""';
2053     }
2054     } @$value[1..$#$value];
2055     } elsif ($value->[0] eq 'INHERIT') {
2056     return 'inherit';
2057     } else {
2058     return undef;
2059     }
2060     },
2061     initial => ['FONT', ['KEYWORD', '-manakai-default']],
2062     inherited => 1,
2063     compute => $compute_as_specified,
2064     };
2065     $Attr->{font_family} = $Prop->{'font-family'};
2066     $Key->{font_family} = $Prop->{'font-family'};
2067    
2068 wakaba 1.17 $Prop->{cursor} = {
2069     css => 'cursor',
2070     dom => 'cursor',
2071     key => 'cursor',
2072     parse => sub {
2073     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2074    
2075     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
2076     ## compatibility issues.
2077    
2078     my @prop_value = ('CURSOR');
2079    
2080     F: {
2081     if ($t->{type} == IDENT_TOKEN) {
2082     my $v = lc $t->{value}; ## TODO: case
2083     $t = $tt->get_next_token;
2084     if ($Prop->{$prop_name}->{keyword}->{$v}) {
2085     push @prop_value, ['KEYWORD', $v];
2086     last F;
2087     } elsif ($v eq 'inherit' and @prop_value == 1) {
2088     return ($t, {$prop_name => ['INHERIT']});
2089     } else {
2090     $onerror->(type => 'syntax error:'.$prop_name,
2091     level => $self->{must_level},
2092     token => $t);
2093     return ($t, undef);
2094     }
2095     } elsif ($t->{type} == URI_TOKEN) {
2096     push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
2097     $t = $tt->get_next_token;
2098     } else {
2099     $onerror->(type => 'syntax error:'.$prop_name,
2100     level => $self->{must_level},
2101     token => $t);
2102     return ($t, undef);
2103     }
2104    
2105     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2106     if ($t->{type} == COMMA_TOKEN) {
2107     $t = $tt->get_next_token;
2108     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2109     redo F;
2110     }
2111     } # F
2112    
2113     return ($t, {$prop_name => \@prop_value});
2114     },
2115     serialize => sub {
2116     my ($self, $prop_name, $value) = @_;
2117    
2118     if ($value->[0] eq 'CURSOR') {
2119     return join ', ', map {
2120     if ($_->[0] eq 'URI') {
2121     'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
2122     } elsif ($_->[0] eq 'KEYWORD') {
2123     $_->[1];
2124     } else {
2125     ## NOTE: This should be an error.
2126     '""';
2127     }
2128     } @$value[1..$#$value];
2129     } elsif ($value->[0] eq 'INHERIT') {
2130     return 'inherit';
2131     } else {
2132     return undef;
2133     }
2134     },
2135     keyword => {
2136     auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
2137     'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
2138     'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
2139     'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
2140     },
2141     initial => ['CURSOR', ['KEYWORD', 'auto']],
2142     inherited => 1,
2143     compute => sub {
2144     my ($self, $element, $prop_name, $specified_value) = @_;
2145    
2146     if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
2147     my @new_value = ('CURSOR');
2148     for my $value (@$specified_value[1..$#$specified_value]) {
2149     if ($value->[0] eq 'URI') {
2150     if (defined $value->[2]) {
2151     require Message::DOM::DOMImplementation;
2152     push @new_value, ['URI',
2153     Message::DOM::DOMImplementation
2154     ->create_uri_reference ($value->[1])
2155     ->get_absolute_reference (${$value->[2]})
2156     ->get_uri_reference,
2157     $value->[2]];
2158     } else {
2159     push @new_value, $value;
2160     }
2161     } else {
2162     push @new_value, $value;
2163     }
2164     }
2165     return \@new_value;
2166     }
2167    
2168     return $specified_value;
2169     },
2170     };
2171     $Attr->{cursor} = $Prop->{cursor};
2172     $Key->{cursor} = $Prop->{cursor};
2173    
2174 wakaba 1.7 $Prop->{'border-style'} = {
2175     css => 'border-style',
2176     dom => 'border_style',
2177     parse => sub {
2178     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2179    
2180     my %prop_value;
2181     if ($t->{type} == IDENT_TOKEN) {
2182     my $prop_value = lc $t->{value}; ## TODO: case folding
2183     $t = $tt->get_next_token;
2184     if ($border_style_keyword->{$prop_value} and
2185     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
2186     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
2187     } elsif ($prop_value eq 'inherit') {
2188 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
2189 wakaba 1.19 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2190     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2191     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2192     return ($t, \%prop_value);
2193 wakaba 1.7 } else {
2194     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2195     level => $self->{must_level},
2196     token => $t);
2197     return ($t, undef);
2198     }
2199     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2200     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2201     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2202     } else {
2203     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2204     level => $self->{must_level},
2205     token => $t);
2206     return ($t, undef);
2207     }
2208    
2209     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2210     if ($t->{type} == IDENT_TOKEN) {
2211     my $prop_value = lc $t->{value}; ## TODO: case folding
2212     $t = $tt->get_next_token;
2213 wakaba 1.19 if ($border_style_keyword->{$prop_value} and
2214 wakaba 1.7 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
2215     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
2216     } else {
2217     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2218     level => $self->{must_level},
2219     token => $t);
2220     return ($t, undef);
2221     }
2222     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2223    
2224     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2225     if ($t->{type} == IDENT_TOKEN) {
2226     my $prop_value = lc $t->{value}; ## TODO: case folding
2227     $t = $tt->get_next_token;
2228     if ($border_style_keyword->{$prop_value} and
2229     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
2230     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
2231     } else {
2232     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2233     level => $self->{must_level},
2234     token => $t);
2235     return ($t, undef);
2236     }
2237    
2238     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2239     if ($t->{type} == IDENT_TOKEN) {
2240     my $prop_value = lc $t->{value}; ## TODO: case folding
2241     $t = $tt->get_next_token;
2242     if ($border_style_keyword->{$prop_value} and
2243     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
2244     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
2245     } else {
2246     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2247     level => $self->{must_level},
2248     token => $t);
2249     return ($t, undef);
2250     }
2251     }
2252     }
2253     }
2254    
2255     return ($t, \%prop_value);
2256     },
2257     serialize => sub {
2258     my ($self, $prop_name, $value) = @_;
2259    
2260     local $Error::Depth = $Error::Depth + 1;
2261     my @v;
2262     push @v, $self->border_top_style;
2263     return undef unless defined $v[-1];
2264     push @v, $self->border_right_style;
2265     return undef unless defined $v[-1];
2266     push @v, $self->border_bottom_style;
2267     return undef unless defined $v[-1];
2268 wakaba 1.19 push @v, $self->border_left_style;
2269 wakaba 1.7 return undef unless defined $v[-1];
2270    
2271     pop @v if $v[1] eq $v[3];
2272     pop @v if $v[0] eq $v[2];
2273     pop @v if $v[0] eq $v[1];
2274     return join ' ', @v;
2275     },
2276     };
2277     $Attr->{border_style} = $Prop->{'border-style'};
2278    
2279 wakaba 1.19 $Prop->{margin} = {
2280     css => 'margin',
2281     dom => 'margin',
2282     parse => sub {
2283     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2284    
2285     my %prop_value;
2286    
2287     my $sign = 1;
2288     if ($t->{type} == MINUS_TOKEN) {
2289     $t = $tt->get_next_token;
2290     $sign = -1;
2291     }
2292    
2293     if ($t->{type} == DIMENSION_TOKEN) {
2294     my $value = $t->{number} * $sign;
2295     my $unit = lc $t->{value}; ## TODO: case
2296     $t = $tt->get_next_token;
2297     if ($length_unit->{$unit}) {
2298     $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
2299     } else {
2300     $onerror->(type => 'syntax error:'.$prop_name,
2301     level => $self->{must_level},
2302     token => $t);
2303     return ($t, undef);
2304     }
2305     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2306     my $value = $t->{number} * $sign;
2307     $t = $tt->get_next_token;
2308     $prop_value{'margin-top'} = ['PERCENTAGE', $value];
2309 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2310 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2311     my $value = $t->{number} * $sign;
2312     $t = $tt->get_next_token;
2313     $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
2314     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2315     my $prop_value = lc $t->{value}; ## TODO: case folding
2316     $t = $tt->get_next_token;
2317     if ($prop_value eq 'auto') {
2318     $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
2319     } elsif ($prop_value eq 'inherit') {
2320     $prop_value{'margin-top'} = ['INHERIT'];
2321     $prop_value{'margin-right'} = $prop_value{'margin-top'};
2322     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2323     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2324     return ($t, \%prop_value);
2325     } else {
2326     $onerror->(type => 'syntax error:'.$prop_name,
2327     level => $self->{must_level},
2328     token => $t);
2329     return ($t, undef);
2330     }
2331     } else {
2332     $onerror->(type => 'syntax error:'.$prop_name,
2333     level => $self->{must_level},
2334     token => $t);
2335     return ($t, undef);
2336     }
2337     $prop_value{'margin-right'} = $prop_value{'margin-top'};
2338     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2339     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2340    
2341     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2342     $sign = 1;
2343     if ($t->{type} == MINUS_TOKEN) {
2344     $t = $tt->get_next_token;
2345     $sign = -1;
2346     }
2347    
2348     if ($t->{type} == DIMENSION_TOKEN) {
2349     my $value = $t->{number} * $sign;
2350     my $unit = lc $t->{value}; ## TODO: case
2351     $t = $tt->get_next_token;
2352     if ($length_unit->{$unit}) {
2353     $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
2354     } else {
2355     $onerror->(type => 'syntax error:'.$prop_name,
2356     level => $self->{must_level},
2357     token => $t);
2358     return ($t, undef);
2359     }
2360     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2361     my $value = $t->{number} * $sign;
2362     $t = $tt->get_next_token;
2363     $prop_value{'margin-right'} = ['PERCENTAGE', $value];
2364 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2365 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2366     my $value = $t->{number} * $sign;
2367     $t = $tt->get_next_token;
2368     $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
2369     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2370     my $prop_value = lc $t->{value}; ## TODO: case folding
2371     $t = $tt->get_next_token;
2372     if ($prop_value eq 'auto') {
2373     $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
2374     } else {
2375     $onerror->(type => 'syntax error:'.$prop_name,
2376     level => $self->{must_level},
2377     token => $t);
2378     return ($t, undef);
2379     }
2380     } else {
2381     return ($t, \%prop_value);
2382     }
2383     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2384    
2385     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2386     $sign = 1;
2387     if ($t->{type} == MINUS_TOKEN) {
2388     $t = $tt->get_next_token;
2389     $sign = -1;
2390     }
2391    
2392     if ($t->{type} == DIMENSION_TOKEN) {
2393     my $value = $t->{number} * $sign;
2394     my $unit = lc $t->{value}; ## TODO: case
2395     $t = $tt->get_next_token;
2396     if ($length_unit->{$unit}) {
2397     $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
2398     } else {
2399     $onerror->(type => 'syntax error:'.$prop_name,
2400     level => $self->{must_level},
2401     token => $t);
2402     return ($t, undef);
2403     }
2404     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2405     my $value = $t->{number} * $sign;
2406     $t = $tt->get_next_token;
2407     $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
2408 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2409 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2410     my $value = $t->{number} * $sign;
2411     $t = $tt->get_next_token;
2412     $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
2413     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2414     my $prop_value = lc $t->{value}; ## TODO: case folding
2415     $t = $tt->get_next_token;
2416     if ($prop_value eq 'auto') {
2417     $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
2418     } else {
2419     $onerror->(type => 'syntax error:'.$prop_name,
2420     level => $self->{must_level},
2421     token => $t);
2422     return ($t, undef);
2423     }
2424     } else {
2425     return ($t, \%prop_value);
2426     }
2427    
2428     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2429     $sign = 1;
2430     if ($t->{type} == MINUS_TOKEN) {
2431     $t = $tt->get_next_token;
2432     $sign = -1;
2433     }
2434    
2435     if ($t->{type} == DIMENSION_TOKEN) {
2436     my $value = $t->{number} * $sign;
2437     my $unit = lc $t->{value}; ## TODO: case
2438     $t = $tt->get_next_token;
2439     if ($length_unit->{$unit}) {
2440     $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
2441     } else {
2442     $onerror->(type => 'syntax error:'.$prop_name,
2443     level => $self->{must_level},
2444     token => $t);
2445     return ($t, undef);
2446     }
2447     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2448     my $value = $t->{number} * $sign;
2449     $t = $tt->get_next_token;
2450     $prop_value{'margin-left'} = ['PERCENTAGE', $value];
2451 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2452 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2453     my $value = $t->{number} * $sign;
2454     $t = $tt->get_next_token;
2455     $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
2456     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2457     my $prop_value = lc $t->{value}; ## TODO: case folding
2458     $t = $tt->get_next_token;
2459     if ($prop_value eq 'auto') {
2460     $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
2461     } else {
2462     $onerror->(type => 'syntax error:'.$prop_name,
2463     level => $self->{must_level},
2464     token => $t);
2465     return ($t, undef);
2466     }
2467     } else {
2468     return ($t, \%prop_value);
2469     }
2470    
2471     return ($t, \%prop_value);
2472     },
2473     serialize => sub {
2474     my ($self, $prop_name, $value) = @_;
2475    
2476     local $Error::Depth = $Error::Depth + 1;
2477     my @v;
2478     push @v, $self->margin_top;
2479     return undef unless defined $v[-1];
2480     push @v, $self->margin_right;
2481     return undef unless defined $v[-1];
2482     push @v, $self->margin_bottom;
2483     return undef unless defined $v[-1];
2484     push @v, $self->margin_left;
2485     return undef unless defined $v[-1];
2486    
2487     pop @v if $v[1] eq $v[3];
2488     pop @v if $v[0] eq $v[2];
2489     pop @v if $v[0] eq $v[1];
2490     return join ' ', @v;
2491     },
2492     };
2493     $Attr->{margin} = $Prop->{margin};
2494    
2495     $Prop->{padding} = {
2496     css => 'padding',
2497     dom => 'padding',
2498     parse => sub {
2499     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2500    
2501     my %prop_value;
2502    
2503     my $sign = 1;
2504     if ($t->{type} == MINUS_TOKEN) {
2505     $t = $tt->get_next_token;
2506     $sign = -1;
2507     }
2508    
2509     if ($t->{type} == DIMENSION_TOKEN) {
2510     my $value = $t->{number} * $sign;
2511     my $unit = lc $t->{value}; ## TODO: case
2512     $t = $tt->get_next_token;
2513     if ($length_unit->{$unit} and $value >= 0) {
2514     $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
2515     } else {
2516     $onerror->(type => 'syntax error:'.$prop_name,
2517     level => $self->{must_level},
2518     token => $t);
2519     return ($t, undef);
2520     }
2521     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2522     my $value = $t->{number} * $sign;
2523     $t = $tt->get_next_token;
2524     $prop_value{'padding-top'} = ['PERCENTAGE', $value];
2525     unless ($value >= 0) {
2526     $onerror->(type => 'syntax error:'.$prop_name,
2527     level => $self->{must_level},
2528     token => $t);
2529     return ($t, undef);
2530     }
2531 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2532 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2533     my $value = $t->{number} * $sign;
2534     $t = $tt->get_next_token;
2535     $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
2536     unless ($value >= 0) {
2537     $onerror->(type => 'syntax error:'.$prop_name,
2538     level => $self->{must_level},
2539     token => $t);
2540     return ($t, undef);
2541     }
2542     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2543     my $prop_value = lc $t->{value}; ## TODO: case folding
2544     $t = $tt->get_next_token;
2545     if ($prop_value eq 'inherit') {
2546     $prop_value{'padding-top'} = ['INHERIT'];
2547     $prop_value{'padding-right'} = $prop_value{'padding-top'};
2548     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2549     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2550     return ($t, \%prop_value);
2551     } else {
2552     $onerror->(type => 'syntax error:'.$prop_name,
2553     level => $self->{must_level},
2554     token => $t);
2555     return ($t, undef);
2556     }
2557     } else {
2558     $onerror->(type => 'syntax error:'.$prop_name,
2559     level => $self->{must_level},
2560     token => $t);
2561     return ($t, undef);
2562     }
2563     $prop_value{'padding-right'} = $prop_value{'padding-top'};
2564     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2565     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2566    
2567     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2568     $sign = 1;
2569     if ($t->{type} == MINUS_TOKEN) {
2570     $t = $tt->get_next_token;
2571     $sign = -1;
2572     }
2573    
2574     if ($t->{type} == DIMENSION_TOKEN) {
2575     my $value = $t->{number} * $sign;
2576     my $unit = lc $t->{value}; ## TODO: case
2577     $t = $tt->get_next_token;
2578     if ($length_unit->{$unit} and $value >= 0) {
2579     $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
2580     } else {
2581     $onerror->(type => 'syntax error:'.$prop_name,
2582     level => $self->{must_level},
2583     token => $t);
2584     return ($t, undef);
2585     }
2586     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2587     my $value = $t->{number} * $sign;
2588     $t = $tt->get_next_token;
2589     $prop_value{'padding-right'} = ['PERCENTAGE', $value];
2590     unless ($value >= 0) {
2591     $onerror->(type => 'syntax error:'.$prop_name,
2592     level => $self->{must_level},
2593     token => $t);
2594     return ($t, undef);
2595     }
2596 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2597 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2598     my $value = $t->{number} * $sign;
2599     $t = $tt->get_next_token;
2600     $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
2601     unless ($value >= 0) {
2602     $onerror->(type => 'syntax error:'.$prop_name,
2603     level => $self->{must_level},
2604     token => $t);
2605     return ($t, undef);
2606     }
2607     } else {
2608     return ($t, \%prop_value);
2609     }
2610     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2611    
2612     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2613     $sign = 1;
2614     if ($t->{type} == MINUS_TOKEN) {
2615     $t = $tt->get_next_token;
2616     $sign = -1;
2617     }
2618    
2619     if ($t->{type} == DIMENSION_TOKEN) {
2620     my $value = $t->{number} * $sign;
2621     my $unit = lc $t->{value}; ## TODO: case
2622     $t = $tt->get_next_token;
2623     if ($length_unit->{$unit} and $value >= 0) {
2624     $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
2625     } else {
2626     $onerror->(type => 'syntax error:'.$prop_name,
2627     level => $self->{must_level},
2628     token => $t);
2629     return ($t, undef);
2630     }
2631     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2632     my $value = $t->{number} * $sign;
2633     $t = $tt->get_next_token;
2634     $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
2635     unless ($value >= 0) {
2636     $onerror->(type => 'syntax error:'.$prop_name,
2637     level => $self->{must_level},
2638     token => $t);
2639     return ($t, undef);
2640     }
2641 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2642 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2643     my $value = $t->{number} * $sign;
2644     $t = $tt->get_next_token;
2645     $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
2646     unless ($value >= 0) {
2647     $onerror->(type => 'syntax error:'.$prop_name,
2648     level => $self->{must_level},
2649     token => $t);
2650     return ($t, undef);
2651     }
2652     } else {
2653     return ($t, \%prop_value);
2654     }
2655    
2656     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2657     $sign = 1;
2658     if ($t->{type} == MINUS_TOKEN) {
2659     $t = $tt->get_next_token;
2660     $sign = -1;
2661     }
2662    
2663     if ($t->{type} == DIMENSION_TOKEN) {
2664     my $value = $t->{number} * $sign;
2665     my $unit = lc $t->{value}; ## TODO: case
2666     $t = $tt->get_next_token;
2667     if ($length_unit->{$unit} and $value >= 0) {
2668     $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
2669     } else {
2670     $onerror->(type => 'syntax error:'.$prop_name,
2671     level => $self->{must_level},
2672     token => $t);
2673     return ($t, undef);
2674     }
2675     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2676     my $value = $t->{number} * $sign;
2677     $t = $tt->get_next_token;
2678     $prop_value{'padding-left'} = ['PERCENTAGE', $value];
2679     unless ($value >= 0) {
2680     $onerror->(type => 'syntax error:'.$prop_name,
2681     level => $self->{must_level},
2682     token => $t);
2683     return ($t, undef);
2684     }
2685 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2686 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2687     my $value = $t->{number} * $sign;
2688     $t = $tt->get_next_token;
2689     $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
2690     unless ($value >= 0) {
2691     $onerror->(type => 'syntax error:'.$prop_name,
2692     level => $self->{must_level},
2693     token => $t);
2694     return ($t, undef);
2695     }
2696     } else {
2697     return ($t, \%prop_value);
2698     }
2699    
2700     return ($t, \%prop_value);
2701     },
2702     serialize => sub {
2703     my ($self, $prop_name, $value) = @_;
2704    
2705     local $Error::Depth = $Error::Depth + 1;
2706     my @v;
2707     push @v, $self->padding_top;
2708     return undef unless defined $v[-1];
2709     push @v, $self->padding_right;
2710     return undef unless defined $v[-1];
2711     push @v, $self->padding_bottom;
2712     return undef unless defined $v[-1];
2713     push @v, $self->padding_left;
2714     return undef unless defined $v[-1];
2715    
2716     pop @v if $v[1] eq $v[3];
2717     pop @v if $v[0] eq $v[2];
2718     pop @v if $v[0] eq $v[1];
2719     return join ' ', @v;
2720     },
2721     };
2722     $Attr->{padding} = $Prop->{padding};
2723    
2724 wakaba 1.20 $Prop->{'border-width'} = {
2725     css => 'border-width',
2726     dom => 'border_width',
2727     parse => sub {
2728     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2729    
2730     my %prop_value;
2731    
2732     my $sign = 1;
2733     if ($t->{type} == MINUS_TOKEN) {
2734     $t = $tt->get_next_token;
2735     $sign = -1;
2736     }
2737    
2738     if ($t->{type} == DIMENSION_TOKEN) {
2739     my $value = $t->{number} * $sign;
2740     my $unit = lc $t->{value}; ## TODO: case
2741     $t = $tt->get_next_token;
2742     if ($length_unit->{$unit} and $value >= 0) {
2743     $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
2744     } else {
2745     $onerror->(type => 'syntax error:'.$prop_name,
2746     level => $self->{must_level},
2747     token => $t);
2748     return ($t, undef);
2749     }
2750     } elsif ($t->{type} == NUMBER_TOKEN and
2751     ($self->{unitless_px} or $t->{number} == 0)) {
2752     my $value = $t->{number} * $sign;
2753     $t = $tt->get_next_token;
2754     $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
2755     unless ($value >= 0) {
2756     $onerror->(type => 'syntax error:'.$prop_name,
2757     level => $self->{must_level},
2758     token => $t);
2759     return ($t, undef);
2760     }
2761     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2762     my $prop_value = lc $t->{value}; ## TODO: case folding
2763     $t = $tt->get_next_token;
2764     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2765     $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
2766     } elsif ($prop_value eq 'inherit') {
2767     $prop_value{'border-top-width'} = ['INHERIT'];
2768     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
2769     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
2770     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
2771     return ($t, \%prop_value);
2772     } else {
2773     $onerror->(type => 'syntax error:'.$prop_name,
2774     level => $self->{must_level},
2775     token => $t);
2776     return ($t, undef);
2777     }
2778     } else {
2779     $onerror->(type => 'syntax error:'.$prop_name,
2780     level => $self->{must_level},
2781     token => $t);
2782     return ($t, undef);
2783     }
2784     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
2785     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
2786     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
2787    
2788     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2789     $sign = 1;
2790     if ($t->{type} == MINUS_TOKEN) {
2791     $t = $tt->get_next_token;
2792     $sign = -1;
2793     }
2794    
2795     if ($t->{type} == DIMENSION_TOKEN) {
2796     my $value = $t->{number} * $sign;
2797     my $unit = lc $t->{value}; ## TODO: case
2798     $t = $tt->get_next_token;
2799     if ($length_unit->{$unit} and $value >= 0) {
2800     $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
2801     } else {
2802     $onerror->(type => 'syntax error:'.$prop_name,
2803     level => $self->{must_level},
2804     token => $t);
2805     return ($t, undef);
2806     }
2807     } elsif ($t->{type} == NUMBER_TOKEN and
2808     ($self->{unitless_px} or $t->{number} == 0)) {
2809     my $value = $t->{number} * $sign;
2810     $t = $tt->get_next_token;
2811     $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
2812     unless ($value >= 0) {
2813     $onerror->(type => 'syntax error:'.$prop_name,
2814     level => $self->{must_level},
2815     token => $t);
2816     return ($t, undef);
2817     }
2818     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2819     my $prop_value = lc $t->{value}; ## TODO: case
2820     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2821     $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
2822     $t = $tt->get_next_token;
2823     }
2824     } else {
2825     return ($t, \%prop_value);
2826     }
2827     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
2828    
2829     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2830     $sign = 1;
2831     if ($t->{type} == MINUS_TOKEN) {
2832     $t = $tt->get_next_token;
2833     $sign = -1;
2834     }
2835    
2836     if ($t->{type} == DIMENSION_TOKEN) {
2837     my $value = $t->{number} * $sign;
2838     my $unit = lc $t->{value}; ## TODO: case
2839     $t = $tt->get_next_token;
2840     if ($length_unit->{$unit} and $value >= 0) {
2841     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
2842     } else {
2843     $onerror->(type => 'syntax error:'.$prop_name,
2844     level => $self->{must_level},
2845     token => $t);
2846     return ($t, undef);
2847     }
2848     } elsif ($t->{type} == NUMBER_TOKEN and
2849     ($self->{unitless_px} or $t->{number} == 0)) {
2850     my $value = $t->{number} * $sign;
2851     $t = $tt->get_next_token;
2852     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
2853     unless ($value >= 0) {
2854     $onerror->(type => 'syntax error:'.$prop_name,
2855     level => $self->{must_level},
2856     token => $t);
2857     return ($t, undef);
2858     }
2859     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2860     my $prop_value = lc $t->{value}; ## TODO: case
2861     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2862     $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
2863     $t = $tt->get_next_token;
2864     }
2865     } else {
2866     return ($t, \%prop_value);
2867     }
2868    
2869     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2870     $sign = 1;
2871     if ($t->{type} == MINUS_TOKEN) {
2872     $t = $tt->get_next_token;
2873     $sign = -1;
2874     }
2875    
2876     if ($t->{type} == DIMENSION_TOKEN) {
2877     my $value = $t->{number} * $sign;
2878     my $unit = lc $t->{value}; ## TODO: case
2879     $t = $tt->get_next_token;
2880     if ($length_unit->{$unit} and $value >= 0) {
2881     $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
2882     } else {
2883     $onerror->(type => 'syntax error:'.$prop_name,
2884     level => $self->{must_level},
2885     token => $t);
2886     return ($t, undef);
2887     }
2888     } elsif ($t->{type} == NUMBER_TOKEN and
2889     ($self->{unitless_px} or $t->{number} == 0)) {
2890     my $value = $t->{number} * $sign;
2891     $t = $tt->get_next_token;
2892     $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
2893     unless ($value >= 0) {
2894     $onerror->(type => 'syntax error:'.$prop_name,
2895     level => $self->{must_level},
2896     token => $t);
2897     return ($t, undef);
2898     }
2899     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2900     my $prop_value = lc $t->{value}; ## TODO: case
2901     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2902     $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
2903     $t = $tt->get_next_token;
2904     }
2905     } else {
2906     return ($t, \%prop_value);
2907     }
2908    
2909     return ($t, \%prop_value);
2910     },
2911     serialize => sub {
2912     my ($self, $prop_name, $value) = @_;
2913    
2914     local $Error::Depth = $Error::Depth + 1;
2915     my @v;
2916     push @v, $self->padding_top;
2917     return undef unless defined $v[-1];
2918     push @v, $self->padding_right;
2919     return undef unless defined $v[-1];
2920     push @v, $self->padding_bottom;
2921     return undef unless defined $v[-1];
2922     push @v, $self->padding_left;
2923     return undef unless defined $v[-1];
2924    
2925     pop @v if $v[1] eq $v[3];
2926     pop @v if $v[0] eq $v[2];
2927     pop @v if $v[0] eq $v[1];
2928     return join ' ', @v;
2929     },
2930     };
2931     $Attr->{padding} = $Prop->{padding};
2932    
2933 wakaba 1.12 $Prop->{'list-style'} = {
2934     css => 'list-style',
2935     dom => 'list_style',
2936     parse => sub {
2937     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2938    
2939     my %prop_value;
2940     my $none = 0;
2941    
2942     F: for my $f (1..3) {
2943     if ($t->{type} == IDENT_TOKEN) {
2944     my $prop_value = lc $t->{value}; ## TODO: case folding
2945     $t = $tt->get_next_token;
2946    
2947     if ($prop_value eq 'none') {
2948     $none++;
2949     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
2950     if (exists $prop_value{'list-style-type'}) {
2951     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
2952     $prop_name,
2953     level => $self->{must_level},
2954     token => $t);
2955     return ($t, undef);
2956     } else {
2957     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
2958     }
2959     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
2960     if (exists $prop_value{'list-style-position'}) {
2961     $onerror->(type => q[syntax error:duplicate:'list-style-position':].
2962     $prop_name,
2963     level => $self->{must_level},
2964     token => $t);
2965     return ($t, undef);
2966     }
2967    
2968     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
2969     } elsif ($f == 1 and $prop_value eq 'inherit') {
2970     $prop_value{'list-style-type'} = ["INHERIT"];
2971     $prop_value{'list-style-position'} = ["INHERIT"];
2972     $prop_value{'list-style-image'} = ["INHERIT"];
2973     last F;
2974     } else {
2975     if ($f == 1) {
2976     $onerror->(type => 'syntax error:'.$prop_name,
2977     level => $self->{must_level},
2978     token => $t);
2979     return ($t, undef);
2980     } else {
2981     last F;
2982     }
2983     }
2984     } elsif ($t->{type} == URI_TOKEN) {
2985     if (exists $prop_value{'list-style-image'}) {
2986     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
2987     $prop_name,
2988     level => $self->{must_level},
2989     token => $t);
2990     return ($t, undef);
2991     }
2992    
2993     $prop_value{'list-style-image'}
2994 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
2995 wakaba 1.12 $t = $tt->get_next_token;
2996     } else {
2997     if ($f == 1) {
2998     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2999     level => $self->{must_level},
3000     token => $t);
3001     return ($t, undef);
3002     } else {
3003     last F;
3004     }
3005     }
3006    
3007     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3008     } # F
3009     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
3010    
3011     if ($none == 1) {
3012     if (exists $prop_value{'list-style-type'}) {
3013     if (exists $prop_value{'list-style-image'}) {
3014     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3015     $prop_name,
3016     level => $self->{must_level},
3017     token => $t);
3018     return ($t, undef);
3019     } else {
3020     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3021     }
3022     } else {
3023     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3024     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
3025     unless exists $prop_value{'list-style-image'};
3026     }
3027     } elsif ($none == 2) {
3028     if (exists $prop_value{'list-style-type'}) {
3029     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3030     $prop_name,
3031     level => $self->{must_level},
3032     token => $t);
3033     return ($t, undef);
3034     }
3035     if (exists $prop_value{'list-style-image'}) {
3036     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3037     $prop_name,
3038     level => $self->{must_level},
3039     token => $t);
3040     return ($t, undef);
3041     }
3042    
3043     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3044     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3045     } elsif ($none == 3) {
3046     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3047     $prop_name,
3048     level => $self->{must_level},
3049     token => $t);
3050     return ($t, undef);
3051     }
3052    
3053     for (qw/list-style-type list-style-position list-style-image/) {
3054     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
3055     }
3056    
3057     return ($t, \%prop_value);
3058     },
3059     serialize => sub {
3060     my ($self, $prop_name, $value) = @_;
3061    
3062     local $Error::Depth = $Error::Depth + 1;
3063     return $self->list_style_type . ' ' . $self->list_style_position .
3064     ' ' . $self->list_style_image;
3065     },
3066     };
3067     $Attr->{list_style} = $Prop->{'list-style'};
3068    
3069 wakaba 1.16 ## NOTE: Future version of the implementation will change the way to
3070     ## store the parsed value to support CSS 3 properties.
3071     $Prop->{'text-decoration'} = {
3072     css => 'text-decoration',
3073     dom => 'text_decoration',
3074     key => 'text_decoration',
3075     parse => sub {
3076     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3077    
3078     my $value = ['DECORATION']; # , underline, overline, line-through, blink
3079    
3080     if ($t->{type} == IDENT_TOKEN) {
3081     my $v = lc $t->{value}; ## TODO: case
3082     $t = $tt->get_next_token;
3083     if ($v eq 'inherit') {
3084     return ($t, {$prop_name => ['INHERIT']});
3085     } elsif ($v eq 'none') {
3086     return ($t, {$prop_name => $value});
3087     } elsif ($v eq 'underline' and
3088     $self->{prop_value}->{$prop_name}->{$v}) {
3089     $value->[1] = 1;
3090     } elsif ($v eq 'overline' and
3091     $self->{prop_value}->{$prop_name}->{$v}) {
3092     $value->[2] = 1;
3093     } elsif ($v eq 'line-through' and
3094     $self->{prop_value}->{$prop_name}->{$v}) {
3095     $value->[3] = 1;
3096     } elsif ($v eq 'blink' and
3097     $self->{prop_value}->{$prop_name}->{$v}) {
3098     $value->[4] = 1;
3099     } else {
3100     $onerror->(type => 'syntax error:'.$prop_name,
3101     level => $self->{must_level},
3102     token => $t);
3103     return ($t, undef);
3104     }
3105     }
3106    
3107     F: {
3108     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3109     last F unless $t->{type} == IDENT_TOKEN;
3110    
3111     my $v = lc $t->{value}; ## TODO: case
3112     $t = $tt->get_next_token;
3113     if ($v eq 'underline' and
3114     $self->{prop_value}->{$prop_name}->{$v}) {
3115     $value->[1] = 1;
3116     } elsif ($v eq 'overline' and
3117     $self->{prop_value}->{$prop_name}->{$v}) {
3118     $value->[1] = 2;
3119     } elsif ($v eq 'line-through' and
3120     $self->{prop_value}->{$prop_name}->{$v}) {
3121     $value->[1] = 3;
3122     } elsif ($v eq 'blink' and
3123     $self->{prop_value}->{$prop_name}->{$v}) {
3124     $value->[1] = 4;
3125     } else {
3126     last F;
3127     }
3128    
3129     redo F;
3130     } # F
3131    
3132     return ($t, {$prop_name => $value});
3133     },
3134     serialize => $default_serializer,
3135     initial => ["KEYWORD", "none"],
3136     #inherited => 0,
3137     compute => $compute_as_specified,
3138     };
3139     $Attr->{text_decoration} = $Prop->{'text-decoration'};
3140     $Key->{text_decoration} = $Prop->{'text-decoration'};
3141    
3142 wakaba 1.1 1;
3143 wakaba 1.21 ## $Date: 2008/01/03 12:23:46 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24