/[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.20 - (hide annotations) (download)
Thu Jan 3 12:23:46 2008 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.19: +330 -12 lines
++ whatpm/Whatpm/CSS/ChangeLog	3 Jan 2008 12:23:31 -0000
	* Parser.pm: Some condition operators were incorrect.
	The 'border-width' property family is 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     my $sign = 1;
1282     if ($t->{type} == MINUS_TOKEN) {
1283     $t = $tt->get_next_token;
1284     $sign = -1;
1285     }
1286    
1287     if ($t->{type} == DIMENSION_TOKEN) {
1288     my $value = $t->{number} * $sign;
1289     my $unit = lc $t->{value}; ## TODO: case
1290     $t = $tt->get_next_token;
1291     if ($length_unit->{$unit}) {
1292     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1293     }
1294     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1295     my $value = $t->{number} * $sign;
1296     $t = $tt->get_next_token;
1297     return ($t, {$prop_name => ['PERCENTAGE', $value]});
1298 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
1299 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
1300     my $value = $t->{number} * $sign;
1301     $t = $tt->get_next_token;
1302     return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
1303     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1304     my $value = lc $t->{value}; ## TODO: case
1305     $t = $tt->get_next_token;
1306     if ($value eq 'auto') {
1307     return ($t, {$prop_name => ['KEYWORD', $value]});
1308     } elsif ($value eq 'inherit') {
1309     return ($t, {$prop_name => ['INHERIT']});
1310     }
1311     }
1312    
1313     $onerror->(type => 'syntax error:'.$prop_name,
1314     level => $self->{must_level},
1315     token => $t);
1316     return ($t, undef);
1317     },
1318     serialize => $default_serializer,
1319     initial => ['DIMENSION', 0, 'px'],
1320     #inherited => 0,
1321     compute => $compute_length,
1322     };
1323     $Attr->{margin_top} = $Prop->{'margin-top'};
1324     $Key->{margin_top} = $Prop->{'margin-top'};
1325    
1326     $Prop->{'margin-bottom'} = {
1327     css => 'margin-bottom',
1328     dom => 'margin_bottom',
1329     key => 'margin_bottom',
1330     parse => $Prop->{'margin-top'}->{parse},
1331     serialize => $default_serializer,
1332     initial => ['DIMENSION', 0, 'px'],
1333     #inherited => 0,
1334     compute => $compute_length,
1335     };
1336     $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
1337     $Key->{margin_bottom} = $Prop->{'margin-bottom'};
1338    
1339     $Prop->{'margin-right'} = {
1340     css => 'margin-right',
1341     dom => 'margin_right',
1342     key => 'margin_right',
1343     parse => $Prop->{'margin-top'}->{parse},
1344     serialize => $default_serializer,
1345     initial => ['DIMENSION', 0, 'px'],
1346     #inherited => 0,
1347     compute => $compute_length,
1348     };
1349     $Attr->{margin_right} = $Prop->{'margin-right'};
1350     $Key->{margin_right} = $Prop->{'margin-right'};
1351    
1352     $Prop->{'margin-left'} = {
1353     css => 'margin-left',
1354     dom => 'margin_left',
1355     key => 'margin_left',
1356     parse => $Prop->{'margin-top'}->{parse},
1357     serialize => $default_serializer,
1358     initial => ['DIMENSION', 0, 'px'],
1359     #inherited => 0,
1360     compute => $compute_length,
1361     };
1362     $Attr->{margin_left} = $Prop->{'margin-left'};
1363     $Key->{margin_left} = $Prop->{'margin-left'};
1364    
1365     $Prop->{'padding-top'} = {
1366     css => 'padding-top',
1367     dom => 'padding_top',
1368     key => 'padding_top',
1369     parse => sub {
1370     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1371    
1372     my $sign = 1;
1373     if ($t->{type} == MINUS_TOKEN) {
1374     $t = $tt->get_next_token;
1375     $sign = -1;
1376     }
1377    
1378     if ($t->{type} == DIMENSION_TOKEN) {
1379     my $value = $t->{number} * $sign;
1380     my $unit = lc $t->{value}; ## TODO: case
1381     $t = $tt->get_next_token;
1382     if ($length_unit->{$unit} and $value >= 0) {
1383     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1384     }
1385     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1386     my $value = $t->{number} * $sign;
1387     $t = $tt->get_next_token;
1388     return ($t, {$prop_name => ['PERCENTAGE', $value]}) if $value >= 0;
1389 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
1390 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
1391     my $value = $t->{number} * $sign;
1392     $t = $tt->get_next_token;
1393     return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1394     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1395     my $value = lc $t->{value}; ## TODO: case
1396     $t = $tt->get_next_token;
1397     if ($value eq 'inherit') {
1398     return ($t, {$prop_name => ['INHERIT']});
1399     }
1400     }
1401    
1402     $onerror->(type => 'syntax error:'.$prop_name,
1403     level => $self->{must_level},
1404     token => $t);
1405     return ($t, undef);
1406     },
1407     serialize => $default_serializer,
1408     initial => ['DIMENSION', 0, 'px'],
1409     #inherited => 0,
1410     compute => $compute_length,
1411     };
1412     $Attr->{padding_top} = $Prop->{'padding-top'};
1413     $Key->{padding_top} = $Prop->{'padding-top'};
1414    
1415     $Prop->{'padding-bottom'} = {
1416     css => 'padding-bottom',
1417     dom => 'padding_bottom',
1418     key => 'padding_bottom',
1419     parse => $Prop->{'padding-top'}->{parse},
1420     serialize => $default_serializer,
1421     initial => ['DIMENSION', 0, 'px'],
1422     #inherited => 0,
1423     compute => $compute_length,
1424     };
1425     $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
1426     $Key->{padding_bottom} = $Prop->{'padding-bottom'};
1427    
1428     $Prop->{'padding-right'} = {
1429     css => 'padding-right',
1430     dom => 'padding_right',
1431     key => 'padding_right',
1432     parse => $Prop->{'padding-top'}->{parse},
1433     serialize => $default_serializer,
1434     initial => ['DIMENSION', 0, 'px'],
1435     #inherited => 0,
1436     compute => $compute_length,
1437     };
1438     $Attr->{padding_right} = $Prop->{'padding-right'};
1439     $Key->{padding_right} = $Prop->{'padding-right'};
1440    
1441     $Prop->{'padding-left'} = {
1442     css => 'padding-left',
1443     dom => 'padding_left',
1444     key => 'padding_left',
1445     parse => $Prop->{'padding-top'}->{parse},
1446     serialize => $default_serializer,
1447     initial => ['DIMENSION', 0, 'px'],
1448     #inherited => 0,
1449     compute => $compute_length,
1450     };
1451     $Attr->{padding_left} = $Prop->{'padding-left'};
1452     $Key->{padding_left} = $Prop->{'padding-left'};
1453    
1454 wakaba 1.20 $Prop->{'border-top-width'} = {
1455     css => 'border-top-width',
1456     dom => 'border_top_width',
1457     key => 'border_top_width',
1458     parse => sub {
1459     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1460    
1461     my $sign = 1;
1462     if ($t->{type} == MINUS_TOKEN) {
1463     $t = $tt->get_next_token;
1464     $sign = -1;
1465     }
1466    
1467     if ($t->{type} == DIMENSION_TOKEN) {
1468     my $value = $t->{number} * $sign;
1469     my $unit = lc $t->{value}; ## TODO: case
1470     $t = $tt->get_next_token;
1471     if ($length_unit->{$unit} and $value >= 0) {
1472     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1473     }
1474     } elsif ($t->{type} == NUMBER_TOKEN and
1475     ($self->{unitless_px} or $t->{number} == 0)) {
1476     my $value = $t->{number} * $sign;
1477     $t = $tt->get_next_token;
1478     return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1479     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1480     my $value = lc $t->{value}; ## TODO: case
1481     $t = $tt->get_next_token;
1482     if ({thin => 1, medium => 1, thick => 1}->{$value}) {
1483     return ($t, {$prop_name => ['KEYWORD', $value]});
1484     } elsif ($value eq 'inherit') {
1485     return ($t, {$prop_name => ['INHERIT']});
1486     }
1487     }
1488    
1489     $onerror->(type => 'syntax error:'.$prop_name,
1490     level => $self->{must_level},
1491     token => $t);
1492     return ($t, undef);
1493     },
1494     serialize => $default_serializer,
1495     initial => ['KEYWORD', 'medium'],
1496     #inherited => 0,
1497     compute => sub {
1498     my ($self, $element, $prop_name, $specified_value) = @_;
1499    
1500     my $style_prop = $prop_name;
1501     $style_prop =~ s/width/style/;
1502     my $style = $self->get_computed_value ($element, $style_prop);
1503     if (defined $style and $style->[0] eq 'KEYWORD' and
1504     ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
1505     return ['DIMENSION', 0, 'px'];
1506     }
1507    
1508     my $value = $compute_length->(@_);
1509     if (defined $value and $value->[0] eq 'KEYWORD') {
1510     if ($value->[1] eq 'thin') {
1511     return ['DIMENSION', 1, 'px']; ## Firefox/Opera
1512     } elsif ($value->[1] eq 'medium') {
1513     return ['DIMENSION', 3, 'px']; ## Firefox/Opera
1514     } elsif ($value->[1] eq 'thick') {
1515     return ['DIMENSION', 5, 'px']; ## Firefox
1516     }
1517     }
1518     return $value;
1519     },
1520     };
1521     $Attr->{border_top_width} = $Prop->{'border-top-width'};
1522     $Key->{border_top_width} = $Prop->{'border-top-width'};
1523    
1524     $Prop->{'border-right-width'} = {
1525     css => 'border-right-width',
1526     dom => 'border_right_width',
1527     key => 'border_right_width',
1528     parse => $Prop->{'border-top-width'}->{parse},
1529     serialize => $default_serializer,
1530     initial => ['KEYWORD', 'medium'],
1531     #inherited => 0,
1532     compute => $Prop->{'border-top-width'}->{compute},
1533     };
1534     $Attr->{border_right_width} = $Prop->{'border-right-width'};
1535     $Key->{border_right_width} = $Prop->{'border-right-width'};
1536    
1537     $Prop->{'border-bottom-width'} = {
1538     css => 'border-bottom-width',
1539     dom => 'border_bottom_width',
1540     key => 'border_bottom_width',
1541     parse => $Prop->{'border-top-width'}->{parse},
1542     serialize => $default_serializer,
1543     initial => ['KEYWORD', 'medium'],
1544     #inherited => 0,
1545     compute => $Prop->{'border-top-width'}->{compute},
1546     };
1547     $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
1548     $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
1549    
1550     $Prop->{'border-left-width'} = {
1551     css => 'border-left-width',
1552     dom => 'border_left_width',
1553     key => 'border_left_width',
1554     parse => $Prop->{'border-top-width'}->{parse},
1555     serialize => $default_serializer,
1556     initial => ['KEYWORD', 'medium'],
1557     #inherited => 0,
1558     compute => $Prop->{'border-top-width'}->{compute},
1559     };
1560     $Attr->{border_left_width} = $Prop->{'border-left-width'};
1561     $Key->{border_left_width} = $Prop->{'border-left-width'};
1562    
1563 wakaba 1.15 $Prop->{'font-weight'} = {
1564     css => 'font-weight',
1565     dom => 'font_weight',
1566     key => 'font_weight',
1567     parse => sub {
1568     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1569    
1570     if ($t->{type} == NUMBER_TOKEN) {
1571     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
1572     ## browser compatibility issue.
1573     my $value = $t->{number};
1574     $t = $tt->get_next_token;
1575     if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
1576     return ($t, {$prop_name => ['WEIGHT', $value, 0]});
1577     }
1578     } elsif ($t->{type} == IDENT_TOKEN) {
1579     my $value = lc $t->{value}; ## TODO: case
1580     $t = $tt->get_next_token;
1581     if ({
1582     normal => 1, bold => 1, bolder => 1, lighter => 1,
1583     }->{$value}) {
1584     return ($t, {$prop_name => ['KEYWORD', $value]});
1585     } elsif ($value eq 'inherit') {
1586     return ($t, {$prop_name => ['INHERIT']});
1587     }
1588     }
1589    
1590     $onerror->(type => 'syntax error:'.$prop_name,
1591     level => $self->{must_level},
1592     token => $t);
1593     return ($t, undef);
1594     },
1595     serialize => $default_serializer,
1596     initial => ['KEYWORD', 'normal'],
1597     inherited => 1,
1598     compute => sub {
1599     my ($self, $element, $prop_name, $specified_value) = @_;
1600    
1601 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1602 wakaba 1.15 if ($specified_value->[1] eq 'normal') {
1603     return ['WEIGHT', 400, 0];
1604     } elsif ($specified_value->[1] eq 'bold') {
1605     return ['WEIGHT', 700, 0];
1606     } elsif ($specified_value->[1] eq 'bolder') {
1607     my $parent_element = $element->manakai_parent_element;
1608     if (defined $parent_element) {
1609     my $parent_value = $self->get_cascaded_value
1610     ($parent_element, $prop_name); ## NOTE: What Firefox does.
1611     return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
1612     } else {
1613     return ['WEIGHT', 400, 1];
1614     }
1615     } elsif ($specified_value->[1] eq 'lighter') {
1616     my $parent_element = $element->manakai_parent_element;
1617     if (defined $parent_element) {
1618     my $parent_value = $self->get_cascaded_value
1619     ($parent_element, $prop_name); ## NOTE: What Firefox does.
1620     return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
1621     } else {
1622     return ['WEIGHT', 400, 1];
1623     }
1624     }
1625 wakaba 1.17 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
1626 wakaba 1.15 #
1627     }
1628    
1629     return $specified_value;
1630     },
1631     };
1632     $Attr->{font_weight} = $Prop->{'font-weight'};
1633     $Key->{font_weight} = $Prop->{'font-weight'};
1634    
1635 wakaba 1.13 my $uri_or_none_parser = sub {
1636 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1637    
1638 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
1639 wakaba 1.11 my $value = $t->{value};
1640     $t = $tt->get_next_token;
1641     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
1642     } elsif ($t->{type} == IDENT_TOKEN) {
1643     my $value = lc $t->{value}; ## TODO: case
1644     $t = $tt->get_next_token;
1645     if ($value eq 'none') {
1646     ## NOTE: |none| is the default value and therefore it must be
1647     ## supported anyway.
1648     return ($t, {$prop_name => ["KEYWORD", 'none']});
1649     } elsif ($value eq 'inherit') {
1650     return ($t, {$prop_name => ['INHERIT']});
1651     }
1652     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
1653     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
1654     # my $value = $t->{value};
1655     # $t = $tt->get_next_token;
1656     # if ($t->{type} == EOF_TOKEN) {
1657     # $onerror->(type => 'syntax error:eof:'.$prop_name,
1658     # level => $self->{must_level},
1659     # token => $t);
1660     #
1661     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
1662     # }
1663     }
1664    
1665     $onerror->(type => 'syntax error:'.$prop_name,
1666     level => $self->{must_level},
1667     token => $t);
1668     return ($t, undef);
1669 wakaba 1.13 }; # $uri_or_none_parser
1670    
1671 wakaba 1.14 my $compute_uri_or_none = sub {
1672 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
1673    
1674     if (defined $specified_value and
1675     $specified_value->[0] eq 'URI' and
1676     defined $specified_value->[2]) {
1677     require Message::DOM::DOMImplementation;
1678     return ['URI',
1679     Message::DOM::DOMImplementation->create_uri_reference
1680     ($specified_value->[1])
1681     ->get_absolute_reference (${$specified_value->[2]})
1682     ->get_uri_reference,
1683     $specified_value->[2]];
1684     }
1685    
1686     return $specified_value;
1687 wakaba 1.14 }; # $compute_uri_or_none
1688    
1689     $Prop->{'list-style-image'} = {
1690     css => 'list-style-image',
1691     dom => 'list_style_image',
1692     key => 'list_style_image',
1693     parse => $uri_or_none_parser,
1694     serialize => $default_serializer,
1695     initial => ['KEYWORD', 'none'],
1696     inherited => 1,
1697     compute => $compute_uri_or_none,
1698 wakaba 1.11 };
1699     $Attr->{list_style_image} = $Prop->{'list-style-image'};
1700     $Key->{list_style_image} = $Prop->{'list-style-image'};
1701    
1702 wakaba 1.15 $Prop->{'background-image'} = {
1703     css => 'background-image',
1704     dom => 'background_image',
1705     key => 'background_image',
1706     parse => $uri_or_none_parser,
1707     serialize => $default_serializer,
1708     initial => ['KEYWORD', 'none'],
1709     #inherited => 0,
1710     compute => $compute_uri_or_none,
1711     };
1712     $Attr->{background_image} = $Prop->{'background-image'};
1713     $Key->{background_image} = $Prop->{'background-image'};
1714    
1715 wakaba 1.7 my $border_style_keyword = {
1716     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
1717     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
1718     };
1719    
1720     $Prop->{'border-top-style'} = {
1721     css => 'border-top-style',
1722     dom => 'border_top_style',
1723     key => 'border_top_style',
1724     parse => $one_keyword_parser,
1725 wakaba 1.11 serialize => $default_serializer,
1726 wakaba 1.7 keyword => $border_style_keyword,
1727 wakaba 1.9 initial => ["KEYWORD", "none"],
1728     #inherited => 0,
1729     compute => $compute_as_specified,
1730 wakaba 1.7 };
1731     $Attr->{border_top_style} = $Prop->{'border-top-style'};
1732     $Key->{border_top_style} = $Prop->{'border-top-style'};
1733    
1734     $Prop->{'border-right-style'} = {
1735     css => 'border-right-style',
1736     dom => 'border_right_style',
1737     key => 'border_right_style',
1738     parse => $one_keyword_parser,
1739 wakaba 1.11 serialize => $default_serializer,
1740 wakaba 1.7 keyword => $border_style_keyword,
1741 wakaba 1.9 initial => ["KEYWORD", "none"],
1742     #inherited => 0,
1743     compute => $compute_as_specified,
1744 wakaba 1.7 };
1745     $Attr->{border_right_style} = $Prop->{'border-right-style'};
1746     $Key->{border_right_style} = $Prop->{'border-right-style'};
1747    
1748     $Prop->{'border-bottom-style'} = {
1749     css => 'border-bottom-style',
1750     dom => 'border_bottom_style',
1751     key => 'border_bottom_style',
1752     parse => $one_keyword_parser,
1753 wakaba 1.11 serialize => $default_serializer,
1754 wakaba 1.7 keyword => $border_style_keyword,
1755 wakaba 1.9 initial => ["KEYWORD", "none"],
1756     #inherited => 0,
1757     compute => $compute_as_specified,
1758 wakaba 1.7 };
1759     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
1760     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
1761    
1762     $Prop->{'border-left-style'} = {
1763     css => 'border-left-style',
1764     dom => 'border_left_style',
1765     key => 'border_left_style',
1766     parse => $one_keyword_parser,
1767 wakaba 1.11 serialize => $default_serializer,
1768 wakaba 1.7 keyword => $border_style_keyword,
1769 wakaba 1.9 initial => ["KEYWORD", "none"],
1770     #inherited => 0,
1771     compute => $compute_as_specified,
1772 wakaba 1.7 };
1773     $Attr->{border_left_style} = $Prop->{'border-left-style'};
1774     $Key->{border_left_style} = $Prop->{'border-left-style'};
1775    
1776 wakaba 1.16 $Prop->{'outline-style'} = {
1777     css => 'outline-style',
1778     dom => 'outline_style',
1779     key => 'outline_style',
1780     parse => $one_keyword_parser,
1781     serialize => $default_serializer,
1782     keyword => $border_style_keyword,
1783     initial => ['KEYWORD', 'none'],
1784     #inherited => 0,
1785     compute => $compute_as_specified,
1786     };
1787     $Attr->{outline_style} = $Prop->{'outline-style'};
1788     $Key->{outline_style} = $Prop->{'outline-style'};
1789    
1790 wakaba 1.15 $Prop->{'font-family'} = {
1791     css => 'font-family',
1792     dom => 'font_family',
1793     key => 'font_family',
1794     parse => sub {
1795     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1796    
1797     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
1798     ## how chaotic browsers are!
1799    
1800     my @prop_value;
1801    
1802     my $font_name = '';
1803     my $may_be_generic = 1;
1804     my $may_be_inherit = 1;
1805     my $has_s = 0;
1806     F: {
1807     if ($t->{type} == IDENT_TOKEN) {
1808     undef $may_be_inherit if $has_s or length $font_name;
1809     undef $may_be_generic if $has_s or length $font_name;
1810     $font_name .= ' ' if $has_s;
1811     $font_name .= $t->{value};
1812     undef $has_s;
1813     $t = $tt->get_next_token;
1814     } elsif ($t->{type} == STRING_TOKEN) {
1815     $font_name .= ' ' if $has_s;
1816     $font_name .= $t->{value};
1817     undef $may_be_inherit;
1818     undef $may_be_generic;
1819     undef $has_s;
1820     $t = $tt->get_next_token;
1821     } elsif ($t->{type} == COMMA_TOKEN) {
1822     if ($may_be_generic and
1823     {
1824     serif => 1, 'sans-serif' => 1, cursive => 1,
1825     fantasy => 1, monospace => 1, '-manakai-default' => 1,
1826     }->{lc $font_name}) { ## TODO: case
1827     push @prop_value, ['KEYWORD', $font_name];
1828     } elsif (not $may_be_generic or length $font_name) {
1829     push @prop_value, ["STRING", $font_name];
1830     }
1831     undef $may_be_inherit;
1832     $may_be_generic = 1;
1833     undef $has_s;
1834     $font_name = '';
1835     $t = $tt->get_next_token;
1836     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1837     } elsif ($t->{type} == S_TOKEN) {
1838     $has_s = 1;
1839     $t = $tt->get_next_token;
1840     } else {
1841     if ($may_be_generic and
1842     {
1843     serif => 1, 'sans-serif' => 1, cursive => 1,
1844     fantasy => 1, monospace => 1, '-manakai-default' => 1,
1845     }->{lc $font_name}) { ## TODO: case
1846     push @prop_value, ['KEYWORD', $font_name];
1847     } elsif (not $may_be_generic or length $font_name) {
1848     push @prop_value, ['STRING', $font_name];
1849     } else {
1850     $onerror->(type => 'syntax error:'.$prop_name,
1851     level => $self->{must_level},
1852     token => $t);
1853     return ($t, undef);
1854     }
1855     last F;
1856     }
1857     redo F;
1858     } # F
1859    
1860     if ($may_be_inherit and
1861     @prop_value == 1 and
1862     $prop_value[0]->[0] eq 'STRING' and
1863     lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
1864     return ($t, {$prop_name => ['INHERIT']});
1865     } else {
1866     unshift @prop_value, 'FONT';
1867     return ($t, {$prop_name => \@prop_value});
1868     }
1869     },
1870     serialize => sub {
1871     my ($self, $prop_name, $value) = @_;
1872    
1873     if ($value->[0] eq 'FONT') {
1874     return join ', ', map {
1875     if ($_->[0] eq 'STRING') {
1876     '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
1877     } elsif ($_->[0] eq 'KEYWORD') {
1878     $_->[1]; ## NOTE: This is what Firefox does.
1879     } else {
1880     ## NOTE: This should be an error.
1881     '""';
1882     }
1883     } @$value[1..$#$value];
1884     } elsif ($value->[0] eq 'INHERIT') {
1885     return 'inherit';
1886     } else {
1887     return undef;
1888     }
1889     },
1890     initial => ['FONT', ['KEYWORD', '-manakai-default']],
1891     inherited => 1,
1892     compute => $compute_as_specified,
1893     };
1894     $Attr->{font_family} = $Prop->{'font-family'};
1895     $Key->{font_family} = $Prop->{'font-family'};
1896    
1897 wakaba 1.17 $Prop->{cursor} = {
1898     css => 'cursor',
1899     dom => 'cursor',
1900     key => 'cursor',
1901     parse => sub {
1902     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1903    
1904     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
1905     ## compatibility issues.
1906    
1907     my @prop_value = ('CURSOR');
1908    
1909     F: {
1910     if ($t->{type} == IDENT_TOKEN) {
1911     my $v = lc $t->{value}; ## TODO: case
1912     $t = $tt->get_next_token;
1913     if ($Prop->{$prop_name}->{keyword}->{$v}) {
1914     push @prop_value, ['KEYWORD', $v];
1915     last F;
1916     } elsif ($v eq 'inherit' and @prop_value == 1) {
1917     return ($t, {$prop_name => ['INHERIT']});
1918     } else {
1919     $onerror->(type => 'syntax error:'.$prop_name,
1920     level => $self->{must_level},
1921     token => $t);
1922     return ($t, undef);
1923     }
1924     } elsif ($t->{type} == URI_TOKEN) {
1925     push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
1926     $t = $tt->get_next_token;
1927     } else {
1928     $onerror->(type => 'syntax error:'.$prop_name,
1929     level => $self->{must_level},
1930     token => $t);
1931     return ($t, undef);
1932     }
1933    
1934     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1935     if ($t->{type} == COMMA_TOKEN) {
1936     $t = $tt->get_next_token;
1937     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1938     redo F;
1939     }
1940     } # F
1941    
1942     return ($t, {$prop_name => \@prop_value});
1943     },
1944     serialize => sub {
1945     my ($self, $prop_name, $value) = @_;
1946    
1947     if ($value->[0] eq 'CURSOR') {
1948     return join ', ', map {
1949     if ($_->[0] eq 'URI') {
1950     'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
1951     } elsif ($_->[0] eq 'KEYWORD') {
1952     $_->[1];
1953     } else {
1954     ## NOTE: This should be an error.
1955     '""';
1956     }
1957     } @$value[1..$#$value];
1958     } elsif ($value->[0] eq 'INHERIT') {
1959     return 'inherit';
1960     } else {
1961     return undef;
1962     }
1963     },
1964     keyword => {
1965     auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
1966     'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
1967     'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
1968     'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
1969     },
1970     initial => ['CURSOR', ['KEYWORD', 'auto']],
1971     inherited => 1,
1972     compute => sub {
1973     my ($self, $element, $prop_name, $specified_value) = @_;
1974    
1975     if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
1976     my @new_value = ('CURSOR');
1977     for my $value (@$specified_value[1..$#$specified_value]) {
1978     if ($value->[0] eq 'URI') {
1979     if (defined $value->[2]) {
1980     require Message::DOM::DOMImplementation;
1981     push @new_value, ['URI',
1982     Message::DOM::DOMImplementation
1983     ->create_uri_reference ($value->[1])
1984     ->get_absolute_reference (${$value->[2]})
1985     ->get_uri_reference,
1986     $value->[2]];
1987     } else {
1988     push @new_value, $value;
1989     }
1990     } else {
1991     push @new_value, $value;
1992     }
1993     }
1994     return \@new_value;
1995     }
1996    
1997     return $specified_value;
1998     },
1999     };
2000     $Attr->{cursor} = $Prop->{cursor};
2001     $Key->{cursor} = $Prop->{cursor};
2002    
2003 wakaba 1.7 $Prop->{'border-style'} = {
2004     css => 'border-style',
2005     dom => 'border_style',
2006     parse => sub {
2007     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2008    
2009     my %prop_value;
2010     if ($t->{type} == IDENT_TOKEN) {
2011     my $prop_value = lc $t->{value}; ## TODO: case folding
2012     $t = $tt->get_next_token;
2013     if ($border_style_keyword->{$prop_value} and
2014     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
2015     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
2016     } elsif ($prop_value eq 'inherit') {
2017 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
2018 wakaba 1.19 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2019     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2020     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2021     return ($t, \%prop_value);
2022 wakaba 1.7 } else {
2023     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2024     level => $self->{must_level},
2025     token => $t);
2026     return ($t, undef);
2027     }
2028     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2029     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2030     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2031     } else {
2032     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2033     level => $self->{must_level},
2034     token => $t);
2035     return ($t, undef);
2036     }
2037    
2038     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2039     if ($t->{type} == IDENT_TOKEN) {
2040     my $prop_value = lc $t->{value}; ## TODO: case folding
2041     $t = $tt->get_next_token;
2042 wakaba 1.19 if ($border_style_keyword->{$prop_value} and
2043 wakaba 1.7 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
2044     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
2045     } else {
2046     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2047     level => $self->{must_level},
2048     token => $t);
2049     return ($t, undef);
2050     }
2051     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2052    
2053     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2054     if ($t->{type} == IDENT_TOKEN) {
2055     my $prop_value = lc $t->{value}; ## TODO: case folding
2056     $t = $tt->get_next_token;
2057     if ($border_style_keyword->{$prop_value} and
2058     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
2059     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
2060     } else {
2061     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2062     level => $self->{must_level},
2063     token => $t);
2064     return ($t, undef);
2065     }
2066    
2067     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2068     if ($t->{type} == IDENT_TOKEN) {
2069     my $prop_value = lc $t->{value}; ## TODO: case folding
2070     $t = $tt->get_next_token;
2071     if ($border_style_keyword->{$prop_value} and
2072     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
2073     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
2074     } else {
2075     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2076     level => $self->{must_level},
2077     token => $t);
2078     return ($t, undef);
2079     }
2080     }
2081     }
2082     }
2083    
2084     return ($t, \%prop_value);
2085     },
2086     serialize => sub {
2087     my ($self, $prop_name, $value) = @_;
2088    
2089     local $Error::Depth = $Error::Depth + 1;
2090     my @v;
2091     push @v, $self->border_top_style;
2092     return undef unless defined $v[-1];
2093     push @v, $self->border_right_style;
2094     return undef unless defined $v[-1];
2095     push @v, $self->border_bottom_style;
2096     return undef unless defined $v[-1];
2097 wakaba 1.19 push @v, $self->border_left_style;
2098 wakaba 1.7 return undef unless defined $v[-1];
2099    
2100     pop @v if $v[1] eq $v[3];
2101     pop @v if $v[0] eq $v[2];
2102     pop @v if $v[0] eq $v[1];
2103     return join ' ', @v;
2104     },
2105     };
2106     $Attr->{border_style} = $Prop->{'border-style'};
2107    
2108 wakaba 1.19 $Prop->{margin} = {
2109     css => 'margin',
2110     dom => 'margin',
2111     parse => sub {
2112     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2113    
2114     my %prop_value;
2115    
2116     my $sign = 1;
2117     if ($t->{type} == MINUS_TOKEN) {
2118     $t = $tt->get_next_token;
2119     $sign = -1;
2120     }
2121    
2122     if ($t->{type} == DIMENSION_TOKEN) {
2123     my $value = $t->{number} * $sign;
2124     my $unit = lc $t->{value}; ## TODO: case
2125     $t = $tt->get_next_token;
2126     if ($length_unit->{$unit}) {
2127     $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
2128     } else {
2129     $onerror->(type => 'syntax error:'.$prop_name,
2130     level => $self->{must_level},
2131     token => $t);
2132     return ($t, undef);
2133     }
2134     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2135     my $value = $t->{number} * $sign;
2136     $t = $tt->get_next_token;
2137     $prop_value{'margin-top'} = ['PERCENTAGE', $value];
2138 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2139 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2140     my $value = $t->{number} * $sign;
2141     $t = $tt->get_next_token;
2142     $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
2143     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2144     my $prop_value = lc $t->{value}; ## TODO: case folding
2145     $t = $tt->get_next_token;
2146     if ($prop_value eq 'auto') {
2147     $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
2148     } elsif ($prop_value eq 'inherit') {
2149     $prop_value{'margin-top'} = ['INHERIT'];
2150     $prop_value{'margin-right'} = $prop_value{'margin-top'};
2151     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2152     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2153     return ($t, \%prop_value);
2154     } else {
2155     $onerror->(type => 'syntax error:'.$prop_name,
2156     level => $self->{must_level},
2157     token => $t);
2158     return ($t, undef);
2159     }
2160     } else {
2161     $onerror->(type => 'syntax error:'.$prop_name,
2162     level => $self->{must_level},
2163     token => $t);
2164     return ($t, undef);
2165     }
2166     $prop_value{'margin-right'} = $prop_value{'margin-top'};
2167     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2168     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2169    
2170     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2171     $sign = 1;
2172     if ($t->{type} == MINUS_TOKEN) {
2173     $t = $tt->get_next_token;
2174     $sign = -1;
2175     }
2176    
2177     if ($t->{type} == DIMENSION_TOKEN) {
2178     my $value = $t->{number} * $sign;
2179     my $unit = lc $t->{value}; ## TODO: case
2180     $t = $tt->get_next_token;
2181     if ($length_unit->{$unit}) {
2182     $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
2183     } else {
2184     $onerror->(type => 'syntax error:'.$prop_name,
2185     level => $self->{must_level},
2186     token => $t);
2187     return ($t, undef);
2188     }
2189     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2190     my $value = $t->{number} * $sign;
2191     $t = $tt->get_next_token;
2192     $prop_value{'margin-right'} = ['PERCENTAGE', $value];
2193 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2194 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2195     my $value = $t->{number} * $sign;
2196     $t = $tt->get_next_token;
2197     $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
2198     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2199     my $prop_value = lc $t->{value}; ## TODO: case folding
2200     $t = $tt->get_next_token;
2201     if ($prop_value eq 'auto') {
2202     $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
2203     } else {
2204     $onerror->(type => 'syntax error:'.$prop_name,
2205     level => $self->{must_level},
2206     token => $t);
2207     return ($t, undef);
2208     }
2209     } else {
2210     return ($t, \%prop_value);
2211     }
2212     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2213    
2214     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2215     $sign = 1;
2216     if ($t->{type} == MINUS_TOKEN) {
2217     $t = $tt->get_next_token;
2218     $sign = -1;
2219     }
2220    
2221     if ($t->{type} == DIMENSION_TOKEN) {
2222     my $value = $t->{number} * $sign;
2223     my $unit = lc $t->{value}; ## TODO: case
2224     $t = $tt->get_next_token;
2225     if ($length_unit->{$unit}) {
2226     $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
2227     } else {
2228     $onerror->(type => 'syntax error:'.$prop_name,
2229     level => $self->{must_level},
2230     token => $t);
2231     return ($t, undef);
2232     }
2233     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2234     my $value = $t->{number} * $sign;
2235     $t = $tt->get_next_token;
2236     $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
2237 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2238 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2239     my $value = $t->{number} * $sign;
2240     $t = $tt->get_next_token;
2241     $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
2242     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2243     my $prop_value = lc $t->{value}; ## TODO: case folding
2244     $t = $tt->get_next_token;
2245     if ($prop_value eq 'auto') {
2246     $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
2247     } else {
2248     $onerror->(type => 'syntax error:'.$prop_name,
2249     level => $self->{must_level},
2250     token => $t);
2251     return ($t, undef);
2252     }
2253     } else {
2254     return ($t, \%prop_value);
2255     }
2256    
2257     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2258     $sign = 1;
2259     if ($t->{type} == MINUS_TOKEN) {
2260     $t = $tt->get_next_token;
2261     $sign = -1;
2262     }
2263    
2264     if ($t->{type} == DIMENSION_TOKEN) {
2265     my $value = $t->{number} * $sign;
2266     my $unit = lc $t->{value}; ## TODO: case
2267     $t = $tt->get_next_token;
2268     if ($length_unit->{$unit}) {
2269     $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
2270     } else {
2271     $onerror->(type => 'syntax error:'.$prop_name,
2272     level => $self->{must_level},
2273     token => $t);
2274     return ($t, undef);
2275     }
2276     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2277     my $value = $t->{number} * $sign;
2278     $t = $tt->get_next_token;
2279     $prop_value{'margin-left'} = ['PERCENTAGE', $value];
2280 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2281 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2282     my $value = $t->{number} * $sign;
2283     $t = $tt->get_next_token;
2284     $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
2285     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2286     my $prop_value = lc $t->{value}; ## TODO: case folding
2287     $t = $tt->get_next_token;
2288     if ($prop_value eq 'auto') {
2289     $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
2290     } else {
2291     $onerror->(type => 'syntax error:'.$prop_name,
2292     level => $self->{must_level},
2293     token => $t);
2294     return ($t, undef);
2295     }
2296     } else {
2297     return ($t, \%prop_value);
2298     }
2299    
2300     return ($t, \%prop_value);
2301     },
2302     serialize => sub {
2303     my ($self, $prop_name, $value) = @_;
2304    
2305     local $Error::Depth = $Error::Depth + 1;
2306     my @v;
2307     push @v, $self->margin_top;
2308     return undef unless defined $v[-1];
2309     push @v, $self->margin_right;
2310     return undef unless defined $v[-1];
2311     push @v, $self->margin_bottom;
2312     return undef unless defined $v[-1];
2313     push @v, $self->margin_left;
2314     return undef unless defined $v[-1];
2315    
2316     pop @v if $v[1] eq $v[3];
2317     pop @v if $v[0] eq $v[2];
2318     pop @v if $v[0] eq $v[1];
2319     return join ' ', @v;
2320     },
2321     };
2322     $Attr->{margin} = $Prop->{margin};
2323    
2324     $Prop->{padding} = {
2325     css => 'padding',
2326     dom => 'padding',
2327     parse => sub {
2328     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2329    
2330     my %prop_value;
2331    
2332     my $sign = 1;
2333     if ($t->{type} == MINUS_TOKEN) {
2334     $t = $tt->get_next_token;
2335     $sign = -1;
2336     }
2337    
2338     if ($t->{type} == DIMENSION_TOKEN) {
2339     my $value = $t->{number} * $sign;
2340     my $unit = lc $t->{value}; ## TODO: case
2341     $t = $tt->get_next_token;
2342     if ($length_unit->{$unit} and $value >= 0) {
2343     $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
2344     } else {
2345     $onerror->(type => 'syntax error:'.$prop_name,
2346     level => $self->{must_level},
2347     token => $t);
2348     return ($t, undef);
2349     }
2350     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2351     my $value = $t->{number} * $sign;
2352     $t = $tt->get_next_token;
2353     $prop_value{'padding-top'} = ['PERCENTAGE', $value];
2354     unless ($value >= 0) {
2355     $onerror->(type => 'syntax error:'.$prop_name,
2356     level => $self->{must_level},
2357     token => $t);
2358     return ($t, undef);
2359     }
2360 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2361 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2362     my $value = $t->{number} * $sign;
2363     $t = $tt->get_next_token;
2364     $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
2365     unless ($value >= 0) {
2366     $onerror->(type => 'syntax error:'.$prop_name,
2367     level => $self->{must_level},
2368     token => $t);
2369     return ($t, undef);
2370     }
2371     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2372     my $prop_value = lc $t->{value}; ## TODO: case folding
2373     $t = $tt->get_next_token;
2374     if ($prop_value eq 'inherit') {
2375     $prop_value{'padding-top'} = ['INHERIT'];
2376     $prop_value{'padding-right'} = $prop_value{'padding-top'};
2377     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2378     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2379     return ($t, \%prop_value);
2380     } else {
2381     $onerror->(type => 'syntax error:'.$prop_name,
2382     level => $self->{must_level},
2383     token => $t);
2384     return ($t, undef);
2385     }
2386     } else {
2387     $onerror->(type => 'syntax error:'.$prop_name,
2388     level => $self->{must_level},
2389     token => $t);
2390     return ($t, undef);
2391     }
2392     $prop_value{'padding-right'} = $prop_value{'padding-top'};
2393     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2394     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2395    
2396     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2397     $sign = 1;
2398     if ($t->{type} == MINUS_TOKEN) {
2399     $t = $tt->get_next_token;
2400     $sign = -1;
2401     }
2402    
2403     if ($t->{type} == DIMENSION_TOKEN) {
2404     my $value = $t->{number} * $sign;
2405     my $unit = lc $t->{value}; ## TODO: case
2406     $t = $tt->get_next_token;
2407     if ($length_unit->{$unit} and $value >= 0) {
2408     $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
2409     } else {
2410     $onerror->(type => 'syntax error:'.$prop_name,
2411     level => $self->{must_level},
2412     token => $t);
2413     return ($t, undef);
2414     }
2415     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2416     my $value = $t->{number} * $sign;
2417     $t = $tt->get_next_token;
2418     $prop_value{'padding-right'} = ['PERCENTAGE', $value];
2419     unless ($value >= 0) {
2420     $onerror->(type => 'syntax error:'.$prop_name,
2421     level => $self->{must_level},
2422     token => $t);
2423     return ($t, undef);
2424     }
2425 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2426 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2427     my $value = $t->{number} * $sign;
2428     $t = $tt->get_next_token;
2429     $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
2430     unless ($value >= 0) {
2431     $onerror->(type => 'syntax error:'.$prop_name,
2432     level => $self->{must_level},
2433     token => $t);
2434     return ($t, undef);
2435     }
2436     } else {
2437     return ($t, \%prop_value);
2438     }
2439     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2440    
2441     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2442     $sign = 1;
2443     if ($t->{type} == MINUS_TOKEN) {
2444     $t = $tt->get_next_token;
2445     $sign = -1;
2446     }
2447    
2448     if ($t->{type} == DIMENSION_TOKEN) {
2449     my $value = $t->{number} * $sign;
2450     my $unit = lc $t->{value}; ## TODO: case
2451     $t = $tt->get_next_token;
2452     if ($length_unit->{$unit} and $value >= 0) {
2453     $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
2454     } else {
2455     $onerror->(type => 'syntax error:'.$prop_name,
2456     level => $self->{must_level},
2457     token => $t);
2458     return ($t, undef);
2459     }
2460     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2461     my $value = $t->{number} * $sign;
2462     $t = $tt->get_next_token;
2463     $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
2464     unless ($value >= 0) {
2465     $onerror->(type => 'syntax error:'.$prop_name,
2466     level => $self->{must_level},
2467     token => $t);
2468     return ($t, undef);
2469     }
2470 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2471 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2472     my $value = $t->{number} * $sign;
2473     $t = $tt->get_next_token;
2474     $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
2475     unless ($value >= 0) {
2476     $onerror->(type => 'syntax error:'.$prop_name,
2477     level => $self->{must_level},
2478     token => $t);
2479     return ($t, undef);
2480     }
2481     } else {
2482     return ($t, \%prop_value);
2483     }
2484    
2485     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2486     $sign = 1;
2487     if ($t->{type} == MINUS_TOKEN) {
2488     $t = $tt->get_next_token;
2489     $sign = -1;
2490     }
2491    
2492     if ($t->{type} == DIMENSION_TOKEN) {
2493     my $value = $t->{number} * $sign;
2494     my $unit = lc $t->{value}; ## TODO: case
2495     $t = $tt->get_next_token;
2496     if ($length_unit->{$unit} and $value >= 0) {
2497     $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
2498     } else {
2499     $onerror->(type => 'syntax error:'.$prop_name,
2500     level => $self->{must_level},
2501     token => $t);
2502     return ($t, undef);
2503     }
2504     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2505     my $value = $t->{number} * $sign;
2506     $t = $tt->get_next_token;
2507     $prop_value{'padding-left'} = ['PERCENTAGE', $value];
2508     unless ($value >= 0) {
2509     $onerror->(type => 'syntax error:'.$prop_name,
2510     level => $self->{must_level},
2511     token => $t);
2512     return ($t, undef);
2513     }
2514 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2515 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2516     my $value = $t->{number} * $sign;
2517     $t = $tt->get_next_token;
2518     $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
2519     unless ($value >= 0) {
2520     $onerror->(type => 'syntax error:'.$prop_name,
2521     level => $self->{must_level},
2522     token => $t);
2523     return ($t, undef);
2524     }
2525     } else {
2526     return ($t, \%prop_value);
2527     }
2528    
2529     return ($t, \%prop_value);
2530     },
2531     serialize => sub {
2532     my ($self, $prop_name, $value) = @_;
2533    
2534     local $Error::Depth = $Error::Depth + 1;
2535     my @v;
2536     push @v, $self->padding_top;
2537     return undef unless defined $v[-1];
2538     push @v, $self->padding_right;
2539     return undef unless defined $v[-1];
2540     push @v, $self->padding_bottom;
2541     return undef unless defined $v[-1];
2542     push @v, $self->padding_left;
2543     return undef unless defined $v[-1];
2544    
2545     pop @v if $v[1] eq $v[3];
2546     pop @v if $v[0] eq $v[2];
2547     pop @v if $v[0] eq $v[1];
2548     return join ' ', @v;
2549     },
2550     };
2551     $Attr->{padding} = $Prop->{padding};
2552    
2553 wakaba 1.20 $Prop->{'border-width'} = {
2554     css => 'border-width',
2555     dom => 'border_width',
2556     parse => sub {
2557     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2558    
2559     my %prop_value;
2560    
2561     my $sign = 1;
2562     if ($t->{type} == MINUS_TOKEN) {
2563     $t = $tt->get_next_token;
2564     $sign = -1;
2565     }
2566    
2567     if ($t->{type} == DIMENSION_TOKEN) {
2568     my $value = $t->{number} * $sign;
2569     my $unit = lc $t->{value}; ## TODO: case
2570     $t = $tt->get_next_token;
2571     if ($length_unit->{$unit} and $value >= 0) {
2572     $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
2573     } else {
2574     $onerror->(type => 'syntax error:'.$prop_name,
2575     level => $self->{must_level},
2576     token => $t);
2577     return ($t, undef);
2578     }
2579     } elsif ($t->{type} == NUMBER_TOKEN and
2580     ($self->{unitless_px} or $t->{number} == 0)) {
2581     my $value = $t->{number} * $sign;
2582     $t = $tt->get_next_token;
2583     $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
2584     unless ($value >= 0) {
2585     $onerror->(type => 'syntax error:'.$prop_name,
2586     level => $self->{must_level},
2587     token => $t);
2588     return ($t, undef);
2589     }
2590     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2591     my $prop_value = lc $t->{value}; ## TODO: case folding
2592     $t = $tt->get_next_token;
2593     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2594     $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
2595     } elsif ($prop_value eq 'inherit') {
2596     $prop_value{'border-top-width'} = ['INHERIT'];
2597     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
2598     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
2599     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
2600     return ($t, \%prop_value);
2601     } else {
2602     $onerror->(type => 'syntax error:'.$prop_name,
2603     level => $self->{must_level},
2604     token => $t);
2605     return ($t, undef);
2606     }
2607     } else {
2608     $onerror->(type => 'syntax error:'.$prop_name,
2609     level => $self->{must_level},
2610     token => $t);
2611     return ($t, undef);
2612     }
2613     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
2614     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
2615     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
2616    
2617     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2618     $sign = 1;
2619     if ($t->{type} == MINUS_TOKEN) {
2620     $t = $tt->get_next_token;
2621     $sign = -1;
2622     }
2623    
2624     if ($t->{type} == DIMENSION_TOKEN) {
2625     my $value = $t->{number} * $sign;
2626     my $unit = lc $t->{value}; ## TODO: case
2627     $t = $tt->get_next_token;
2628     if ($length_unit->{$unit} and $value >= 0) {
2629     $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
2630     } else {
2631     $onerror->(type => 'syntax error:'.$prop_name,
2632     level => $self->{must_level},
2633     token => $t);
2634     return ($t, undef);
2635     }
2636     } elsif ($t->{type} == NUMBER_TOKEN and
2637     ($self->{unitless_px} or $t->{number} == 0)) {
2638     my $value = $t->{number} * $sign;
2639     $t = $tt->get_next_token;
2640     $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
2641     unless ($value >= 0) {
2642     $onerror->(type => 'syntax error:'.$prop_name,
2643     level => $self->{must_level},
2644     token => $t);
2645     return ($t, undef);
2646     }
2647     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2648     my $prop_value = lc $t->{value}; ## TODO: case
2649     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2650     $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
2651     $t = $tt->get_next_token;
2652     }
2653     } else {
2654     return ($t, \%prop_value);
2655     }
2656     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
2657    
2658     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2659     $sign = 1;
2660     if ($t->{type} == MINUS_TOKEN) {
2661     $t = $tt->get_next_token;
2662     $sign = -1;
2663     }
2664    
2665     if ($t->{type} == DIMENSION_TOKEN) {
2666     my $value = $t->{number} * $sign;
2667     my $unit = lc $t->{value}; ## TODO: case
2668     $t = $tt->get_next_token;
2669     if ($length_unit->{$unit} and $value >= 0) {
2670     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
2671     } else {
2672     $onerror->(type => 'syntax error:'.$prop_name,
2673     level => $self->{must_level},
2674     token => $t);
2675     return ($t, undef);
2676     }
2677     } elsif ($t->{type} == NUMBER_TOKEN and
2678     ($self->{unitless_px} or $t->{number} == 0)) {
2679     my $value = $t->{number} * $sign;
2680     $t = $tt->get_next_token;
2681     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
2682     unless ($value >= 0) {
2683     $onerror->(type => 'syntax error:'.$prop_name,
2684     level => $self->{must_level},
2685     token => $t);
2686     return ($t, undef);
2687     }
2688     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2689     my $prop_value = lc $t->{value}; ## TODO: case
2690     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2691     $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
2692     $t = $tt->get_next_token;
2693     }
2694     } else {
2695     return ($t, \%prop_value);
2696     }
2697    
2698     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2699     $sign = 1;
2700     if ($t->{type} == MINUS_TOKEN) {
2701     $t = $tt->get_next_token;
2702     $sign = -1;
2703     }
2704    
2705     if ($t->{type} == DIMENSION_TOKEN) {
2706     my $value = $t->{number} * $sign;
2707     my $unit = lc $t->{value}; ## TODO: case
2708     $t = $tt->get_next_token;
2709     if ($length_unit->{$unit} and $value >= 0) {
2710     $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
2711     } else {
2712     $onerror->(type => 'syntax error:'.$prop_name,
2713     level => $self->{must_level},
2714     token => $t);
2715     return ($t, undef);
2716     }
2717     } elsif ($t->{type} == NUMBER_TOKEN and
2718     ($self->{unitless_px} or $t->{number} == 0)) {
2719     my $value = $t->{number} * $sign;
2720     $t = $tt->get_next_token;
2721     $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
2722     unless ($value >= 0) {
2723     $onerror->(type => 'syntax error:'.$prop_name,
2724     level => $self->{must_level},
2725     token => $t);
2726     return ($t, undef);
2727     }
2728     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2729     my $prop_value = lc $t->{value}; ## TODO: case
2730     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2731     $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
2732     $t = $tt->get_next_token;
2733     }
2734     } else {
2735     return ($t, \%prop_value);
2736     }
2737    
2738     return ($t, \%prop_value);
2739     },
2740     serialize => sub {
2741     my ($self, $prop_name, $value) = @_;
2742    
2743     local $Error::Depth = $Error::Depth + 1;
2744     my @v;
2745     push @v, $self->padding_top;
2746     return undef unless defined $v[-1];
2747     push @v, $self->padding_right;
2748     return undef unless defined $v[-1];
2749     push @v, $self->padding_bottom;
2750     return undef unless defined $v[-1];
2751     push @v, $self->padding_left;
2752     return undef unless defined $v[-1];
2753    
2754     pop @v if $v[1] eq $v[3];
2755     pop @v if $v[0] eq $v[2];
2756     pop @v if $v[0] eq $v[1];
2757     return join ' ', @v;
2758     },
2759     };
2760     $Attr->{padding} = $Prop->{padding};
2761    
2762 wakaba 1.12 $Prop->{'list-style'} = {
2763     css => 'list-style',
2764     dom => 'list_style',
2765     parse => sub {
2766     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2767    
2768     my %prop_value;
2769     my $none = 0;
2770    
2771     F: for my $f (1..3) {
2772     if ($t->{type} == IDENT_TOKEN) {
2773     my $prop_value = lc $t->{value}; ## TODO: case folding
2774     $t = $tt->get_next_token;
2775    
2776     if ($prop_value eq 'none') {
2777     $none++;
2778     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
2779     if (exists $prop_value{'list-style-type'}) {
2780     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
2781     $prop_name,
2782     level => $self->{must_level},
2783     token => $t);
2784     return ($t, undef);
2785     } else {
2786     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
2787     }
2788     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
2789     if (exists $prop_value{'list-style-position'}) {
2790     $onerror->(type => q[syntax error:duplicate:'list-style-position':].
2791     $prop_name,
2792     level => $self->{must_level},
2793     token => $t);
2794     return ($t, undef);
2795     }
2796    
2797     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
2798     } elsif ($f == 1 and $prop_value eq 'inherit') {
2799     $prop_value{'list-style-type'} = ["INHERIT"];
2800     $prop_value{'list-style-position'} = ["INHERIT"];
2801     $prop_value{'list-style-image'} = ["INHERIT"];
2802     last F;
2803     } else {
2804     if ($f == 1) {
2805     $onerror->(type => 'syntax error:'.$prop_name,
2806     level => $self->{must_level},
2807     token => $t);
2808     return ($t, undef);
2809     } else {
2810     last F;
2811     }
2812     }
2813     } elsif ($t->{type} == URI_TOKEN) {
2814     if (exists $prop_value{'list-style-image'}) {
2815     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
2816     $prop_name,
2817     level => $self->{must_level},
2818     token => $t);
2819     return ($t, undef);
2820     }
2821    
2822     $prop_value{'list-style-image'}
2823 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
2824 wakaba 1.12 $t = $tt->get_next_token;
2825     } else {
2826     if ($f == 1) {
2827     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2828     level => $self->{must_level},
2829     token => $t);
2830     return ($t, undef);
2831     } else {
2832     last F;
2833     }
2834     }
2835    
2836     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2837     } # F
2838     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
2839    
2840     if ($none == 1) {
2841     if (exists $prop_value{'list-style-type'}) {
2842     if (exists $prop_value{'list-style-image'}) {
2843     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
2844     $prop_name,
2845     level => $self->{must_level},
2846     token => $t);
2847     return ($t, undef);
2848     } else {
2849     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
2850     }
2851     } else {
2852     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
2853     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
2854     unless exists $prop_value{'list-style-image'};
2855     }
2856     } elsif ($none == 2) {
2857     if (exists $prop_value{'list-style-type'}) {
2858     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
2859     $prop_name,
2860     level => $self->{must_level},
2861     token => $t);
2862     return ($t, undef);
2863     }
2864     if (exists $prop_value{'list-style-image'}) {
2865     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
2866     $prop_name,
2867     level => $self->{must_level},
2868     token => $t);
2869     return ($t, undef);
2870     }
2871    
2872     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
2873     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
2874     } elsif ($none == 3) {
2875     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
2876     $prop_name,
2877     level => $self->{must_level},
2878     token => $t);
2879     return ($t, undef);
2880     }
2881    
2882     for (qw/list-style-type list-style-position list-style-image/) {
2883     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
2884     }
2885    
2886     return ($t, \%prop_value);
2887     },
2888     serialize => sub {
2889     my ($self, $prop_name, $value) = @_;
2890    
2891     local $Error::Depth = $Error::Depth + 1;
2892     return $self->list_style_type . ' ' . $self->list_style_position .
2893     ' ' . $self->list_style_image;
2894     },
2895     };
2896     $Attr->{list_style} = $Prop->{'list-style'};
2897    
2898 wakaba 1.16 ## NOTE: Future version of the implementation will change the way to
2899     ## store the parsed value to support CSS 3 properties.
2900     $Prop->{'text-decoration'} = {
2901     css => 'text-decoration',
2902     dom => 'text_decoration',
2903     key => 'text_decoration',
2904     parse => sub {
2905     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2906    
2907     my $value = ['DECORATION']; # , underline, overline, line-through, blink
2908    
2909     if ($t->{type} == IDENT_TOKEN) {
2910     my $v = lc $t->{value}; ## TODO: case
2911     $t = $tt->get_next_token;
2912     if ($v eq 'inherit') {
2913     return ($t, {$prop_name => ['INHERIT']});
2914     } elsif ($v eq 'none') {
2915     return ($t, {$prop_name => $value});
2916     } elsif ($v eq 'underline' and
2917     $self->{prop_value}->{$prop_name}->{$v}) {
2918     $value->[1] = 1;
2919     } elsif ($v eq 'overline' and
2920     $self->{prop_value}->{$prop_name}->{$v}) {
2921     $value->[2] = 1;
2922     } elsif ($v eq 'line-through' and
2923     $self->{prop_value}->{$prop_name}->{$v}) {
2924     $value->[3] = 1;
2925     } elsif ($v eq 'blink' and
2926     $self->{prop_value}->{$prop_name}->{$v}) {
2927     $value->[4] = 1;
2928     } else {
2929     $onerror->(type => 'syntax error:'.$prop_name,
2930     level => $self->{must_level},
2931     token => $t);
2932     return ($t, undef);
2933     }
2934     }
2935    
2936     F: {
2937     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2938     last F unless $t->{type} == IDENT_TOKEN;
2939    
2940     my $v = lc $t->{value}; ## TODO: case
2941     $t = $tt->get_next_token;
2942     if ($v eq 'underline' and
2943     $self->{prop_value}->{$prop_name}->{$v}) {
2944     $value->[1] = 1;
2945     } elsif ($v eq 'overline' and
2946     $self->{prop_value}->{$prop_name}->{$v}) {
2947     $value->[1] = 2;
2948     } elsif ($v eq 'line-through' and
2949     $self->{prop_value}->{$prop_name}->{$v}) {
2950     $value->[1] = 3;
2951     } elsif ($v eq 'blink' and
2952     $self->{prop_value}->{$prop_name}->{$v}) {
2953     $value->[1] = 4;
2954     } else {
2955     last F;
2956     }
2957    
2958     redo F;
2959     } # F
2960    
2961     return ($t, {$prop_name => $value});
2962     },
2963     serialize => $default_serializer,
2964     initial => ["KEYWORD", "none"],
2965     #inherited => 0,
2966     compute => $compute_as_specified,
2967     };
2968     $Attr->{text_decoration} = $Prop->{'text-decoration'};
2969     $Key->{text_decoration} = $Prop->{'text-decoration'};
2970    
2971 wakaba 1.1 1;
2972 wakaba 1.20 ## $Date: 2008/01/03 10:02:08 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24