/[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.22 - (hide annotations) (download)
Fri Jan 4 05:36:36 2008 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.21: +191 -51 lines
++ whatpm/Whatpm/CSS/ChangeLog	4 Jan 2008 05:36:22 -0000
2008-01-04  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm ($default_serializer): Support for
	the |PERCENTAGE| data type.
	(padding-top, border-top-width): The parser now
	refers the parser of the 'margin-top'.
	(width, height, min-width, min-height, max-width, max-height,
	vertical-align, line-height): Implemented.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24