/[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.15 - (hide annotations) (download)
Tue Jan 1 15:43:47 2008 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.14: +263 -4 lines
++ whatpm/Whatpm/CSS/ChangeLog	1 Jan 2008 15:43:43 -0000
2008-01-02  Wakaba  <wakaba@suika.fam.cx>

	* Cascade.pm (get_computed_value): Resolve initial value referred
	when |inherit| is specified as if it were the specified value.

	* Parser.pm: Some properties were incorrectly marked as
	inherited.
	(background-repeat, background-attachment, font-style,
	font-variant, font-weight, background-image, font-family): Implemented.

1 wakaba 1.1 package Whatpm::CSS::Parser;
2     use strict;
3     use Whatpm::CSS::Tokenizer qw(:token);
4     require Whatpm::CSS::SelectorsParser;
5    
6     sub new ($) {
7 wakaba 1.3 my $self = bless {onerror => sub { }, must_level => 'm',
8 wakaba 1.5 message_level => 'w',
9 wakaba 1.3 unsupported_level => 'unsupported'}, shift;
10 wakaba 1.11 # $self->{base_uri}
11 wakaba 1.1
12     return $self;
13     } # new
14    
15     sub BEFORE_STATEMENT_STATE () { 0 }
16     sub BEFORE_DECLARATION_STATE () { 1 }
17     sub IGNORED_STATEMENT_STATE () { 2 }
18     sub IGNORED_DECLARATION_STATE () { 3 }
19    
20 wakaba 1.5 our $Prop; ## By CSS property name
21     our $Attr; ## By CSSOM attribute name
22     our $Key; ## By internal key
23    
24 wakaba 1.1 sub parse_char_string ($$) {
25     my $self = $_[0];
26    
27     my $s = $_[1];
28     pos ($s) = 0;
29 wakaba 1.2 my $line = 1;
30     my $column = 0;
31    
32     my $_onerror = $self->{onerror};
33     my $onerror = sub {
34     $_onerror->(@_, line => $line, column => $column);
35     };
36 wakaba 1.1
37     my $tt = Whatpm::CSS::Tokenizer->new;
38 wakaba 1.2 $tt->{onerror} = $onerror;
39 wakaba 1.1 $tt->{get_char} = sub {
40     if (pos $s < length $s) {
41 wakaba 1.2 my $c = ord substr $s, pos ($s)++, 1;
42     if ($c == 0x000A) {
43     $line++;
44     $column = 0;
45     } elsif ($c == 0x000D) {
46     unless (substr ($s, pos ($s), 1) eq "\x0A") {
47     $line++;
48     $column = 0;
49     } else {
50     $column++;
51     }
52     } else {
53     $column++;
54     }
55     return $c;
56 wakaba 1.1 } else {
57     return -1;
58     }
59     }; # $tt->{get_char}
60     $tt->init;
61    
62     my $sp = Whatpm::CSS::SelectorsParser->new;
63 wakaba 1.2 $sp->{onerror} = $onerror;
64 wakaba 1.1 $sp->{must_level} = $self->{must_level};
65 wakaba 1.2 $sp->{pseudo_element} = $self->{pseudo_element};
66     $sp->{pseudo_class} = $self->{pseudo_class};
67 wakaba 1.1
68 wakaba 1.4 my $nsmap = {};
69     $sp->{lookup_namespace_uri} = sub {
70     return $nsmap->{$_[0]}; # $_[0] is '' (default namespace) or prefix
71     }; # $sp->{lookup_namespace_uri}
72 wakaba 1.1
73     ## TODO: Supported pseudo classes and elements...
74    
75     require Message::DOM::CSSStyleSheet;
76     require Message::DOM::CSSRule;
77     require Message::DOM::CSSStyleDeclaration;
78    
79 wakaba 1.11 $self->{base_uri} = $self->{href} unless defined $self->{base_uri};
80    
81 wakaba 1.1 my $state = BEFORE_STATEMENT_STATE;
82     my $t = $tt->get_next_token;
83    
84     my $open_rules = [[]];
85     my $current_rules = $open_rules->[-1];
86     my $current_decls;
87     my $closing_tokens = [];
88 wakaba 1.3 my $charset_allowed = 1;
89 wakaba 1.4 my $namespace_allowed = 1;
90 wakaba 1.1
91     S: {
92     if ($state == BEFORE_STATEMENT_STATE) {
93     $t = $tt->get_next_token
94     while $t->{type} == S_TOKEN or
95     $t->{type} == CDO_TOKEN or
96     $t->{type} == CDC_TOKEN;
97    
98     if ($t->{type} == ATKEYWORD_TOKEN) {
99 wakaba 1.5 if (lc $t->{value} eq 'namespace') { ## TODO: case folding
100 wakaba 1.4 $t = $tt->get_next_token;
101     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
102    
103     my $prefix;
104     if ($t->{type} == IDENT_TOKEN) {
105     $prefix = lc $t->{value};
106     ## TODO: Unicode lowercase
107    
108     $t = $tt->get_next_token;
109     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
110     }
111    
112     if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
113     my $uri = $t->{value};
114    
115     $t = $tt->get_next_token;
116     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
117    
118     ## ISSUE: On handling of empty namespace URI, Firefox 2 and
119     ## Opera 9 work differently (See SuikaWiki:namespace).
120     ## TODO: We need to check what we do once it is specced.
121    
122     if ($t->{type} == SEMICOLON_TOKEN) {
123     if ($namespace_allowed) {
124     $nsmap->{defined $prefix ? $prefix : ''} = $uri;
125     push @$current_rules,
126     Message::DOM::CSSNamespaceRule->____new ($prefix, $uri);
127     undef $charset_allowed;
128     } else {
129     $onerror->(type => 'at:namespace:not allowed',
130     level => $self->{must_level},
131     token => $t);
132     }
133    
134     $t = $tt->get_next_token;
135     ## Stay in the state.
136     redo S;
137     } else {
138     #
139     }
140     } else {
141     #
142     }
143    
144     $onerror->(type => 'syntax error:at:namespace',
145     level => $self->{must_level},
146     token => $t);
147     #
148 wakaba 1.5 } elsif (lc $t->{value} eq 'charset') { ## TODO: case folding
149 wakaba 1.3 $t = $tt->get_next_token;
150     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
151    
152     if ($t->{type} == STRING_TOKEN) {
153     my $encoding = $t->{value};
154    
155     $t = $tt->get_next_token;
156     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
157    
158     if ($t->{type} == SEMICOLON_TOKEN) {
159     if ($charset_allowed) {
160     push @$current_rules,
161     Message::DOM::CSSCharsetRule->____new ($encoding);
162     undef $charset_allowed;
163     } else {
164     $onerror->(type => 'at:charset:not allowed',
165     level => $self->{must_level},
166     token => $t);
167     }
168    
169     ## TODO: Detect the conformance errors for @charset...
170    
171     $t = $tt->get_next_token;
172     ## Stay in the state.
173     redo S;
174     } else {
175     #
176     }
177     } else {
178     #
179     }
180    
181     $onerror->(type => 'syntax error:at:charset',
182     level => $self->{must_level},
183     token => $t);
184 wakaba 1.4 #
185 wakaba 1.3 ## NOTE: When adding support for new at-rule, insert code
186 wakaba 1.4 ## "undef $charset_allowed" and "undef $namespace_token" as
187     ## appropriate.
188 wakaba 1.3 } else {
189     $onerror->(type => 'not supported:at:'.$t->{value},
190     level => $self->{unsupported_level},
191     token => $t);
192     }
193 wakaba 1.1
194     $t = $tt->get_next_token;
195     $state = IGNORED_STATEMENT_STATE;
196     redo S;
197     } elsif (@$open_rules > 1 and $t->{type} == RBRACE_TOKEN) {
198     pop @$open_rules;
199     ## Stay in the state.
200     $t = $tt->get_next_token;
201     redo S;
202     } elsif ($t->{type} == EOF_TOKEN) {
203     if (@$open_rules > 1) {
204 wakaba 1.2 $onerror->(type => 'syntax error:block not closed',
205     level => $self->{must_level},
206     token => $t);
207 wakaba 1.1 }
208    
209     last S;
210     } else {
211 wakaba 1.3 undef $charset_allowed;
212 wakaba 1.4 undef $namespace_allowed;
213 wakaba 1.3
214 wakaba 1.1 ($t, my $selectors) = $sp->_parse_selectors_with_tokenizer
215     ($tt, LBRACE_TOKEN, $t);
216    
217     $t = $tt->get_next_token
218     while $t->{type} != LBRACE_TOKEN and $t->{type} != EOF_TOKEN;
219    
220     if ($t->{type} == LBRACE_TOKEN) {
221     $current_decls = Message::DOM::CSSStyleDeclaration->____new;
222     my $rs = Message::DOM::CSSStyleRule->____new
223     ($selectors, $current_decls);
224     push @{$current_rules}, $rs if defined $selectors;
225    
226     $state = BEFORE_DECLARATION_STATE;
227     $t = $tt->get_next_token;
228     redo S;
229     } else {
230 wakaba 1.2 $onerror->(type => 'syntax error:after selectors',
231     level => $self->{must_level},
232     token => $t);
233 wakaba 1.1
234     ## Stay in the state.
235     $t = $tt->get_next_token;
236     redo S;
237     }
238     }
239     } elsif ($state == BEFORE_DECLARATION_STATE) {
240     ## NOTE: DELIM? in declaration will be removed:
241     ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.
242    
243 wakaba 1.5 my $prop_def;
244     my $prop_value;
245     my $prop_flag;
246 wakaba 1.1 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
247     if ($t->{type} == IDENT_TOKEN) { # property
248 wakaba 1.5 my $prop_name = lc $t->{value}; ## TODO: case folding
249     $t = $tt->get_next_token;
250     if ($t->{type} == COLON_TOKEN) {
251     $t = $tt->get_next_token;
252     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
253    
254     $prop_def = $Prop->{$prop_name};
255 wakaba 1.6 if ($prop_def and $self->{prop}->{$prop_name}) {
256 wakaba 1.5 ($t, $prop_value)
257     = $prop_def->{parse}->($self, $prop_name, $tt, $t, $onerror);
258     if ($prop_value) {
259     ## NOTE: {parse} don't have to consume trailing spaces.
260     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
261    
262     if ($t->{type} == EXCLAMATION_TOKEN) {
263     $t = $tt->get_next_token;
264     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
265     if ($t->{type} == IDENT_TOKEN and
266     lc $t->{value} eq 'important') { ## TODO: case folding
267     $prop_flag = 'important';
268    
269     $t = $tt->get_next_token;
270     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
271    
272     #
273     } else {
274     $onerror->(type => 'syntax error:important',
275     level => $self->{must_level},
276     token => $t);
277    
278     ## Reprocess.
279     $state = IGNORED_DECLARATION_STATE;
280     redo S;
281     }
282     }
283    
284     #
285     } else {
286     ## Syntax error.
287    
288     ## Reprocess.
289     $state = IGNORED_DECLARATION_STATE;
290     redo S;
291     }
292     } else {
293     $onerror->(type => 'not supported:property',
294     level => $self->{unsupported_level},
295     token => $t, value => $prop_name);
296    
297     #
298     $state = IGNORED_DECLARATION_STATE;
299     redo S;
300     }
301     } else {
302     $onerror->(type => 'syntax error:property colon',
303     level => $self->{must_level},
304     token => $t);
305 wakaba 1.1
306 wakaba 1.5 #
307     $state = IGNORED_DECLARATION_STATE;
308     redo S;
309     }
310     }
311    
312     if ($t->{type} == RBRACE_TOKEN) {
313 wakaba 1.1 $t = $tt->get_next_token;
314 wakaba 1.5 $state = BEFORE_STATEMENT_STATE;
315     #redo S;
316     } elsif ($t->{type} == SEMICOLON_TOKEN) {
317 wakaba 1.1 $t = $tt->get_next_token;
318 wakaba 1.5 ## Stay in the state.
319     #redo S;
320 wakaba 1.1 } elsif ($t->{type} == EOF_TOKEN) {
321 wakaba 1.2 $onerror->(type => 'syntax error:ruleset not closed',
322     level => $self->{must_level},
323     token => $t);
324 wakaba 1.1 ## Reprocess.
325     $state = BEFORE_STATEMENT_STATE;
326 wakaba 1.5 #redo S;
327     } else {
328     if ($prop_value) {
329     $onerror->(type => 'syntax error:property semicolon',
330     level => $self->{must_level},
331     token => $t);
332     } else {
333     $onerror->(type => 'syntax error:property name',
334     level => $self->{must_level},
335     token => $t);
336     }
337    
338     #
339     $state = IGNORED_DECLARATION_STATE;
340 wakaba 1.1 redo S;
341     }
342    
343 wakaba 1.7 my $important = (defined $prop_flag and $prop_flag eq 'important');
344     for my $set_prop_name (keys %{$prop_value or {}}) {
345     my $set_prop_def = $Prop->{$set_prop_name};
346     $$current_decls->{$set_prop_def->{key}}
347     = [$prop_value->{$set_prop_name}, $prop_flag]
348     if $important or
349     not $$current_decls->{$set_prop_def->{key}} or
350     not defined $$current_decls->{$set_prop_def->{key}}->[1];
351 wakaba 1.5 }
352 wakaba 1.1 redo S;
353     } elsif ($state == IGNORED_STATEMENT_STATE or
354     $state == IGNORED_DECLARATION_STATE) {
355     if (@$closing_tokens) { ## Something is yet in opening state.
356     if ($t->{type} == EOF_TOKEN) {
357     @$closing_tokens = ();
358     ## Reprocess.
359     $state = $state == IGNORED_STATEMENT_STATE
360     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
361     redo S;
362     } elsif ($t->{type} == $closing_tokens->[-1]) {
363     pop @$closing_tokens;
364     if (@$closing_tokens == 0 and
365     $t->{type} == RBRACE_TOKEN and
366     $state == IGNORED_STATEMENT_STATE) {
367     $t = $tt->get_next_token;
368     $state = BEFORE_STATEMENT_STATE;
369     redo S;
370     } else {
371     $t = $tt->get_next_token;
372     ## Stay in the state.
373     redo S;
374     }
375     } else {
376     #
377     }
378     } else {
379     if ($t->{type} == SEMICOLON_TOKEN) {
380     $t = $tt->get_next_token;
381     $state = $state == IGNORED_STATEMENT_STATE
382     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
383     redo S;
384     } elsif ($state == IGNORED_DECLARATION_STATE and
385     $t->{type} == RBRACE_TOKEN) {
386     $t = $tt->get_next_token;
387     $state = BEFORE_STATEMENT_STATE;
388     redo S;
389     } elsif ($t->{type} == EOF_TOKEN) {
390     ## Reprocess.
391     $state = $state == IGNORED_STATEMENT_STATE
392     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
393     redo S;
394     } else {
395     #
396     }
397     }
398    
399     while (not {
400     EOF_TOKEN, 1,
401     RBRACE_TOKEN, 1,
402     RBRACKET_TOKEN, 1,
403     RPAREN_TOKEN, 1,
404     SEMICOLON_TOKEN, 1,
405     }->{$t->{type}}) {
406     if ($t->{type} == LBRACE_TOKEN) {
407     push @$closing_tokens, RBRACE_TOKEN;
408     } elsif ($t->{type} == LBRACKET_TOKEN) {
409     push @$closing_tokens, RBRACKET_TOKEN;
410     } elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {
411     push @$closing_tokens, RPAREN_TOKEN;
412     }
413    
414     $t = $tt->get_next_token;
415     }
416    
417     #
418     ## Stay in the state.
419     redo S;
420     } else {
421     die "$0: parse_char_string: Unknown state: $state";
422     }
423     } # S
424    
425     my $ss = Message::DOM::CSSStyleSheet->____new
426 wakaba 1.11 (manakai_base_uri => $self->{base_uri},
427     css_rules => $open_rules->[0],
428 wakaba 1.1 ## TODO: href
429     ## TODO: owner_node
430     ## TODO: media
431     type => 'text/css', ## TODO: OK?
432     _parser => $self);
433     return $ss;
434     } # parse_char_string
435    
436 wakaba 1.9 my $compute_as_specified = sub ($$$$) {
437     #my ($self, $element, $prop_name, $specified_value) = @_;
438     return $_[3];
439     }; # $compute_as_specified
440    
441 wakaba 1.11 my $default_serializer = sub {
442     my ($self, $prop_name, $value) = @_;
443 wakaba 1.15 if ($value->[0] eq 'NUMBER' or $value->[0] eq 'WEIGHT') {
444     ## TODO: What we currently do for 'font-weight' is different from
445     ## any browser for lighter/bolder cases. We need to fix this, but
446     ## how?
447 wakaba 1.11 return $value->[1]; ## TODO: big or small number cases?
448     } elsif ($value->[0] eq 'KEYWORD') {
449     return $value->[1];
450     } elsif ($value->[0] eq 'URI') {
451     ## NOTE: This is what browsers do.
452     return 'url('.$value->[1].')';
453     } elsif ($value->[0] eq 'INHERIT') {
454     return 'inherit';
455     } else {
456     return undef;
457     }
458     }; # $default_serializer
459    
460 wakaba 1.5 $Prop->{color} = {
461     css => 'color',
462     dom => 'color',
463     key => 'color',
464     parse => sub {
465     my ($self, $prop_name, $tt, $t, $onerror) = @_;
466    
467     if ($t->{type} == IDENT_TOKEN) {
468     if (lc $t->{value} eq 'blue') { ## TODO: case folding
469     $t = $tt->get_next_token;
470 wakaba 1.7 return ($t, {$prop_name => ["RGBA", 0, 0, 255, 1]});
471 wakaba 1.5 } else {
472     #
473     }
474     } else {
475     #
476     }
477    
478     $onerror->(type => 'syntax error:color',
479     level => $self->{must_level},
480     token => $t);
481    
482     return ($t, undef);
483     },
484     serialize => sub {
485     my ($self, $prop_name, $value) = @_;
486     if ($value->[0] eq 'RGBA') { ## TODO: %d? %f?
487     return sprintf 'rgba(%d, %d, %d, %f)', @$value[1, 2, 3, 4];
488     } else {
489     return undef;
490     }
491     },
492 wakaba 1.9 initial => ["KEYWORD", "-manakai-initial-color"], ## NOTE: UA-dependent in CSS 2.1.
493     inherited => 1,
494     compute => $compute_as_specified,
495 wakaba 1.5 };
496     $Attr->{color} = $Prop->{color};
497     $Key->{color} = $Prop->{color};
498    
499 wakaba 1.6 my $one_keyword_parser = sub {
500     my ($self, $prop_name, $tt, $t, $onerror) = @_;
501    
502     if ($t->{type} == IDENT_TOKEN) {
503     my $prop_value = lc $t->{value}; ## TODO: case folding
504     $t = $tt->get_next_token;
505     if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
506     $self->{prop_value}->{$prop_name}->{$prop_value}) {
507 wakaba 1.7 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
508 wakaba 1.6 } elsif ($prop_value eq 'inherit') {
509 wakaba 1.10 return ($t, {$prop_name => ['INHERIT']});
510 wakaba 1.6 }
511     }
512    
513 wakaba 1.7 $onerror->(type => 'syntax error:keyword:'.$prop_name,
514 wakaba 1.6 level => $self->{must_level},
515     token => $t);
516     return ($t, undef);
517     };
518    
519     $Prop->{display} = {
520     css => 'display',
521     dom => 'display',
522     key => 'display',
523     parse => $one_keyword_parser,
524 wakaba 1.11 serialize => $default_serializer,
525 wakaba 1.6 keyword => {
526     block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
527     'list-item' => 1, none => 1,
528     table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
529     'table-column-group' => 1, 'table-header-group' => 1,
530     'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
531     },
532 wakaba 1.9 initial => ["KEYWORD", "inline"],
533     #inherited => 0,
534     compute => sub {
535     my ($self, $element, $prop_name, $specified_value) = @_;
536     ## NOTE: CSS 2.1 Section 9.7.
537    
538     ## WARNING: |compute| for 'float' property invoke this CODE
539     ## in some case. Careless modification might cause a infinite loop.
540    
541     if ($specified_value->[0] eq 'KEYWORD') {
542     if ($specified_value->[1] eq 'none') {
543     ## Case 1 [CSS 2.1]
544     return $specified_value;
545     } else {
546     my $position = $self->get_computed_value ($element, 'position');
547     if ($position->[0] eq 'KEYWORD' and
548     ($position->[1] eq 'absolute' or
549     $position->[1] eq 'fixed')) {
550     ## Case 2 [CSS 2.1]
551     #
552     } else {
553     my $float = $self->get_computed_value ($element, 'float');
554     if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
555     ## Caes 3 [CSS 2.1]
556     #
557     } elsif (not defined $element->manakai_parent_element) {
558     ## Case 4 [CSS 2.1]
559     #
560     } else {
561     ## Case 5 [CSS 2.1]
562     return $specified_value;
563     }
564     }
565    
566     return ["KEYWORD",
567     {
568     'inline-table' => 'table',
569     inline => 'block',
570     'run-in' => 'block',
571     'table-row-group' => 'block',
572     'table-column' => 'block',
573     'table-column-group' => 'block',
574     'table-header-group' => 'block',
575     'table-footer-group' => 'block',
576     'table-row' => 'block',
577     'table-cell' => 'block',
578     'table-caption' => 'block',
579     'inline-block' => 'block',
580     }->{$specified_value->[1]} || $specified_value->[1]];
581     }
582     } else {
583     return $specified_value; ## Maybe an error of the implementation.
584     }
585     },
586 wakaba 1.6 };
587     $Attr->{display} = $Prop->{display};
588     $Key->{display} = $Prop->{display};
589    
590     $Prop->{position} = {
591     css => 'position',
592     dom => 'position',
593     key => 'position',
594     parse => $one_keyword_parser,
595 wakaba 1.11 serialize => $default_serializer,
596 wakaba 1.6 keyword => {
597     static => 1, relative => 1, absolute => 1, fixed => 1,
598     },
599 wakaba 1.10 initial => ["KEYWORD", "static"],
600 wakaba 1.9 #inherited => 0,
601     compute => $compute_as_specified,
602 wakaba 1.6 };
603     $Attr->{position} = $Prop->{position};
604     $Key->{position} = $Prop->{position};
605    
606     $Prop->{float} = {
607     css => 'float',
608     dom => 'css_float',
609     key => 'float',
610     parse => $one_keyword_parser,
611 wakaba 1.11 serialize => $default_serializer,
612 wakaba 1.6 keyword => {
613     left => 1, right => 1, none => 1,
614     },
615 wakaba 1.9 initial => ["KEYWORD", "none"],
616     #inherited => 0,
617     compute => sub {
618     my ($self, $element, $prop_name, $specified_value) = @_;
619     ## NOTE: CSS 2.1 Section 9.7.
620    
621     ## WARNING: |compute| for 'display' property invoke this CODE
622     ## in some case. Careless modification might cause a infinite loop.
623    
624     if ($specified_value->[0] eq 'KEYWORD') {
625     if ($specified_value->[1] eq 'none') {
626     ## Case 1 [CSS 2.1]
627     return $specified_value;
628     } else {
629     my $position = $self->get_computed_value ($element, 'position');
630     if ($position->[0] eq 'KEYWORD' and
631     ($position->[1] eq 'absolute' or
632     $position->[1] eq 'fixed')) {
633     ## Case 2 [CSS 2.1]
634     return ["KEYWORD", "none"];
635     }
636     }
637     }
638    
639     ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
640     ## on computed value of 'float' property.
641    
642     ## Case 3, 4, and 5 [CSS 2.1]
643     return $specified_value;
644     },
645 wakaba 1.6 };
646     $Attr->{css_float} = $Prop->{float};
647     $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
648     $Key->{float} = $Prop->{float};
649    
650     $Prop->{clear} = {
651     css => 'clear',
652     dom => 'clear',
653     key => 'clear',
654     parse => $one_keyword_parser,
655 wakaba 1.11 serialize => $default_serializer,
656 wakaba 1.6 keyword => {
657     left => 1, right => 1, none => 1, both => 1,
658     },
659 wakaba 1.9 initial => ["KEYWORD", "none"],
660     #inherited => 0,
661     compute => $compute_as_specified,
662 wakaba 1.6 };
663     $Attr->{clear} = $Prop->{clear};
664     $Key->{clear} = $Prop->{clear};
665    
666     $Prop->{direction} = {
667     css => 'direction',
668     dom => 'direction',
669     key => 'direction',
670     parse => $one_keyword_parser,
671 wakaba 1.11 serialize => $default_serializer,
672 wakaba 1.6 keyword => {
673     ltr => 1, rtl => 1,
674     },
675 wakaba 1.9 initial => ["KEYWORD", "ltr"],
676     inherited => 1,
677     compute => $compute_as_specified,
678 wakaba 1.6 };
679     $Attr->{direction} = $Prop->{direction};
680     $Key->{direction} = $Prop->{direction};
681    
682     $Prop->{'unicode-bidi'} = {
683     css => 'unicode-bidi',
684     dom => 'unicode_bidi',
685     key => 'unicode_bidi',
686     parse => $one_keyword_parser,
687 wakaba 1.11 serialize => $default_serializer,
688 wakaba 1.6 keyword => {
689     normal => 1, embed => 1, 'bidi-override' => 1,
690     },
691 wakaba 1.9 initial => ["KEYWORD", "normal"],
692     #inherited => 0,
693     compute => $compute_as_specified,
694 wakaba 1.6 };
695     $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
696     $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
697    
698 wakaba 1.11 $Prop->{overflow} = {
699     css => 'overflow',
700     dom => 'overflow',
701     key => 'overflow',
702     parse => $one_keyword_parser,
703     serialize => $default_serializer,
704     keyword => {
705     visible => 1, hidden => 1, scroll => 1, auto => 1,
706     },
707     initial => ["KEYWORD", "visible"],
708     #inherited => 0,
709     compute => $compute_as_specified,
710     };
711     $Attr->{overflow} = $Prop->{overflow};
712     $Key->{overflow} = $Prop->{overflow};
713    
714     $Prop->{visibility} = {
715     css => 'visibility',
716     dom => 'visibility',
717     key => 'visibility',
718     parse => $one_keyword_parser,
719     serialize => $default_serializer,
720     keyword => {
721     visible => 1, hidden => 1, collapse => 1,
722     },
723     initial => ["KEYWORD", "visible"],
724     #inherited => 0,
725     compute => $compute_as_specified,
726     };
727     $Attr->{visibility} = $Prop->{visibility};
728     $Key->{visibility} = $Prop->{visibility};
729    
730     $Prop->{'list-style-type'} = {
731     css => 'list-style-type',
732     dom => 'list_style_type',
733     key => 'list_style_type',
734     parse => $one_keyword_parser,
735     serialize => $default_serializer,
736     keyword => {
737     qw/
738     disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
739     lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
740     upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
741     none 1
742     /,
743     },
744     initial => ["KEYWORD", 'disc'],
745     inherited => 1,
746     compute => $compute_as_specified,
747     };
748     $Attr->{list_style_type} = $Prop->{'list-style-type'};
749     $Key->{list_style_type} = $Prop->{'list-style-type'};
750    
751     $Prop->{'list-style-position'} = {
752     css => 'list-style-position',
753     dom => 'list_style_position',
754     key => 'list_style_position',
755     parse => $one_keyword_parser,
756     serialize => $default_serializer,
757     keyword => {
758     inside => 1, outside => 1,
759     },
760     initial => ["KEYWORD", 'outside'],
761     inherited => 1,
762     compute => $compute_as_specified,
763     };
764     $Attr->{list_style_position} = $Prop->{'list-style-position'};
765     $Key->{list_style_position} = $Prop->{'list-style-position'};
766    
767 wakaba 1.12 $Prop->{'page-break-before'} = {
768     css => 'page-break-before',
769     dom => 'page_break_before',
770     key => 'page_break_before',
771     parse => $one_keyword_parser,
772     serialize => $default_serializer,
773     keyword => {
774     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
775     },
776     initial => ["KEYWORD", 'auto'],
777 wakaba 1.15 #inherited => 0,
778 wakaba 1.12 compute => $compute_as_specified,
779     };
780     $Attr->{page_break_before} = $Prop->{'page-break-before'};
781     $Key->{page_break_before} = $Prop->{'page-break-before'};
782    
783     $Prop->{'page-break-after'} = {
784     css => 'page-break-after',
785     dom => 'page_break_after',
786     key => 'page_break_after',
787     parse => $one_keyword_parser,
788     serialize => $default_serializer,
789     keyword => {
790     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
791     },
792     initial => ["KEYWORD", 'auto'],
793 wakaba 1.15 #inherited => 0,
794 wakaba 1.12 compute => $compute_as_specified,
795     };
796     $Attr->{page_break_after} = $Prop->{'page-break-after'};
797     $Key->{page_break_after} = $Prop->{'page-break-after'};
798    
799     $Prop->{'page-break-inside'} = {
800     css => 'page-break-inside',
801     dom => 'page_break_inside',
802     key => 'page_break_inside',
803     parse => $one_keyword_parser,
804     serialize => $default_serializer,
805     keyword => {
806     auto => 1, avoid => 1,
807     },
808     initial => ["KEYWORD", 'auto'],
809     inherited => 1,
810     compute => $compute_as_specified,
811     };
812     $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
813     $Key->{page_break_inside} = $Prop->{'page-break-inside'};
814    
815 wakaba 1.15 $Prop->{'background-repeat'} = {
816     css => 'background-repeat',
817     dom => 'background_repeat',
818     key => 'background_repeat',
819     parse => $one_keyword_parser,
820     serialize => $default_serializer,
821     keyword => {
822     repeat => 1, 'repeat-x' => 1, 'repeat-y' => 1, 'no-repeat' => 1,
823     },
824     initial => ["KEYWORD", 'repeat'],
825     #inherited => 0,
826     compute => $compute_as_specified,
827     };
828     $Attr->{background_repeat} = $Prop->{'background-repeat'};
829     $Key->{backgroud_repeat} = $Prop->{'background-repeat'};
830    
831     $Prop->{'background-attachment'} = {
832     css => 'background-attachment',
833     dom => 'background_attachment',
834     key => 'background_attachment',
835     parse => $one_keyword_parser,
836     serialize => $default_serializer,
837     keyword => {
838     scroll => 1, fixed => 1,
839     },
840     initial => ["KEYWORD", 'scroll'],
841     #inherited => 0,
842     compute => $compute_as_specified,
843     };
844     $Attr->{background_attachment} = $Prop->{'background-attachment'};
845     $Key->{backgroud_attachment} = $Prop->{'background-attachment'};
846    
847     $Prop->{'font-style'} = {
848     css => 'font-style',
849     dom => 'font_size',
850     key => 'font_size',
851     parse => $one_keyword_parser,
852     serialize => $default_serializer,
853     keyword => {
854     normal => 1, italic => 1, oblique => 1,
855     },
856     initial => ["KEYWORD", 'normal'],
857     inherited => 1,
858     compute => $compute_as_specified,
859     };
860     $Attr->{font_style} = $Prop->{'font-style'};
861     $Key->{font_style} = $Prop->{'font-style'};
862    
863     $Prop->{'font-variant'} = {
864     css => 'font-variant',
865     dom => 'font_variant',
866     key => 'font_variant',
867     parse => $one_keyword_parser,
868     serialize => $default_serializer,
869     keyword => {
870     normal => 1, 'small-caps' => 1,
871     },
872     initial => ["KEYWORD", 'normal'],
873     inherited => 1,
874     compute => $compute_as_specified,
875     };
876     $Attr->{font_variant} = $Prop->{'font-variant'};
877     $Key->{font_variant} = $Prop->{'font-variant'};
878    
879 wakaba 1.11 $Prop->{'z-index'} = {
880     css => 'z-index',
881     dom => 'z_index',
882     key => 'z_index',
883     parse => sub {
884     my ($self, $prop_name, $tt, $t, $onerror) = @_;
885    
886 wakaba 1.12 my $sign = 1;
887     if ($t->{type} == MINUS_TOKEN) {
888     $sign = -1;
889     $t = $tt->get_next_token;
890     }
891    
892 wakaba 1.11 if ($t->{type} == NUMBER_TOKEN) {
893     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
894     ## browser compatibility issue.
895     my $value = $t->{number};
896     $t = $tt->get_next_token;
897 wakaba 1.12 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
898     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
899 wakaba 1.11 my $value = lc $t->{value}; ## TODO: case
900     $t = $tt->get_next_token;
901     if ($value eq 'auto') {
902     ## NOTE: |z-index| is the default value and therefore it must be
903     ## supported anyway.
904     return ($t, {$prop_name => ["KEYWORD", 'auto']});
905     } elsif ($value eq 'inherit') {
906     return ($t, {$prop_name => ['INHERIT']});
907     }
908     }
909    
910     $onerror->(type => 'syntax error:'.$prop_name,
911     level => $self->{must_level},
912     token => $t);
913     return ($t, undef);
914     },
915     serialize => $default_serializer,
916     initial => ['KEYWORD', 'auto'],
917     #inherited => 0,
918     compute => $compute_as_specified,
919     };
920     $Attr->{z_index} = $Prop->{'z-index'};
921     $Key->{z_index} = $Prop->{'z-index'};
922    
923 wakaba 1.12 $Prop->{orphans} = {
924     css => 'orphans',
925     dom => 'orphans',
926     key => 'orphans',
927     parse => sub {
928     my ($self, $prop_name, $tt, $t, $onerror) = @_;
929    
930     my $sign = 1;
931     if ($t->{type} == MINUS_TOKEN) {
932     $t = $tt->get_next_token;
933     $sign = -1;
934     }
935    
936     if ($t->{type} == NUMBER_TOKEN) {
937     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
938     ## <http://suika.fam.cx/gate/2005/sw/widows> for
939     ## browser compatibility issue.
940     my $value = $t->{number};
941     $t = $tt->get_next_token;
942     return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
943     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
944     my $value = lc $t->{value}; ## TODO: case
945     $t = $tt->get_next_token;
946     if ($value eq 'inherit') {
947     return ($t, {$prop_name => ['INHERIT']});
948     }
949     }
950    
951     $onerror->(type => 'syntax error:'.$prop_name,
952     level => $self->{must_level},
953     token => $t);
954     return ($t, undef);
955     },
956     serialize => $default_serializer,
957     initial => ['NUMBER', 2],
958     inherited => 1,
959     compute => $compute_as_specified,
960     };
961     $Attr->{orphans} = $Prop->{orphans};
962     $Key->{orphans} = $Prop->{orphans};
963    
964     $Prop->{widows} = {
965     css => 'widows',
966     dom => 'widows',
967     key => 'widows',
968     parse => $Prop->{orphans}->{parse},
969     serialize => $default_serializer,
970     initial => ['NUMBER', 2],
971     inherited => 1,
972     compute => $compute_as_specified,
973     };
974     $Attr->{widows} = $Prop->{widows};
975     $Key->{widows} = $Prop->{widows};
976    
977 wakaba 1.15 $Prop->{'font-weight'} = {
978     css => 'font-weight',
979     dom => 'font_weight',
980     key => 'font_weight',
981     parse => sub {
982     my ($self, $prop_name, $tt, $t, $onerror) = @_;
983    
984     if ($t->{type} == NUMBER_TOKEN) {
985     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
986     ## browser compatibility issue.
987     my $value = $t->{number};
988     $t = $tt->get_next_token;
989     if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
990     return ($t, {$prop_name => ['WEIGHT', $value, 0]});
991     }
992     } elsif ($t->{type} == IDENT_TOKEN) {
993     my $value = lc $t->{value}; ## TODO: case
994     $t = $tt->get_next_token;
995     if ({
996     normal => 1, bold => 1, bolder => 1, lighter => 1,
997     }->{$value}) {
998     return ($t, {$prop_name => ['KEYWORD', $value]});
999     } elsif ($value eq 'inherit') {
1000     return ($t, {$prop_name => ['INHERIT']});
1001     }
1002     }
1003    
1004     $onerror->(type => 'syntax error:'.$prop_name,
1005     level => $self->{must_level},
1006     token => $t);
1007     return ($t, undef);
1008     },
1009     serialize => $default_serializer,
1010     initial => ['KEYWORD', 'normal'],
1011     inherited => 1,
1012     compute => sub {
1013     my ($self, $element, $prop_name, $specified_value) = @_;
1014    
1015     if ($specified_value->[0] eq 'KEYWORD') {
1016     if ($specified_value->[1] eq 'normal') {
1017     return ['WEIGHT', 400, 0];
1018     } elsif ($specified_value->[1] eq 'bold') {
1019     return ['WEIGHT', 700, 0];
1020     } elsif ($specified_value->[1] eq 'bolder') {
1021     my $parent_element = $element->manakai_parent_element;
1022     if (defined $parent_element) {
1023     my $parent_value = $self->get_cascaded_value
1024     ($parent_element, $prop_name); ## NOTE: What Firefox does.
1025     return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
1026     } else {
1027     return ['WEIGHT', 400, 1];
1028     }
1029     } elsif ($specified_value->[1] eq 'lighter') {
1030     my $parent_element = $element->manakai_parent_element;
1031     if (defined $parent_element) {
1032     my $parent_value = $self->get_cascaded_value
1033     ($parent_element, $prop_name); ## NOTE: What Firefox does.
1034     return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
1035     } else {
1036     return ['WEIGHT', 400, 1];
1037     }
1038     }
1039     } elsif ($specified_value->[0] eq 'WEIGHT') {
1040     #
1041     }
1042    
1043     return $specified_value;
1044     },
1045     };
1046     $Attr->{font_weight} = $Prop->{'font-weight'};
1047     $Key->{font_weight} = $Prop->{'font-weight'};
1048    
1049 wakaba 1.13 my $uri_or_none_parser = sub {
1050 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1051    
1052 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
1053 wakaba 1.11 my $value = $t->{value};
1054     $t = $tt->get_next_token;
1055     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
1056     } elsif ($t->{type} == IDENT_TOKEN) {
1057     my $value = lc $t->{value}; ## TODO: case
1058     $t = $tt->get_next_token;
1059     if ($value eq 'none') {
1060     ## NOTE: |none| is the default value and therefore it must be
1061     ## supported anyway.
1062     return ($t, {$prop_name => ["KEYWORD", 'none']});
1063     } elsif ($value eq 'inherit') {
1064     return ($t, {$prop_name => ['INHERIT']});
1065     }
1066     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
1067     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
1068     # my $value = $t->{value};
1069     # $t = $tt->get_next_token;
1070     # if ($t->{type} == EOF_TOKEN) {
1071     # $onerror->(type => 'syntax error:eof:'.$prop_name,
1072     # level => $self->{must_level},
1073     # token => $t);
1074     #
1075     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
1076     # }
1077     }
1078    
1079     $onerror->(type => 'syntax error:'.$prop_name,
1080     level => $self->{must_level},
1081     token => $t);
1082     return ($t, undef);
1083 wakaba 1.13 }; # $uri_or_none_parser
1084    
1085 wakaba 1.14 my $compute_uri_or_none = sub {
1086 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
1087    
1088     if (defined $specified_value and
1089     $specified_value->[0] eq 'URI' and
1090     defined $specified_value->[2]) {
1091     require Message::DOM::DOMImplementation;
1092     return ['URI',
1093     Message::DOM::DOMImplementation->create_uri_reference
1094     ($specified_value->[1])
1095     ->get_absolute_reference (${$specified_value->[2]})
1096     ->get_uri_reference,
1097     $specified_value->[2]];
1098     }
1099    
1100     return $specified_value;
1101 wakaba 1.14 }; # $compute_uri_or_none
1102    
1103     $Prop->{'list-style-image'} = {
1104     css => 'list-style-image',
1105     dom => 'list_style_image',
1106     key => 'list_style_image',
1107     parse => $uri_or_none_parser,
1108     serialize => $default_serializer,
1109     initial => ['KEYWORD', 'none'],
1110     inherited => 1,
1111     compute => $compute_uri_or_none,
1112 wakaba 1.11 };
1113     $Attr->{list_style_image} = $Prop->{'list-style-image'};
1114     $Key->{list_style_image} = $Prop->{'list-style-image'};
1115    
1116 wakaba 1.15 $Prop->{'background-image'} = {
1117     css => 'background-image',
1118     dom => 'background_image',
1119     key => 'background_image',
1120     parse => $uri_or_none_parser,
1121     serialize => $default_serializer,
1122     initial => ['KEYWORD', 'none'],
1123     #inherited => 0,
1124     compute => $compute_uri_or_none,
1125     };
1126     $Attr->{background_image} = $Prop->{'background-image'};
1127     $Key->{background_image} = $Prop->{'background-image'};
1128    
1129 wakaba 1.7 my $border_style_keyword = {
1130     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
1131     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
1132     };
1133    
1134     $Prop->{'border-top-style'} = {
1135     css => 'border-top-style',
1136     dom => 'border_top_style',
1137     key => 'border_top_style',
1138     parse => $one_keyword_parser,
1139 wakaba 1.11 serialize => $default_serializer,
1140 wakaba 1.7 keyword => $border_style_keyword,
1141 wakaba 1.9 initial => ["KEYWORD", "none"],
1142     #inherited => 0,
1143     compute => $compute_as_specified,
1144 wakaba 1.7 };
1145     $Attr->{border_top_style} = $Prop->{'border-top-style'};
1146     $Key->{border_top_style} = $Prop->{'border-top-style'};
1147    
1148     $Prop->{'border-right-style'} = {
1149     css => 'border-right-style',
1150     dom => 'border_right_style',
1151     key => 'border_right_style',
1152     parse => $one_keyword_parser,
1153 wakaba 1.11 serialize => $default_serializer,
1154 wakaba 1.7 keyword => $border_style_keyword,
1155 wakaba 1.9 initial => ["KEYWORD", "none"],
1156     #inherited => 0,
1157     compute => $compute_as_specified,
1158 wakaba 1.7 };
1159     $Attr->{border_right_style} = $Prop->{'border-right-style'};
1160     $Key->{border_right_style} = $Prop->{'border-right-style'};
1161    
1162     $Prop->{'border-bottom-style'} = {
1163     css => 'border-bottom-style',
1164     dom => 'border_bottom_style',
1165     key => 'border_bottom_style',
1166     parse => $one_keyword_parser,
1167 wakaba 1.11 serialize => $default_serializer,
1168 wakaba 1.7 keyword => $border_style_keyword,
1169 wakaba 1.9 initial => ["KEYWORD", "none"],
1170     #inherited => 0,
1171     compute => $compute_as_specified,
1172 wakaba 1.7 };
1173     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
1174     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
1175    
1176     $Prop->{'border-left-style'} = {
1177     css => 'border-left-style',
1178     dom => 'border_left_style',
1179     key => 'border_left_style',
1180     parse => $one_keyword_parser,
1181 wakaba 1.11 serialize => $default_serializer,
1182 wakaba 1.7 keyword => $border_style_keyword,
1183 wakaba 1.9 initial => ["KEYWORD", "none"],
1184     #inherited => 0,
1185     compute => $compute_as_specified,
1186 wakaba 1.7 };
1187     $Attr->{border_left_style} = $Prop->{'border-left-style'};
1188     $Key->{border_left_style} = $Prop->{'border-left-style'};
1189    
1190 wakaba 1.15 $Prop->{'font-family'} = {
1191     css => 'font-family',
1192     dom => 'font_family',
1193     key => 'font_family',
1194     parse => sub {
1195     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1196    
1197     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
1198     ## how chaotic browsers are!
1199    
1200     my @prop_value;
1201    
1202     my $font_name = '';
1203     my $may_be_generic = 1;
1204     my $may_be_inherit = 1;
1205     my $has_s = 0;
1206     F: {
1207     if ($t->{type} == IDENT_TOKEN) {
1208     undef $may_be_inherit if $has_s or length $font_name;
1209     undef $may_be_generic if $has_s or length $font_name;
1210     $font_name .= ' ' if $has_s;
1211     $font_name .= $t->{value};
1212     undef $has_s;
1213     $t = $tt->get_next_token;
1214     } elsif ($t->{type} == STRING_TOKEN) {
1215     $font_name .= ' ' if $has_s;
1216     $font_name .= $t->{value};
1217     undef $may_be_inherit;
1218     undef $may_be_generic;
1219     undef $has_s;
1220     $t = $tt->get_next_token;
1221     } elsif ($t->{type} == COMMA_TOKEN) {
1222     if ($may_be_generic and
1223     {
1224     serif => 1, 'sans-serif' => 1, cursive => 1,
1225     fantasy => 1, monospace => 1, '-manakai-default' => 1,
1226     }->{lc $font_name}) { ## TODO: case
1227     push @prop_value, ['KEYWORD', $font_name];
1228     } elsif (not $may_be_generic or length $font_name) {
1229     push @prop_value, ["STRING", $font_name];
1230     }
1231     undef $may_be_inherit;
1232     $may_be_generic = 1;
1233     undef $has_s;
1234     $font_name = '';
1235     $t = $tt->get_next_token;
1236     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1237     } elsif ($t->{type} == S_TOKEN) {
1238     $has_s = 1;
1239     $t = $tt->get_next_token;
1240     } else {
1241     if ($may_be_generic and
1242     {
1243     serif => 1, 'sans-serif' => 1, cursive => 1,
1244     fantasy => 1, monospace => 1, '-manakai-default' => 1,
1245     }->{lc $font_name}) { ## TODO: case
1246     push @prop_value, ['KEYWORD', $font_name];
1247     } elsif (not $may_be_generic or length $font_name) {
1248     push @prop_value, ['STRING', $font_name];
1249     } else {
1250     $onerror->(type => 'syntax error:'.$prop_name,
1251     level => $self->{must_level},
1252     token => $t);
1253     return ($t, undef);
1254     }
1255     last F;
1256     }
1257     redo F;
1258     } # F
1259    
1260     if ($may_be_inherit and
1261     @prop_value == 1 and
1262     $prop_value[0]->[0] eq 'STRING' and
1263     lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
1264     return ($t, {$prop_name => ['INHERIT']});
1265     } else {
1266     unshift @prop_value, 'FONT';
1267     return ($t, {$prop_name => \@prop_value});
1268     }
1269     },
1270     serialize => sub {
1271     my ($self, $prop_name, $value) = @_;
1272    
1273     if ($value->[0] eq 'FONT') {
1274     return join ', ', map {
1275     if ($_->[0] eq 'STRING') {
1276     '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
1277     } elsif ($_->[0] eq 'KEYWORD') {
1278     $_->[1]; ## NOTE: This is what Firefox does.
1279     } else {
1280     ## NOTE: This should be an error.
1281     '""';
1282     }
1283     } @$value[1..$#$value];
1284     } elsif ($value->[0] eq 'INHERIT') {
1285     return 'inherit';
1286     } else {
1287     return undef;
1288     }
1289     },
1290     initial => ['FONT', ['KEYWORD', '-manakai-default']],
1291     inherited => 1,
1292     compute => $compute_as_specified,
1293     };
1294     $Attr->{font_family} = $Prop->{'font-family'};
1295     $Key->{font_family} = $Prop->{'font-family'};
1296    
1297 wakaba 1.7 $Prop->{'border-style'} = {
1298     css => 'border-style',
1299     dom => 'border_style',
1300     parse => sub {
1301     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1302    
1303     my %prop_value;
1304     my $has_inherit;
1305     if ($t->{type} == IDENT_TOKEN) {
1306     my $prop_value = lc $t->{value}; ## TODO: case folding
1307     $t = $tt->get_next_token;
1308     if ($border_style_keyword->{$prop_value} and
1309     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
1310     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
1311     } elsif ($prop_value eq 'inherit') {
1312 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
1313 wakaba 1.7 $has_inherit = 1;
1314     } else {
1315     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1316     level => $self->{must_level},
1317     token => $t);
1318     return ($t, undef);
1319     }
1320     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
1321     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
1322     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
1323     } else {
1324     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1325     level => $self->{must_level},
1326     token => $t);
1327     return ($t, undef);
1328     }
1329    
1330     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1331     if ($t->{type} == IDENT_TOKEN) {
1332     my $prop_value = lc $t->{value}; ## TODO: case folding
1333     $t = $tt->get_next_token;
1334     if (not $has_inherit and
1335     $border_style_keyword->{$prop_value} and
1336     $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
1337     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
1338     } else {
1339     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1340     level => $self->{must_level},
1341     token => $t);
1342     return ($t, undef);
1343     }
1344     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
1345    
1346     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1347     if ($t->{type} == IDENT_TOKEN) {
1348     my $prop_value = lc $t->{value}; ## TODO: case folding
1349     $t = $tt->get_next_token;
1350     if ($border_style_keyword->{$prop_value} and
1351     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
1352     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
1353     } else {
1354     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1355     level => $self->{must_level},
1356     token => $t);
1357     return ($t, undef);
1358     }
1359    
1360     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1361     if ($t->{type} == IDENT_TOKEN) {
1362     my $prop_value = lc $t->{value}; ## TODO: case folding
1363     $t = $tt->get_next_token;
1364     if ($border_style_keyword->{$prop_value} and
1365     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
1366     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
1367     } else {
1368     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1369     level => $self->{must_level},
1370     token => $t);
1371     return ($t, undef);
1372     }
1373     }
1374     }
1375     }
1376    
1377     return ($t, \%prop_value);
1378     },
1379     serialize => sub {
1380     my ($self, $prop_name, $value) = @_;
1381    
1382     local $Error::Depth = $Error::Depth + 1;
1383     my @v;
1384     push @v, $self->border_top_style;
1385     return undef unless defined $v[-1];
1386     push @v, $self->border_right_style;
1387     return undef unless defined $v[-1];
1388     push @v, $self->border_bottom_style;
1389     return undef unless defined $v[-1];
1390     push @v, $self->border_bottom_style;
1391     return undef unless defined $v[-1];
1392    
1393     pop @v if $v[1] eq $v[3];
1394     pop @v if $v[0] eq $v[2];
1395     pop @v if $v[0] eq $v[1];
1396     return join ' ', @v;
1397     },
1398     };
1399     $Attr->{border_style} = $Prop->{'border-style'};
1400    
1401 wakaba 1.12 $Prop->{'list-style'} = {
1402     css => 'list-style',
1403     dom => 'list_style',
1404     parse => sub {
1405     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1406    
1407     my %prop_value;
1408     my $none = 0;
1409    
1410     F: for my $f (1..3) {
1411     if ($t->{type} == IDENT_TOKEN) {
1412     my $prop_value = lc $t->{value}; ## TODO: case folding
1413     $t = $tt->get_next_token;
1414    
1415     if ($prop_value eq 'none') {
1416     $none++;
1417     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
1418     if (exists $prop_value{'list-style-type'}) {
1419     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1420     $prop_name,
1421     level => $self->{must_level},
1422     token => $t);
1423     return ($t, undef);
1424     } else {
1425     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
1426     }
1427     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
1428     if (exists $prop_value{'list-style-position'}) {
1429     $onerror->(type => q[syntax error:duplicate:'list-style-position':].
1430     $prop_name,
1431     level => $self->{must_level},
1432     token => $t);
1433     return ($t, undef);
1434     }
1435    
1436     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
1437     } elsif ($f == 1 and $prop_value eq 'inherit') {
1438     $prop_value{'list-style-type'} = ["INHERIT"];
1439     $prop_value{'list-style-position'} = ["INHERIT"];
1440     $prop_value{'list-style-image'} = ["INHERIT"];
1441     last F;
1442     } else {
1443     if ($f == 1) {
1444     $onerror->(type => 'syntax error:'.$prop_name,
1445     level => $self->{must_level},
1446     token => $t);
1447     return ($t, undef);
1448     } else {
1449     last F;
1450     }
1451     }
1452     } elsif ($t->{type} == URI_TOKEN) {
1453     if (exists $prop_value{'list-style-image'}) {
1454     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1455     $prop_name,
1456     level => $self->{must_level},
1457     token => $t);
1458     return ($t, undef);
1459     }
1460    
1461     $prop_value{'list-style-image'}
1462 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
1463 wakaba 1.12 $t = $tt->get_next_token;
1464     } else {
1465     if ($f == 1) {
1466     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1467     level => $self->{must_level},
1468     token => $t);
1469     return ($t, undef);
1470     } else {
1471     last F;
1472     }
1473     }
1474    
1475     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1476     } # F
1477     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
1478    
1479     if ($none == 1) {
1480     if (exists $prop_value{'list-style-type'}) {
1481     if (exists $prop_value{'list-style-image'}) {
1482     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1483     $prop_name,
1484     level => $self->{must_level},
1485     token => $t);
1486     return ($t, undef);
1487     } else {
1488     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
1489     }
1490     } else {
1491     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
1492     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
1493     unless exists $prop_value{'list-style-image'};
1494     }
1495     } elsif ($none == 2) {
1496     if (exists $prop_value{'list-style-type'}) {
1497     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1498     $prop_name,
1499     level => $self->{must_level},
1500     token => $t);
1501     return ($t, undef);
1502     }
1503     if (exists $prop_value{'list-style-image'}) {
1504     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1505     $prop_name,
1506     level => $self->{must_level},
1507     token => $t);
1508     return ($t, undef);
1509     }
1510    
1511     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
1512     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
1513     } elsif ($none == 3) {
1514     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1515     $prop_name,
1516     level => $self->{must_level},
1517     token => $t);
1518     return ($t, undef);
1519     }
1520    
1521     for (qw/list-style-type list-style-position list-style-image/) {
1522     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
1523     }
1524    
1525     return ($t, \%prop_value);
1526     },
1527     serialize => sub {
1528     my ($self, $prop_name, $value) = @_;
1529    
1530     local $Error::Depth = $Error::Depth + 1;
1531     return $self->list_style_type . ' ' . $self->list_style_position .
1532     ' ' . $self->list_style_image;
1533     },
1534     };
1535     $Attr->{list_style} = $Prop->{'list-style'};
1536    
1537 wakaba 1.1 1;
1538 wakaba 1.15 ## $Date: 2008/01/01 11:27:42 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24