/[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.27 - (hide annotations) (download)
Sun Jan 6 10:33:01 2008 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.26: +214 -2 lines
++ whatpm/Whatpm/CSS/ChangeLog	6 Jan 2008 10:32:49 -0000
	* Parser.pm (background-position, background-position-x,
	background-position-y): Implemented.

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

1 wakaba 1.1 package Whatpm::CSS::Parser;
2     use strict;
3     use Whatpm::CSS::Tokenizer qw(:token);
4     require Whatpm::CSS::SelectorsParser;
5    
6     sub new ($) {
7 wakaba 1.3 my $self = bless {onerror => sub { }, must_level => 'm',
8 wakaba 1.5 message_level => 'w',
9 wakaba 1.3 unsupported_level => 'unsupported'}, shift;
10 wakaba 1.11 # $self->{base_uri}
11 wakaba 1.18 # $self->{unitless_px} = 1/0
12 wakaba 1.1
13     return $self;
14     } # new
15    
16     sub BEFORE_STATEMENT_STATE () { 0 }
17     sub BEFORE_DECLARATION_STATE () { 1 }
18     sub IGNORED_STATEMENT_STATE () { 2 }
19     sub IGNORED_DECLARATION_STATE () { 3 }
20    
21 wakaba 1.5 our $Prop; ## By CSS property name
22     our $Attr; ## By CSSOM attribute name
23     our $Key; ## By internal key
24    
25 wakaba 1.1 sub parse_char_string ($$) {
26     my $self = $_[0];
27    
28     my $s = $_[1];
29     pos ($s) = 0;
30 wakaba 1.2 my $line = 1;
31     my $column = 0;
32    
33     my $_onerror = $self->{onerror};
34     my $onerror = sub {
35     $_onerror->(@_, line => $line, column => $column);
36     };
37 wakaba 1.1
38     my $tt = Whatpm::CSS::Tokenizer->new;
39 wakaba 1.2 $tt->{onerror} = $onerror;
40 wakaba 1.1 $tt->{get_char} = sub {
41     if (pos $s < length $s) {
42 wakaba 1.2 my $c = ord substr $s, pos ($s)++, 1;
43     if ($c == 0x000A) {
44     $line++;
45     $column = 0;
46     } elsif ($c == 0x000D) {
47     unless (substr ($s, pos ($s), 1) eq "\x0A") {
48     $line++;
49     $column = 0;
50     } else {
51     $column++;
52     }
53     } else {
54     $column++;
55     }
56     return $c;
57 wakaba 1.1 } else {
58     return -1;
59     }
60     }; # $tt->{get_char}
61     $tt->init;
62    
63     my $sp = Whatpm::CSS::SelectorsParser->new;
64 wakaba 1.2 $sp->{onerror} = $onerror;
65 wakaba 1.1 $sp->{must_level} = $self->{must_level};
66 wakaba 1.2 $sp->{pseudo_element} = $self->{pseudo_element};
67     $sp->{pseudo_class} = $self->{pseudo_class};
68 wakaba 1.1
69 wakaba 1.4 my $nsmap = {};
70     $sp->{lookup_namespace_uri} = sub {
71     return $nsmap->{$_[0]}; # $_[0] is '' (default namespace) or prefix
72     }; # $sp->{lookup_namespace_uri}
73 wakaba 1.1
74     ## TODO: Supported pseudo classes and elements...
75    
76     require Message::DOM::CSSStyleSheet;
77     require Message::DOM::CSSRule;
78     require Message::DOM::CSSStyleDeclaration;
79    
80 wakaba 1.11 $self->{base_uri} = $self->{href} unless defined $self->{base_uri};
81    
82 wakaba 1.1 my $state = BEFORE_STATEMENT_STATE;
83     my $t = $tt->get_next_token;
84    
85     my $open_rules = [[]];
86     my $current_rules = $open_rules->[-1];
87     my $current_decls;
88     my $closing_tokens = [];
89 wakaba 1.3 my $charset_allowed = 1;
90 wakaba 1.4 my $namespace_allowed = 1;
91 wakaba 1.1
92     S: {
93     if ($state == BEFORE_STATEMENT_STATE) {
94     $t = $tt->get_next_token
95     while $t->{type} == S_TOKEN or
96     $t->{type} == CDO_TOKEN or
97     $t->{type} == CDC_TOKEN;
98    
99     if ($t->{type} == ATKEYWORD_TOKEN) {
100 wakaba 1.5 if (lc $t->{value} eq 'namespace') { ## TODO: case folding
101 wakaba 1.4 $t = $tt->get_next_token;
102     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
103    
104     my $prefix;
105     if ($t->{type} == IDENT_TOKEN) {
106     $prefix = lc $t->{value};
107     ## TODO: Unicode lowercase
108    
109     $t = $tt->get_next_token;
110     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
111     }
112    
113     if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
114     my $uri = $t->{value};
115    
116     $t = $tt->get_next_token;
117     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
118    
119     ## ISSUE: On handling of empty namespace URI, Firefox 2 and
120     ## Opera 9 work differently (See SuikaWiki:namespace).
121     ## TODO: We need to check what we do once it is specced.
122    
123     if ($t->{type} == SEMICOLON_TOKEN) {
124     if ($namespace_allowed) {
125     $nsmap->{defined $prefix ? $prefix : ''} = $uri;
126     push @$current_rules,
127     Message::DOM::CSSNamespaceRule->____new ($prefix, $uri);
128     undef $charset_allowed;
129     } else {
130     $onerror->(type => 'at:namespace:not allowed',
131     level => $self->{must_level},
132     token => $t);
133     }
134    
135     $t = $tt->get_next_token;
136     ## Stay in the state.
137     redo S;
138     } else {
139     #
140     }
141     } else {
142     #
143     }
144    
145     $onerror->(type => 'syntax error:at:namespace',
146     level => $self->{must_level},
147     token => $t);
148     #
149 wakaba 1.5 } elsif (lc $t->{value} eq 'charset') { ## TODO: case folding
150 wakaba 1.3 $t = $tt->get_next_token;
151     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
152    
153     if ($t->{type} == STRING_TOKEN) {
154     my $encoding = $t->{value};
155    
156     $t = $tt->get_next_token;
157     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
158    
159     if ($t->{type} == SEMICOLON_TOKEN) {
160     if ($charset_allowed) {
161     push @$current_rules,
162     Message::DOM::CSSCharsetRule->____new ($encoding);
163     undef $charset_allowed;
164     } else {
165     $onerror->(type => 'at:charset:not allowed',
166     level => $self->{must_level},
167     token => $t);
168     }
169    
170     ## TODO: Detect the conformance errors for @charset...
171    
172     $t = $tt->get_next_token;
173     ## Stay in the state.
174     redo S;
175     } else {
176     #
177     }
178     } else {
179     #
180     }
181    
182     $onerror->(type => 'syntax error:at:charset',
183     level => $self->{must_level},
184     token => $t);
185 wakaba 1.4 #
186 wakaba 1.3 ## NOTE: When adding support for new at-rule, insert code
187 wakaba 1.4 ## "undef $charset_allowed" and "undef $namespace_token" as
188     ## appropriate.
189 wakaba 1.3 } else {
190     $onerror->(type => 'not supported:at:'.$t->{value},
191     level => $self->{unsupported_level},
192     token => $t);
193     }
194 wakaba 1.1
195     $t = $tt->get_next_token;
196     $state = IGNORED_STATEMENT_STATE;
197     redo S;
198     } elsif (@$open_rules > 1 and $t->{type} == RBRACE_TOKEN) {
199     pop @$open_rules;
200     ## Stay in the state.
201     $t = $tt->get_next_token;
202     redo S;
203     } elsif ($t->{type} == EOF_TOKEN) {
204     if (@$open_rules > 1) {
205 wakaba 1.2 $onerror->(type => 'syntax error:block not closed',
206     level => $self->{must_level},
207     token => $t);
208 wakaba 1.1 }
209    
210     last S;
211     } else {
212 wakaba 1.3 undef $charset_allowed;
213 wakaba 1.4 undef $namespace_allowed;
214 wakaba 1.3
215 wakaba 1.1 ($t, my $selectors) = $sp->_parse_selectors_with_tokenizer
216     ($tt, LBRACE_TOKEN, $t);
217    
218     $t = $tt->get_next_token
219     while $t->{type} != LBRACE_TOKEN and $t->{type} != EOF_TOKEN;
220    
221     if ($t->{type} == LBRACE_TOKEN) {
222     $current_decls = Message::DOM::CSSStyleDeclaration->____new;
223     my $rs = Message::DOM::CSSStyleRule->____new
224     ($selectors, $current_decls);
225     push @{$current_rules}, $rs if defined $selectors;
226    
227     $state = BEFORE_DECLARATION_STATE;
228     $t = $tt->get_next_token;
229     redo S;
230     } else {
231 wakaba 1.2 $onerror->(type => 'syntax error:after selectors',
232     level => $self->{must_level},
233     token => $t);
234 wakaba 1.1
235     ## Stay in the state.
236     $t = $tt->get_next_token;
237     redo S;
238     }
239     }
240     } elsif ($state == BEFORE_DECLARATION_STATE) {
241     ## NOTE: DELIM? in declaration will be removed:
242     ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.
243    
244 wakaba 1.5 my $prop_def;
245     my $prop_value;
246     my $prop_flag;
247 wakaba 1.1 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
248     if ($t->{type} == IDENT_TOKEN) { # property
249 wakaba 1.5 my $prop_name = lc $t->{value}; ## TODO: case folding
250     $t = $tt->get_next_token;
251     if ($t->{type} == COLON_TOKEN) {
252     $t = $tt->get_next_token;
253     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
254    
255     $prop_def = $Prop->{$prop_name};
256 wakaba 1.6 if ($prop_def and $self->{prop}->{$prop_name}) {
257 wakaba 1.5 ($t, $prop_value)
258     = $prop_def->{parse}->($self, $prop_name, $tt, $t, $onerror);
259     if ($prop_value) {
260     ## NOTE: {parse} don't have to consume trailing spaces.
261     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
262    
263     if ($t->{type} == EXCLAMATION_TOKEN) {
264     $t = $tt->get_next_token;
265     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
266     if ($t->{type} == IDENT_TOKEN and
267     lc $t->{value} eq 'important') { ## TODO: case folding
268     $prop_flag = 'important';
269    
270     $t = $tt->get_next_token;
271     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
272    
273     #
274     } else {
275     $onerror->(type => 'syntax error:important',
276     level => $self->{must_level},
277     token => $t);
278    
279     ## Reprocess.
280     $state = IGNORED_DECLARATION_STATE;
281     redo S;
282     }
283     }
284    
285     #
286     } else {
287     ## Syntax error.
288    
289     ## Reprocess.
290     $state = IGNORED_DECLARATION_STATE;
291     redo S;
292     }
293     } else {
294     $onerror->(type => 'not supported:property',
295     level => $self->{unsupported_level},
296     token => $t, value => $prop_name);
297    
298     #
299     $state = IGNORED_DECLARATION_STATE;
300     redo S;
301     }
302     } else {
303     $onerror->(type => 'syntax error:property colon',
304     level => $self->{must_level},
305     token => $t);
306 wakaba 1.1
307 wakaba 1.5 #
308     $state = IGNORED_DECLARATION_STATE;
309     redo S;
310     }
311     }
312    
313     if ($t->{type} == RBRACE_TOKEN) {
314 wakaba 1.1 $t = $tt->get_next_token;
315 wakaba 1.5 $state = BEFORE_STATEMENT_STATE;
316     #redo S;
317     } elsif ($t->{type} == SEMICOLON_TOKEN) {
318 wakaba 1.1 $t = $tt->get_next_token;
319 wakaba 1.5 ## Stay in the state.
320     #redo S;
321 wakaba 1.1 } elsif ($t->{type} == EOF_TOKEN) {
322 wakaba 1.2 $onerror->(type => 'syntax error:ruleset not closed',
323     level => $self->{must_level},
324     token => $t);
325 wakaba 1.1 ## Reprocess.
326     $state = BEFORE_STATEMENT_STATE;
327 wakaba 1.5 #redo S;
328     } else {
329     if ($prop_value) {
330     $onerror->(type => 'syntax error:property semicolon',
331     level => $self->{must_level},
332     token => $t);
333     } else {
334     $onerror->(type => 'syntax error:property name',
335     level => $self->{must_level},
336     token => $t);
337     }
338    
339     #
340     $state = IGNORED_DECLARATION_STATE;
341 wakaba 1.1 redo S;
342     }
343    
344 wakaba 1.7 my $important = (defined $prop_flag and $prop_flag eq 'important');
345     for my $set_prop_name (keys %{$prop_value or {}}) {
346     my $set_prop_def = $Prop->{$set_prop_name};
347     $$current_decls->{$set_prop_def->{key}}
348     = [$prop_value->{$set_prop_name}, $prop_flag]
349     if $important or
350     not $$current_decls->{$set_prop_def->{key}} or
351     not defined $$current_decls->{$set_prop_def->{key}}->[1];
352 wakaba 1.5 }
353 wakaba 1.1 redo S;
354     } elsif ($state == IGNORED_STATEMENT_STATE or
355     $state == IGNORED_DECLARATION_STATE) {
356     if (@$closing_tokens) { ## Something is yet in opening state.
357     if ($t->{type} == EOF_TOKEN) {
358     @$closing_tokens = ();
359     ## Reprocess.
360     $state = $state == IGNORED_STATEMENT_STATE
361     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
362     redo S;
363     } elsif ($t->{type} == $closing_tokens->[-1]) {
364     pop @$closing_tokens;
365     if (@$closing_tokens == 0 and
366     $t->{type} == RBRACE_TOKEN and
367     $state == IGNORED_STATEMENT_STATE) {
368     $t = $tt->get_next_token;
369     $state = BEFORE_STATEMENT_STATE;
370     redo S;
371     } else {
372     $t = $tt->get_next_token;
373     ## Stay in the state.
374     redo S;
375     }
376     } else {
377     #
378     }
379     } else {
380     if ($t->{type} == SEMICOLON_TOKEN) {
381     $t = $tt->get_next_token;
382     $state = $state == IGNORED_STATEMENT_STATE
383     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
384     redo S;
385     } elsif ($state == IGNORED_DECLARATION_STATE and
386     $t->{type} == RBRACE_TOKEN) {
387     $t = $tt->get_next_token;
388     $state = BEFORE_STATEMENT_STATE;
389     redo S;
390     } elsif ($t->{type} == EOF_TOKEN) {
391     ## Reprocess.
392     $state = $state == IGNORED_STATEMENT_STATE
393     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
394     redo S;
395     } else {
396     #
397     }
398     }
399    
400     while (not {
401     EOF_TOKEN, 1,
402     RBRACE_TOKEN, 1,
403     RBRACKET_TOKEN, 1,
404     RPAREN_TOKEN, 1,
405     SEMICOLON_TOKEN, 1,
406     }->{$t->{type}}) {
407     if ($t->{type} == LBRACE_TOKEN) {
408     push @$closing_tokens, RBRACE_TOKEN;
409     } elsif ($t->{type} == LBRACKET_TOKEN) {
410     push @$closing_tokens, RBRACKET_TOKEN;
411     } elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {
412     push @$closing_tokens, RPAREN_TOKEN;
413     }
414    
415     $t = $tt->get_next_token;
416     }
417    
418     #
419     ## Stay in the state.
420     redo S;
421     } else {
422     die "$0: parse_char_string: Unknown state: $state";
423     }
424     } # S
425    
426     my $ss = Message::DOM::CSSStyleSheet->____new
427 wakaba 1.11 (manakai_base_uri => $self->{base_uri},
428     css_rules => $open_rules->[0],
429 wakaba 1.1 ## TODO: href
430     ## TODO: owner_node
431     ## TODO: media
432     type => 'text/css', ## TODO: OK?
433     _parser => $self);
434     return $ss;
435     } # parse_char_string
436    
437 wakaba 1.9 my $compute_as_specified = sub ($$$$) {
438     #my ($self, $element, $prop_name, $specified_value) = @_;
439     return $_[3];
440     }; # $compute_as_specified
441    
442 wakaba 1.11 my $default_serializer = sub {
443     my ($self, $prop_name, $value) = @_;
444 wakaba 1.15 if ($value->[0] eq 'NUMBER' or $value->[0] eq 'WEIGHT') {
445     ## TODO: What we currently do for 'font-weight' is different from
446     ## any browser for lighter/bolder cases. We need to fix this, but
447     ## how?
448 wakaba 1.11 return $value->[1]; ## TODO: big or small number cases?
449 wakaba 1.18 } elsif ($value->[0] eq 'DIMENSION') {
450     return $value->[1] . $value->[2]; ## NOTE: This is what browsers do.
451 wakaba 1.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 wakaba 1.25 serialize_multiple => sub {
1355     my $self = shift;
1356    
1357     local $Error::Depth = $Error::Depth + 1;
1358     my $x = $self->_manakai_border_spacing_x;
1359     my $y = $self->_manakai_border_spacing_y;
1360     my $xi = $self->get_property_priority ('-manakai-border-spacing-x');
1361     my $yi = $self->get_property_priority ('-manakai-border-spacing-y');
1362     $xi = ' !' . $xi if length $xi;
1363     $yi = ' !' . $yi if length $yi;
1364     if (defined $x) {
1365     if (defined $y) {
1366 wakaba 1.26 if ($xi eq $yi) {
1367 wakaba 1.25 if ($x eq $y) {
1368     return {'border-spacing' => $x . $xi};
1369     } else {
1370     return {'border-spacing' => $x . ' ' . $y . $xi};
1371     }
1372     } else {
1373     return {'-manakai-border-spacing-x' => $x . $xi,
1374     '-manakai-border-spacing-y' => $y . $yi};
1375     }
1376     } else {
1377     return {'-manakai-border-spacing-x' => $x . $xi};
1378     }
1379     } else {
1380     if (defined $y) {
1381     return {'-manakai-border-spacing-y' => $y . $yi};
1382     } else {
1383     return {};
1384     }
1385     }
1386     },
1387 wakaba 1.24 initial => ['DIMENSION', 0, 'px'],
1388     inherited => 1,
1389     compute => $compute_length,
1390     };
1391     $Attr->{_manakai_border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
1392     $Key->{border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
1393    
1394     $Prop->{'-manakai-border-spacing-y'} = {
1395     css => '-manakai-border-spacing-y',
1396     dom => '_manakai_border_spacing_y',
1397     key => 'border_spacing_y',
1398     parse => $Prop->{'letter-spacing'}->{parse},
1399     #allow_negative => 0,
1400     #keyword => {},
1401     serialize => $default_serializer,
1402 wakaba 1.25 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
1403     ->{serialize_multiple},
1404 wakaba 1.24 initial => ['DIMENSION', 0, 'px'],
1405     inherited => 1,
1406     compute => $compute_length,
1407     };
1408     $Attr->{_manakai_border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
1409     $Key->{border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
1410    
1411 wakaba 1.19 $Prop->{'margin-top'} = {
1412     css => 'margin-top',
1413     dom => 'margin_top',
1414     key => 'margin_top',
1415     parse => sub {
1416     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1417    
1418 wakaba 1.21 ## NOTE: Used for 'margin-top', 'margin-right', 'margin-bottom',
1419 wakaba 1.22 ## 'margin-left', 'top', 'right', 'bottom', 'left', 'padding-top',
1420     ## 'padding-right', 'padding-bottom', 'padding-left',
1421     ## 'border-top-width', 'border-right-width', 'border-bottom-width',
1422 wakaba 1.27 ## 'border-left-width', 'text-indent', 'background-position-x',
1423     ## and 'background-position-y'.
1424 wakaba 1.21
1425 wakaba 1.19 my $sign = 1;
1426     if ($t->{type} == MINUS_TOKEN) {
1427     $t = $tt->get_next_token;
1428     $sign = -1;
1429     }
1430 wakaba 1.22 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1431 wakaba 1.19
1432     if ($t->{type} == DIMENSION_TOKEN) {
1433     my $value = $t->{number} * $sign;
1434     my $unit = lc $t->{value}; ## TODO: case
1435     $t = $tt->get_next_token;
1436 wakaba 1.22 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
1437 wakaba 1.19 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1438     }
1439     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1440     my $value = $t->{number} * $sign;
1441     $t = $tt->get_next_token;
1442 wakaba 1.22 return ($t, {$prop_name => ['PERCENTAGE', $value]})
1443     if $allow_negative or $value >= 0;
1444 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
1445 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
1446     my $value = $t->{number} * $sign;
1447     $t = $tt->get_next_token;
1448 wakaba 1.22 return ($t, {$prop_name => ['DIMENSION', $value, 'px']})
1449     if $allow_negative or $value >= 0;
1450 wakaba 1.19 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1451     my $value = lc $t->{value}; ## TODO: case
1452     $t = $tt->get_next_token;
1453 wakaba 1.22 if ($Prop->{$prop_name}->{keyword}->{$value}) {
1454 wakaba 1.19 return ($t, {$prop_name => ['KEYWORD', $value]});
1455     } elsif ($value eq 'inherit') {
1456     return ($t, {$prop_name => ['INHERIT']});
1457     }
1458     }
1459    
1460     $onerror->(type => 'syntax error:'.$prop_name,
1461     level => $self->{must_level},
1462     token => $t);
1463     return ($t, undef);
1464     },
1465 wakaba 1.22 allow_negative => 1,
1466     keyword => {auto => 1},
1467 wakaba 1.19 serialize => $default_serializer,
1468     initial => ['DIMENSION', 0, 'px'],
1469     #inherited => 0,
1470     compute => $compute_length,
1471     };
1472     $Attr->{margin_top} = $Prop->{'margin-top'};
1473     $Key->{margin_top} = $Prop->{'margin-top'};
1474    
1475     $Prop->{'margin-bottom'} = {
1476     css => 'margin-bottom',
1477     dom => 'margin_bottom',
1478     key => 'margin_bottom',
1479     parse => $Prop->{'margin-top'}->{parse},
1480 wakaba 1.22 allow_negative => 1,
1481     keyword => {auto => 1},
1482 wakaba 1.19 serialize => $default_serializer,
1483     initial => ['DIMENSION', 0, 'px'],
1484     #inherited => 0,
1485     compute => $compute_length,
1486     };
1487     $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
1488     $Key->{margin_bottom} = $Prop->{'margin-bottom'};
1489    
1490     $Prop->{'margin-right'} = {
1491     css => 'margin-right',
1492     dom => 'margin_right',
1493     key => 'margin_right',
1494     parse => $Prop->{'margin-top'}->{parse},
1495 wakaba 1.22 allow_negative => 1,
1496     keyword => {auto => 1},
1497 wakaba 1.19 serialize => $default_serializer,
1498     initial => ['DIMENSION', 0, 'px'],
1499     #inherited => 0,
1500     compute => $compute_length,
1501     };
1502     $Attr->{margin_right} = $Prop->{'margin-right'};
1503     $Key->{margin_right} = $Prop->{'margin-right'};
1504    
1505     $Prop->{'margin-left'} = {
1506     css => 'margin-left',
1507     dom => 'margin_left',
1508     key => 'margin_left',
1509     parse => $Prop->{'margin-top'}->{parse},
1510 wakaba 1.22 allow_negative => 1,
1511     keyword => {auto => 1},
1512 wakaba 1.19 serialize => $default_serializer,
1513     initial => ['DIMENSION', 0, 'px'],
1514     #inherited => 0,
1515     compute => $compute_length,
1516     };
1517     $Attr->{margin_left} = $Prop->{'margin-left'};
1518     $Key->{margin_left} = $Prop->{'margin-left'};
1519    
1520 wakaba 1.21 $Prop->{top} = {
1521     css => 'top',
1522     dom => 'top',
1523     key => 'top',
1524     parse => $Prop->{'margin-top'}->{parse},
1525 wakaba 1.22 allow_negative => 1,
1526     keyword => {auto => 1},
1527 wakaba 1.21 serialize => $default_serializer,
1528     initial => ['KEYWORD', 'auto'],
1529     #inherited => 0,
1530     compute_multiple => sub {
1531     my ($self, $element, $eid, $prop_name) = @_;
1532    
1533     my $pos_value = $self->get_computed_value ($element, 'position');
1534     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1535     if ($pos_value->[1] eq 'static') {
1536     $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
1537     $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
1538     return;
1539     } elsif ($pos_value->[1] eq 'relative') {
1540     my $top_specified = $self->get_specified_value_no_inherit
1541     ($element, 'top');
1542     if (defined $top_specified and
1543     ($top_specified->[0] eq 'DIMENSION' or
1544     $top_specified->[0] eq 'PERCENTAGE')) {
1545     my $tv = $self->{computed_value}->{$eid}->{top}
1546     = $compute_length->($self, $element, 'top', $top_specified);
1547     $self->{computed_value}->{$eid}->{bottom}
1548     = [$tv->[0], -$tv->[1], $tv->[2]];
1549     } else { # top: auto
1550     my $bottom_specified = $self->get_specified_value_no_inherit
1551     ($element, 'bottom');
1552     if (defined $bottom_specified and
1553     ($bottom_specified->[0] eq 'DIMENSION' or
1554     $bottom_specified->[0] eq 'PERCENTAGE')) {
1555     my $tv = $self->{computed_value}->{$eid}->{bottom}
1556     = $compute_length->($self, $element, 'bottom',
1557     $bottom_specified);
1558     $self->{computed_value}->{$eid}->{top}
1559     = [$tv->[0], -$tv->[1], $tv->[2]];
1560     } else { # bottom: auto
1561     $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
1562     $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
1563     }
1564     }
1565     return;
1566     }
1567     }
1568    
1569     my $top_specified = $self->get_specified_value_no_inherit
1570     ($element, 'top');
1571     $self->{computed_value}->{$eid}->{top}
1572     = $compute_length->($self, $element, 'top', $top_specified);
1573     my $bottom_specified = $self->get_specified_value_no_inherit
1574     ($element, 'bottom');
1575     $self->{computed_value}->{$eid}->{bottom}
1576     = $compute_length->($self, $element, 'bottom', $bottom_specified);
1577     },
1578     };
1579     $Attr->{top} = $Prop->{top};
1580     $Key->{top} = $Prop->{top};
1581    
1582     $Prop->{bottom} = {
1583     css => 'bottom',
1584     dom => 'bottom',
1585     key => 'bottom',
1586     parse => $Prop->{'margin-top'}->{parse},
1587 wakaba 1.22 allow_negative => 1,
1588     keyword => {auto => 1},
1589 wakaba 1.21 serialize => $default_serializer,
1590     initial => ['KEYWORD', 'auto'],
1591     #inherited => 0,
1592     compute_multiple => $Prop->{top}->{compute_multiple},
1593     };
1594     $Attr->{bottom} = $Prop->{bottom};
1595     $Key->{bottom} = $Prop->{bottom};
1596    
1597     $Prop->{left} = {
1598     css => 'left',
1599     dom => 'left',
1600     key => 'left',
1601     parse => $Prop->{'margin-top'}->{parse},
1602 wakaba 1.22 allow_negative => 1,
1603     keyword => {auto => 1},
1604 wakaba 1.21 serialize => $default_serializer,
1605     initial => ['KEYWORD', 'auto'],
1606     #inherited => 0,
1607     compute_multiple => sub {
1608     my ($self, $element, $eid, $prop_name) = @_;
1609    
1610     my $pos_value = $self->get_computed_value ($element, 'position');
1611     if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1612     if ($pos_value->[1] eq 'static') {
1613     $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
1614     $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
1615     return;
1616     } elsif ($pos_value->[1] eq 'relative') {
1617     my $left_specified = $self->get_specified_value_no_inherit
1618     ($element, 'left');
1619     if (defined $left_specified and
1620     ($left_specified->[0] eq 'DIMENSION' or
1621     $left_specified->[0] eq 'PERCENTAGE')) {
1622     my $right_specified = $self->get_specified_value_no_inherit
1623     ($element, 'right');
1624     if (defined $right_specified and
1625     ($right_specified->[0] eq 'DIMENSION' or
1626     $right_specified->[0] eq 'PERCENTAGE')) {
1627     my $direction = $self->get_computed_value ($element, 'direction');
1628     if (defined $direction and $direction->[0] eq 'KEYWORD' and
1629     $direction->[0] eq 'ltr') {
1630     my $tv = $self->{computed_value}->{$eid}->{left}
1631     = $compute_length->($self, $element, 'left',
1632     $left_specified);
1633     $self->{computed_value}->{$eid}->{right}
1634     = [$tv->[0], -$tv->[1], $tv->[2]];
1635     } else {
1636     my $tv = $self->{computed_value}->{$eid}->{right}
1637     = $compute_length->($self, $element, 'right',
1638     $right_specified);
1639     $self->{computed_value}->{$eid}->{left}
1640     = [$tv->[0], -$tv->[1], $tv->[2]];
1641     }
1642     } else {
1643     my $tv = $self->{computed_value}->{$eid}->{left}
1644     = $compute_length->($self, $element, 'left', $left_specified);
1645     $self->{computed_value}->{$eid}->{right}
1646     = [$tv->[0], -$tv->[1], $tv->[2]];
1647     }
1648     } else { # left: auto
1649     my $right_specified = $self->get_specified_value_no_inherit
1650     ($element, 'right');
1651     if (defined $right_specified and
1652     ($right_specified->[0] eq 'DIMENSION' or
1653     $right_specified->[0] eq 'PERCENTAGE')) {
1654     my $tv = $self->{computed_value}->{$eid}->{right}
1655     = $compute_length->($self, $element, 'right',
1656     $right_specified);
1657     $self->{computed_value}->{$eid}->{left}
1658     = [$tv->[0], -$tv->[1], $tv->[2]];
1659     } else { # right: auto
1660     $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
1661     $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
1662     }
1663     }
1664     return;
1665     }
1666     }
1667    
1668     my $left_specified = $self->get_specified_value_no_inherit
1669     ($element, 'left');
1670     $self->{computed_value}->{$eid}->{left}
1671     = $compute_length->($self, $element, 'left', $left_specified);
1672     my $right_specified = $self->get_specified_value_no_inherit
1673     ($element, 'right');
1674     $self->{computed_value}->{$eid}->{right}
1675     = $compute_length->($self, $element, 'right', $right_specified);
1676     },
1677     };
1678     $Attr->{left} = $Prop->{left};
1679     $Key->{left} = $Prop->{left};
1680    
1681     $Prop->{right} = {
1682     css => 'right',
1683     dom => 'right',
1684     key => 'right',
1685     parse => $Prop->{'margin-top'}->{parse},
1686     serialize => $default_serializer,
1687     initial => ['KEYWORD', 'auto'],
1688     #inherited => 0,
1689     compute_multiple => $Prop->{left}->{compute_multiple},
1690     };
1691     $Attr->{right} = $Prop->{right};
1692     $Key->{right} = $Prop->{right};
1693    
1694 wakaba 1.22 $Prop->{width} = {
1695     css => 'width',
1696     dom => 'width',
1697     key => 'width',
1698     parse => $Prop->{'margin-top'}->{parse},
1699     #allow_negative => 0,
1700     keyword => {auto => 1},
1701     serialize => $default_serializer,
1702     initial => ['KEYWORD', 'auto'],
1703     #inherited => 0,
1704     compute => $compute_length,
1705     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/width> for
1706     ## browser compatibility issues.
1707     };
1708     $Attr->{width} = $Prop->{width};
1709     $Key->{width} = $Prop->{width};
1710    
1711     $Prop->{'min-width'} = {
1712     css => 'min-width',
1713     dom => 'min_width',
1714     key => 'min_width',
1715     parse => $Prop->{'margin-top'}->{parse},
1716     #allow_negative => 0,
1717     #keyword => {},
1718     serialize => $default_serializer,
1719     initial => ['DIMENSION', 0, 'px'],
1720     #inherited => 0,
1721     compute => $compute_length,
1722     };
1723     $Attr->{min_width} = $Prop->{'min-width'};
1724     $Key->{min_width} = $Prop->{'min-width'};
1725    
1726     $Prop->{'max-width'} = {
1727     css => 'max-width',
1728     dom => 'max_width',
1729     key => 'max_width',
1730     parse => $Prop->{'margin-top'}->{parse},
1731     #allow_negative => 0,
1732     keyword => {none => 1},
1733     serialize => $default_serializer,
1734     initial => ['KEYWORD', 'none'],
1735     #inherited => 0,
1736     compute => $compute_length,
1737     };
1738     $Attr->{max_width} = $Prop->{'max-width'};
1739     $Key->{max_width} = $Prop->{'max-width'};
1740    
1741     $Prop->{height} = {
1742     css => 'height',
1743     dom => 'height',
1744     key => 'height',
1745     parse => $Prop->{'margin-top'}->{parse},
1746     #allow_negative => 0,
1747     keyword => {auto => 1},
1748     serialize => $default_serializer,
1749     initial => ['KEYWORD', 'auto'],
1750     #inherited => 0,
1751     compute => $compute_length,
1752     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/height> for
1753     ## browser compatibility issues.
1754     };
1755     $Attr->{height} = $Prop->{height};
1756     $Key->{height} = $Prop->{height};
1757    
1758     $Prop->{'min-height'} = {
1759     css => 'min-height',
1760     dom => 'min_height',
1761     key => 'min_height',
1762     parse => $Prop->{'margin-top'}->{parse},
1763     #allow_negative => 0,
1764     #keyword => {},
1765     serialize => $default_serializer,
1766     initial => ['DIMENSION', 0, 'px'],
1767     #inherited => 0,
1768     compute => $compute_length,
1769     };
1770     $Attr->{min_height} = $Prop->{'min-height'};
1771     $Key->{min_height} = $Prop->{'min-height'};
1772    
1773     $Prop->{'max-height'} = {
1774     css => 'max-height',
1775     dom => 'max_height',
1776     key => 'max_height',
1777     parse => $Prop->{'margin-top'}->{parse},
1778     #allow_negative => 0,
1779     keyword => {none => 1},
1780     serialize => $default_serializer,
1781     initial => ['KEYWORD', 'none'],
1782     #inherited => 0,
1783     compute => $compute_length,
1784     };
1785     $Attr->{max_height} = $Prop->{'max-height'};
1786     $Key->{max_height} = $Prop->{'max-height'};
1787    
1788     $Prop->{'line-height'} = {
1789     css => 'line-height',
1790     dom => 'line_height',
1791     key => 'line_height',
1792 wakaba 1.19 parse => sub {
1793     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1794    
1795 wakaba 1.22 ## NOTE: Similar to 'margin-top', but different handling
1796     ## for unitless numbers.
1797    
1798 wakaba 1.19 my $sign = 1;
1799     if ($t->{type} == MINUS_TOKEN) {
1800     $t = $tt->get_next_token;
1801     $sign = -1;
1802     }
1803 wakaba 1.22 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1804 wakaba 1.19
1805     if ($t->{type} == DIMENSION_TOKEN) {
1806     my $value = $t->{number} * $sign;
1807     my $unit = lc $t->{value}; ## TODO: case
1808     $t = $tt->get_next_token;
1809     if ($length_unit->{$unit} and $value >= 0) {
1810     return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1811     }
1812     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1813     my $value = $t->{number} * $sign;
1814     $t = $tt->get_next_token;
1815 wakaba 1.22 return ($t, {$prop_name => ['PERCENTAGE', $value]})
1816     if $value >= 0;
1817     } elsif ($t->{type} == NUMBER_TOKEN) {
1818 wakaba 1.19 my $value = $t->{number} * $sign;
1819     $t = $tt->get_next_token;
1820 wakaba 1.22 return ($t, {$prop_name => ['NUMBER', $value]}) if $value >= 0;
1821 wakaba 1.19 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1822     my $value = lc $t->{value}; ## TODO: case
1823     $t = $tt->get_next_token;
1824 wakaba 1.22 if ($value eq 'normal') {
1825     return ($t, {$prop_name => ['KEYWORD', $value]});
1826     } elsif ($value eq 'inherit') {
1827 wakaba 1.19 return ($t, {$prop_name => ['INHERIT']});
1828     }
1829     }
1830    
1831     $onerror->(type => 'syntax error:'.$prop_name,
1832     level => $self->{must_level},
1833     token => $t);
1834     return ($t, undef);
1835     },
1836     serialize => $default_serializer,
1837 wakaba 1.22 initial => ['KEYWORD', 'normal'],
1838     inherited => 1,
1839     compute => $compute_length,
1840     };
1841     $Attr->{line_height} = $Prop->{'line-height'};
1842     $Key->{line_height} = $Prop->{'line-height'};
1843    
1844     $Prop->{'vertical-align'} = {
1845     css => 'vertical-align',
1846     dom => 'vertical_align',
1847     key => 'vertical_align',
1848     parse => $Prop->{'margin-top'}->{parse},
1849     allow_negative => 1,
1850     keyword => {
1851     baseline => 1, sub => 1, super => 1, top => 1, 'text-top' => 1,
1852     middle => 1, bottom => 1, 'text-bottom' => 1,
1853     },
1854     ## NOTE: Currently, we don't support option to select subset of keywords
1855     ## supported by application (i.e.
1856     ## $parser->{prop_value}->{'line-height'->{$keyword}). Should we support
1857     ## it?
1858     serialize => $default_serializer,
1859     initial => ['KEYWORD', 'baseline'],
1860     #inherited => 0,
1861     compute => $compute_length,
1862     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/vertical-align> for
1863     ## browser compatibility issues.
1864     };
1865     $Attr->{vertical_align} = $Prop->{'vertical-align'};
1866     $Key->{vertical_align} = $Prop->{'vertical-align'};
1867    
1868 wakaba 1.23 $Prop->{'text-indent'} = {
1869     css => 'text-indent',
1870     dom => 'text_indent',
1871     key => 'text_indent',
1872     parse => $Prop->{'margin-top'}->{parse},
1873     allow_negative => 1,
1874     keyword => {},
1875     serialize => $default_serializer,
1876     initial => ['DIMENSION', 0, 'px'],
1877     inherited => 1,
1878     compute => $compute_length,
1879     };
1880     $Attr->{text_indent} = $Prop->{'text-indent'};
1881     $Key->{text_indent} = $Prop->{'text-indent'};
1882    
1883 wakaba 1.27 $Prop->{'background-position-x'} = {
1884     css => 'background-position-x',
1885     dom => 'background_position_x',
1886     key => 'background_position_x',
1887     parse => $Prop->{'margin-top'}->{parse},
1888     allow_negative => 1,
1889     keyword => {left => 1, center => 1, right => 1},
1890     serialize => $default_serializer,
1891     initial => ['PERCENTAGE', 0],
1892     #inherited => 0,
1893     compute => sub {
1894     my ($self, $element, $prop_name, $specified_value) = @_;
1895    
1896     if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1897     my $v = {
1898     left => 0, center => 50, right => 100, top => 0, bottom => 100,
1899     }->{$specified_value->[1]};
1900     if (defined $v) {
1901     return ['PERCENTAGE', $v];
1902     } else {
1903     return $specified_value;
1904     }
1905     } else {
1906     return $compute_length->(@_);
1907     }
1908     },
1909     serialize_multiple => sub {
1910     my $self = shift;
1911    
1912     local $Error::Depth = $Error::Depth + 1;
1913     my $x = $self->background_position_x;
1914     my $y = $self->background_position_y;
1915     my $xi = $self->get_property_priority ('background-position-x');
1916     my $yi = $self->get_property_priority ('background-position-y');
1917     $xi = ' !' . $xi if length $xi;
1918     $yi = ' !' . $yi if length $yi;
1919     if (defined $x) {
1920     if (defined $y) {
1921     if ($xi eq $yi) {
1922     return {'background-position' => $x . ' ' . $y . $xi};
1923     } else {
1924     return {'background-position-x' => $x . $xi,
1925     'background-position-y' => $y . $yi};
1926     }
1927     } else {
1928     return {'background-position-x' => $x . $xi};
1929     }
1930     } else {
1931     if (defined $y) {
1932     return {'background-position-y' => $y . $yi};
1933     } else {
1934     return {};
1935     }
1936     }
1937     },
1938     };
1939     $Attr->{background_position_x} = $Prop->{'background-position-x'};
1940     $Key->{background_position_x} = $Prop->{'background-position-x'};
1941    
1942     $Prop->{'background-position-y'} = {
1943     css => 'background-position-y',
1944     dom => 'background_position_y',
1945     key => 'background_position_y',
1946     parse => $Prop->{'margin-top'}->{parse},
1947     allow_negative => 1,
1948     keyword => {top => 1, center => 1, bottom => 1},
1949     serialize => $default_serializer,
1950     serialize_multiple => $Prop->{'background-position-x'}->{serialize_multiple},
1951     initial => ['PERCENTAGE', 0],
1952     #inherited => 0,
1953     compute => $Prop->{'background-position-x'}->{compute},
1954     };
1955     $Attr->{background_position_y} = $Prop->{'background-position-y'};
1956     $Key->{background_position_y} = $Prop->{'background-position-y'};
1957    
1958 wakaba 1.22 $Prop->{'padding-top'} = {
1959     css => 'padding-top',
1960     dom => 'padding_top',
1961     key => 'padding_top',
1962     parse => $Prop->{'margin-top'}->{parse},
1963     #allow_negative => 0,
1964     #keyword => {},
1965     serialize => $default_serializer,
1966 wakaba 1.19 initial => ['DIMENSION', 0, 'px'],
1967     #inherited => 0,
1968     compute => $compute_length,
1969     };
1970     $Attr->{padding_top} = $Prop->{'padding-top'};
1971     $Key->{padding_top} = $Prop->{'padding-top'};
1972    
1973     $Prop->{'padding-bottom'} = {
1974     css => 'padding-bottom',
1975     dom => 'padding_bottom',
1976     key => 'padding_bottom',
1977     parse => $Prop->{'padding-top'}->{parse},
1978 wakaba 1.22 #allow_negative => 0,
1979     #keyword => {},
1980 wakaba 1.19 serialize => $default_serializer,
1981     initial => ['DIMENSION', 0, 'px'],
1982     #inherited => 0,
1983     compute => $compute_length,
1984     };
1985     $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
1986     $Key->{padding_bottom} = $Prop->{'padding-bottom'};
1987    
1988     $Prop->{'padding-right'} = {
1989     css => 'padding-right',
1990     dom => 'padding_right',
1991     key => 'padding_right',
1992     parse => $Prop->{'padding-top'}->{parse},
1993 wakaba 1.22 #allow_negative => 0,
1994     #keyword => {},
1995 wakaba 1.19 serialize => $default_serializer,
1996     initial => ['DIMENSION', 0, 'px'],
1997     #inherited => 0,
1998     compute => $compute_length,
1999     };
2000     $Attr->{padding_right} = $Prop->{'padding-right'};
2001     $Key->{padding_right} = $Prop->{'padding-right'};
2002    
2003     $Prop->{'padding-left'} = {
2004     css => 'padding-left',
2005     dom => 'padding_left',
2006     key => 'padding_left',
2007     parse => $Prop->{'padding-top'}->{parse},
2008 wakaba 1.22 #allow_negative => 0,
2009     #keyword => {},
2010 wakaba 1.19 serialize => $default_serializer,
2011     initial => ['DIMENSION', 0, 'px'],
2012     #inherited => 0,
2013     compute => $compute_length,
2014     };
2015     $Attr->{padding_left} = $Prop->{'padding-left'};
2016     $Key->{padding_left} = $Prop->{'padding-left'};
2017    
2018 wakaba 1.20 $Prop->{'border-top-width'} = {
2019     css => 'border-top-width',
2020     dom => 'border_top_width',
2021     key => 'border_top_width',
2022 wakaba 1.22 parse => $Prop->{'margin-top'}->{parse},
2023     #allow_negative => 0,
2024     keyword => {thin => 1, medium => 1, thick => 1},
2025 wakaba 1.20 serialize => $default_serializer,
2026     initial => ['KEYWORD', 'medium'],
2027     #inherited => 0,
2028     compute => sub {
2029     my ($self, $element, $prop_name, $specified_value) = @_;
2030    
2031 wakaba 1.23 ## NOTE: Used for 'border-top-width', 'border-right-width',
2032     ## 'border-bottom-width', 'border-right-width', and
2033     ## 'outline-width'.
2034    
2035 wakaba 1.20 my $style_prop = $prop_name;
2036     $style_prop =~ s/width/style/;
2037     my $style = $self->get_computed_value ($element, $style_prop);
2038     if (defined $style and $style->[0] eq 'KEYWORD' and
2039     ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
2040     return ['DIMENSION', 0, 'px'];
2041     }
2042    
2043     my $value = $compute_length->(@_);
2044     if (defined $value and $value->[0] eq 'KEYWORD') {
2045     if ($value->[1] eq 'thin') {
2046     return ['DIMENSION', 1, 'px']; ## Firefox/Opera
2047     } elsif ($value->[1] eq 'medium') {
2048     return ['DIMENSION', 3, 'px']; ## Firefox/Opera
2049     } elsif ($value->[1] eq 'thick') {
2050     return ['DIMENSION', 5, 'px']; ## Firefox
2051     }
2052     }
2053     return $value;
2054     },
2055 wakaba 1.23 ## NOTE: CSS3 will allow <percentage> as an option in <border-width>.
2056     ## Opera 9 has already implemented it.
2057 wakaba 1.20 };
2058     $Attr->{border_top_width} = $Prop->{'border-top-width'};
2059     $Key->{border_top_width} = $Prop->{'border-top-width'};
2060    
2061     $Prop->{'border-right-width'} = {
2062     css => 'border-right-width',
2063     dom => 'border_right_width',
2064     key => 'border_right_width',
2065     parse => $Prop->{'border-top-width'}->{parse},
2066 wakaba 1.22 #allow_negative => 0,
2067     keyword => {thin => 1, medium => 1, thick => 1},
2068 wakaba 1.20 serialize => $default_serializer,
2069     initial => ['KEYWORD', 'medium'],
2070     #inherited => 0,
2071     compute => $Prop->{'border-top-width'}->{compute},
2072     };
2073     $Attr->{border_right_width} = $Prop->{'border-right-width'};
2074     $Key->{border_right_width} = $Prop->{'border-right-width'};
2075    
2076     $Prop->{'border-bottom-width'} = {
2077     css => 'border-bottom-width',
2078     dom => 'border_bottom_width',
2079     key => 'border_bottom_width',
2080     parse => $Prop->{'border-top-width'}->{parse},
2081 wakaba 1.22 #allow_negative => 0,
2082     keyword => {thin => 1, medium => 1, thick => 1},
2083 wakaba 1.20 serialize => $default_serializer,
2084     initial => ['KEYWORD', 'medium'],
2085     #inherited => 0,
2086     compute => $Prop->{'border-top-width'}->{compute},
2087     };
2088     $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
2089     $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
2090    
2091     $Prop->{'border-left-width'} = {
2092     css => 'border-left-width',
2093     dom => 'border_left_width',
2094     key => 'border_left_width',
2095     parse => $Prop->{'border-top-width'}->{parse},
2096 wakaba 1.22 #allow_negative => 0,
2097     keyword => {thin => 1, medium => 1, thick => 1},
2098 wakaba 1.20 serialize => $default_serializer,
2099     initial => ['KEYWORD', 'medium'],
2100     #inherited => 0,
2101     compute => $Prop->{'border-top-width'}->{compute},
2102     };
2103     $Attr->{border_left_width} = $Prop->{'border-left-width'};
2104     $Key->{border_left_width} = $Prop->{'border-left-width'};
2105    
2106 wakaba 1.23 $Prop->{'outline-width'} = {
2107     css => 'outline-width',
2108     dom => 'outline_width',
2109     key => 'outline_width',
2110     parse => $Prop->{'border-top-width'}->{parse},
2111     #allow_negative => 0,
2112     keyword => {thin => 1, medium => 1, thick => 1},
2113     serialize => $default_serializer,
2114     initial => ['KEYWORD', 'medium'],
2115     #inherited => 0,
2116     compute => $Prop->{'border-top-width'}->{compute},
2117     };
2118     $Attr->{outline_width} = $Prop->{'outline-width'};
2119     $Key->{outline_width} = $Prop->{'outline-width'};
2120    
2121 wakaba 1.15 $Prop->{'font-weight'} = {
2122     css => 'font-weight',
2123     dom => 'font_weight',
2124     key => 'font_weight',
2125     parse => sub {
2126     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2127    
2128     if ($t->{type} == NUMBER_TOKEN) {
2129     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
2130     ## browser compatibility issue.
2131     my $value = $t->{number};
2132     $t = $tt->get_next_token;
2133     if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
2134     return ($t, {$prop_name => ['WEIGHT', $value, 0]});
2135     }
2136     } elsif ($t->{type} == IDENT_TOKEN) {
2137     my $value = lc $t->{value}; ## TODO: case
2138     $t = $tt->get_next_token;
2139     if ({
2140     normal => 1, bold => 1, bolder => 1, lighter => 1,
2141     }->{$value}) {
2142     return ($t, {$prop_name => ['KEYWORD', $value]});
2143     } elsif ($value eq 'inherit') {
2144     return ($t, {$prop_name => ['INHERIT']});
2145     }
2146     }
2147    
2148     $onerror->(type => 'syntax error:'.$prop_name,
2149     level => $self->{must_level},
2150     token => $t);
2151     return ($t, undef);
2152     },
2153     serialize => $default_serializer,
2154     initial => ['KEYWORD', 'normal'],
2155     inherited => 1,
2156     compute => sub {
2157     my ($self, $element, $prop_name, $specified_value) = @_;
2158    
2159 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
2160 wakaba 1.15 if ($specified_value->[1] eq 'normal') {
2161     return ['WEIGHT', 400, 0];
2162     } elsif ($specified_value->[1] eq 'bold') {
2163     return ['WEIGHT', 700, 0];
2164     } elsif ($specified_value->[1] eq 'bolder') {
2165     my $parent_element = $element->manakai_parent_element;
2166     if (defined $parent_element) {
2167     my $parent_value = $self->get_cascaded_value
2168     ($parent_element, $prop_name); ## NOTE: What Firefox does.
2169     return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
2170     } else {
2171     return ['WEIGHT', 400, 1];
2172     }
2173     } elsif ($specified_value->[1] eq 'lighter') {
2174     my $parent_element = $element->manakai_parent_element;
2175     if (defined $parent_element) {
2176     my $parent_value = $self->get_cascaded_value
2177     ($parent_element, $prop_name); ## NOTE: What Firefox does.
2178     return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
2179     } else {
2180     return ['WEIGHT', 400, 1];
2181     }
2182     }
2183 wakaba 1.17 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
2184 wakaba 1.15 #
2185     }
2186    
2187     return $specified_value;
2188     },
2189     };
2190     $Attr->{font_weight} = $Prop->{'font-weight'};
2191     $Key->{font_weight} = $Prop->{'font-weight'};
2192    
2193 wakaba 1.13 my $uri_or_none_parser = sub {
2194 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2195    
2196 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
2197 wakaba 1.11 my $value = $t->{value};
2198     $t = $tt->get_next_token;
2199     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2200     } elsif ($t->{type} == IDENT_TOKEN) {
2201     my $value = lc $t->{value}; ## TODO: case
2202     $t = $tt->get_next_token;
2203     if ($value eq 'none') {
2204     ## NOTE: |none| is the default value and therefore it must be
2205     ## supported anyway.
2206     return ($t, {$prop_name => ["KEYWORD", 'none']});
2207     } elsif ($value eq 'inherit') {
2208     return ($t, {$prop_name => ['INHERIT']});
2209     }
2210     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
2211     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
2212     # my $value = $t->{value};
2213     # $t = $tt->get_next_token;
2214     # if ($t->{type} == EOF_TOKEN) {
2215     # $onerror->(type => 'syntax error:eof:'.$prop_name,
2216     # level => $self->{must_level},
2217     # token => $t);
2218     #
2219     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2220     # }
2221     }
2222    
2223     $onerror->(type => 'syntax error:'.$prop_name,
2224     level => $self->{must_level},
2225     token => $t);
2226     return ($t, undef);
2227 wakaba 1.13 }; # $uri_or_none_parser
2228    
2229 wakaba 1.14 my $compute_uri_or_none = sub {
2230 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
2231    
2232     if (defined $specified_value and
2233     $specified_value->[0] eq 'URI' and
2234     defined $specified_value->[2]) {
2235     require Message::DOM::DOMImplementation;
2236     return ['URI',
2237     Message::DOM::DOMImplementation->create_uri_reference
2238     ($specified_value->[1])
2239     ->get_absolute_reference (${$specified_value->[2]})
2240     ->get_uri_reference,
2241     $specified_value->[2]];
2242     }
2243    
2244     return $specified_value;
2245 wakaba 1.14 }; # $compute_uri_or_none
2246    
2247     $Prop->{'list-style-image'} = {
2248     css => 'list-style-image',
2249     dom => 'list_style_image',
2250     key => 'list_style_image',
2251     parse => $uri_or_none_parser,
2252     serialize => $default_serializer,
2253     initial => ['KEYWORD', 'none'],
2254     inherited => 1,
2255     compute => $compute_uri_or_none,
2256 wakaba 1.11 };
2257     $Attr->{list_style_image} = $Prop->{'list-style-image'};
2258     $Key->{list_style_image} = $Prop->{'list-style-image'};
2259    
2260 wakaba 1.15 $Prop->{'background-image'} = {
2261     css => 'background-image',
2262     dom => 'background_image',
2263     key => 'background_image',
2264     parse => $uri_or_none_parser,
2265     serialize => $default_serializer,
2266     initial => ['KEYWORD', 'none'],
2267     #inherited => 0,
2268     compute => $compute_uri_or_none,
2269     };
2270     $Attr->{background_image} = $Prop->{'background-image'};
2271     $Key->{background_image} = $Prop->{'background-image'};
2272    
2273 wakaba 1.7 my $border_style_keyword = {
2274     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
2275     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
2276     };
2277    
2278     $Prop->{'border-top-style'} = {
2279     css => 'border-top-style',
2280     dom => 'border_top_style',
2281     key => 'border_top_style',
2282     parse => $one_keyword_parser,
2283 wakaba 1.11 serialize => $default_serializer,
2284 wakaba 1.7 keyword => $border_style_keyword,
2285 wakaba 1.9 initial => ["KEYWORD", "none"],
2286     #inherited => 0,
2287     compute => $compute_as_specified,
2288 wakaba 1.7 };
2289     $Attr->{border_top_style} = $Prop->{'border-top-style'};
2290     $Key->{border_top_style} = $Prop->{'border-top-style'};
2291    
2292     $Prop->{'border-right-style'} = {
2293     css => 'border-right-style',
2294     dom => 'border_right_style',
2295     key => 'border_right_style',
2296     parse => $one_keyword_parser,
2297 wakaba 1.11 serialize => $default_serializer,
2298 wakaba 1.7 keyword => $border_style_keyword,
2299 wakaba 1.9 initial => ["KEYWORD", "none"],
2300     #inherited => 0,
2301     compute => $compute_as_specified,
2302 wakaba 1.7 };
2303     $Attr->{border_right_style} = $Prop->{'border-right-style'};
2304     $Key->{border_right_style} = $Prop->{'border-right-style'};
2305    
2306     $Prop->{'border-bottom-style'} = {
2307     css => 'border-bottom-style',
2308     dom => 'border_bottom_style',
2309     key => 'border_bottom_style',
2310     parse => $one_keyword_parser,
2311 wakaba 1.11 serialize => $default_serializer,
2312 wakaba 1.7 keyword => $border_style_keyword,
2313 wakaba 1.9 initial => ["KEYWORD", "none"],
2314     #inherited => 0,
2315     compute => $compute_as_specified,
2316 wakaba 1.7 };
2317     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
2318     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
2319    
2320     $Prop->{'border-left-style'} = {
2321     css => 'border-left-style',
2322     dom => 'border_left_style',
2323     key => 'border_left_style',
2324     parse => $one_keyword_parser,
2325 wakaba 1.11 serialize => $default_serializer,
2326 wakaba 1.7 keyword => $border_style_keyword,
2327 wakaba 1.9 initial => ["KEYWORD", "none"],
2328     #inherited => 0,
2329     compute => $compute_as_specified,
2330 wakaba 1.7 };
2331     $Attr->{border_left_style} = $Prop->{'border-left-style'};
2332     $Key->{border_left_style} = $Prop->{'border-left-style'};
2333    
2334 wakaba 1.16 $Prop->{'outline-style'} = {
2335     css => 'outline-style',
2336     dom => 'outline_style',
2337     key => 'outline_style',
2338     parse => $one_keyword_parser,
2339     serialize => $default_serializer,
2340 wakaba 1.23 keyword => {%$border_style_keyword},
2341 wakaba 1.16 initial => ['KEYWORD', 'none'],
2342     #inherited => 0,
2343     compute => $compute_as_specified,
2344     };
2345     $Attr->{outline_style} = $Prop->{'outline-style'};
2346     $Key->{outline_style} = $Prop->{'outline-style'};
2347 wakaba 1.23 delete $Prop->{'outline-style'}->{keyword}->{hidden};
2348 wakaba 1.16
2349 wakaba 1.15 $Prop->{'font-family'} = {
2350     css => 'font-family',
2351     dom => 'font_family',
2352     key => 'font_family',
2353     parse => sub {
2354     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2355    
2356     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
2357     ## how chaotic browsers are!
2358    
2359     my @prop_value;
2360    
2361     my $font_name = '';
2362     my $may_be_generic = 1;
2363     my $may_be_inherit = 1;
2364     my $has_s = 0;
2365     F: {
2366     if ($t->{type} == IDENT_TOKEN) {
2367     undef $may_be_inherit if $has_s or length $font_name;
2368     undef $may_be_generic if $has_s or length $font_name;
2369     $font_name .= ' ' if $has_s;
2370     $font_name .= $t->{value};
2371     undef $has_s;
2372     $t = $tt->get_next_token;
2373     } elsif ($t->{type} == STRING_TOKEN) {
2374     $font_name .= ' ' if $has_s;
2375     $font_name .= $t->{value};
2376     undef $may_be_inherit;
2377     undef $may_be_generic;
2378     undef $has_s;
2379     $t = $tt->get_next_token;
2380     } elsif ($t->{type} == COMMA_TOKEN) {
2381     if ($may_be_generic and
2382     {
2383     serif => 1, 'sans-serif' => 1, cursive => 1,
2384     fantasy => 1, monospace => 1, '-manakai-default' => 1,
2385     }->{lc $font_name}) { ## TODO: case
2386     push @prop_value, ['KEYWORD', $font_name];
2387     } elsif (not $may_be_generic or length $font_name) {
2388     push @prop_value, ["STRING", $font_name];
2389     }
2390     undef $may_be_inherit;
2391     $may_be_generic = 1;
2392     undef $has_s;
2393     $font_name = '';
2394     $t = $tt->get_next_token;
2395     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2396     } elsif ($t->{type} == S_TOKEN) {
2397     $has_s = 1;
2398     $t = $tt->get_next_token;
2399     } else {
2400     if ($may_be_generic and
2401     {
2402     serif => 1, 'sans-serif' => 1, cursive => 1,
2403     fantasy => 1, monospace => 1, '-manakai-default' => 1,
2404     }->{lc $font_name}) { ## TODO: case
2405     push @prop_value, ['KEYWORD', $font_name];
2406     } elsif (not $may_be_generic or length $font_name) {
2407     push @prop_value, ['STRING', $font_name];
2408     } else {
2409     $onerror->(type => 'syntax error:'.$prop_name,
2410     level => $self->{must_level},
2411     token => $t);
2412     return ($t, undef);
2413     }
2414     last F;
2415     }
2416     redo F;
2417     } # F
2418    
2419     if ($may_be_inherit and
2420     @prop_value == 1 and
2421     $prop_value[0]->[0] eq 'STRING' and
2422     lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
2423     return ($t, {$prop_name => ['INHERIT']});
2424     } else {
2425     unshift @prop_value, 'FONT';
2426     return ($t, {$prop_name => \@prop_value});
2427     }
2428     },
2429     serialize => sub {
2430     my ($self, $prop_name, $value) = @_;
2431    
2432     if ($value->[0] eq 'FONT') {
2433     return join ', ', map {
2434     if ($_->[0] eq 'STRING') {
2435     '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
2436     } elsif ($_->[0] eq 'KEYWORD') {
2437     $_->[1]; ## NOTE: This is what Firefox does.
2438     } else {
2439     ## NOTE: This should be an error.
2440     '""';
2441     }
2442     } @$value[1..$#$value];
2443     } elsif ($value->[0] eq 'INHERIT') {
2444     return 'inherit';
2445     } else {
2446     return undef;
2447     }
2448     },
2449     initial => ['FONT', ['KEYWORD', '-manakai-default']],
2450     inherited => 1,
2451     compute => $compute_as_specified,
2452     };
2453     $Attr->{font_family} = $Prop->{'font-family'};
2454     $Key->{font_family} = $Prop->{'font-family'};
2455    
2456 wakaba 1.17 $Prop->{cursor} = {
2457     css => 'cursor',
2458     dom => 'cursor',
2459     key => 'cursor',
2460     parse => sub {
2461     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2462    
2463     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
2464     ## compatibility issues.
2465    
2466     my @prop_value = ('CURSOR');
2467    
2468     F: {
2469     if ($t->{type} == IDENT_TOKEN) {
2470     my $v = lc $t->{value}; ## TODO: case
2471     $t = $tt->get_next_token;
2472     if ($Prop->{$prop_name}->{keyword}->{$v}) {
2473     push @prop_value, ['KEYWORD', $v];
2474     last F;
2475     } elsif ($v eq 'inherit' and @prop_value == 1) {
2476     return ($t, {$prop_name => ['INHERIT']});
2477     } else {
2478     $onerror->(type => 'syntax error:'.$prop_name,
2479     level => $self->{must_level},
2480     token => $t);
2481     return ($t, undef);
2482     }
2483     } elsif ($t->{type} == URI_TOKEN) {
2484     push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
2485     $t = $tt->get_next_token;
2486     } else {
2487     $onerror->(type => 'syntax error:'.$prop_name,
2488     level => $self->{must_level},
2489     token => $t);
2490     return ($t, undef);
2491     }
2492    
2493     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2494     if ($t->{type} == COMMA_TOKEN) {
2495     $t = $tt->get_next_token;
2496     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2497     redo F;
2498     }
2499     } # F
2500    
2501     return ($t, {$prop_name => \@prop_value});
2502     },
2503     serialize => sub {
2504     my ($self, $prop_name, $value) = @_;
2505    
2506     if ($value->[0] eq 'CURSOR') {
2507     return join ', ', map {
2508     if ($_->[0] eq 'URI') {
2509     'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
2510     } elsif ($_->[0] eq 'KEYWORD') {
2511     $_->[1];
2512     } else {
2513     ## NOTE: This should be an error.
2514     '""';
2515     }
2516     } @$value[1..$#$value];
2517     } elsif ($value->[0] eq 'INHERIT') {
2518     return 'inherit';
2519     } else {
2520     return undef;
2521     }
2522     },
2523     keyword => {
2524     auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
2525     'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
2526     'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
2527     'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
2528     },
2529     initial => ['CURSOR', ['KEYWORD', 'auto']],
2530     inherited => 1,
2531     compute => sub {
2532     my ($self, $element, $prop_name, $specified_value) = @_;
2533    
2534     if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
2535     my @new_value = ('CURSOR');
2536     for my $value (@$specified_value[1..$#$specified_value]) {
2537     if ($value->[0] eq 'URI') {
2538     if (defined $value->[2]) {
2539     require Message::DOM::DOMImplementation;
2540     push @new_value, ['URI',
2541     Message::DOM::DOMImplementation
2542     ->create_uri_reference ($value->[1])
2543     ->get_absolute_reference (${$value->[2]})
2544     ->get_uri_reference,
2545     $value->[2]];
2546     } else {
2547     push @new_value, $value;
2548     }
2549     } else {
2550     push @new_value, $value;
2551     }
2552     }
2553     return \@new_value;
2554     }
2555    
2556     return $specified_value;
2557     },
2558     };
2559     $Attr->{cursor} = $Prop->{cursor};
2560     $Key->{cursor} = $Prop->{cursor};
2561    
2562 wakaba 1.7 $Prop->{'border-style'} = {
2563     css => 'border-style',
2564     dom => 'border_style',
2565     parse => sub {
2566     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2567    
2568     my %prop_value;
2569     if ($t->{type} == IDENT_TOKEN) {
2570     my $prop_value = lc $t->{value}; ## TODO: case folding
2571     $t = $tt->get_next_token;
2572     if ($border_style_keyword->{$prop_value} and
2573     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
2574     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
2575     } elsif ($prop_value eq 'inherit') {
2576 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
2577 wakaba 1.19 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2578     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2579     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2580     return ($t, \%prop_value);
2581 wakaba 1.7 } else {
2582     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2583     level => $self->{must_level},
2584     token => $t);
2585     return ($t, undef);
2586     }
2587     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2588     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2589     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2590     } else {
2591     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2592     level => $self->{must_level},
2593     token => $t);
2594     return ($t, undef);
2595     }
2596    
2597     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2598     if ($t->{type} == IDENT_TOKEN) {
2599     my $prop_value = lc $t->{value}; ## TODO: case folding
2600     $t = $tt->get_next_token;
2601 wakaba 1.19 if ($border_style_keyword->{$prop_value} and
2602 wakaba 1.7 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
2603     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
2604     } else {
2605     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2606     level => $self->{must_level},
2607     token => $t);
2608     return ($t, undef);
2609     }
2610     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2611    
2612     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2613     if ($t->{type} == IDENT_TOKEN) {
2614     my $prop_value = lc $t->{value}; ## TODO: case folding
2615     $t = $tt->get_next_token;
2616     if ($border_style_keyword->{$prop_value} and
2617     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
2618     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
2619     } else {
2620     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2621     level => $self->{must_level},
2622     token => $t);
2623     return ($t, undef);
2624     }
2625    
2626     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2627     if ($t->{type} == IDENT_TOKEN) {
2628     my $prop_value = lc $t->{value}; ## TODO: case folding
2629     $t = $tt->get_next_token;
2630     if ($border_style_keyword->{$prop_value} and
2631     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
2632     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
2633     } else {
2634     $onerror->(type => 'syntax error:keyword:'.$prop_name,
2635     level => $self->{must_level},
2636     token => $t);
2637     return ($t, undef);
2638     }
2639     }
2640     }
2641     }
2642    
2643     return ($t, \%prop_value);
2644     },
2645     serialize => sub {
2646     my ($self, $prop_name, $value) = @_;
2647    
2648     local $Error::Depth = $Error::Depth + 1;
2649     my @v;
2650     push @v, $self->border_top_style;
2651     return undef unless defined $v[-1];
2652     push @v, $self->border_right_style;
2653     return undef unless defined $v[-1];
2654     push @v, $self->border_bottom_style;
2655     return undef unless defined $v[-1];
2656 wakaba 1.19 push @v, $self->border_left_style;
2657 wakaba 1.7 return undef unless defined $v[-1];
2658    
2659     pop @v if $v[1] eq $v[3];
2660     pop @v if $v[0] eq $v[2];
2661     pop @v if $v[0] eq $v[1];
2662     return join ' ', @v;
2663     },
2664     };
2665     $Attr->{border_style} = $Prop->{'border-style'};
2666    
2667 wakaba 1.19 $Prop->{margin} = {
2668     css => 'margin',
2669     dom => 'margin',
2670     parse => sub {
2671     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2672    
2673     my %prop_value;
2674    
2675     my $sign = 1;
2676     if ($t->{type} == MINUS_TOKEN) {
2677     $t = $tt->get_next_token;
2678     $sign = -1;
2679     }
2680    
2681     if ($t->{type} == DIMENSION_TOKEN) {
2682     my $value = $t->{number} * $sign;
2683     my $unit = lc $t->{value}; ## TODO: case
2684     $t = $tt->get_next_token;
2685     if ($length_unit->{$unit}) {
2686     $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
2687     } else {
2688     $onerror->(type => 'syntax error:'.$prop_name,
2689     level => $self->{must_level},
2690     token => $t);
2691     return ($t, undef);
2692     }
2693     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2694     my $value = $t->{number} * $sign;
2695     $t = $tt->get_next_token;
2696     $prop_value{'margin-top'} = ['PERCENTAGE', $value];
2697 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2698 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2699     my $value = $t->{number} * $sign;
2700     $t = $tt->get_next_token;
2701     $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
2702     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2703     my $prop_value = lc $t->{value}; ## TODO: case folding
2704     $t = $tt->get_next_token;
2705     if ($prop_value eq 'auto') {
2706     $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
2707     } elsif ($prop_value eq 'inherit') {
2708     $prop_value{'margin-top'} = ['INHERIT'];
2709     $prop_value{'margin-right'} = $prop_value{'margin-top'};
2710     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2711     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2712     return ($t, \%prop_value);
2713     } else {
2714     $onerror->(type => 'syntax error:'.$prop_name,
2715     level => $self->{must_level},
2716     token => $t);
2717     return ($t, undef);
2718     }
2719     } else {
2720     $onerror->(type => 'syntax error:'.$prop_name,
2721     level => $self->{must_level},
2722     token => $t);
2723     return ($t, undef);
2724     }
2725     $prop_value{'margin-right'} = $prop_value{'margin-top'};
2726     $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2727     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2728    
2729     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2730     $sign = 1;
2731     if ($t->{type} == MINUS_TOKEN) {
2732     $t = $tt->get_next_token;
2733     $sign = -1;
2734     }
2735    
2736     if ($t->{type} == DIMENSION_TOKEN) {
2737     my $value = $t->{number} * $sign;
2738     my $unit = lc $t->{value}; ## TODO: case
2739     $t = $tt->get_next_token;
2740     if ($length_unit->{$unit}) {
2741     $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
2742     } else {
2743     $onerror->(type => 'syntax error:'.$prop_name,
2744     level => $self->{must_level},
2745     token => $t);
2746     return ($t, undef);
2747     }
2748     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2749     my $value = $t->{number} * $sign;
2750     $t = $tt->get_next_token;
2751     $prop_value{'margin-right'} = ['PERCENTAGE', $value];
2752 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2753 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2754     my $value = $t->{number} * $sign;
2755     $t = $tt->get_next_token;
2756     $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
2757     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2758     my $prop_value = lc $t->{value}; ## TODO: case folding
2759     $t = $tt->get_next_token;
2760     if ($prop_value eq 'auto') {
2761     $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
2762     } else {
2763     $onerror->(type => 'syntax error:'.$prop_name,
2764     level => $self->{must_level},
2765     token => $t);
2766     return ($t, undef);
2767     }
2768     } else {
2769 wakaba 1.24 if ($sign < 0) {
2770     $onerror->(type => 'syntax error:'.$prop_name,
2771     level => $self->{must_level},
2772     token => $t);
2773     return ($t, undef);
2774     }
2775 wakaba 1.19 return ($t, \%prop_value);
2776     }
2777     $prop_value{'margin-left'} = $prop_value{'margin-right'};
2778    
2779     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2780     $sign = 1;
2781     if ($t->{type} == MINUS_TOKEN) {
2782     $t = $tt->get_next_token;
2783     $sign = -1;
2784     }
2785    
2786     if ($t->{type} == DIMENSION_TOKEN) {
2787     my $value = $t->{number} * $sign;
2788     my $unit = lc $t->{value}; ## TODO: case
2789     $t = $tt->get_next_token;
2790     if ($length_unit->{$unit}) {
2791     $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
2792     } else {
2793     $onerror->(type => 'syntax error:'.$prop_name,
2794     level => $self->{must_level},
2795     token => $t);
2796     return ($t, undef);
2797     }
2798     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2799     my $value = $t->{number} * $sign;
2800     $t = $tt->get_next_token;
2801     $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
2802 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2803 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2804     my $value = $t->{number} * $sign;
2805     $t = $tt->get_next_token;
2806     $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
2807     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2808     my $prop_value = lc $t->{value}; ## TODO: case folding
2809     $t = $tt->get_next_token;
2810     if ($prop_value eq 'auto') {
2811     $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
2812     } else {
2813     $onerror->(type => 'syntax error:'.$prop_name,
2814     level => $self->{must_level},
2815     token => $t);
2816     return ($t, undef);
2817     }
2818     } else {
2819 wakaba 1.24 if ($sign < 0) {
2820     $onerror->(type => 'syntax error:'.$prop_name,
2821     level => $self->{must_level},
2822     token => $t);
2823     return ($t, undef);
2824     }
2825 wakaba 1.19 return ($t, \%prop_value);
2826     }
2827    
2828     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2829     $sign = 1;
2830     if ($t->{type} == MINUS_TOKEN) {
2831     $t = $tt->get_next_token;
2832     $sign = -1;
2833     }
2834    
2835     if ($t->{type} == DIMENSION_TOKEN) {
2836     my $value = $t->{number} * $sign;
2837     my $unit = lc $t->{value}; ## TODO: case
2838     $t = $tt->get_next_token;
2839     if ($length_unit->{$unit}) {
2840     $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
2841     } else {
2842     $onerror->(type => 'syntax error:'.$prop_name,
2843     level => $self->{must_level},
2844     token => $t);
2845     return ($t, undef);
2846     }
2847     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2848     my $value = $t->{number} * $sign;
2849     $t = $tt->get_next_token;
2850     $prop_value{'margin-left'} = ['PERCENTAGE', $value];
2851 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2852 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2853     my $value = $t->{number} * $sign;
2854     $t = $tt->get_next_token;
2855     $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
2856     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2857     my $prop_value = lc $t->{value}; ## TODO: case folding
2858     $t = $tt->get_next_token;
2859     if ($prop_value eq 'auto') {
2860     $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
2861     } else {
2862     $onerror->(type => 'syntax error:'.$prop_name,
2863     level => $self->{must_level},
2864     token => $t);
2865     return ($t, undef);
2866     }
2867     } else {
2868 wakaba 1.24 if ($sign < 0) {
2869     $onerror->(type => 'syntax error:'.$prop_name,
2870     level => $self->{must_level},
2871     token => $t);
2872     return ($t, undef);
2873     }
2874 wakaba 1.19 return ($t, \%prop_value);
2875     }
2876    
2877     return ($t, \%prop_value);
2878     },
2879     serialize => sub {
2880     my ($self, $prop_name, $value) = @_;
2881    
2882     local $Error::Depth = $Error::Depth + 1;
2883     my @v;
2884     push @v, $self->margin_top;
2885     return undef unless defined $v[-1];
2886     push @v, $self->margin_right;
2887     return undef unless defined $v[-1];
2888     push @v, $self->margin_bottom;
2889     return undef unless defined $v[-1];
2890     push @v, $self->margin_left;
2891     return undef unless defined $v[-1];
2892    
2893     pop @v if $v[1] eq $v[3];
2894     pop @v if $v[0] eq $v[2];
2895     pop @v if $v[0] eq $v[1];
2896     return join ' ', @v;
2897     },
2898     };
2899     $Attr->{margin} = $Prop->{margin};
2900    
2901     $Prop->{padding} = {
2902     css => 'padding',
2903     dom => 'padding',
2904     parse => sub {
2905     my ($self, $prop_name, $tt, $t, $onerror) = @_;
2906    
2907     my %prop_value;
2908    
2909     my $sign = 1;
2910     if ($t->{type} == MINUS_TOKEN) {
2911     $t = $tt->get_next_token;
2912     $sign = -1;
2913     }
2914    
2915     if ($t->{type} == DIMENSION_TOKEN) {
2916     my $value = $t->{number} * $sign;
2917     my $unit = lc $t->{value}; ## TODO: case
2918     $t = $tt->get_next_token;
2919     if ($length_unit->{$unit} and $value >= 0) {
2920     $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
2921     } else {
2922     $onerror->(type => 'syntax error:'.$prop_name,
2923     level => $self->{must_level},
2924     token => $t);
2925     return ($t, undef);
2926     }
2927     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2928     my $value = $t->{number} * $sign;
2929     $t = $tt->get_next_token;
2930     $prop_value{'padding-top'} = ['PERCENTAGE', $value];
2931     unless ($value >= 0) {
2932     $onerror->(type => 'syntax error:'.$prop_name,
2933     level => $self->{must_level},
2934     token => $t);
2935     return ($t, undef);
2936     }
2937 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
2938 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
2939     my $value = $t->{number} * $sign;
2940     $t = $tt->get_next_token;
2941     $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
2942     unless ($value >= 0) {
2943     $onerror->(type => 'syntax error:'.$prop_name,
2944     level => $self->{must_level},
2945     token => $t);
2946     return ($t, undef);
2947     }
2948     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2949     my $prop_value = lc $t->{value}; ## TODO: case folding
2950     $t = $tt->get_next_token;
2951     if ($prop_value eq 'inherit') {
2952     $prop_value{'padding-top'} = ['INHERIT'];
2953     $prop_value{'padding-right'} = $prop_value{'padding-top'};
2954     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2955     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2956     return ($t, \%prop_value);
2957     } else {
2958     $onerror->(type => 'syntax error:'.$prop_name,
2959     level => $self->{must_level},
2960     token => $t);
2961     return ($t, undef);
2962     }
2963     } else {
2964     $onerror->(type => 'syntax error:'.$prop_name,
2965     level => $self->{must_level},
2966     token => $t);
2967     return ($t, undef);
2968     }
2969     $prop_value{'padding-right'} = $prop_value{'padding-top'};
2970     $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2971     $prop_value{'padding-left'} = $prop_value{'padding-right'};
2972    
2973     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2974     $sign = 1;
2975     if ($t->{type} == MINUS_TOKEN) {
2976     $t = $tt->get_next_token;
2977     $sign = -1;
2978     }
2979    
2980     if ($t->{type} == DIMENSION_TOKEN) {
2981     my $value = $t->{number} * $sign;
2982     my $unit = lc $t->{value}; ## TODO: case
2983     $t = $tt->get_next_token;
2984     if ($length_unit->{$unit} and $value >= 0) {
2985     $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
2986     } else {
2987     $onerror->(type => 'syntax error:'.$prop_name,
2988     level => $self->{must_level},
2989     token => $t);
2990     return ($t, undef);
2991     }
2992     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2993     my $value = $t->{number} * $sign;
2994     $t = $tt->get_next_token;
2995     $prop_value{'padding-right'} = ['PERCENTAGE', $value];
2996     unless ($value >= 0) {
2997     $onerror->(type => 'syntax error:'.$prop_name,
2998     level => $self->{must_level},
2999     token => $t);
3000     return ($t, undef);
3001     }
3002 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3003 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3004     my $value = $t->{number} * $sign;
3005     $t = $tt->get_next_token;
3006     $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
3007     unless ($value >= 0) {
3008     $onerror->(type => 'syntax error:'.$prop_name,
3009     level => $self->{must_level},
3010     token => $t);
3011     return ($t, undef);
3012     }
3013     } else {
3014 wakaba 1.24 if ($sign < 0) {
3015     $onerror->(type => 'syntax error:'.$prop_name,
3016     level => $self->{must_level},
3017     token => $t);
3018     return ($t, undef);
3019     }
3020 wakaba 1.19 return ($t, \%prop_value);
3021     }
3022     $prop_value{'padding-left'} = $prop_value{'padding-right'};
3023    
3024     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3025     $sign = 1;
3026     if ($t->{type} == MINUS_TOKEN) {
3027     $t = $tt->get_next_token;
3028     $sign = -1;
3029     }
3030    
3031     if ($t->{type} == DIMENSION_TOKEN) {
3032     my $value = $t->{number} * $sign;
3033     my $unit = lc $t->{value}; ## TODO: case
3034     $t = $tt->get_next_token;
3035     if ($length_unit->{$unit} and $value >= 0) {
3036     $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
3037     } else {
3038     $onerror->(type => 'syntax error:'.$prop_name,
3039     level => $self->{must_level},
3040     token => $t);
3041     return ($t, undef);
3042     }
3043     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3044     my $value = $t->{number} * $sign;
3045     $t = $tt->get_next_token;
3046     $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
3047     unless ($value >= 0) {
3048     $onerror->(type => 'syntax error:'.$prop_name,
3049     level => $self->{must_level},
3050     token => $t);
3051     return ($t, undef);
3052     }
3053 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3054 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3055     my $value = $t->{number} * $sign;
3056     $t = $tt->get_next_token;
3057     $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
3058     unless ($value >= 0) {
3059     $onerror->(type => 'syntax error:'.$prop_name,
3060     level => $self->{must_level},
3061     token => $t);
3062     return ($t, undef);
3063     }
3064     } else {
3065 wakaba 1.24 if ($sign < 0) {
3066     $onerror->(type => 'syntax error:'.$prop_name,
3067     level => $self->{must_level},
3068     token => $t);
3069     return ($t, undef);
3070     }
3071 wakaba 1.19 return ($t, \%prop_value);
3072     }
3073    
3074     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3075     $sign = 1;
3076     if ($t->{type} == MINUS_TOKEN) {
3077     $t = $tt->get_next_token;
3078     $sign = -1;
3079     }
3080    
3081     if ($t->{type} == DIMENSION_TOKEN) {
3082     my $value = $t->{number} * $sign;
3083     my $unit = lc $t->{value}; ## TODO: case
3084     $t = $tt->get_next_token;
3085     if ($length_unit->{$unit} and $value >= 0) {
3086     $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
3087     } else {
3088     $onerror->(type => 'syntax error:'.$prop_name,
3089     level => $self->{must_level},
3090     token => $t);
3091     return ($t, undef);
3092     }
3093     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3094     my $value = $t->{number} * $sign;
3095     $t = $tt->get_next_token;
3096     $prop_value{'padding-left'} = ['PERCENTAGE', $value];
3097     unless ($value >= 0) {
3098     $onerror->(type => 'syntax error:'.$prop_name,
3099     level => $self->{must_level},
3100     token => $t);
3101     return ($t, undef);
3102     }
3103 wakaba 1.20 } elsif ($t->{type} == NUMBER_TOKEN and
3104 wakaba 1.19 ($self->{unitless_px} or $t->{number} == 0)) {
3105     my $value = $t->{number} * $sign;
3106     $t = $tt->get_next_token;
3107     $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
3108     unless ($value >= 0) {
3109     $onerror->(type => 'syntax error:'.$prop_name,
3110     level => $self->{must_level},
3111     token => $t);
3112     return ($t, undef);
3113     }
3114     } else {
3115 wakaba 1.24 if ($sign < 0) {
3116     $onerror->(type => 'syntax error:'.$prop_name,
3117     level => $self->{must_level},
3118     token => $t);
3119     return ($t, undef);
3120     }
3121 wakaba 1.19 return ($t, \%prop_value);
3122     }
3123    
3124     return ($t, \%prop_value);
3125     },
3126     serialize => sub {
3127     my ($self, $prop_name, $value) = @_;
3128    
3129     local $Error::Depth = $Error::Depth + 1;
3130     my @v;
3131     push @v, $self->padding_top;
3132     return undef unless defined $v[-1];
3133     push @v, $self->padding_right;
3134     return undef unless defined $v[-1];
3135     push @v, $self->padding_bottom;
3136     return undef unless defined $v[-1];
3137     push @v, $self->padding_left;
3138     return undef unless defined $v[-1];
3139    
3140     pop @v if $v[1] eq $v[3];
3141     pop @v if $v[0] eq $v[2];
3142     pop @v if $v[0] eq $v[1];
3143     return join ' ', @v;
3144     },
3145     };
3146     $Attr->{padding} = $Prop->{padding};
3147    
3148 wakaba 1.24 $Prop->{'border-spacing'} = {
3149     css => 'border-spacing',
3150     dom => 'border_spacing',
3151     parse => sub {
3152     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3153    
3154     my %prop_value;
3155    
3156     my $sign = 1;
3157     if ($t->{type} == MINUS_TOKEN) {
3158     $t = $tt->get_next_token;
3159     $sign = -1;
3160     }
3161    
3162     if ($t->{type} == DIMENSION_TOKEN) {
3163     my $value = $t->{number} * $sign;
3164     my $unit = lc $t->{value}; ## TODO: case
3165     $t = $tt->get_next_token;
3166     if ($length_unit->{$unit} and $value >= 0) {
3167     $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, $unit];
3168     } else {
3169     $onerror->(type => 'syntax error:'.$prop_name,
3170     level => $self->{must_level},
3171     token => $t);
3172     return ($t, undef);
3173     }
3174     } elsif ($t->{type} == NUMBER_TOKEN and
3175     ($self->{unitless_px} or $t->{number} == 0)) {
3176     my $value = $t->{number} * $sign;
3177     $t = $tt->get_next_token;
3178     $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, 'px'];
3179     unless ($value >= 0) {
3180     $onerror->(type => 'syntax error:'.$prop_name,
3181     level => $self->{must_level},
3182     token => $t);
3183     return ($t, undef);
3184     }
3185     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3186     my $prop_value = lc $t->{value}; ## TODO: case folding
3187     $t = $tt->get_next_token;
3188     if ($prop_value eq 'inherit') {
3189     $prop_value{'-manakai-border-spacing-x'} = ['INHERIT'];
3190     $prop_value{'-manakai-border-spacing-y'}
3191     = $prop_value{'-manakai-border-spacing-x'};
3192     return ($t, \%prop_value);
3193     } else {
3194     $onerror->(type => 'syntax error:'.$prop_name,
3195     level => $self->{must_level},
3196     token => $t);
3197     return ($t, undef);
3198     }
3199     } else {
3200     $onerror->(type => 'syntax error:'.$prop_name,
3201     level => $self->{must_level},
3202     token => $t);
3203     return ($t, undef);
3204     }
3205     $prop_value{'-manakai-border-spacing-y'}
3206     = $prop_value{'-manakai-border-spacing-x'};
3207    
3208     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3209     $sign = 1;
3210     if ($t->{type} == MINUS_TOKEN) {
3211     $t = $tt->get_next_token;
3212     $sign = -1;
3213     }
3214    
3215     if ($t->{type} == DIMENSION_TOKEN) {
3216     my $value = $t->{number} * $sign;
3217     my $unit = lc $t->{value}; ## TODO: case
3218     $t = $tt->get_next_token;
3219     if ($length_unit->{$unit} and $value >= 0) {
3220     $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, $unit];
3221     } else {
3222     $onerror->(type => 'syntax error:'.$prop_name,
3223     level => $self->{must_level},
3224     token => $t);
3225     return ($t, undef);
3226     }
3227     } elsif ($t->{type} == NUMBER_TOKEN and
3228     ($self->{unitless_px} or $t->{number} == 0)) {
3229     my $value = $t->{number} * $sign;
3230     $t = $tt->get_next_token;
3231     $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, 'px'];
3232     unless ($value >= 0) {
3233     $onerror->(type => 'syntax error:'.$prop_name,
3234     level => $self->{must_level},
3235     token => $t);
3236     return ($t, undef);
3237     }
3238     } else {
3239     if ($sign < 0) {
3240     $onerror->(type => 'syntax error:'.$prop_name,
3241     level => $self->{must_level},
3242     token => $t);
3243     return ($t, undef);
3244     }
3245     return ($t, \%prop_value);
3246     }
3247    
3248     return ($t, \%prop_value);
3249     },
3250     serialize => sub {
3251     my ($self, $prop_name, $value) = @_;
3252    
3253     local $Error::Depth = $Error::Depth + 1;
3254     my @v;
3255     push @v, $self->_manakai_border_spacing_x;
3256     return undef unless defined $v[-1];
3257     push @v, $self->_manakai_border_spacing_y;
3258     return undef unless defined $v[-1];
3259    
3260     pop @v if $v[0] eq $v[1];
3261     return join ' ', @v;
3262     },
3263 wakaba 1.25 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
3264     ->{serialize_multiple},
3265 wakaba 1.24 };
3266     $Attr->{border_spacing} = $Prop->{'border-spacing'};
3267    
3268 wakaba 1.27 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/background-position> for
3269     ## browser compatibility problems.
3270     $Prop->{'background-position'} = {
3271     css => 'background-position',
3272     dom => 'background_position',
3273     parse => sub {
3274     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3275    
3276     my %prop_value;
3277    
3278     my $sign = 1;
3279     if ($t->{type} == MINUS_TOKEN) {
3280     $t = $tt->get_next_token;
3281     $sign = -1;
3282     }
3283    
3284     if ($t->{type} == DIMENSION_TOKEN) {
3285     my $value = $t->{number} * $sign;
3286     my $unit = lc $t->{value}; ## TODO: case
3287     $t = $tt->get_next_token;
3288     if ($length_unit->{$unit}) {
3289     $prop_value{'background-position-x'} = ['DIMENSION', $value, $unit];
3290     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
3291     } else {
3292     $onerror->(type => 'syntax error:'.$prop_name,
3293     level => $self->{must_level},
3294     token => $t);
3295     return ($t, undef);
3296     }
3297     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3298     my $value = $t->{number} * $sign;
3299     $t = $tt->get_next_token;
3300     $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
3301     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
3302     } elsif ($t->{type} == NUMBER_TOKEN and
3303     ($self->{unitless_px} or $t->{number} == 0)) {
3304     my $value = $t->{number} * $sign;
3305     $t = $tt->get_next_token;
3306     $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
3307     $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
3308     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3309     my $prop_value = lc $t->{value}; ## TODO: case folding
3310     $t = $tt->get_next_token;
3311     if ({left => 1, center => 1, right => 1}->{$prop_value}) {
3312     $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
3313     $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
3314     } elsif ($prop_value eq 'top' or $prop_value eq 'bottom') {
3315     $prop_value{'background-position-y'} = ['KEYWORD', $prop_value];
3316    
3317     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3318     if ($t->{type} == IDENT_TOKEN) {
3319     my $prop_value = lc $t->{value}; ## TODO: case folding
3320     if ({left => 1, center => 1, right => 1}->{$prop_value}) {
3321     $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
3322     $t = $tt->get_next_token;
3323     return ($t, \%prop_value);
3324     }
3325     }
3326     $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
3327     return ($t, \%prop_value);
3328     } elsif ($prop_value eq 'inherit') {
3329     $prop_value{'background-position-x'} = ['INHERIT'];
3330     $prop_value{'background-position-y'} = ['INHERIT'];
3331     return ($t, \%prop_value);
3332     } else {
3333     $onerror->(type => 'syntax error:'.$prop_name,
3334     level => $self->{must_level},
3335     token => $t);
3336     return ($t, undef);
3337     }
3338     } else {
3339     $onerror->(type => 'syntax error:'.$prop_name,
3340     level => $self->{must_level},
3341     token => $t);
3342     return ($t, undef);
3343     }
3344    
3345     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3346     $sign = 1;
3347     if ($t->{type} == MINUS_TOKEN) {
3348     $t = $tt->get_next_token;
3349     $sign = -1;
3350     }
3351    
3352     if ($t->{type} == DIMENSION_TOKEN) {
3353     my $value = $t->{number} * $sign;
3354     my $unit = lc $t->{value}; ## TODO: case
3355     $t = $tt->get_next_token;
3356     if ($length_unit->{$unit}) {
3357     $prop_value{'background-position-y'} = ['DIMENSION', $value, $unit];
3358     } else {
3359     $onerror->(type => 'syntax error:'.$prop_name,
3360     level => $self->{must_level},
3361     token => $t);
3362     return ($t, undef);
3363     }
3364     } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3365     my $value = $t->{number} * $sign;
3366     $t = $tt->get_next_token;
3367     $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
3368     } elsif ($t->{type} == NUMBER_TOKEN and $self->{unitless_px}) {
3369     my $value = $t->{number} * $sign;
3370     $t = $tt->get_next_token;
3371     $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
3372     } elsif ($t->{type} == IDENT_TOKEN) {
3373     my $value = lc $t->{value}; ## TODO: case
3374     if ({top => 1, center => 1, bottom => 1}->{$value}) {
3375     $prop_value{'background-position-y'} = ['KEYWORD', $value];
3376     $t = $tt->get_next_token;
3377     }
3378     } else {
3379     if ($sign < 0) {
3380     $onerror->(type => 'syntax error:'.$prop_name,
3381     level => $self->{must_level},
3382     token => $t);
3383     return ($t, undef);
3384     }
3385     return ($t, \%prop_value);
3386     }
3387    
3388     return ($t, \%prop_value);
3389     },
3390     serialize => sub {
3391     my ($self, $prop_name, $value) = @_;
3392    
3393     local $Error::Depth = $Error::Depth + 1;
3394     my $x = $self->background_position_x;
3395     my $y = $self->background_position_y;
3396     return $x . ' ' . $y if defined $x and defined $y;
3397     return undef;
3398     },
3399     serialize_multiple => $Prop->{'background-position-x'}
3400     ->{serialize_multiple},
3401     };
3402     $Attr->{background_position} = $Prop->{'background-position'};
3403    
3404 wakaba 1.20 $Prop->{'border-width'} = {
3405     css => 'border-width',
3406     dom => 'border_width',
3407     parse => sub {
3408     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3409    
3410     my %prop_value;
3411    
3412     my $sign = 1;
3413     if ($t->{type} == MINUS_TOKEN) {
3414     $t = $tt->get_next_token;
3415     $sign = -1;
3416     }
3417    
3418     if ($t->{type} == DIMENSION_TOKEN) {
3419     my $value = $t->{number} * $sign;
3420     my $unit = lc $t->{value}; ## TODO: case
3421     $t = $tt->get_next_token;
3422     if ($length_unit->{$unit} and $value >= 0) {
3423     $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
3424     } else {
3425     $onerror->(type => 'syntax error:'.$prop_name,
3426     level => $self->{must_level},
3427     token => $t);
3428     return ($t, undef);
3429     }
3430     } elsif ($t->{type} == NUMBER_TOKEN and
3431     ($self->{unitless_px} or $t->{number} == 0)) {
3432     my $value = $t->{number} * $sign;
3433     $t = $tt->get_next_token;
3434     $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
3435     unless ($value >= 0) {
3436     $onerror->(type => 'syntax error:'.$prop_name,
3437     level => $self->{must_level},
3438     token => $t);
3439     return ($t, undef);
3440     }
3441     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3442     my $prop_value = lc $t->{value}; ## TODO: case folding
3443     $t = $tt->get_next_token;
3444     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3445     $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
3446     } elsif ($prop_value eq 'inherit') {
3447     $prop_value{'border-top-width'} = ['INHERIT'];
3448     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
3449     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
3450     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3451     return ($t, \%prop_value);
3452     } else {
3453     $onerror->(type => 'syntax error:'.$prop_name,
3454     level => $self->{must_level},
3455     token => $t);
3456     return ($t, undef);
3457     }
3458     } else {
3459     $onerror->(type => 'syntax error:'.$prop_name,
3460     level => $self->{must_level},
3461     token => $t);
3462     return ($t, undef);
3463     }
3464     $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
3465     $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
3466     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3467    
3468     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3469     $sign = 1;
3470     if ($t->{type} == MINUS_TOKEN) {
3471     $t = $tt->get_next_token;
3472     $sign = -1;
3473     }
3474    
3475     if ($t->{type} == DIMENSION_TOKEN) {
3476     my $value = $t->{number} * $sign;
3477     my $unit = lc $t->{value}; ## TODO: case
3478     $t = $tt->get_next_token;
3479     if ($length_unit->{$unit} and $value >= 0) {
3480     $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
3481     } else {
3482     $onerror->(type => 'syntax error:'.$prop_name,
3483     level => $self->{must_level},
3484     token => $t);
3485     return ($t, undef);
3486     }
3487     } elsif ($t->{type} == NUMBER_TOKEN and
3488     ($self->{unitless_px} or $t->{number} == 0)) {
3489     my $value = $t->{number} * $sign;
3490     $t = $tt->get_next_token;
3491     $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
3492     unless ($value >= 0) {
3493     $onerror->(type => 'syntax error:'.$prop_name,
3494     level => $self->{must_level},
3495     token => $t);
3496     return ($t, undef);
3497     }
3498     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3499     my $prop_value = lc $t->{value}; ## TODO: case
3500     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3501     $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
3502     $t = $tt->get_next_token;
3503     }
3504     } else {
3505 wakaba 1.24 if ($sign < 0) {
3506     $onerror->(type => 'syntax error:'.$prop_name,
3507     level => $self->{must_level},
3508     token => $t);
3509     return ($t, undef);
3510     }
3511 wakaba 1.20 return ($t, \%prop_value);
3512     }
3513     $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3514    
3515     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3516     $sign = 1;
3517     if ($t->{type} == MINUS_TOKEN) {
3518     $t = $tt->get_next_token;
3519     $sign = -1;
3520     }
3521    
3522     if ($t->{type} == DIMENSION_TOKEN) {
3523     my $value = $t->{number} * $sign;
3524     my $unit = lc $t->{value}; ## TODO: case
3525     $t = $tt->get_next_token;
3526     if ($length_unit->{$unit} and $value >= 0) {
3527     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
3528     } else {
3529     $onerror->(type => 'syntax error:'.$prop_name,
3530     level => $self->{must_level},
3531     token => $t);
3532     return ($t, undef);
3533     }
3534     } elsif ($t->{type} == NUMBER_TOKEN and
3535     ($self->{unitless_px} or $t->{number} == 0)) {
3536     my $value = $t->{number} * $sign;
3537     $t = $tt->get_next_token;
3538     $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
3539     unless ($value >= 0) {
3540     $onerror->(type => 'syntax error:'.$prop_name,
3541     level => $self->{must_level},
3542     token => $t);
3543     return ($t, undef);
3544     }
3545     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3546     my $prop_value = lc $t->{value}; ## TODO: case
3547     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3548     $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
3549     $t = $tt->get_next_token;
3550     }
3551     } else {
3552 wakaba 1.24 if ($sign < 0) {
3553     $onerror->(type => 'syntax error:'.$prop_name,
3554     level => $self->{must_level},
3555     token => $t);
3556     return ($t, undef);
3557     }
3558 wakaba 1.20 return ($t, \%prop_value);
3559     }
3560    
3561     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3562     $sign = 1;
3563     if ($t->{type} == MINUS_TOKEN) {
3564     $t = $tt->get_next_token;
3565     $sign = -1;
3566     }
3567    
3568     if ($t->{type} == DIMENSION_TOKEN) {
3569     my $value = $t->{number} * $sign;
3570     my $unit = lc $t->{value}; ## TODO: case
3571     $t = $tt->get_next_token;
3572     if ($length_unit->{$unit} and $value >= 0) {
3573     $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
3574     } else {
3575     $onerror->(type => 'syntax error:'.$prop_name,
3576     level => $self->{must_level},
3577     token => $t);
3578     return ($t, undef);
3579     }
3580     } elsif ($t->{type} == NUMBER_TOKEN and
3581     ($self->{unitless_px} or $t->{number} == 0)) {
3582     my $value = $t->{number} * $sign;
3583     $t = $tt->get_next_token;
3584     $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
3585     unless ($value >= 0) {
3586     $onerror->(type => 'syntax error:'.$prop_name,
3587     level => $self->{must_level},
3588     token => $t);
3589     return ($t, undef);
3590     }
3591     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3592     my $prop_value = lc $t->{value}; ## TODO: case
3593     if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3594     $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
3595     $t = $tt->get_next_token;
3596     }
3597     } else {
3598 wakaba 1.24 if ($sign < 0) {
3599     $onerror->(type => 'syntax error:'.$prop_name,
3600     level => $self->{must_level},
3601     token => $t);
3602     return ($t, undef);
3603     }
3604 wakaba 1.20 return ($t, \%prop_value);
3605     }
3606    
3607     return ($t, \%prop_value);
3608     },
3609     serialize => sub {
3610     my ($self, $prop_name, $value) = @_;
3611    
3612     local $Error::Depth = $Error::Depth + 1;
3613     my @v;
3614 wakaba 1.24 push @v, $self->border_top_width;
3615 wakaba 1.20 return undef unless defined $v[-1];
3616 wakaba 1.24 push @v, $self->border_right_width;
3617 wakaba 1.20 return undef unless defined $v[-1];
3618 wakaba 1.24 push @v, $self->border_bottom_width;
3619 wakaba 1.20 return undef unless defined $v[-1];
3620 wakaba 1.24 push @v, $self->border_left_width;
3621 wakaba 1.20 return undef unless defined $v[-1];
3622    
3623     pop @v if $v[1] eq $v[3];
3624     pop @v if $v[0] eq $v[2];
3625     pop @v if $v[0] eq $v[1];
3626     return join ' ', @v;
3627     },
3628     };
3629 wakaba 1.24 $Attr->{border_width} = $Prop->{'border-width'};
3630 wakaba 1.20
3631 wakaba 1.12 $Prop->{'list-style'} = {
3632     css => 'list-style',
3633     dom => 'list_style',
3634     parse => sub {
3635     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3636    
3637     my %prop_value;
3638     my $none = 0;
3639    
3640     F: for my $f (1..3) {
3641     if ($t->{type} == IDENT_TOKEN) {
3642     my $prop_value = lc $t->{value}; ## TODO: case folding
3643     $t = $tt->get_next_token;
3644    
3645     if ($prop_value eq 'none') {
3646     $none++;
3647     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
3648     if (exists $prop_value{'list-style-type'}) {
3649     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3650     $prop_name,
3651     level => $self->{must_level},
3652     token => $t);
3653     return ($t, undef);
3654     } else {
3655     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
3656     }
3657     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
3658     if (exists $prop_value{'list-style-position'}) {
3659     $onerror->(type => q[syntax error:duplicate:'list-style-position':].
3660     $prop_name,
3661     level => $self->{must_level},
3662     token => $t);
3663     return ($t, undef);
3664     }
3665    
3666     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
3667     } elsif ($f == 1 and $prop_value eq 'inherit') {
3668     $prop_value{'list-style-type'} = ["INHERIT"];
3669     $prop_value{'list-style-position'} = ["INHERIT"];
3670     $prop_value{'list-style-image'} = ["INHERIT"];
3671     last F;
3672     } else {
3673     if ($f == 1) {
3674     $onerror->(type => 'syntax error:'.$prop_name,
3675     level => $self->{must_level},
3676     token => $t);
3677     return ($t, undef);
3678     } else {
3679     last F;
3680     }
3681     }
3682     } elsif ($t->{type} == URI_TOKEN) {
3683     if (exists $prop_value{'list-style-image'}) {
3684     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3685     $prop_name,
3686     level => $self->{must_level},
3687     token => $t);
3688     return ($t, undef);
3689     }
3690    
3691     $prop_value{'list-style-image'}
3692 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
3693 wakaba 1.12 $t = $tt->get_next_token;
3694     } else {
3695     if ($f == 1) {
3696     $onerror->(type => 'syntax error:keyword:'.$prop_name,
3697     level => $self->{must_level},
3698     token => $t);
3699     return ($t, undef);
3700     } else {
3701     last F;
3702     }
3703     }
3704    
3705     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3706     } # F
3707     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
3708    
3709     if ($none == 1) {
3710     if (exists $prop_value{'list-style-type'}) {
3711     if (exists $prop_value{'list-style-image'}) {
3712     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3713     $prop_name,
3714     level => $self->{must_level},
3715     token => $t);
3716     return ($t, undef);
3717     } else {
3718     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3719     }
3720     } else {
3721     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3722     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
3723     unless exists $prop_value{'list-style-image'};
3724     }
3725     } elsif ($none == 2) {
3726     if (exists $prop_value{'list-style-type'}) {
3727     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3728     $prop_name,
3729     level => $self->{must_level},
3730     token => $t);
3731     return ($t, undef);
3732     }
3733     if (exists $prop_value{'list-style-image'}) {
3734     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3735     $prop_name,
3736     level => $self->{must_level},
3737     token => $t);
3738     return ($t, undef);
3739     }
3740    
3741     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3742     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3743     } elsif ($none == 3) {
3744     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3745     $prop_name,
3746     level => $self->{must_level},
3747     token => $t);
3748     return ($t, undef);
3749     }
3750    
3751     for (qw/list-style-type list-style-position list-style-image/) {
3752     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
3753     }
3754    
3755     return ($t, \%prop_value);
3756     },
3757     serialize => sub {
3758     my ($self, $prop_name, $value) = @_;
3759    
3760     local $Error::Depth = $Error::Depth + 1;
3761     return $self->list_style_type . ' ' . $self->list_style_position .
3762     ' ' . $self->list_style_image;
3763     },
3764     };
3765     $Attr->{list_style} = $Prop->{'list-style'};
3766    
3767 wakaba 1.16 ## NOTE: Future version of the implementation will change the way to
3768     ## store the parsed value to support CSS 3 properties.
3769     $Prop->{'text-decoration'} = {
3770     css => 'text-decoration',
3771     dom => 'text_decoration',
3772     key => 'text_decoration',
3773     parse => sub {
3774     my ($self, $prop_name, $tt, $t, $onerror) = @_;
3775    
3776     my $value = ['DECORATION']; # , underline, overline, line-through, blink
3777    
3778     if ($t->{type} == IDENT_TOKEN) {
3779     my $v = lc $t->{value}; ## TODO: case
3780     $t = $tt->get_next_token;
3781     if ($v eq 'inherit') {
3782     return ($t, {$prop_name => ['INHERIT']});
3783     } elsif ($v eq 'none') {
3784     return ($t, {$prop_name => $value});
3785     } elsif ($v eq 'underline' and
3786     $self->{prop_value}->{$prop_name}->{$v}) {
3787     $value->[1] = 1;
3788     } elsif ($v eq 'overline' and
3789     $self->{prop_value}->{$prop_name}->{$v}) {
3790     $value->[2] = 1;
3791     } elsif ($v eq 'line-through' and
3792     $self->{prop_value}->{$prop_name}->{$v}) {
3793     $value->[3] = 1;
3794     } elsif ($v eq 'blink' and
3795     $self->{prop_value}->{$prop_name}->{$v}) {
3796     $value->[4] = 1;
3797     } else {
3798     $onerror->(type => 'syntax error:'.$prop_name,
3799     level => $self->{must_level},
3800     token => $t);
3801     return ($t, undef);
3802     }
3803     }
3804    
3805     F: {
3806     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3807     last F unless $t->{type} == IDENT_TOKEN;
3808    
3809     my $v = lc $t->{value}; ## TODO: case
3810     $t = $tt->get_next_token;
3811     if ($v eq 'underline' and
3812     $self->{prop_value}->{$prop_name}->{$v}) {
3813     $value->[1] = 1;
3814     } elsif ($v eq 'overline' and
3815     $self->{prop_value}->{$prop_name}->{$v}) {
3816     $value->[1] = 2;
3817     } elsif ($v eq 'line-through' and
3818     $self->{prop_value}->{$prop_name}->{$v}) {
3819     $value->[1] = 3;
3820     } elsif ($v eq 'blink' and
3821     $self->{prop_value}->{$prop_name}->{$v}) {
3822     $value->[1] = 4;
3823     } else {
3824     last F;
3825     }
3826    
3827     redo F;
3828     } # F
3829    
3830     return ($t, {$prop_name => $value});
3831     },
3832     serialize => $default_serializer,
3833     initial => ["KEYWORD", "none"],
3834     #inherited => 0,
3835     compute => $compute_as_specified,
3836     };
3837     $Attr->{text_decoration} = $Prop->{'text-decoration'};
3838     $Key->{text_decoration} = $Prop->{'text-decoration'};
3839    
3840 wakaba 1.1 1;
3841 wakaba 1.27 ## $Date: 2008/01/06 04:35:18 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24