/[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.24 - (hide annotations) (download)
Sun Jan 6 02:56:21 2008 UTC (17 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.23: +218 -10 lines
++ whatpm/Whatpm/CSS/ChangeLog	6 Jan 2008 02:56:17 -0000
2008-01-06  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm: Disallow '-' without following NUMBER or DIMENSION.
	(border-width serialize): Was incorrect.

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

	* Parser.pm (-manakai-border-spacing-x, -manakai-border-spacing-y,
	border-spacing): 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 wakaba 1.23 $Prop->{'letter-spacing'} = {
1277     css => 'letter-spacing',
1278     dom => 'letter_spacing',
1279     key => 'letter_spacing',
1280     parse => sub {
1281     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1282    
1283 wakaba 1.24 ## NOTE: Used also for 'word-spacing', '-manakai-border-spacing-x',
1284     ## and '-manakai-border-spacing-y'.
1285 wakaba 1.23
1286     my $sign = 1;
1287     if ($t->{type} == MINUS_TOKEN) {
1288     $t = $tt->get_next_token;
1289     $sign = -1;
1290     }
1291     my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1292    
1293     if ($t->{type} == DIMENSION_TOKEN) {
1294     my $value = $t->{number} * $sign;
1295     my $unit = lc $t->{value}; ## TODO: case
1296     $t = $tt->get_next_token;
1297 wakaba 1.24 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
1298 wakaba 1.23 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1299     }
1300     } elsif ($t->{type} == NUMBER_TOKEN and
1301     ($self->{unitless_px} or $t->{number} == 0)) {
1302     my $value = $t->{number} * $sign;
1303     $t = $tt->get_next_token;
1304 wakaba 1.24 return ($t, {$prop_name => ['DIMENSION', $value, 'px']})
1305     if $allow_negative or $value >= 0;
1306 wakaba 1.23 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1307     my $value = lc $t->{value}; ## TODO: case
1308     $t = $tt->get_next_token;
1309 wakaba 1.24 if ($Prop->{$prop_name}->{keyword}->{$value}) {
1310 wakaba 1.23 return ($t, {$prop_name => ['KEYWORD', $value]});
1311     } elsif ($value eq 'inherit') {
1312     return ($t, {$prop_name => ['INHERIT']});
1313     }
1314     }
1315    
1316     $onerror->(type => 'syntax error:'.$prop_name,
1317     level => $self->{must_level},
1318     token => $t);
1319     return ($t, undef);
1320     },
1321 wakaba 1.24 allow_negative => 1,
1322     keyword => {normal => 1},
1323 wakaba 1.23 serialize => $default_serializer,
1324     initial => ['KEYWORD', 'normal'],
1325     inherited => 1,
1326     compute => $compute_length,
1327     };
1328     $Attr->{letter_spacing} = $Prop->{'letter-spacing'};
1329     $Key->{letter_spacing} = $Prop->{'letter-spacing'};
1330    
1331     $Prop->{'word-spacing'} = {
1332     css => 'word-spacing',
1333     dom => 'word_spacing',
1334     key => 'word_spacing',
1335     parse => $Prop->{'letter-spacing'}->{parse},
1336 wakaba 1.24 allow_negative => 1,
1337     keyword => {normal => 1},
1338 wakaba 1.23 serialize => $default_serializer,
1339     initial => ['KEYWORD', 'normal'],
1340     inherited => 1,
1341     compute => $compute_length,
1342     };
1343     $Attr->{word_spacing} = $Prop->{'word-spacing'};
1344     $Key->{word_spacing} = $Prop->{'word-spacing'};
1345    
1346 wakaba 1.24 $Prop->{'-manakai-border-spacing-x'} = {
1347     css => '-manakai-border-spacing-x',
1348     dom => '_manakai_border_spacing_x',
1349     key => 'border_spacing_x',
1350     parse => $Prop->{'letter-spacing'}->{parse},
1351     #allow_negative => 0,
1352     #keyword => {},
1353     serialize => $default_serializer,
1354     initial => ['DIMENSION', 0, 'px'],
1355     inherited => 1,
1356     compute => $compute_length,
1357     };
1358     $Attr->{_manakai_border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
1359     $Key->{border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
1360    
1361     $Prop->{'-manakai-border-spacing-y'} = {
1362     css => '-manakai-border-spacing-y',
1363     dom => '_manakai_border_spacing_y',
1364     key => 'border_spacing_y',
1365     parse => $Prop->{'letter-spacing'}->{parse},
1366     #allow_negative => 0,
1367     #keyword => {},
1368     serialize => $default_serializer,
1369     initial => ['DIMENSION', 0, 'px'],
1370     inherited => 1,
1371     compute => $compute_length,
1372     };
1373     $Attr->{_manakai_border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
1374     $Key->{border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
1375    
1376 wakaba 1.19 $Prop->{'margin-top'} = {
1377     css => 'margin-top',
1378     dom => 'margin_top',
1379     key => 'margin_top',
1380     parse => sub {
1381     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1382    
1383 wakaba 1.21 ## NOTE: Used for 'margin-top', 'margin-right', 'margin-bottom',
1384 wakaba 1.22 ## 'margin-left', 'top', 'right', 'bottom', 'left', 'padding-top',
1385     ## 'padding-right', 'padding-bottom', 'padding-left',
1386     ## 'border-top-width', 'border-right-width', 'border-bottom-width',
1387 wakaba 1.23 ## 'border-left-width', and 'text-indent'.
1388 wakaba 1.21
1389 wakaba 1.19 my $sign = 1;
1390     if ($t->{type} == MINUS_TOKEN) {
1391     $t = $tt->get_next_token;
1392     $sign = -1;
1393     }
1394 wakaba 1.22 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1395 wakaba 1.19
1396     if ($t->{type} == DIMENSION_TOKEN) {
1397     my $value = $t->{number} * $sign;
1398     my $unit = lc $t->{value}; ## TODO: case
1399     $t = $tt->get_next_token;
1400 wakaba 1.22 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
1401 wakaba 1.19 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1402     }
1403     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1404     my $value = $t->{number} * $sign;
1405     $t = $tt->get_next_token;
1406 wakaba 1.22 return ($t, {$prop_name => ['PERCENTAGE', $value]})
1407     if $allow_negative or $value >= 0;
1408 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
1409 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
1410     my $value = $t->{number} * $sign;
1411     $t = $tt->get_next_token;
1412 wakaba 1.22 return ($t, {$prop_name => ['DIMENSION', $value, 'px']})
1413     if $allow_negative or $value >= 0;
1414 wakaba 1.19 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1415     my $value = lc $t->{value}; ## TODO: case
1416     $t = $tt->get_next_token;
1417 wakaba 1.22 if ($Prop->{$prop_name}->{keyword}->{$value}) {
1418 wakaba 1.19 return ($t, {$prop_name => ['KEYWORD', $value]});
1419     } elsif ($value eq 'inherit') {
1420     return ($t, {$prop_name => ['INHERIT']});
1421     }
1422     }
1423    
1424     $onerror->(type => 'syntax error:'.$prop_name,
1425     level => $self->{must_level},
1426     token => $t);
1427     return ($t, undef);
1428     },
1429 wakaba 1.22 allow_negative => 1,
1430     keyword => {auto => 1},
1431 wakaba 1.19 serialize => $default_serializer,
1432     initial => ['DIMENSION', 0, 'px'],
1433     #inherited => 0,
1434     compute => $compute_length,
1435     };
1436     $Attr->{margin_top} = $Prop->{'margin-top'};
1437     $Key->{margin_top} = $Prop->{'margin-top'};
1438    
1439     $Prop->{'margin-bottom'} = {
1440     css => 'margin-bottom',
1441     dom => 'margin_bottom',
1442     key => 'margin_bottom',
1443     parse => $Prop->{'margin-top'}->{parse},
1444 wakaba 1.22 allow_negative => 1,
1445     keyword => {auto => 1},
1446 wakaba 1.19 serialize => $default_serializer,
1447     initial => ['DIMENSION', 0, 'px'],
1448     #inherited => 0,
1449     compute => $compute_length,
1450     };
1451     $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
1452     $Key->{margin_bottom} = $Prop->{'margin-bottom'};
1453    
1454     $Prop->{'margin-right'} = {
1455     css => 'margin-right',
1456     dom => 'margin_right',
1457     key => 'margin_right',
1458     parse => $Prop->{'margin-top'}->{parse},
1459 wakaba 1.22 allow_negative => 1,
1460     keyword => {auto => 1},
1461 wakaba 1.19 serialize => $default_serializer,
1462     initial => ['DIMENSION', 0, 'px'],
1463     #inherited => 0,
1464     compute => $compute_length,
1465     };
1466     $Attr->{margin_right} = $Prop->{'margin-right'};
1467     $Key->{margin_right} = $Prop->{'margin-right'};
1468    
1469     $Prop->{'margin-left'} = {
1470     css => 'margin-left',
1471     dom => 'margin_left',
1472     key => 'margin_left',
1473     parse => $Prop->{'margin-top'}->{parse},
1474 wakaba 1.22 allow_negative => 1,
1475     keyword => {auto => 1},
1476 wakaba 1.19 serialize => $default_serializer,
1477     initial => ['DIMENSION', 0, 'px'],
1478     #inherited => 0,
1479     compute => $compute_length,
1480     };
1481     $Attr->{margin_left} = $Prop->{'margin-left'};
1482     $Key->{margin_left} = $Prop->{'margin-left'};
1483    
1484 wakaba 1.21 $Prop->{top} = {
1485     css => 'top',
1486     dom => 'top',
1487     key => 'top',
1488     parse => $Prop->{'margin-top'}->{parse},
1489 wakaba 1.22 allow_negative => 1,
1490     keyword => {auto => 1},
1491 wakaba 1.21 serialize => $default_serializer,
1492     initial => ['KEYWORD', 'auto'],
1493     #inherited => 0,
1494     compute_multiple => sub {
1495     my ($self, $element, $eid, $prop_name) = @_;
1496    
1497     my $pos_value = $self->get_computed_value ($element, 'position');
1498     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1499     if ($pos_value->[1] eq 'static') {
1500     $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
1501     $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
1502     return;
1503     } elsif ($pos_value->[1] eq 'relative') {
1504     my $top_specified = $self->get_specified_value_no_inherit
1505     ($element, 'top');
1506     if (defined $top_specified and
1507     ($top_specified->[0] eq 'DIMENSION' or
1508     $top_specified->[0] eq 'PERCENTAGE')) {
1509     my $tv = $self->{computed_value}->{$eid}->{top}
1510     = $compute_length->($self, $element, 'top', $top_specified);
1511     $self->{computed_value}->{$eid}->{bottom}
1512     = [$tv->[0], -$tv->[1], $tv->[2]];
1513     } else { # top: auto
1514     my $bottom_specified = $self->get_specified_value_no_inherit
1515     ($element, 'bottom');
1516     if (defined $bottom_specified and
1517     ($bottom_specified->[0] eq 'DIMENSION' or
1518     $bottom_specified->[0] eq 'PERCENTAGE')) {
1519     my $tv = $self->{computed_value}->{$eid}->{bottom}
1520     = $compute_length->($self, $element, 'bottom',
1521     $bottom_specified);
1522     $self->{computed_value}->{$eid}->{top}
1523     = [$tv->[0], -$tv->[1], $tv->[2]];
1524     } else { # bottom: auto
1525     $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
1526     $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
1527     }
1528     }
1529     return;
1530     }
1531     }
1532    
1533     my $top_specified = $self->get_specified_value_no_inherit
1534     ($element, 'top');
1535     $self->{computed_value}->{$eid}->{top}
1536     = $compute_length->($self, $element, 'top', $top_specified);
1537     my $bottom_specified = $self->get_specified_value_no_inherit
1538     ($element, 'bottom');
1539     $self->{computed_value}->{$eid}->{bottom}
1540     = $compute_length->($self, $element, 'bottom', $bottom_specified);
1541     },
1542     };
1543     $Attr->{top} = $Prop->{top};
1544     $Key->{top} = $Prop->{top};
1545    
1546     $Prop->{bottom} = {
1547     css => 'bottom',
1548     dom => 'bottom',
1549     key => 'bottom',
1550     parse => $Prop->{'margin-top'}->{parse},
1551 wakaba 1.22 allow_negative => 1,
1552     keyword => {auto => 1},
1553 wakaba 1.21 serialize => $default_serializer,
1554     initial => ['KEYWORD', 'auto'],
1555     #inherited => 0,
1556     compute_multiple => $Prop->{top}->{compute_multiple},
1557     };
1558     $Attr->{bottom} = $Prop->{bottom};
1559     $Key->{bottom} = $Prop->{bottom};
1560    
1561     $Prop->{left} = {
1562     css => 'left',
1563     dom => 'left',
1564     key => 'left',
1565     parse => $Prop->{'margin-top'}->{parse},
1566 wakaba 1.22 allow_negative => 1,
1567     keyword => {auto => 1},
1568 wakaba 1.21 serialize => $default_serializer,
1569     initial => ['KEYWORD', 'auto'],
1570     #inherited => 0,
1571     compute_multiple => sub {
1572     my ($self, $element, $eid, $prop_name) = @_;
1573    
1574     my $pos_value = $self->get_computed_value ($element, 'position');
1575     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1576     if ($pos_value->[1] eq 'static') {
1577     $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
1578     $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
1579     return;
1580     } elsif ($pos_value->[1] eq 'relative') {
1581     my $left_specified = $self->get_specified_value_no_inherit
1582     ($element, 'left');
1583     if (defined $left_specified and
1584     ($left_specified->[0] eq 'DIMENSION' or
1585     $left_specified->[0] eq 'PERCENTAGE')) {
1586     my $right_specified = $self->get_specified_value_no_inherit
1587     ($element, 'right');
1588     if (defined $right_specified and
1589     ($right_specified->[0] eq 'DIMENSION' or
1590     $right_specified->[0] eq 'PERCENTAGE')) {
1591     my $direction = $self->get_computed_value ($element, 'direction');
1592     if (defined $direction and $direction->[0] eq 'KEYWORD' and
1593     $direction->[0] eq 'ltr') {
1594     my $tv = $self->{computed_value}->{$eid}->{left}
1595     = $compute_length->($self, $element, 'left',
1596     $left_specified);
1597     $self->{computed_value}->{$eid}->{right}
1598     = [$tv->[0], -$tv->[1], $tv->[2]];
1599     } else {
1600     my $tv = $self->{computed_value}->{$eid}->{right}
1601     = $compute_length->($self, $element, 'right',
1602     $right_specified);
1603     $self->{computed_value}->{$eid}->{left}
1604     = [$tv->[0], -$tv->[1], $tv->[2]];
1605     }
1606     } else {
1607     my $tv = $self->{computed_value}->{$eid}->{left}
1608     = $compute_length->($self, $element, 'left', $left_specified);
1609     $self->{computed_value}->{$eid}->{right}
1610     = [$tv->[0], -$tv->[1], $tv->[2]];
1611     }
1612     } else { # left: auto
1613     my $right_specified = $self->get_specified_value_no_inherit
1614     ($element, 'right');
1615     if (defined $right_specified and
1616     ($right_specified->[0] eq 'DIMENSION' or
1617     $right_specified->[0] eq 'PERCENTAGE')) {
1618     my $tv = $self->{computed_value}->{$eid}->{right}
1619     = $compute_length->($self, $element, 'right',
1620     $right_specified);
1621     $self->{computed_value}->{$eid}->{left}
1622     = [$tv->[0], -$tv->[1], $tv->[2]];
1623     } else { # right: auto
1624     $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
1625     $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
1626     }
1627     }
1628     return;
1629     }
1630     }
1631    
1632     my $left_specified = $self->get_specified_value_no_inherit
1633     ($element, 'left');
1634     $self->{computed_value}->{$eid}->{left}
1635     = $compute_length->($self, $element, 'left', $left_specified);
1636     my $right_specified = $self->get_specified_value_no_inherit
1637     ($element, 'right');
1638     $self->{computed_value}->{$eid}->{right}
1639     = $compute_length->($self, $element, 'right', $right_specified);
1640     },
1641     };
1642     $Attr->{left} = $Prop->{left};
1643     $Key->{left} = $Prop->{left};
1644    
1645     $Prop->{right} = {
1646     css => 'right',
1647     dom => 'right',
1648     key => 'right',
1649     parse => $Prop->{'margin-top'}->{parse},
1650     serialize => $default_serializer,
1651     initial => ['KEYWORD', 'auto'],
1652     #inherited => 0,
1653     compute_multiple => $Prop->{left}->{compute_multiple},
1654     };
1655     $Attr->{right} = $Prop->{right};
1656     $Key->{right} = $Prop->{right};
1657    
1658 wakaba 1.22 $Prop->{width} = {
1659     css => 'width',
1660     dom => 'width',
1661     key => 'width',
1662     parse => $Prop->{'margin-top'}->{parse},
1663     #allow_negative => 0,
1664     keyword => {auto => 1},
1665     serialize => $default_serializer,
1666     initial => ['KEYWORD', 'auto'],
1667     #inherited => 0,
1668     compute => $compute_length,
1669     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/width> for
1670     ## browser compatibility issues.
1671     };
1672     $Attr->{width} = $Prop->{width};
1673     $Key->{width} = $Prop->{width};
1674    
1675     $Prop->{'min-width'} = {
1676     css => 'min-width',
1677     dom => 'min_width',
1678     key => 'min_width',
1679     parse => $Prop->{'margin-top'}->{parse},
1680     #allow_negative => 0,
1681     #keyword => {},
1682     serialize => $default_serializer,
1683     initial => ['DIMENSION', 0, 'px'],
1684     #inherited => 0,
1685     compute => $compute_length,
1686     };
1687     $Attr->{min_width} = $Prop->{'min-width'};
1688     $Key->{min_width} = $Prop->{'min-width'};
1689    
1690     $Prop->{'max-width'} = {
1691     css => 'max-width',
1692     dom => 'max_width',
1693     key => 'max_width',
1694     parse => $Prop->{'margin-top'}->{parse},
1695     #allow_negative => 0,
1696     keyword => {none => 1},
1697     serialize => $default_serializer,
1698     initial => ['KEYWORD', 'none'],
1699     #inherited => 0,
1700     compute => $compute_length,
1701     };
1702     $Attr->{max_width} = $Prop->{'max-width'};
1703     $Key->{max_width} = $Prop->{'max-width'};
1704    
1705     $Prop->{height} = {
1706     css => 'height',
1707     dom => 'height',
1708     key => 'height',
1709     parse => $Prop->{'margin-top'}->{parse},
1710     #allow_negative => 0,
1711     keyword => {auto => 1},
1712     serialize => $default_serializer,
1713     initial => ['KEYWORD', 'auto'],
1714     #inherited => 0,
1715     compute => $compute_length,
1716     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/height> for
1717     ## browser compatibility issues.
1718     };
1719     $Attr->{height} = $Prop->{height};
1720     $Key->{height} = $Prop->{height};
1721    
1722     $Prop->{'min-height'} = {
1723     css => 'min-height',
1724     dom => 'min_height',
1725     key => 'min_height',
1726     parse => $Prop->{'margin-top'}->{parse},
1727     #allow_negative => 0,
1728     #keyword => {},
1729     serialize => $default_serializer,
1730     initial => ['DIMENSION', 0, 'px'],
1731     #inherited => 0,
1732     compute => $compute_length,
1733     };
1734     $Attr->{min_height} = $Prop->{'min-height'};
1735     $Key->{min_height} = $Prop->{'min-height'};
1736    
1737     $Prop->{'max-height'} = {
1738     css => 'max-height',
1739     dom => 'max_height',
1740     key => 'max_height',
1741     parse => $Prop->{'margin-top'}->{parse},
1742     #allow_negative => 0,
1743     keyword => {none => 1},
1744     serialize => $default_serializer,
1745     initial => ['KEYWORD', 'none'],
1746     #inherited => 0,
1747     compute => $compute_length,
1748     };
1749     $Attr->{max_height} = $Prop->{'max-height'};
1750     $Key->{max_height} = $Prop->{'max-height'};
1751    
1752     $Prop->{'line-height'} = {
1753     css => 'line-height',
1754     dom => 'line_height',
1755     key => 'line_height',
1756 wakaba 1.19 parse => sub {
1757     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1758    
1759 wakaba 1.22 ## NOTE: Similar to 'margin-top', but different handling
1760     ## for unitless numbers.
1761    
1762 wakaba 1.19 my $sign = 1;
1763     if ($t->{type} == MINUS_TOKEN) {
1764     $t = $tt->get_next_token;
1765     $sign = -1;
1766     }
1767 wakaba 1.22 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1768 wakaba 1.19
1769     if ($t->{type} == DIMENSION_TOKEN) {
1770     my $value = $t->{number} * $sign;
1771     my $unit = lc $t->{value}; ## TODO: case
1772     $t = $tt->get_next_token;
1773     if ($length_unit->{$unit} and $value >= 0) {
1774     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1775     }
1776     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1777     my $value = $t->{number} * $sign;
1778     $t = $tt->get_next_token;
1779 wakaba 1.22 return ($t, {$prop_name => ['PERCENTAGE', $value]})
1780     if $value >= 0;
1781     } elsif ($t->{type} == NUMBER_TOKEN) {
1782 wakaba 1.19 my $value = $t->{number} * $sign;
1783     $t = $tt->get_next_token;
1784 wakaba 1.22 return ($t, {$prop_name => ['NUMBER', $value]}) if $value >= 0;
1785 wakaba 1.19 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1786     my $value = lc $t->{value}; ## TODO: case
1787     $t = $tt->get_next_token;
1788 wakaba 1.22 if ($value eq 'normal') {
1789     return ($t, {$prop_name => ['KEYWORD', $value]});
1790     } elsif ($value eq 'inherit') {
1791 wakaba 1.19 return ($t, {$prop_name => ['INHERIT']});
1792     }
1793     }
1794    
1795     $onerror->(type => 'syntax error:'.$prop_name,
1796     level => $self->{must_level},
1797     token => $t);
1798     return ($t, undef);
1799     },
1800     serialize => $default_serializer,
1801 wakaba 1.22 initial => ['KEYWORD', 'normal'],
1802     inherited => 1,
1803     compute => $compute_length,
1804     };
1805     $Attr->{line_height} = $Prop->{'line-height'};
1806     $Key->{line_height} = $Prop->{'line-height'};
1807    
1808     $Prop->{'vertical-align'} = {
1809     css => 'vertical-align',
1810     dom => 'vertical_align',
1811     key => 'vertical_align',
1812     parse => $Prop->{'margin-top'}->{parse},
1813     allow_negative => 1,
1814     keyword => {
1815     baseline => 1, sub => 1, super => 1, top => 1, 'text-top' => 1,
1816     middle => 1, bottom => 1, 'text-bottom' => 1,
1817     },
1818     ## NOTE: Currently, we don't support option to select subset of keywords
1819     ## supported by application (i.e.
1820     ## $parser->{prop_value}->{'line-height'->{$keyword}). Should we support
1821     ## it?
1822     serialize => $default_serializer,
1823     initial => ['KEYWORD', 'baseline'],
1824     #inherited => 0,
1825     compute => $compute_length,
1826     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/vertical-align> for
1827     ## browser compatibility issues.
1828     };
1829     $Attr->{vertical_align} = $Prop->{'vertical-align'};
1830     $Key->{vertical_align} = $Prop->{'vertical-align'};
1831    
1832 wakaba 1.23 $Prop->{'text-indent'} = {
1833     css => 'text-indent',
1834     dom => 'text_indent',
1835     key => 'text_indent',
1836     parse => $Prop->{'margin-top'}->{parse},
1837     allow_negative => 1,
1838     keyword => {},
1839     serialize => $default_serializer,
1840     initial => ['DIMENSION', 0, 'px'],
1841     inherited => 1,
1842     compute => $compute_length,
1843     };
1844     $Attr->{text_indent} = $Prop->{'text-indent'};
1845     $Key->{text_indent} = $Prop->{'text-indent'};
1846    
1847 wakaba 1.22 $Prop->{'padding-top'} = {
1848     css => 'padding-top',
1849     dom => 'padding_top',
1850     key => 'padding_top',
1851     parse => $Prop->{'margin-top'}->{parse},
1852     #allow_negative => 0,
1853     #keyword => {},
1854     serialize => $default_serializer,
1855 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
1856     #inherited => 0,
1857     compute => $compute_length,
1858     };
1859     $Attr->{padding_top} = $Prop->{'padding-top'};
1860     $Key->{padding_top} = $Prop->{'padding-top'};
1861    
1862     $Prop->{'padding-bottom'} = {
1863     css => 'padding-bottom',
1864     dom => 'padding_bottom',
1865     key => 'padding_bottom',
1866     parse => $Prop->{'padding-top'}->{parse},
1867 wakaba 1.22 #allow_negative => 0,
1868     #keyword => {},
1869 wakaba 1.19 serialize => $default_serializer,
1870     initial => ['DIMENSION', 0, 'px'],
1871     #inherited => 0,
1872     compute => $compute_length,
1873     };
1874     $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
1875     $Key->{padding_bottom} = $Prop->{'padding-bottom'};
1876    
1877     $Prop->{'padding-right'} = {
1878     css => 'padding-right',
1879     dom => 'padding_right',
1880     key => 'padding_right',
1881     parse => $Prop->{'padding-top'}->{parse},
1882 wakaba 1.22 #allow_negative => 0,
1883     #keyword => {},
1884 wakaba 1.19 serialize => $default_serializer,
1885     initial => ['DIMENSION', 0, 'px'],
1886     #inherited => 0,
1887     compute => $compute_length,
1888     };
1889     $Attr->{padding_right} = $Prop->{'padding-right'};
1890     $Key->{padding_right} = $Prop->{'padding-right'};
1891    
1892     $Prop->{'padding-left'} = {
1893     css => 'padding-left',
1894     dom => 'padding_left',
1895     key => 'padding_left',
1896     parse => $Prop->{'padding-top'}->{parse},
1897 wakaba 1.22 #allow_negative => 0,
1898     #keyword => {},
1899 wakaba 1.19 serialize => $default_serializer,
1900     initial => ['DIMENSION', 0, 'px'],
1901     #inherited => 0,
1902     compute => $compute_length,
1903     };
1904     $Attr->{padding_left} = $Prop->{'padding-left'};
1905     $Key->{padding_left} = $Prop->{'padding-left'};
1906    
1907 wakaba 1.20 $Prop->{'border-top-width'} = {
1908     css => 'border-top-width',
1909     dom => 'border_top_width',
1910     key => 'border_top_width',
1911 wakaba 1.22 parse => $Prop->{'margin-top'}->{parse},
1912     #allow_negative => 0,
1913     keyword => {thin => 1, medium => 1, thick => 1},
1914 wakaba 1.20 serialize => $default_serializer,
1915     initial => ['KEYWORD', 'medium'],
1916     #inherited => 0,
1917     compute => sub {
1918     my ($self, $element, $prop_name, $specified_value) = @_;
1919    
1920 wakaba 1.23 ## NOTE: Used for 'border-top-width', 'border-right-width',
1921     ## 'border-bottom-width', 'border-right-width', and
1922     ## 'outline-width'.
1923    
1924 wakaba 1.20 my $style_prop = $prop_name;
1925     $style_prop =~ s/width/style/;
1926     my $style = $self->get_computed_value ($element, $style_prop);
1927     if (defined $style and $style->[0] eq 'KEYWORD' and
1928     ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
1929     return ['DIMENSION', 0, 'px'];
1930     }
1931    
1932     my $value = $compute_length->(@_);
1933     if (defined $value and $value->[0] eq 'KEYWORD') {
1934     if ($value->[1] eq 'thin') {
1935     return ['DIMENSION', 1, 'px']; ## Firefox/Opera
1936     } elsif ($value->[1] eq 'medium') {
1937     return ['DIMENSION', 3, 'px']; ## Firefox/Opera
1938     } elsif ($value->[1] eq 'thick') {
1939     return ['DIMENSION', 5, 'px']; ## Firefox
1940     }
1941     }
1942     return $value;
1943     },
1944 wakaba 1.23 ## NOTE: CSS3 will allow <percentage> as an option in <border-width>.
1945     ## Opera 9 has already implemented it.
1946 wakaba 1.20 };
1947     $Attr->{border_top_width} = $Prop->{'border-top-width'};
1948     $Key->{border_top_width} = $Prop->{'border-top-width'};
1949    
1950     $Prop->{'border-right-width'} = {
1951     css => 'border-right-width',
1952     dom => 'border_right_width',
1953     key => 'border_right_width',
1954     parse => $Prop->{'border-top-width'}->{parse},
1955 wakaba 1.22 #allow_negative => 0,
1956     keyword => {thin => 1, medium => 1, thick => 1},
1957 wakaba 1.20 serialize => $default_serializer,
1958     initial => ['KEYWORD', 'medium'],
1959     #inherited => 0,
1960     compute => $Prop->{'border-top-width'}->{compute},
1961     };
1962     $Attr->{border_right_width} = $Prop->{'border-right-width'};
1963     $Key->{border_right_width} = $Prop->{'border-right-width'};
1964    
1965     $Prop->{'border-bottom-width'} = {
1966     css => 'border-bottom-width',
1967     dom => 'border_bottom_width',
1968     key => 'border_bottom_width',
1969     parse => $Prop->{'border-top-width'}->{parse},
1970 wakaba 1.22 #allow_negative => 0,
1971     keyword => {thin => 1, medium => 1, thick => 1},
1972 wakaba 1.20 serialize => $default_serializer,
1973     initial => ['KEYWORD', 'medium'],
1974     #inherited => 0,
1975     compute => $Prop->{'border-top-width'}->{compute},
1976     };
1977     $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
1978     $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
1979    
1980     $Prop->{'border-left-width'} = {
1981     css => 'border-left-width',
1982     dom => 'border_left_width',
1983     key => 'border_left_width',
1984     parse => $Prop->{'border-top-width'}->{parse},
1985 wakaba 1.22 #allow_negative => 0,
1986     keyword => {thin => 1, medium => 1, thick => 1},
1987 wakaba 1.20 serialize => $default_serializer,
1988     initial => ['KEYWORD', 'medium'],
1989     #inherited => 0,
1990     compute => $Prop->{'border-top-width'}->{compute},
1991     };
1992     $Attr->{border_left_width} = $Prop->{'border-left-width'};
1993     $Key->{border_left_width} = $Prop->{'border-left-width'};
1994    
1995 wakaba 1.23 $Prop->{'outline-width'} = {
1996     css => 'outline-width',
1997     dom => 'outline_width',
1998     key => 'outline_width',
1999     parse => $Prop->{'border-top-width'}->{parse},
2000     #allow_negative => 0,
2001     keyword => {thin => 1, medium => 1, thick => 1},
2002     serialize => $default_serializer,
2003     initial => ['KEYWORD', 'medium'],
2004     #inherited => 0,
2005     compute => $Prop->{'border-top-width'}->{compute},
2006     };
2007     $Attr->{outline_width} = $Prop->{'outline-width'};
2008     $Key->{outline_width} = $Prop->{'outline-width'};
2009    
2010 wakaba 1.15 $Prop->{'font-weight'} = {
2011     css => 'font-weight',
2012     dom => 'font_weight',
2013     key => 'font_weight',
2014     parse => sub {
2015     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2016    
2017     if ($t->{type} == NUMBER_TOKEN) {
2018     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
2019     ## browser compatibility issue.
2020     my $value = $t->{number};
2021     $t = $tt->get_next_token;
2022     if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
2023     return ($t, {$prop_name => ['WEIGHT', $value, 0]});
2024     }
2025     } elsif ($t->{type} == IDENT_TOKEN) {
2026     my $value = lc $t->{value}; ## TODO: case
2027     $t = $tt->get_next_token;
2028     if ({
2029     normal => 1, bold => 1, bolder => 1, lighter => 1,
2030     }->{$value}) {
2031     return ($t, {$prop_name => ['KEYWORD', $value]});
2032     } elsif ($value eq 'inherit') {
2033     return ($t, {$prop_name => ['INHERIT']});
2034     }
2035     }
2036    
2037     $onerror->(type => 'syntax error:'.$prop_name,
2038     level => $self->{must_level},
2039     token => $t);
2040     return ($t, undef);
2041     },
2042     serialize => $default_serializer,
2043     initial => ['KEYWORD', 'normal'],
2044     inherited => 1,
2045     compute => sub {
2046     my ($self, $element, $prop_name, $specified_value) = @_;
2047    
2048 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
2049 wakaba 1.15 if ($specified_value->[1] eq 'normal') {
2050     return ['WEIGHT', 400, 0];
2051     } elsif ($specified_value->[1] eq 'bold') {
2052     return ['WEIGHT', 700, 0];
2053     } elsif ($specified_value->[1] eq 'bolder') {
2054     my $parent_element = $element->manakai_parent_element;
2055     if (defined $parent_element) {
2056     my $parent_value = $self->get_cascaded_value
2057     ($parent_element, $prop_name); ## NOTE: What Firefox does.
2058     return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
2059     } else {
2060     return ['WEIGHT', 400, 1];
2061     }
2062     } elsif ($specified_value->[1] eq 'lighter') {
2063     my $parent_element = $element->manakai_parent_element;
2064     if (defined $parent_element) {
2065     my $parent_value = $self->get_cascaded_value
2066     ($parent_element, $prop_name); ## NOTE: What Firefox does.
2067     return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
2068     } else {
2069     return ['WEIGHT', 400, 1];
2070     }
2071     }
2072 wakaba 1.17 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
2073 wakaba 1.15 #
2074     }
2075    
2076     return $specified_value;
2077     },
2078     };
2079     $Attr->{font_weight} = $Prop->{'font-weight'};
2080     $Key->{font_weight} = $Prop->{'font-weight'};
2081    
2082 wakaba 1.13 my $uri_or_none_parser = sub {
2083 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2084    
2085 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
2086 wakaba 1.11 my $value = $t->{value};
2087     $t = $tt->get_next_token;
2088     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2089     } elsif ($t->{type} == IDENT_TOKEN) {
2090     my $value = lc $t->{value}; ## TODO: case
2091     $t = $tt->get_next_token;
2092     if ($value eq 'none') {
2093     ## NOTE: |none| is the default value and therefore it must be
2094     ## supported anyway.
2095     return ($t, {$prop_name => ["KEYWORD", 'none']});
2096     } elsif ($value eq 'inherit') {
2097     return ($t, {$prop_name => ['INHERIT']});
2098     }
2099     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
2100     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
2101     # my $value = $t->{value};
2102     # $t = $tt->get_next_token;
2103     # if ($t->{type} == EOF_TOKEN) {
2104     # $onerror->(type => 'syntax error:eof:'.$prop_name,
2105     # level => $self->{must_level},
2106     # token => $t);
2107     #
2108     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2109     # }
2110     }
2111    
2112     $onerror->(type => 'syntax error:'.$prop_name,
2113     level => $self->{must_level},
2114     token => $t);
2115     return ($t, undef);
2116 wakaba 1.13 }; # $uri_or_none_parser
2117    
2118 wakaba 1.14 my $compute_uri_or_none = sub {
2119 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
2120    
2121     if (defined $specified_value and
2122     $specified_value->[0] eq 'URI' and
2123     defined $specified_value->[2]) {
2124     require Message::DOM::DOMImplementation;
2125     return ['URI',
2126     Message::DOM::DOMImplementation->create_uri_reference
2127     ($specified_value->[1])
2128     ->get_absolute_reference (${$specified_value->[2]})
2129     ->get_uri_reference,
2130     $specified_value->[2]];
2131     }
2132    
2133     return $specified_value;
2134 wakaba 1.14 }; # $compute_uri_or_none
2135    
2136     $Prop->{'list-style-image'} = {
2137     css => 'list-style-image',
2138     dom => 'list_style_image',
2139     key => 'list_style_image',
2140     parse => $uri_or_none_parser,
2141     serialize => $default_serializer,
2142     initial => ['KEYWORD', 'none'],
2143     inherited => 1,
2144     compute => $compute_uri_or_none,
2145 wakaba 1.11 };
2146     $Attr->{list_style_image} = $Prop->{'list-style-image'};
2147     $Key->{list_style_image} = $Prop->{'list-style-image'};
2148    
2149 wakaba 1.15 $Prop->{'background-image'} = {
2150     css => 'background-image',
2151     dom => 'background_image',
2152     key => 'background_image',
2153     parse => $uri_or_none_parser,
2154     serialize => $default_serializer,
2155     initial => ['KEYWORD', 'none'],
2156     #inherited => 0,
2157     compute => $compute_uri_or_none,
2158     };
2159     $Attr->{background_image} = $Prop->{'background-image'};
2160     $Key->{background_image} = $Prop->{'background-image'};
2161    
2162 wakaba 1.7 my $border_style_keyword = {
2163     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
2164     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
2165     };
2166    
2167     $Prop->{'border-top-style'} = {
2168     css => 'border-top-style',
2169     dom => 'border_top_style',
2170     key => 'border_top_style',
2171     parse => $one_keyword_parser,
2172 wakaba 1.11 serialize => $default_serializer,
2173 wakaba 1.7 keyword => $border_style_keyword,
2174 wakaba 1.9 initial => ["KEYWORD", "none"],
2175     #inherited => 0,
2176     compute => $compute_as_specified,
2177 wakaba 1.7 };
2178     $Attr->{border_top_style} = $Prop->{'border-top-style'};
2179     $Key->{border_top_style} = $Prop->{'border-top-style'};
2180    
2181     $Prop->{'border-right-style'} = {
2182     css => 'border-right-style',
2183     dom => 'border_right_style',
2184     key => 'border_right_style',
2185     parse => $one_keyword_parser,
2186 wakaba 1.11 serialize => $default_serializer,
2187 wakaba 1.7 keyword => $border_style_keyword,
2188 wakaba 1.9 initial => ["KEYWORD", "none"],
2189     #inherited => 0,
2190     compute => $compute_as_specified,
2191 wakaba 1.7 };
2192     $Attr->{border_right_style} = $Prop->{'border-right-style'};
2193     $Key->{border_right_style} = $Prop->{'border-right-style'};
2194    
2195     $Prop->{'border-bottom-style'} = {
2196     css => 'border-bottom-style',
2197     dom => 'border_bottom_style',
2198     key => 'border_bottom_style',
2199     parse => $one_keyword_parser,
2200 wakaba 1.11 serialize => $default_serializer,
2201 wakaba 1.7 keyword => $border_style_keyword,
2202 wakaba 1.9 initial => ["KEYWORD", "none"],
2203     #inherited => 0,
2204     compute => $compute_as_specified,
2205 wakaba 1.7 };
2206     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
2207     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
2208    
2209     $Prop->{'border-left-style'} = {
2210     css => 'border-left-style',
2211     dom => 'border_left_style',
2212     key => 'border_left_style',
2213     parse => $one_keyword_parser,
2214 wakaba 1.11 serialize => $default_serializer,
2215 wakaba 1.7 keyword => $border_style_keyword,
2216 wakaba 1.9 initial => ["KEYWORD", "none"],
2217     #inherited => 0,
2218     compute => $compute_as_specified,
2219 wakaba 1.7 };
2220     $Attr->{border_left_style} = $Prop->{'border-left-style'};
2221     $Key->{border_left_style} = $Prop->{'border-left-style'};
2222    
2223 wakaba 1.16 $Prop->{'outline-style'} = {
2224     css => 'outline-style',
2225     dom => 'outline_style',
2226     key => 'outline_style',
2227     parse => $one_keyword_parser,
2228     serialize => $default_serializer,
2229 wakaba 1.23 keyword => {%$border_style_keyword},
2230 wakaba 1.16 initial => ['KEYWORD', 'none'],
2231     #inherited => 0,
2232     compute => $compute_as_specified,
2233     };
2234     $Attr->{outline_style} = $Prop->{'outline-style'};
2235     $Key->{outline_style} = $Prop->{'outline-style'};
2236 wakaba 1.23 delete $Prop->{'outline-style'}->{keyword}->{hidden};
2237 wakaba 1.16
2238 wakaba 1.15 $Prop->{'font-family'} = {
2239     css => 'font-family',
2240     dom => 'font_family',
2241     key => 'font_family',
2242     parse => sub {
2243     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2244    
2245     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
2246     ## how chaotic browsers are!
2247    
2248     my @prop_value;
2249    
2250     my $font_name = '';
2251     my $may_be_generic = 1;
2252     my $may_be_inherit = 1;
2253     my $has_s = 0;
2254     F: {
2255     if ($t->{type} == IDENT_TOKEN) {
2256     undef $may_be_inherit if $has_s or length $font_name;
2257     undef $may_be_generic if $has_s or length $font_name;
2258     $font_name .= ' ' if $has_s;
2259     $font_name .= $t->{value};
2260     undef $has_s;
2261     $t = $tt->get_next_token;
2262     } elsif ($t->{type} == STRING_TOKEN) {
2263     $font_name .= ' ' if $has_s;
2264     $font_name .= $t->{value};
2265     undef $may_be_inherit;
2266     undef $may_be_generic;
2267     undef $has_s;
2268     $t = $tt->get_next_token;
2269     } elsif ($t->{type} == COMMA_TOKEN) {
2270     if ($may_be_generic and
2271     {
2272     serif => 1, 'sans-serif' => 1, cursive => 1,
2273     fantasy => 1, monospace => 1, '-manakai-default' => 1,
2274     }->{lc $font_name}) { ## TODO: case
2275     push @prop_value, ['KEYWORD', $font_name];
2276     } elsif (not $may_be_generic or length $font_name) {
2277     push @prop_value, ["STRING", $font_name];
2278     }
2279     undef $may_be_inherit;
2280     $may_be_generic = 1;
2281     undef $has_s;
2282     $font_name = '';
2283     $t = $tt->get_next_token;
2284     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2285     } elsif ($t->{type} == S_TOKEN) {
2286     $has_s = 1;
2287     $t = $tt->get_next_token;
2288     } else {
2289     if ($may_be_generic and
2290     {
2291     serif => 1, 'sans-serif' => 1, cursive => 1,
2292     fantasy => 1, monospace => 1, '-manakai-default' => 1,
2293     }->{lc $font_name}) { ## TODO: case
2294     push @prop_value, ['KEYWORD', $font_name];
2295     } elsif (not $may_be_generic or length $font_name) {
2296     push @prop_value, ['STRING', $font_name];
2297     } else {
2298     $onerror->(type => 'syntax error:'.$prop_name,
2299     level => $self->{must_level},
2300     token => $t);
2301     return ($t, undef);
2302     }
2303     last F;
2304     }
2305     redo F;
2306     } # F
2307    
2308     if ($may_be_inherit and
2309     @prop_value == 1 and
2310     $prop_value[0]->[0] eq 'STRING' and
2311     lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
2312     return ($t, {$prop_name => ['INHERIT']});
2313     } else {
2314     unshift @prop_value, 'FONT';
2315     return ($t, {$prop_name => \@prop_value});
2316     }
2317     },
2318     serialize => sub {
2319     my ($self, $prop_name, $value) = @_;
2320    
2321     if ($value->[0] eq 'FONT') {
2322     return join ', ', map {
2323     if ($_->[0] eq 'STRING') {
2324     '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
2325     } elsif ($_->[0] eq 'KEYWORD') {
2326     $_->[1]; ## NOTE: This is what Firefox does.
2327     } else {
2328     ## NOTE: This should be an error.
2329     '""';
2330     }
2331     } @$value[1..$#$value];
2332     } elsif ($value->[0] eq 'INHERIT') {
2333     return 'inherit';
2334     } else {
2335     return undef;
2336     }
2337     },
2338     initial => ['FONT', ['KEYWORD', '-manakai-default']],
2339     inherited => 1,
2340     compute => $compute_as_specified,
2341     };
2342     $Attr->{font_family} = $Prop->{'font-family'};
2343     $Key->{font_family} = $Prop->{'font-family'};
2344    
2345 wakaba 1.17 $Prop->{cursor} = {
2346     css => 'cursor',
2347     dom => 'cursor',
2348     key => 'cursor',
2349     parse => sub {
2350     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2351    
2352     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
2353     ## compatibility issues.
2354    
2355     my @prop_value = ('CURSOR');
2356    
2357     F: {
2358     if ($t->{type} == IDENT_TOKEN) {
2359     my $v = lc $t->{value}; ## TODO: case
2360     $t = $tt->get_next_token;
2361     if ($Prop->{$prop_name}->{keyword}->{$v}) {
2362     push @prop_value, ['KEYWORD', $v];
2363     last F;
2364     } elsif ($v eq 'inherit' and @prop_value == 1) {
2365     return ($t, {$prop_name => ['INHERIT']});
2366     } else {
2367     $onerror->(type => 'syntax error:'.$prop_name,
2368     level => $self->{must_level},
2369     token => $t);
2370     return ($t, undef);
2371     }
2372     } elsif ($t->{type} == URI_TOKEN) {
2373     push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
2374     $t = $tt->get_next_token;
2375     } else {
2376     $onerror->(type => 'syntax error:'.$prop_name,
2377     level => $self->{must_level},
2378     token => $t);
2379     return ($t, undef);
2380     }
2381    
2382     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2383     if ($t->{type} == COMMA_TOKEN) {
2384     $t = $tt->get_next_token;
2385     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2386     redo F;
2387     }
2388     } # F
2389    
2390     return ($t, {$prop_name => \@prop_value});
2391     },
2392     serialize => sub {
2393     my ($self, $prop_name, $value) = @_;
2394    
2395     if ($value->[0] eq 'CURSOR') {
2396     return join ', ', map {
2397     if ($_->[0] eq 'URI') {
2398     'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
2399     } elsif ($_->[0] eq 'KEYWORD') {
2400     $_->[1];
2401     } else {
2402     ## NOTE: This should be an error.
2403     '""';
2404     }
2405     } @$value[1..$#$value];
2406     } elsif ($value->[0] eq 'INHERIT') {
2407     return 'inherit';
2408     } else {
2409     return undef;
2410     }
2411     },
2412     keyword => {
2413     auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
2414     'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
2415     'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
2416     'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
2417     },
2418     initial => ['CURSOR', ['KEYWORD', 'auto']],
2419     inherited => 1,
2420     compute => sub {
2421     my ($self, $element, $prop_name, $specified_value) = @_;
2422    
2423     if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
2424     my @new_value = ('CURSOR');
2425     for my $value (@$specified_value[1..$#$specified_value]) {
2426     if ($value->[0] eq 'URI') {
2427     if (defined $value->[2]) {
2428     require Message::DOM::DOMImplementation;
2429     push @new_value, ['URI',
2430     Message::DOM::DOMImplementation
2431     ->create_uri_reference ($value->[1])
2432     ->get_absolute_reference (${$value->[2]})
2433     ->get_uri_reference,
2434     $value->[2]];
2435     } else {
2436     push @new_value, $value;
2437     }
2438     } else {
2439     push @new_value, $value;
2440     }
2441     }
2442     return \@new_value;
2443     }
2444    
2445     return $specified_value;
2446     },
2447     };
2448     $Attr->{cursor} = $Prop->{cursor};
2449     $Key->{cursor} = $Prop->{cursor};
2450    
2451 wakaba 1.7 $Prop->{'border-style'} = {
2452     css => 'border-style',
2453     dom => 'border_style',
2454     parse => sub {
2455     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2456    
2457     my %prop_value;
2458     if ($t->{type} == IDENT_TOKEN) {
2459     my $prop_value = lc $t->{value}; ## TODO: case folding
2460     $t = $tt->get_next_token;
2461     if ($border_style_keyword->{$prop_value} and
2462     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
2463     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
2464     } elsif ($prop_value eq 'inherit') {
2465 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
2466 wakaba 1.19 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2467     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2468     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2469     return ($t, \%prop_value);
2470 wakaba 1.7 } else {
2471     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2472     level => $self->{must_level},
2473     token => $t);
2474     return ($t, undef);
2475     }
2476     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2477     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2478     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2479     } else {
2480     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2481     level => $self->{must_level},
2482     token => $t);
2483     return ($t, undef);
2484     }
2485    
2486     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2487     if ($t->{type} == IDENT_TOKEN) {
2488     my $prop_value = lc $t->{value}; ## TODO: case folding
2489     $t = $tt->get_next_token;
2490 wakaba 1.19 if ($border_style_keyword->{$prop_value} and
2491 wakaba 1.7 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
2492     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
2493     } else {
2494     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2495     level => $self->{must_level},
2496     token => $t);
2497     return ($t, undef);
2498     }
2499     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2500    
2501     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2502     if ($t->{type} == IDENT_TOKEN) {
2503     my $prop_value = lc $t->{value}; ## TODO: case folding
2504     $t = $tt->get_next_token;
2505     if ($border_style_keyword->{$prop_value} and
2506     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
2507     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
2508     } else {
2509     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2510     level => $self->{must_level},
2511     token => $t);
2512     return ($t, undef);
2513     }
2514    
2515     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2516     if ($t->{type} == IDENT_TOKEN) {
2517     my $prop_value = lc $t->{value}; ## TODO: case folding
2518     $t = $tt->get_next_token;
2519     if ($border_style_keyword->{$prop_value} and
2520     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
2521     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
2522     } else {
2523     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2524     level => $self->{must_level},
2525     token => $t);
2526     return ($t, undef);
2527     }
2528     }
2529     }
2530     }
2531    
2532     return ($t, \%prop_value);
2533     },
2534     serialize => sub {
2535     my ($self, $prop_name, $value) = @_;
2536    
2537     local $Error::Depth = $Error::Depth + 1;
2538     my @v;
2539     push @v, $self->border_top_style;
2540     return undef unless defined $v[-1];
2541     push @v, $self->border_right_style;
2542     return undef unless defined $v[-1];
2543     push @v, $self->border_bottom_style;
2544     return undef unless defined $v[-1];
2545 wakaba 1.19 push @v, $self->border_left_style;
2546 wakaba 1.7 return undef unless defined $v[-1];
2547    
2548     pop @v if $v[1] eq $v[3];
2549     pop @v if $v[0] eq $v[2];
2550     pop @v if $v[0] eq $v[1];
2551     return join ' ', @v;
2552     },
2553     };
2554     $Attr->{border_style} = $Prop->{'border-style'};
2555    
2556 wakaba 1.19 $Prop->{margin} = {
2557     css => 'margin',
2558     dom => 'margin',
2559     parse => sub {
2560     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2561    
2562     my %prop_value;
2563    
2564     my $sign = 1;
2565     if ($t->{type} == MINUS_TOKEN) {
2566     $t = $tt->get_next_token;
2567     $sign = -1;
2568     }
2569    
2570     if ($t->{type} == DIMENSION_TOKEN) {
2571     my $value = $t->{number} * $sign;
2572     my $unit = lc $t->{value}; ## TODO: case
2573     $t = $tt->get_next_token;
2574     if ($length_unit->{$unit}) {
2575     $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
2576     } else {
2577     $onerror->(type => 'syntax error:'.$prop_name,
2578     level => $self->{must_level},
2579     token => $t);
2580     return ($t, undef);
2581     }
2582     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2583     my $value = $t->{number} * $sign;
2584     $t = $tt->get_next_token;
2585     $prop_value{'margin-top'} = ['PERCENTAGE', $value];
2586 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2587 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2588     my $value = $t->{number} * $sign;
2589     $t = $tt->get_next_token;
2590     $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
2591     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2592     my $prop_value = lc $t->{value}; ## TODO: case folding
2593     $t = $tt->get_next_token;
2594     if ($prop_value eq 'auto') {
2595     $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
2596     } elsif ($prop_value eq 'inherit') {
2597     $prop_value{'margin-top'} = ['INHERIT'];
2598     $prop_value{'margin-right'} = $prop_value{'margin-top'};
2599     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2600     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2601     return ($t, \%prop_value);
2602     } else {
2603     $onerror->(type => 'syntax error:'.$prop_name,
2604     level => $self->{must_level},
2605     token => $t);
2606     return ($t, undef);
2607     }
2608     } else {
2609     $onerror->(type => 'syntax error:'.$prop_name,
2610     level => $self->{must_level},
2611     token => $t);
2612     return ($t, undef);
2613     }
2614     $prop_value{'margin-right'} = $prop_value{'margin-top'};
2615     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2616     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2617    
2618     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2619     $sign = 1;
2620     if ($t->{type} == MINUS_TOKEN) {
2621     $t = $tt->get_next_token;
2622     $sign = -1;
2623     }
2624    
2625     if ($t->{type} == DIMENSION_TOKEN) {
2626     my $value = $t->{number} * $sign;
2627     my $unit = lc $t->{value}; ## TODO: case
2628     $t = $tt->get_next_token;
2629     if ($length_unit->{$unit}) {
2630     $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
2631     } else {
2632     $onerror->(type => 'syntax error:'.$prop_name,
2633     level => $self->{must_level},
2634     token => $t);
2635     return ($t, undef);
2636     }
2637     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2638     my $value = $t->{number} * $sign;
2639     $t = $tt->get_next_token;
2640     $prop_value{'margin-right'} = ['PERCENTAGE', $value];
2641 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2642 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2643     my $value = $t->{number} * $sign;
2644     $t = $tt->get_next_token;
2645     $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
2646     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2647     my $prop_value = lc $t->{value}; ## TODO: case folding
2648     $t = $tt->get_next_token;
2649     if ($prop_value eq 'auto') {
2650     $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
2651     } else {
2652     $onerror->(type => 'syntax error:'.$prop_name,
2653     level => $self->{must_level},
2654     token => $t);
2655     return ($t, undef);
2656     }
2657     } else {
2658 wakaba 1.24 if ($sign < 0) {
2659     $onerror->(type => 'syntax error:'.$prop_name,
2660     level => $self->{must_level},
2661     token => $t);
2662     return ($t, undef);
2663     }
2664 wakaba 1.19 return ($t, \%prop_value);
2665     }
2666     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2667    
2668     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2669     $sign = 1;
2670     if ($t->{type} == MINUS_TOKEN) {
2671     $t = $tt->get_next_token;
2672     $sign = -1;
2673     }
2674    
2675     if ($t->{type} == DIMENSION_TOKEN) {
2676     my $value = $t->{number} * $sign;
2677     my $unit = lc $t->{value}; ## TODO: case
2678     $t = $tt->get_next_token;
2679     if ($length_unit->{$unit}) {
2680     $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
2681     } else {
2682     $onerror->(type => 'syntax error:'.$prop_name,
2683     level => $self->{must_level},
2684     token => $t);
2685     return ($t, undef);
2686     }
2687     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2688     my $value = $t->{number} * $sign;
2689     $t = $tt->get_next_token;
2690     $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
2691 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2692 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2693     my $value = $t->{number} * $sign;
2694     $t = $tt->get_next_token;
2695     $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
2696     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2697     my $prop_value = lc $t->{value}; ## TODO: case folding
2698     $t = $tt->get_next_token;
2699     if ($prop_value eq 'auto') {
2700     $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
2701     } else {
2702     $onerror->(type => 'syntax error:'.$prop_name,
2703     level => $self->{must_level},
2704     token => $t);
2705     return ($t, undef);
2706     }
2707     } else {
2708 wakaba 1.24 if ($sign < 0) {
2709     $onerror->(type => 'syntax error:'.$prop_name,
2710     level => $self->{must_level},
2711     token => $t);
2712     return ($t, undef);
2713     }
2714 wakaba 1.19 return ($t, \%prop_value);
2715     }
2716    
2717     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2718     $sign = 1;
2719     if ($t->{type} == MINUS_TOKEN) {
2720     $t = $tt->get_next_token;
2721     $sign = -1;
2722     }
2723    
2724     if ($t->{type} == DIMENSION_TOKEN) {
2725     my $value = $t->{number} * $sign;
2726     my $unit = lc $t->{value}; ## TODO: case
2727     $t = $tt->get_next_token;
2728     if ($length_unit->{$unit}) {
2729     $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
2730     } else {
2731     $onerror->(type => 'syntax error:'.$prop_name,
2732     level => $self->{must_level},
2733     token => $t);
2734     return ($t, undef);
2735     }
2736     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2737     my $value = $t->{number} * $sign;
2738     $t = $tt->get_next_token;
2739     $prop_value{'margin-left'} = ['PERCENTAGE', $value];
2740 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2741 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2742     my $value = $t->{number} * $sign;
2743     $t = $tt->get_next_token;
2744     $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
2745     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2746     my $prop_value = lc $t->{value}; ## TODO: case folding
2747     $t = $tt->get_next_token;
2748     if ($prop_value eq 'auto') {
2749     $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
2750     } else {
2751     $onerror->(type => 'syntax error:'.$prop_name,
2752     level => $self->{must_level},
2753     token => $t);
2754     return ($t, undef);
2755     }
2756     } else {
2757 wakaba 1.24 if ($sign < 0) {
2758     $onerror->(type => 'syntax error:'.$prop_name,
2759     level => $self->{must_level},
2760     token => $t);
2761     return ($t, undef);
2762     }
2763 wakaba 1.19 return ($t, \%prop_value);
2764     }
2765    
2766     return ($t, \%prop_value);
2767     },
2768     serialize => sub {
2769     my ($self, $prop_name, $value) = @_;
2770    
2771     local $Error::Depth = $Error::Depth + 1;
2772     my @v;
2773     push @v, $self->margin_top;
2774     return undef unless defined $v[-1];
2775     push @v, $self->margin_right;
2776     return undef unless defined $v[-1];
2777     push @v, $self->margin_bottom;
2778     return undef unless defined $v[-1];
2779     push @v, $self->margin_left;
2780     return undef unless defined $v[-1];
2781    
2782     pop @v if $v[1] eq $v[3];
2783     pop @v if $v[0] eq $v[2];
2784     pop @v if $v[0] eq $v[1];
2785     return join ' ', @v;
2786     },
2787     };
2788     $Attr->{margin} = $Prop->{margin};
2789    
2790     $Prop->{padding} = {
2791     css => 'padding',
2792     dom => 'padding',
2793     parse => sub {
2794     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2795    
2796     my %prop_value;
2797    
2798     my $sign = 1;
2799     if ($t->{type} == MINUS_TOKEN) {
2800     $t = $tt->get_next_token;
2801     $sign = -1;
2802     }
2803    
2804     if ($t->{type} == DIMENSION_TOKEN) {
2805     my $value = $t->{number} * $sign;
2806     my $unit = lc $t->{value}; ## TODO: case
2807     $t = $tt->get_next_token;
2808     if ($length_unit->{$unit} and $value >= 0) {
2809     $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
2810     } else {
2811     $onerror->(type => 'syntax error:'.$prop_name,
2812     level => $self->{must_level},
2813     token => $t);
2814     return ($t, undef);
2815     }
2816     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2817     my $value = $t->{number} * $sign;
2818     $t = $tt->get_next_token;
2819     $prop_value{'padding-top'} = ['PERCENTAGE', $value];
2820     unless ($value >= 0) {
2821     $onerror->(type => 'syntax error:'.$prop_name,
2822     level => $self->{must_level},
2823     token => $t);
2824     return ($t, undef);
2825     }
2826 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2827 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2828     my $value = $t->{number} * $sign;
2829     $t = $tt->get_next_token;
2830     $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
2831     unless ($value >= 0) {
2832     $onerror->(type => 'syntax error:'.$prop_name,
2833     level => $self->{must_level},
2834     token => $t);
2835     return ($t, undef);
2836     }
2837     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2838     my $prop_value = lc $t->{value}; ## TODO: case folding
2839     $t = $tt->get_next_token;
2840     if ($prop_value eq 'inherit') {
2841     $prop_value{'padding-top'} = ['INHERIT'];
2842     $prop_value{'padding-right'} = $prop_value{'padding-top'};
2843     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2844     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2845     return ($t, \%prop_value);
2846     } else {
2847     $onerror->(type => 'syntax error:'.$prop_name,
2848     level => $self->{must_level},
2849     token => $t);
2850     return ($t, undef);
2851     }
2852     } else {
2853     $onerror->(type => 'syntax error:'.$prop_name,
2854     level => $self->{must_level},
2855     token => $t);
2856     return ($t, undef);
2857     }
2858     $prop_value{'padding-right'} = $prop_value{'padding-top'};
2859     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2860     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2861    
2862     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2863     $sign = 1;
2864     if ($t->{type} == MINUS_TOKEN) {
2865     $t = $tt->get_next_token;
2866     $sign = -1;
2867     }
2868    
2869     if ($t->{type} == DIMENSION_TOKEN) {
2870     my $value = $t->{number} * $sign;
2871     my $unit = lc $t->{value}; ## TODO: case
2872     $t = $tt->get_next_token;
2873     if ($length_unit->{$unit} and $value >= 0) {
2874     $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
2875     } else {
2876     $onerror->(type => 'syntax error:'.$prop_name,
2877     level => $self->{must_level},
2878     token => $t);
2879     return ($t, undef);
2880     }
2881     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2882     my $value = $t->{number} * $sign;
2883     $t = $tt->get_next_token;
2884     $prop_value{'padding-right'} = ['PERCENTAGE', $value];
2885     unless ($value >= 0) {
2886     $onerror->(type => 'syntax error:'.$prop_name,
2887     level => $self->{must_level},
2888     token => $t);
2889     return ($t, undef);
2890     }
2891 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2892 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2893     my $value = $t->{number} * $sign;
2894     $t = $tt->get_next_token;
2895     $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
2896     unless ($value >= 0) {
2897     $onerror->(type => 'syntax error:'.$prop_name,
2898     level => $self->{must_level},
2899     token => $t);
2900     return ($t, undef);
2901     }
2902     } else {
2903 wakaba 1.24 if ($sign < 0) {
2904     $onerror->(type => 'syntax error:'.$prop_name,
2905     level => $self->{must_level},
2906     token => $t);
2907     return ($t, undef);
2908     }
2909 wakaba 1.19 return ($t, \%prop_value);
2910     }
2911     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2912    
2913     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2914     $sign = 1;
2915     if ($t->{type} == MINUS_TOKEN) {
2916     $t = $tt->get_next_token;
2917     $sign = -1;
2918     }
2919    
2920     if ($t->{type} == DIMENSION_TOKEN) {
2921     my $value = $t->{number} * $sign;
2922     my $unit = lc $t->{value}; ## TODO: case
2923     $t = $tt->get_next_token;
2924     if ($length_unit->{$unit} and $value >= 0) {
2925     $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
2926     } else {
2927     $onerror->(type => 'syntax error:'.$prop_name,
2928     level => $self->{must_level},
2929     token => $t);
2930     return ($t, undef);
2931     }
2932     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2933     my $value = $t->{number} * $sign;
2934     $t = $tt->get_next_token;
2935     $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
2936     unless ($value >= 0) {
2937     $onerror->(type => 'syntax error:'.$prop_name,
2938     level => $self->{must_level},
2939     token => $t);
2940     return ($t, undef);
2941     }
2942 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2943 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2944     my $value = $t->{number} * $sign;
2945     $t = $tt->get_next_token;
2946     $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
2947     unless ($value >= 0) {
2948     $onerror->(type => 'syntax error:'.$prop_name,
2949     level => $self->{must_level},
2950     token => $t);
2951     return ($t, undef);
2952     }
2953     } else {
2954 wakaba 1.24 if ($sign < 0) {
2955     $onerror->(type => 'syntax error:'.$prop_name,
2956     level => $self->{must_level},
2957     token => $t);
2958     return ($t, undef);
2959     }
2960 wakaba 1.19 return ($t, \%prop_value);
2961     }
2962    
2963     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2964     $sign = 1;
2965     if ($t->{type} == MINUS_TOKEN) {
2966     $t = $tt->get_next_token;
2967     $sign = -1;
2968     }
2969    
2970     if ($t->{type} == DIMENSION_TOKEN) {
2971     my $value = $t->{number} * $sign;
2972     my $unit = lc $t->{value}; ## TODO: case
2973     $t = $tt->get_next_token;
2974     if ($length_unit->{$unit} and $value >= 0) {
2975     $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
2976     } else {
2977     $onerror->(type => 'syntax error:'.$prop_name,
2978     level => $self->{must_level},
2979     token => $t);
2980     return ($t, undef);
2981     }
2982     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2983     my $value = $t->{number} * $sign;
2984     $t = $tt->get_next_token;
2985     $prop_value{'padding-left'} = ['PERCENTAGE', $value];
2986     unless ($value >= 0) {
2987     $onerror->(type => 'syntax error:'.$prop_name,
2988     level => $self->{must_level},
2989     token => $t);
2990     return ($t, undef);
2991     }
2992 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2993 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2994     my $value = $t->{number} * $sign;
2995     $t = $tt->get_next_token;
2996     $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
2997     unless ($value >= 0) {
2998     $onerror->(type => 'syntax error:'.$prop_name,
2999     level => $self->{must_level},
3000     token => $t);
3001     return ($t, undef);
3002     }
3003     } else {
3004 wakaba 1.24 if ($sign < 0) {
3005     $onerror->(type => 'syntax error:'.$prop_name,
3006     level => $self->{must_level},
3007     token => $t);
3008     return ($t, undef);
3009     }
3010 wakaba 1.19 return ($t, \%prop_value);
3011     }
3012    
3013     return ($t, \%prop_value);
3014     },
3015     serialize => sub {
3016     my ($self, $prop_name, $value) = @_;
3017    
3018     local $Error::Depth = $Error::Depth + 1;
3019     my @v;
3020     push @v, $self->padding_top;
3021     return undef unless defined $v[-1];
3022     push @v, $self->padding_right;
3023     return undef unless defined $v[-1];
3024     push @v, $self->padding_bottom;
3025     return undef unless defined $v[-1];
3026     push @v, $self->padding_left;
3027     return undef unless defined $v[-1];
3028    
3029     pop @v if $v[1] eq $v[3];
3030     pop @v if $v[0] eq $v[2];
3031     pop @v if $v[0] eq $v[1];
3032     return join ' ', @v;
3033     },
3034     };
3035     $Attr->{padding} = $Prop->{padding};
3036    
3037 wakaba 1.24 $Prop->{'border-spacing'} = {
3038     css => 'border-spacing',
3039     dom => 'border_spacing',
3040     parse => sub {
3041     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3042    
3043     my %prop_value;
3044    
3045     my $sign = 1;
3046     if ($t->{type} == MINUS_TOKEN) {
3047     $t = $tt->get_next_token;
3048     $sign = -1;
3049     }
3050    
3051     if ($t->{type} == DIMENSION_TOKEN) {
3052     my $value = $t->{number} * $sign;
3053     my $unit = lc $t->{value}; ## TODO: case
3054     $t = $tt->get_next_token;
3055     if ($length_unit->{$unit} and $value >= 0) {
3056     $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, $unit];
3057     } else {
3058     $onerror->(type => 'syntax error:'.$prop_name,
3059     level => $self->{must_level},
3060     token => $t);
3061     return ($t, undef);
3062     }
3063     } elsif ($t->{type} == NUMBER_TOKEN and
3064     ($self->{unitless_px} or $t->{number} == 0)) {
3065     my $value = $t->{number} * $sign;
3066     $t = $tt->get_next_token;
3067     $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, 'px'];
3068     unless ($value >= 0) {
3069     $onerror->(type => 'syntax error:'.$prop_name,
3070     level => $self->{must_level},
3071     token => $t);
3072     return ($t, undef);
3073     }
3074     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3075     my $prop_value = lc $t->{value}; ## TODO: case folding
3076     $t = $tt->get_next_token;
3077     if ($prop_value eq 'inherit') {
3078     $prop_value{'-manakai-border-spacing-x'} = ['INHERIT'];
3079     $prop_value{'-manakai-border-spacing-y'}
3080     = $prop_value{'-manakai-border-spacing-x'};
3081     return ($t, \%prop_value);
3082     } else {
3083     $onerror->(type => 'syntax error:'.$prop_name,
3084     level => $self->{must_level},
3085     token => $t);
3086     return ($t, undef);
3087     }
3088     } else {
3089     $onerror->(type => 'syntax error:'.$prop_name,
3090     level => $self->{must_level},
3091     token => $t);
3092     return ($t, undef);
3093     }
3094     $prop_value{'-manakai-border-spacing-y'}
3095     = $prop_value{'-manakai-border-spacing-x'};
3096    
3097     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3098     $sign = 1;
3099     if ($t->{type} == MINUS_TOKEN) {
3100     $t = $tt->get_next_token;
3101     $sign = -1;
3102     }
3103    
3104     if ($t->{type} == DIMENSION_TOKEN) {
3105     my $value = $t->{number} * $sign;
3106     my $unit = lc $t->{value}; ## TODO: case
3107     $t = $tt->get_next_token;
3108     if ($length_unit->{$unit} and $value >= 0) {
3109     $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, $unit];
3110     } else {
3111     $onerror->(type => 'syntax error:'.$prop_name,
3112     level => $self->{must_level},
3113     token => $t);
3114     return ($t, undef);
3115     }
3116     } elsif ($t->{type} == NUMBER_TOKEN and
3117     ($self->{unitless_px} or $t->{number} == 0)) {
3118     my $value = $t->{number} * $sign;
3119     $t = $tt->get_next_token;
3120     $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, 'px'];
3121     unless ($value >= 0) {
3122     $onerror->(type => 'syntax error:'.$prop_name,
3123     level => $self->{must_level},
3124     token => $t);
3125     return ($t, undef);
3126     }
3127     } else {
3128     if ($sign < 0) {
3129     $onerror->(type => 'syntax error:'.$prop_name,
3130     level => $self->{must_level},
3131     token => $t);
3132     return ($t, undef);
3133     }
3134     return ($t, \%prop_value);
3135     }
3136    
3137     return ($t, \%prop_value);
3138     },
3139     serialize => sub {
3140     my ($self, $prop_name, $value) = @_;
3141    
3142     local $Error::Depth = $Error::Depth + 1;
3143     my @v;
3144     push @v, $self->_manakai_border_spacing_x;
3145     return undef unless defined $v[-1];
3146     push @v, $self->_manakai_border_spacing_y;
3147     return undef unless defined $v[-1];
3148    
3149     pop @v if $v[0] eq $v[1];
3150     return join ' ', @v;
3151     },
3152     };
3153     $Attr->{border_spacing} = $Prop->{'border-spacing'};
3154    
3155 wakaba 1.20 $Prop->{'border-width'} = {
3156     css => 'border-width',
3157     dom => 'border_width',
3158     parse => sub {
3159     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3160    
3161     my %prop_value;
3162    
3163     my $sign = 1;
3164     if ($t->{type} == MINUS_TOKEN) {
3165     $t = $tt->get_next_token;
3166     $sign = -1;
3167     }
3168    
3169     if ($t->{type} == DIMENSION_TOKEN) {
3170     my $value = $t->{number} * $sign;
3171     my $unit = lc $t->{value}; ## TODO: case
3172     $t = $tt->get_next_token;
3173     if ($length_unit->{$unit} and $value >= 0) {
3174     $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
3175     } else {
3176     $onerror->(type => 'syntax error:'.$prop_name,
3177     level => $self->{must_level},
3178     token => $t);
3179     return ($t, undef);
3180     }
3181     } elsif ($t->{type} == NUMBER_TOKEN and
3182     ($self->{unitless_px} or $t->{number} == 0)) {
3183     my $value = $t->{number} * $sign;
3184     $t = $tt->get_next_token;
3185     $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
3186     unless ($value >= 0) {
3187     $onerror->(type => 'syntax error:'.$prop_name,
3188     level => $self->{must_level},
3189     token => $t);
3190     return ($t, undef);
3191     }
3192     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3193     my $prop_value = lc $t->{value}; ## TODO: case folding
3194     $t = $tt->get_next_token;
3195     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3196     $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
3197     } elsif ($prop_value eq 'inherit') {
3198     $prop_value{'border-top-width'} = ['INHERIT'];
3199     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
3200     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
3201     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3202     return ($t, \%prop_value);
3203     } else {
3204     $onerror->(type => 'syntax error:'.$prop_name,
3205     level => $self->{must_level},
3206     token => $t);
3207     return ($t, undef);
3208     }
3209     } else {
3210     $onerror->(type => 'syntax error:'.$prop_name,
3211     level => $self->{must_level},
3212     token => $t);
3213     return ($t, undef);
3214     }
3215     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
3216     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
3217     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3218    
3219     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3220     $sign = 1;
3221     if ($t->{type} == MINUS_TOKEN) {
3222     $t = $tt->get_next_token;
3223     $sign = -1;
3224     }
3225    
3226     if ($t->{type} == DIMENSION_TOKEN) {
3227     my $value = $t->{number} * $sign;
3228     my $unit = lc $t->{value}; ## TODO: case
3229     $t = $tt->get_next_token;
3230     if ($length_unit->{$unit} and $value >= 0) {
3231     $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
3232     } else {
3233     $onerror->(type => 'syntax error:'.$prop_name,
3234     level => $self->{must_level},
3235     token => $t);
3236     return ($t, undef);
3237     }
3238     } elsif ($t->{type} == NUMBER_TOKEN and
3239     ($self->{unitless_px} or $t->{number} == 0)) {
3240     my $value = $t->{number} * $sign;
3241     $t = $tt->get_next_token;
3242     $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
3243     unless ($value >= 0) {
3244     $onerror->(type => 'syntax error:'.$prop_name,
3245     level => $self->{must_level},
3246     token => $t);
3247     return ($t, undef);
3248     }
3249     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3250     my $prop_value = lc $t->{value}; ## TODO: case
3251     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3252     $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
3253     $t = $tt->get_next_token;
3254     }
3255     } else {
3256 wakaba 1.24 if ($sign < 0) {
3257     $onerror->(type => 'syntax error:'.$prop_name,
3258     level => $self->{must_level},
3259     token => $t);
3260     return ($t, undef);
3261     }
3262 wakaba 1.20 return ($t, \%prop_value);
3263     }
3264     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3265    
3266     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3267     $sign = 1;
3268     if ($t->{type} == MINUS_TOKEN) {
3269     $t = $tt->get_next_token;
3270     $sign = -1;
3271     }
3272    
3273     if ($t->{type} == DIMENSION_TOKEN) {
3274     my $value = $t->{number} * $sign;
3275     my $unit = lc $t->{value}; ## TODO: case
3276     $t = $tt->get_next_token;
3277     if ($length_unit->{$unit} and $value >= 0) {
3278     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
3279     } else {
3280     $onerror->(type => 'syntax error:'.$prop_name,
3281     level => $self->{must_level},
3282     token => $t);
3283     return ($t, undef);
3284     }
3285     } elsif ($t->{type} == NUMBER_TOKEN and
3286     ($self->{unitless_px} or $t->{number} == 0)) {
3287     my $value = $t->{number} * $sign;
3288     $t = $tt->get_next_token;
3289     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
3290     unless ($value >= 0) {
3291     $onerror->(type => 'syntax error:'.$prop_name,
3292     level => $self->{must_level},
3293     token => $t);
3294     return ($t, undef);
3295     }
3296     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3297     my $prop_value = lc $t->{value}; ## TODO: case
3298     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3299     $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
3300     $t = $tt->get_next_token;
3301     }
3302     } else {
3303 wakaba 1.24 if ($sign < 0) {
3304     $onerror->(type => 'syntax error:'.$prop_name,
3305     level => $self->{must_level},
3306     token => $t);
3307     return ($t, undef);
3308     }
3309 wakaba 1.20 return ($t, \%prop_value);
3310     }
3311    
3312     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3313     $sign = 1;
3314     if ($t->{type} == MINUS_TOKEN) {
3315     $t = $tt->get_next_token;
3316     $sign = -1;
3317     }
3318    
3319     if ($t->{type} == DIMENSION_TOKEN) {
3320     my $value = $t->{number} * $sign;
3321     my $unit = lc $t->{value}; ## TODO: case
3322     $t = $tt->get_next_token;
3323     if ($length_unit->{$unit} and $value >= 0) {
3324     $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
3325     } else {
3326     $onerror->(type => 'syntax error:'.$prop_name,
3327     level => $self->{must_level},
3328     token => $t);
3329     return ($t, undef);
3330     }
3331     } elsif ($t->{type} == NUMBER_TOKEN and
3332     ($self->{unitless_px} or $t->{number} == 0)) {
3333     my $value = $t->{number} * $sign;
3334     $t = $tt->get_next_token;
3335     $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
3336     unless ($value >= 0) {
3337     $onerror->(type => 'syntax error:'.$prop_name,
3338     level => $self->{must_level},
3339     token => $t);
3340     return ($t, undef);
3341     }
3342     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3343     my $prop_value = lc $t->{value}; ## TODO: case
3344     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3345     $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
3346     $t = $tt->get_next_token;
3347     }
3348     } else {
3349 wakaba 1.24 if ($sign < 0) {
3350     $onerror->(type => 'syntax error:'.$prop_name,
3351     level => $self->{must_level},
3352     token => $t);
3353     return ($t, undef);
3354     }
3355 wakaba 1.20 return ($t, \%prop_value);
3356     }
3357    
3358     return ($t, \%prop_value);
3359     },
3360     serialize => sub {
3361     my ($self, $prop_name, $value) = @_;
3362    
3363     local $Error::Depth = $Error::Depth + 1;
3364     my @v;
3365 wakaba 1.24 push @v, $self->border_top_width;
3366 wakaba 1.20 return undef unless defined $v[-1];
3367 wakaba 1.24 push @v, $self->border_right_width;
3368 wakaba 1.20 return undef unless defined $v[-1];
3369 wakaba 1.24 push @v, $self->border_bottom_width;
3370 wakaba 1.20 return undef unless defined $v[-1];
3371 wakaba 1.24 push @v, $self->border_left_width;
3372 wakaba 1.20 return undef unless defined $v[-1];
3373    
3374     pop @v if $v[1] eq $v[3];
3375     pop @v if $v[0] eq $v[2];
3376     pop @v if $v[0] eq $v[1];
3377     return join ' ', @v;
3378     },
3379     };
3380 wakaba 1.24 $Attr->{border_width} = $Prop->{'border-width'};
3381 wakaba 1.20
3382 wakaba 1.12 $Prop->{'list-style'} = {
3383     css => 'list-style',
3384     dom => 'list_style',
3385     parse => sub {
3386     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3387    
3388     my %prop_value;
3389     my $none = 0;
3390    
3391     F: for my $f (1..3) {
3392     if ($t->{type} == IDENT_TOKEN) {
3393     my $prop_value = lc $t->{value}; ## TODO: case folding
3394     $t = $tt->get_next_token;
3395    
3396     if ($prop_value eq 'none') {
3397     $none++;
3398     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
3399     if (exists $prop_value{'list-style-type'}) {
3400     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3401     $prop_name,
3402     level => $self->{must_level},
3403     token => $t);
3404     return ($t, undef);
3405     } else {
3406     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
3407     }
3408     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
3409     if (exists $prop_value{'list-style-position'}) {
3410     $onerror->(type => q[syntax error:duplicate:'list-style-position':].
3411     $prop_name,
3412     level => $self->{must_level},
3413     token => $t);
3414     return ($t, undef);
3415     }
3416    
3417     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
3418     } elsif ($f == 1 and $prop_value eq 'inherit') {
3419     $prop_value{'list-style-type'} = ["INHERIT"];
3420     $prop_value{'list-style-position'} = ["INHERIT"];
3421     $prop_value{'list-style-image'} = ["INHERIT"];
3422     last F;
3423     } else {
3424     if ($f == 1) {
3425     $onerror->(type => 'syntax error:'.$prop_name,
3426     level => $self->{must_level},
3427     token => $t);
3428     return ($t, undef);
3429     } else {
3430     last F;
3431     }
3432     }
3433     } elsif ($t->{type} == URI_TOKEN) {
3434     if (exists $prop_value{'list-style-image'}) {
3435     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3436     $prop_name,
3437     level => $self->{must_level},
3438     token => $t);
3439     return ($t, undef);
3440     }
3441    
3442     $prop_value{'list-style-image'}
3443 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
3444 wakaba 1.12 $t = $tt->get_next_token;
3445     } else {
3446     if ($f == 1) {
3447     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3448     level => $self->{must_level},
3449     token => $t);
3450     return ($t, undef);
3451     } else {
3452     last F;
3453     }
3454     }
3455    
3456     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3457     } # F
3458     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
3459    
3460     if ($none == 1) {
3461     if (exists $prop_value{'list-style-type'}) {
3462     if (exists $prop_value{'list-style-image'}) {
3463     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3464     $prop_name,
3465     level => $self->{must_level},
3466     token => $t);
3467     return ($t, undef);
3468     } else {
3469     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3470     }
3471     } else {
3472     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3473     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
3474     unless exists $prop_value{'list-style-image'};
3475     }
3476     } elsif ($none == 2) {
3477     if (exists $prop_value{'list-style-type'}) {
3478     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3479     $prop_name,
3480     level => $self->{must_level},
3481     token => $t);
3482     return ($t, undef);
3483     }
3484     if (exists $prop_value{'list-style-image'}) {
3485     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3486     $prop_name,
3487     level => $self->{must_level},
3488     token => $t);
3489     return ($t, undef);
3490     }
3491    
3492     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3493     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3494     } elsif ($none == 3) {
3495     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3496     $prop_name,
3497     level => $self->{must_level},
3498     token => $t);
3499     return ($t, undef);
3500     }
3501    
3502     for (qw/list-style-type list-style-position list-style-image/) {
3503     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
3504     }
3505    
3506     return ($t, \%prop_value);
3507     },
3508     serialize => sub {
3509     my ($self, $prop_name, $value) = @_;
3510    
3511     local $Error::Depth = $Error::Depth + 1;
3512     return $self->list_style_type . ' ' . $self->list_style_position .
3513     ' ' . $self->list_style_image;
3514     },
3515     };
3516     $Attr->{list_style} = $Prop->{'list-style'};
3517    
3518 wakaba 1.16 ## NOTE: Future version of the implementation will change the way to
3519     ## store the parsed value to support CSS 3 properties.
3520     $Prop->{'text-decoration'} = {
3521     css => 'text-decoration',
3522     dom => 'text_decoration',
3523     key => 'text_decoration',
3524     parse => sub {
3525     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3526    
3527     my $value = ['DECORATION']; # , underline, overline, line-through, blink
3528    
3529     if ($t->{type} == IDENT_TOKEN) {
3530     my $v = lc $t->{value}; ## TODO: case
3531     $t = $tt->get_next_token;
3532     if ($v eq 'inherit') {
3533     return ($t, {$prop_name => ['INHERIT']});
3534     } elsif ($v eq 'none') {
3535     return ($t, {$prop_name => $value});
3536     } elsif ($v eq 'underline' and
3537     $self->{prop_value}->{$prop_name}->{$v}) {
3538     $value->[1] = 1;
3539     } elsif ($v eq 'overline' and
3540     $self->{prop_value}->{$prop_name}->{$v}) {
3541     $value->[2] = 1;
3542     } elsif ($v eq 'line-through' and
3543     $self->{prop_value}->{$prop_name}->{$v}) {
3544     $value->[3] = 1;
3545     } elsif ($v eq 'blink' and
3546     $self->{prop_value}->{$prop_name}->{$v}) {
3547     $value->[4] = 1;
3548     } else {
3549     $onerror->(type => 'syntax error:'.$prop_name,
3550     level => $self->{must_level},
3551     token => $t);
3552     return ($t, undef);
3553     }
3554     }
3555    
3556     F: {
3557     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3558     last F unless $t->{type} == IDENT_TOKEN;
3559    
3560     my $v = lc $t->{value}; ## TODO: case
3561     $t = $tt->get_next_token;
3562     if ($v eq 'underline' and
3563     $self->{prop_value}->{$prop_name}->{$v}) {
3564     $value->[1] = 1;
3565     } elsif ($v eq 'overline' and
3566     $self->{prop_value}->{$prop_name}->{$v}) {
3567     $value->[1] = 2;
3568     } elsif ($v eq 'line-through' and
3569     $self->{prop_value}->{$prop_name}->{$v}) {
3570     $value->[1] = 3;
3571     } elsif ($v eq 'blink' and
3572     $self->{prop_value}->{$prop_name}->{$v}) {
3573     $value->[1] = 4;
3574     } else {
3575     last F;
3576     }
3577    
3578     redo F;
3579     } # F
3580    
3581     return ($t, {$prop_name => $value});
3582     },
3583     serialize => $default_serializer,
3584     initial => ["KEYWORD", "none"],
3585     #inherited => 0,
3586     compute => $compute_as_specified,
3587     };
3588     $Attr->{text_decoration} = $Prop->{'text-decoration'};
3589     $Key->{text_decoration} = $Prop->{'text-decoration'};
3590    
3591 wakaba 1.1 1;
3592 wakaba 1.24 ## $Date: 2008/01/04 14:43:44 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24