/[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.17 - (hide annotations) (download)
Wed Jan 2 07:39:22 2008 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.16: +111 -5 lines
++ whatpm/Whatpm/CSS/ChangeLog	2 Jan 2008 07:39:15 -0000
	* Cascade.pm (get_cascaded_value): "*"-only declaration blocks
	were ignored.

	* Parser.pm (cursor): Implemented.

2008-01-02  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.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 wakaba 1.16 } elsif ($value->[0] eq 'DECORATION') {
456     my @v = ();
457     push @v, 'underline' if $value->[1];
458     push @v, 'overline' if $value->[2];
459     push @v, 'line-through' if $value->[3];
460     push @v, 'blink' if $value->[4];
461     return 'none' unless @v;
462     return join ' ', @v;
463 wakaba 1.11 } else {
464     return undef;
465     }
466     }; # $default_serializer
467    
468 wakaba 1.5 $Prop->{color} = {
469     css => 'color',
470     dom => 'color',
471     key => 'color',
472     parse => sub {
473     my ($self, $prop_name, $tt, $t, $onerror) = @_;
474    
475     if ($t->{type} == IDENT_TOKEN) {
476     if (lc $t->{value} eq 'blue') { ## TODO: case folding
477     $t = $tt->get_next_token;
478 wakaba 1.7 return ($t, {$prop_name => ["RGBA", 0, 0, 255, 1]});
479 wakaba 1.5 } else {
480     #
481     }
482     } else {
483     #
484     }
485    
486     $onerror->(type => 'syntax error:color',
487     level => $self->{must_level},
488     token => $t);
489    
490     return ($t, undef);
491     },
492     serialize => sub {
493     my ($self, $prop_name, $value) = @_;
494     if ($value->[0] eq 'RGBA') { ## TODO: %d? %f?
495     return sprintf 'rgba(%d, %d, %d, %f)', @$value[1, 2, 3, 4];
496     } else {
497     return undef;
498     }
499     },
500 wakaba 1.9 initial => ["KEYWORD", "-manakai-initial-color"], ## NOTE: UA-dependent in CSS 2.1.
501     inherited => 1,
502     compute => $compute_as_specified,
503 wakaba 1.5 };
504     $Attr->{color} = $Prop->{color};
505     $Key->{color} = $Prop->{color};
506    
507 wakaba 1.6 my $one_keyword_parser = sub {
508     my ($self, $prop_name, $tt, $t, $onerror) = @_;
509    
510     if ($t->{type} == IDENT_TOKEN) {
511     my $prop_value = lc $t->{value}; ## TODO: case folding
512     $t = $tt->get_next_token;
513     if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
514     $self->{prop_value}->{$prop_name}->{$prop_value}) {
515 wakaba 1.7 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
516 wakaba 1.6 } elsif ($prop_value eq 'inherit') {
517 wakaba 1.10 return ($t, {$prop_name => ['INHERIT']});
518 wakaba 1.6 }
519     }
520    
521 wakaba 1.7 $onerror->(type => 'syntax error:keyword:'.$prop_name,
522 wakaba 1.6 level => $self->{must_level},
523     token => $t);
524     return ($t, undef);
525     };
526    
527     $Prop->{display} = {
528     css => 'display',
529     dom => 'display',
530     key => 'display',
531     parse => $one_keyword_parser,
532 wakaba 1.11 serialize => $default_serializer,
533 wakaba 1.6 keyword => {
534     block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
535     'list-item' => 1, none => 1,
536     table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
537     'table-column-group' => 1, 'table-header-group' => 1,
538     'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
539     },
540 wakaba 1.9 initial => ["KEYWORD", "inline"],
541     #inherited => 0,
542     compute => sub {
543     my ($self, $element, $prop_name, $specified_value) = @_;
544     ## NOTE: CSS 2.1 Section 9.7.
545    
546     ## WARNING: |compute| for 'float' property invoke this CODE
547     ## in some case. Careless modification might cause a infinite loop.
548    
549 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
550 wakaba 1.9 if ($specified_value->[1] eq 'none') {
551     ## Case 1 [CSS 2.1]
552     return $specified_value;
553     } else {
554     my $position = $self->get_computed_value ($element, 'position');
555     if ($position->[0] eq 'KEYWORD' and
556     ($position->[1] eq 'absolute' or
557     $position->[1] eq 'fixed')) {
558     ## Case 2 [CSS 2.1]
559     #
560     } else {
561     my $float = $self->get_computed_value ($element, 'float');
562     if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
563     ## Caes 3 [CSS 2.1]
564     #
565     } elsif (not defined $element->manakai_parent_element) {
566     ## Case 4 [CSS 2.1]
567     #
568     } else {
569     ## Case 5 [CSS 2.1]
570     return $specified_value;
571     }
572     }
573    
574     return ["KEYWORD",
575     {
576     'inline-table' => 'table',
577     inline => 'block',
578     'run-in' => 'block',
579     'table-row-group' => 'block',
580     'table-column' => 'block',
581     'table-column-group' => 'block',
582     'table-header-group' => 'block',
583     'table-footer-group' => 'block',
584     'table-row' => 'block',
585     'table-cell' => 'block',
586     'table-caption' => 'block',
587     'inline-block' => 'block',
588     }->{$specified_value->[1]} || $specified_value->[1]];
589     }
590     } else {
591     return $specified_value; ## Maybe an error of the implementation.
592     }
593     },
594 wakaba 1.6 };
595     $Attr->{display} = $Prop->{display};
596     $Key->{display} = $Prop->{display};
597    
598     $Prop->{position} = {
599     css => 'position',
600     dom => 'position',
601     key => 'position',
602     parse => $one_keyword_parser,
603 wakaba 1.11 serialize => $default_serializer,
604 wakaba 1.6 keyword => {
605     static => 1, relative => 1, absolute => 1, fixed => 1,
606     },
607 wakaba 1.10 initial => ["KEYWORD", "static"],
608 wakaba 1.9 #inherited => 0,
609     compute => $compute_as_specified,
610 wakaba 1.6 };
611     $Attr->{position} = $Prop->{position};
612     $Key->{position} = $Prop->{position};
613    
614     $Prop->{float} = {
615     css => 'float',
616     dom => 'css_float',
617     key => 'float',
618     parse => $one_keyword_parser,
619 wakaba 1.11 serialize => $default_serializer,
620 wakaba 1.6 keyword => {
621     left => 1, right => 1, none => 1,
622     },
623 wakaba 1.9 initial => ["KEYWORD", "none"],
624     #inherited => 0,
625     compute => sub {
626     my ($self, $element, $prop_name, $specified_value) = @_;
627     ## NOTE: CSS 2.1 Section 9.7.
628    
629     ## WARNING: |compute| for 'display' property invoke this CODE
630     ## in some case. Careless modification might cause a infinite loop.
631    
632 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
633 wakaba 1.9 if ($specified_value->[1] eq 'none') {
634     ## Case 1 [CSS 2.1]
635     return $specified_value;
636     } else {
637     my $position = $self->get_computed_value ($element, 'position');
638     if ($position->[0] eq 'KEYWORD' and
639     ($position->[1] eq 'absolute' or
640     $position->[1] eq 'fixed')) {
641     ## Case 2 [CSS 2.1]
642     return ["KEYWORD", "none"];
643     }
644     }
645     }
646    
647     ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
648     ## on computed value of 'float' property.
649    
650     ## Case 3, 4, and 5 [CSS 2.1]
651     return $specified_value;
652     },
653 wakaba 1.6 };
654     $Attr->{css_float} = $Prop->{float};
655     $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
656     $Key->{float} = $Prop->{float};
657    
658     $Prop->{clear} = {
659     css => 'clear',
660     dom => 'clear',
661     key => 'clear',
662     parse => $one_keyword_parser,
663 wakaba 1.11 serialize => $default_serializer,
664 wakaba 1.6 keyword => {
665     left => 1, right => 1, none => 1, both => 1,
666     },
667 wakaba 1.9 initial => ["KEYWORD", "none"],
668     #inherited => 0,
669     compute => $compute_as_specified,
670 wakaba 1.6 };
671     $Attr->{clear} = $Prop->{clear};
672     $Key->{clear} = $Prop->{clear};
673    
674     $Prop->{direction} = {
675     css => 'direction',
676     dom => 'direction',
677     key => 'direction',
678     parse => $one_keyword_parser,
679 wakaba 1.11 serialize => $default_serializer,
680 wakaba 1.6 keyword => {
681     ltr => 1, rtl => 1,
682     },
683 wakaba 1.9 initial => ["KEYWORD", "ltr"],
684     inherited => 1,
685     compute => $compute_as_specified,
686 wakaba 1.6 };
687     $Attr->{direction} = $Prop->{direction};
688     $Key->{direction} = $Prop->{direction};
689    
690     $Prop->{'unicode-bidi'} = {
691     css => 'unicode-bidi',
692     dom => 'unicode_bidi',
693     key => 'unicode_bidi',
694     parse => $one_keyword_parser,
695 wakaba 1.11 serialize => $default_serializer,
696 wakaba 1.6 keyword => {
697     normal => 1, embed => 1, 'bidi-override' => 1,
698     },
699 wakaba 1.9 initial => ["KEYWORD", "normal"],
700     #inherited => 0,
701     compute => $compute_as_specified,
702 wakaba 1.6 };
703     $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
704     $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
705    
706 wakaba 1.11 $Prop->{overflow} = {
707     css => 'overflow',
708     dom => 'overflow',
709     key => 'overflow',
710     parse => $one_keyword_parser,
711     serialize => $default_serializer,
712     keyword => {
713     visible => 1, hidden => 1, scroll => 1, auto => 1,
714     },
715     initial => ["KEYWORD", "visible"],
716     #inherited => 0,
717     compute => $compute_as_specified,
718     };
719     $Attr->{overflow} = $Prop->{overflow};
720     $Key->{overflow} = $Prop->{overflow};
721    
722     $Prop->{visibility} = {
723     css => 'visibility',
724     dom => 'visibility',
725     key => 'visibility',
726     parse => $one_keyword_parser,
727     serialize => $default_serializer,
728     keyword => {
729     visible => 1, hidden => 1, collapse => 1,
730     },
731     initial => ["KEYWORD", "visible"],
732     #inherited => 0,
733     compute => $compute_as_specified,
734     };
735     $Attr->{visibility} = $Prop->{visibility};
736     $Key->{visibility} = $Prop->{visibility};
737    
738     $Prop->{'list-style-type'} = {
739     css => 'list-style-type',
740     dom => 'list_style_type',
741     key => 'list_style_type',
742     parse => $one_keyword_parser,
743     serialize => $default_serializer,
744     keyword => {
745     qw/
746     disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
747     lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
748     upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
749     none 1
750     /,
751     },
752     initial => ["KEYWORD", 'disc'],
753     inherited => 1,
754     compute => $compute_as_specified,
755     };
756     $Attr->{list_style_type} = $Prop->{'list-style-type'};
757     $Key->{list_style_type} = $Prop->{'list-style-type'};
758    
759     $Prop->{'list-style-position'} = {
760     css => 'list-style-position',
761     dom => 'list_style_position',
762     key => 'list_style_position',
763     parse => $one_keyword_parser,
764     serialize => $default_serializer,
765     keyword => {
766     inside => 1, outside => 1,
767     },
768     initial => ["KEYWORD", 'outside'],
769     inherited => 1,
770     compute => $compute_as_specified,
771     };
772     $Attr->{list_style_position} = $Prop->{'list-style-position'};
773     $Key->{list_style_position} = $Prop->{'list-style-position'};
774    
775 wakaba 1.12 $Prop->{'page-break-before'} = {
776     css => 'page-break-before',
777     dom => 'page_break_before',
778     key => 'page_break_before',
779     parse => $one_keyword_parser,
780     serialize => $default_serializer,
781     keyword => {
782     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
783     },
784     initial => ["KEYWORD", 'auto'],
785 wakaba 1.15 #inherited => 0,
786 wakaba 1.12 compute => $compute_as_specified,
787     };
788     $Attr->{page_break_before} = $Prop->{'page-break-before'};
789     $Key->{page_break_before} = $Prop->{'page-break-before'};
790    
791     $Prop->{'page-break-after'} = {
792     css => 'page-break-after',
793     dom => 'page_break_after',
794     key => 'page_break_after',
795     parse => $one_keyword_parser,
796     serialize => $default_serializer,
797     keyword => {
798     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
799     },
800     initial => ["KEYWORD", 'auto'],
801 wakaba 1.15 #inherited => 0,
802 wakaba 1.12 compute => $compute_as_specified,
803     };
804     $Attr->{page_break_after} = $Prop->{'page-break-after'};
805     $Key->{page_break_after} = $Prop->{'page-break-after'};
806    
807     $Prop->{'page-break-inside'} = {
808     css => 'page-break-inside',
809     dom => 'page_break_inside',
810     key => 'page_break_inside',
811     parse => $one_keyword_parser,
812     serialize => $default_serializer,
813     keyword => {
814     auto => 1, avoid => 1,
815     },
816     initial => ["KEYWORD", 'auto'],
817     inherited => 1,
818     compute => $compute_as_specified,
819     };
820     $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
821     $Key->{page_break_inside} = $Prop->{'page-break-inside'};
822    
823 wakaba 1.15 $Prop->{'background-repeat'} = {
824     css => 'background-repeat',
825     dom => 'background_repeat',
826     key => 'background_repeat',
827     parse => $one_keyword_parser,
828     serialize => $default_serializer,
829     keyword => {
830     repeat => 1, 'repeat-x' => 1, 'repeat-y' => 1, 'no-repeat' => 1,
831     },
832     initial => ["KEYWORD", 'repeat'],
833     #inherited => 0,
834     compute => $compute_as_specified,
835     };
836     $Attr->{background_repeat} = $Prop->{'background-repeat'};
837     $Key->{backgroud_repeat} = $Prop->{'background-repeat'};
838    
839     $Prop->{'background-attachment'} = {
840     css => 'background-attachment',
841     dom => 'background_attachment',
842     key => 'background_attachment',
843     parse => $one_keyword_parser,
844     serialize => $default_serializer,
845     keyword => {
846     scroll => 1, fixed => 1,
847     },
848     initial => ["KEYWORD", 'scroll'],
849     #inherited => 0,
850     compute => $compute_as_specified,
851     };
852     $Attr->{background_attachment} = $Prop->{'background-attachment'};
853     $Key->{backgroud_attachment} = $Prop->{'background-attachment'};
854    
855     $Prop->{'font-style'} = {
856     css => 'font-style',
857     dom => 'font_size',
858     key => 'font_size',
859     parse => $one_keyword_parser,
860     serialize => $default_serializer,
861     keyword => {
862     normal => 1, italic => 1, oblique => 1,
863     },
864     initial => ["KEYWORD", 'normal'],
865     inherited => 1,
866     compute => $compute_as_specified,
867     };
868     $Attr->{font_style} = $Prop->{'font-style'};
869     $Key->{font_style} = $Prop->{'font-style'};
870    
871     $Prop->{'font-variant'} = {
872     css => 'font-variant',
873     dom => 'font_variant',
874     key => 'font_variant',
875     parse => $one_keyword_parser,
876     serialize => $default_serializer,
877     keyword => {
878     normal => 1, 'small-caps' => 1,
879     },
880     initial => ["KEYWORD", 'normal'],
881     inherited => 1,
882     compute => $compute_as_specified,
883     };
884     $Attr->{font_variant} = $Prop->{'font-variant'};
885     $Key->{font_variant} = $Prop->{'font-variant'};
886    
887 wakaba 1.16 $Prop->{'text-align'} = {
888     css => 'text-align',
889     dom => 'text_align',
890     key => 'text_align',
891     parse => $one_keyword_parser,
892     serialize => $default_serializer,
893     keyword => {
894     left => 1, right => 1, center => 1, justify => 1, ## CSS 2
895     begin => 1, end => 1, ## CSS 3
896     },
897     initial => ["KEYWORD", 'begin'],
898     inherited => 1,
899     compute => $compute_as_specified,
900     };
901     $Attr->{text_align} = $Prop->{'text-align'};
902     $Key->{text_align} = $Prop->{'text-align'};
903    
904     $Prop->{'text-transform'} = {
905     css => 'text-transform',
906     dom => 'text_transform',
907     key => 'text_transform',
908     parse => $one_keyword_parser,
909     serialize => $default_serializer,
910     keyword => {
911     capitalize => 1, uppercase => 1, lowercase => 1, none => 1,
912     },
913     initial => ["KEYWORD", 'none'],
914     inherited => 1,
915     compute => $compute_as_specified,
916     };
917     $Attr->{text_transform} = $Prop->{'text-transform'};
918     $Key->{text_transform} = $Prop->{'text-transform'};
919    
920     $Prop->{'white-space'} = {
921     css => 'white-space',
922     dom => 'white_space',
923     key => 'white_space',
924     parse => $one_keyword_parser,
925     serialize => $default_serializer,
926     keyword => {
927     normal => 1, pre => 1, nowrap => 1, 'pre-wrap' => 1, 'pre-line' => 1,
928     },
929     initial => ["KEYWORD", 'normal'],
930     inherited => 1,
931     compute => $compute_as_specified,
932     };
933     $Attr->{white_space} = $Prop->{'white-space'};
934     $Key->{white_space} = $Prop->{'white-space'};
935    
936     $Prop->{'caption-side'} = {
937     css => 'caption-side',
938     dom => 'caption_side',
939     key => 'caption_side',
940     parse => $one_keyword_parser,
941     serialize => $default_serializer,
942     keyword => {
943     top => 1, bottom => 1,
944     },
945     initial => ['KEYWORD', 'top'],
946     inherited => 1,
947     compute => $compute_as_specified,
948     };
949     $Attr->{caption_side} = $Prop->{'caption-side'};
950     $Key->{caption_side} = $Prop->{'caption-side'};
951    
952     $Prop->{'table-layout'} = {
953     css => 'table-layout',
954     dom => 'table_layout',
955     key => 'table_layout',
956     parse => $one_keyword_parser,
957     serialize => $default_serializer,
958     keyword => {
959     auto => 1, fixed => 1,
960     },
961     initial => ['KEYWORD', 'auto'],
962     #inherited => 0,
963     compute => $compute_as_specified,
964     };
965     $Attr->{table_layout} = $Prop->{'table-layout'};
966     $Key->{table_layout} = $Prop->{'table-layout'};
967    
968     $Prop->{'border-collapse'} = {
969     css => 'border-collapse',
970     dom => 'border_collapse',
971     key => 'border_collapse',
972     parse => $one_keyword_parser,
973     serialize => $default_serializer,
974     keyword => {
975     collapse => 1, separate => 1,
976     },
977     initial => ['KEYWORD', 'separate'],
978     inherited => 1,
979     compute => $compute_as_specified,
980     };
981     $Attr->{border_collapse} = $Prop->{'border-collapse'};
982     $Key->{border_collapse} = $Prop->{'border-collapse'};
983    
984     $Prop->{'empty-cells'} = {
985     css => 'empty-cells',
986     dom => 'empty_cells',
987     key => 'empty_cells',
988     parse => $one_keyword_parser,
989     serialize => $default_serializer,
990     keyword => {
991     show => 1, hide => 1,
992     },
993     initial => ['KEYWORD', 'show'],
994     inherited => 1,
995     compute => $compute_as_specified,
996     };
997     $Attr->{empty_cells} = $Prop->{'empty-cells'};
998     $Key->{empty_cells} = $Prop->{'empty-cells'};
999    
1000 wakaba 1.11 $Prop->{'z-index'} = {
1001     css => 'z-index',
1002     dom => 'z_index',
1003     key => 'z_index',
1004     parse => sub {
1005     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1006    
1007 wakaba 1.12 my $sign = 1;
1008     if ($t->{type} == MINUS_TOKEN) {
1009     $sign = -1;
1010     $t = $tt->get_next_token;
1011     }
1012    
1013 wakaba 1.11 if ($t->{type} == NUMBER_TOKEN) {
1014     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
1015     ## browser compatibility issue.
1016     my $value = $t->{number};
1017     $t = $tt->get_next_token;
1018 wakaba 1.12 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1019     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1020 wakaba 1.11 my $value = lc $t->{value}; ## TODO: case
1021     $t = $tt->get_next_token;
1022     if ($value eq 'auto') {
1023     ## NOTE: |z-index| is the default value and therefore it must be
1024     ## supported anyway.
1025     return ($t, {$prop_name => ["KEYWORD", 'auto']});
1026     } elsif ($value eq 'inherit') {
1027     return ($t, {$prop_name => ['INHERIT']});
1028     }
1029     }
1030    
1031     $onerror->(type => 'syntax error:'.$prop_name,
1032     level => $self->{must_level},
1033     token => $t);
1034     return ($t, undef);
1035     },
1036     serialize => $default_serializer,
1037     initial => ['KEYWORD', 'auto'],
1038     #inherited => 0,
1039     compute => $compute_as_specified,
1040     };
1041     $Attr->{z_index} = $Prop->{'z-index'};
1042     $Key->{z_index} = $Prop->{'z-index'};
1043    
1044 wakaba 1.12 $Prop->{orphans} = {
1045     css => 'orphans',
1046     dom => 'orphans',
1047     key => 'orphans',
1048     parse => sub {
1049     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1050    
1051     my $sign = 1;
1052     if ($t->{type} == MINUS_TOKEN) {
1053     $t = $tt->get_next_token;
1054     $sign = -1;
1055     }
1056    
1057     if ($t->{type} == NUMBER_TOKEN) {
1058     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
1059     ## <http://suika.fam.cx/gate/2005/sw/widows> for
1060     ## browser compatibility issue.
1061     my $value = $t->{number};
1062     $t = $tt->get_next_token;
1063     return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1064     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1065     my $value = lc $t->{value}; ## TODO: case
1066     $t = $tt->get_next_token;
1067     if ($value eq 'inherit') {
1068     return ($t, {$prop_name => ['INHERIT']});
1069     }
1070     }
1071    
1072     $onerror->(type => 'syntax error:'.$prop_name,
1073     level => $self->{must_level},
1074     token => $t);
1075     return ($t, undef);
1076     },
1077     serialize => $default_serializer,
1078     initial => ['NUMBER', 2],
1079     inherited => 1,
1080     compute => $compute_as_specified,
1081     };
1082     $Attr->{orphans} = $Prop->{orphans};
1083     $Key->{orphans} = $Prop->{orphans};
1084    
1085     $Prop->{widows} = {
1086     css => 'widows',
1087     dom => 'widows',
1088     key => 'widows',
1089     parse => $Prop->{orphans}->{parse},
1090     serialize => $default_serializer,
1091     initial => ['NUMBER', 2],
1092     inherited => 1,
1093     compute => $compute_as_specified,
1094     };
1095     $Attr->{widows} = $Prop->{widows};
1096     $Key->{widows} = $Prop->{widows};
1097    
1098 wakaba 1.15 $Prop->{'font-weight'} = {
1099     css => 'font-weight',
1100     dom => 'font_weight',
1101     key => 'font_weight',
1102     parse => sub {
1103     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1104    
1105     if ($t->{type} == NUMBER_TOKEN) {
1106     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
1107     ## browser compatibility issue.
1108     my $value = $t->{number};
1109     $t = $tt->get_next_token;
1110     if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
1111     return ($t, {$prop_name => ['WEIGHT', $value, 0]});
1112     }
1113     } elsif ($t->{type} == IDENT_TOKEN) {
1114     my $value = lc $t->{value}; ## TODO: case
1115     $t = $tt->get_next_token;
1116     if ({
1117     normal => 1, bold => 1, bolder => 1, lighter => 1,
1118     }->{$value}) {
1119     return ($t, {$prop_name => ['KEYWORD', $value]});
1120     } elsif ($value eq 'inherit') {
1121     return ($t, {$prop_name => ['INHERIT']});
1122     }
1123     }
1124    
1125     $onerror->(type => 'syntax error:'.$prop_name,
1126     level => $self->{must_level},
1127     token => $t);
1128     return ($t, undef);
1129     },
1130     serialize => $default_serializer,
1131     initial => ['KEYWORD', 'normal'],
1132     inherited => 1,
1133     compute => sub {
1134     my ($self, $element, $prop_name, $specified_value) = @_;
1135    
1136 wakaba 1.17 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1137 wakaba 1.15 if ($specified_value->[1] eq 'normal') {
1138     return ['WEIGHT', 400, 0];
1139     } elsif ($specified_value->[1] eq 'bold') {
1140     return ['WEIGHT', 700, 0];
1141     } elsif ($specified_value->[1] eq 'bolder') {
1142     my $parent_element = $element->manakai_parent_element;
1143     if (defined $parent_element) {
1144     my $parent_value = $self->get_cascaded_value
1145     ($parent_element, $prop_name); ## NOTE: What Firefox does.
1146     return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
1147     } else {
1148     return ['WEIGHT', 400, 1];
1149     }
1150     } elsif ($specified_value->[1] eq 'lighter') {
1151     my $parent_element = $element->manakai_parent_element;
1152     if (defined $parent_element) {
1153     my $parent_value = $self->get_cascaded_value
1154     ($parent_element, $prop_name); ## NOTE: What Firefox does.
1155     return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
1156     } else {
1157     return ['WEIGHT', 400, 1];
1158     }
1159     }
1160 wakaba 1.17 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
1161 wakaba 1.15 #
1162     }
1163    
1164     return $specified_value;
1165     },
1166     };
1167     $Attr->{font_weight} = $Prop->{'font-weight'};
1168     $Key->{font_weight} = $Prop->{'font-weight'};
1169    
1170 wakaba 1.13 my $uri_or_none_parser = sub {
1171 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1172    
1173 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
1174 wakaba 1.11 my $value = $t->{value};
1175     $t = $tt->get_next_token;
1176     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
1177     } elsif ($t->{type} == IDENT_TOKEN) {
1178     my $value = lc $t->{value}; ## TODO: case
1179     $t = $tt->get_next_token;
1180     if ($value eq 'none') {
1181     ## NOTE: |none| is the default value and therefore it must be
1182     ## supported anyway.
1183     return ($t, {$prop_name => ["KEYWORD", 'none']});
1184     } elsif ($value eq 'inherit') {
1185     return ($t, {$prop_name => ['INHERIT']});
1186     }
1187     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
1188     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
1189     # my $value = $t->{value};
1190     # $t = $tt->get_next_token;
1191     # if ($t->{type} == EOF_TOKEN) {
1192     # $onerror->(type => 'syntax error:eof:'.$prop_name,
1193     # level => $self->{must_level},
1194     # token => $t);
1195     #
1196     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
1197     # }
1198     }
1199    
1200     $onerror->(type => 'syntax error:'.$prop_name,
1201     level => $self->{must_level},
1202     token => $t);
1203     return ($t, undef);
1204 wakaba 1.13 }; # $uri_or_none_parser
1205    
1206 wakaba 1.14 my $compute_uri_or_none = sub {
1207 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
1208    
1209     if (defined $specified_value and
1210     $specified_value->[0] eq 'URI' and
1211     defined $specified_value->[2]) {
1212     require Message::DOM::DOMImplementation;
1213     return ['URI',
1214     Message::DOM::DOMImplementation->create_uri_reference
1215     ($specified_value->[1])
1216     ->get_absolute_reference (${$specified_value->[2]})
1217     ->get_uri_reference,
1218     $specified_value->[2]];
1219     }
1220    
1221     return $specified_value;
1222 wakaba 1.14 }; # $compute_uri_or_none
1223    
1224     $Prop->{'list-style-image'} = {
1225     css => 'list-style-image',
1226     dom => 'list_style_image',
1227     key => 'list_style_image',
1228     parse => $uri_or_none_parser,
1229     serialize => $default_serializer,
1230     initial => ['KEYWORD', 'none'],
1231     inherited => 1,
1232     compute => $compute_uri_or_none,
1233 wakaba 1.11 };
1234     $Attr->{list_style_image} = $Prop->{'list-style-image'};
1235     $Key->{list_style_image} = $Prop->{'list-style-image'};
1236    
1237 wakaba 1.15 $Prop->{'background-image'} = {
1238     css => 'background-image',
1239     dom => 'background_image',
1240     key => 'background_image',
1241     parse => $uri_or_none_parser,
1242     serialize => $default_serializer,
1243     initial => ['KEYWORD', 'none'],
1244     #inherited => 0,
1245     compute => $compute_uri_or_none,
1246     };
1247     $Attr->{background_image} = $Prop->{'background-image'};
1248     $Key->{background_image} = $Prop->{'background-image'};
1249    
1250 wakaba 1.7 my $border_style_keyword = {
1251     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
1252     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
1253     };
1254    
1255     $Prop->{'border-top-style'} = {
1256     css => 'border-top-style',
1257     dom => 'border_top_style',
1258     key => 'border_top_style',
1259     parse => $one_keyword_parser,
1260 wakaba 1.11 serialize => $default_serializer,
1261 wakaba 1.7 keyword => $border_style_keyword,
1262 wakaba 1.9 initial => ["KEYWORD", "none"],
1263     #inherited => 0,
1264     compute => $compute_as_specified,
1265 wakaba 1.7 };
1266     $Attr->{border_top_style} = $Prop->{'border-top-style'};
1267     $Key->{border_top_style} = $Prop->{'border-top-style'};
1268    
1269     $Prop->{'border-right-style'} = {
1270     css => 'border-right-style',
1271     dom => 'border_right_style',
1272     key => 'border_right_style',
1273     parse => $one_keyword_parser,
1274 wakaba 1.11 serialize => $default_serializer,
1275 wakaba 1.7 keyword => $border_style_keyword,
1276 wakaba 1.9 initial => ["KEYWORD", "none"],
1277     #inherited => 0,
1278     compute => $compute_as_specified,
1279 wakaba 1.7 };
1280     $Attr->{border_right_style} = $Prop->{'border-right-style'};
1281     $Key->{border_right_style} = $Prop->{'border-right-style'};
1282    
1283     $Prop->{'border-bottom-style'} = {
1284     css => 'border-bottom-style',
1285     dom => 'border_bottom_style',
1286     key => 'border_bottom_style',
1287     parse => $one_keyword_parser,
1288 wakaba 1.11 serialize => $default_serializer,
1289 wakaba 1.7 keyword => $border_style_keyword,
1290 wakaba 1.9 initial => ["KEYWORD", "none"],
1291     #inherited => 0,
1292     compute => $compute_as_specified,
1293 wakaba 1.7 };
1294     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
1295     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
1296    
1297     $Prop->{'border-left-style'} = {
1298     css => 'border-left-style',
1299     dom => 'border_left_style',
1300     key => 'border_left_style',
1301     parse => $one_keyword_parser,
1302 wakaba 1.11 serialize => $default_serializer,
1303 wakaba 1.7 keyword => $border_style_keyword,
1304 wakaba 1.9 initial => ["KEYWORD", "none"],
1305     #inherited => 0,
1306     compute => $compute_as_specified,
1307 wakaba 1.7 };
1308     $Attr->{border_left_style} = $Prop->{'border-left-style'};
1309     $Key->{border_left_style} = $Prop->{'border-left-style'};
1310    
1311 wakaba 1.16 $Prop->{'outline-style'} = {
1312     css => 'outline-style',
1313     dom => 'outline_style',
1314     key => 'outline_style',
1315     parse => $one_keyword_parser,
1316     serialize => $default_serializer,
1317     keyword => $border_style_keyword,
1318     initial => ['KEYWORD', 'none'],
1319     #inherited => 0,
1320     compute => $compute_as_specified,
1321     };
1322     $Attr->{outline_style} = $Prop->{'outline-style'};
1323     $Key->{outline_style} = $Prop->{'outline-style'};
1324    
1325 wakaba 1.15 $Prop->{'font-family'} = {
1326     css => 'font-family',
1327     dom => 'font_family',
1328     key => 'font_family',
1329     parse => sub {
1330     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1331    
1332     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
1333     ## how chaotic browsers are!
1334    
1335     my @prop_value;
1336    
1337     my $font_name = '';
1338     my $may_be_generic = 1;
1339     my $may_be_inherit = 1;
1340     my $has_s = 0;
1341     F: {
1342     if ($t->{type} == IDENT_TOKEN) {
1343     undef $may_be_inherit if $has_s or length $font_name;
1344     undef $may_be_generic if $has_s or length $font_name;
1345     $font_name .= ' ' if $has_s;
1346     $font_name .= $t->{value};
1347     undef $has_s;
1348     $t = $tt->get_next_token;
1349     } elsif ($t->{type} == STRING_TOKEN) {
1350     $font_name .= ' ' if $has_s;
1351     $font_name .= $t->{value};
1352     undef $may_be_inherit;
1353     undef $may_be_generic;
1354     undef $has_s;
1355     $t = $tt->get_next_token;
1356     } elsif ($t->{type} == COMMA_TOKEN) {
1357     if ($may_be_generic and
1358     {
1359     serif => 1, 'sans-serif' => 1, cursive => 1,
1360     fantasy => 1, monospace => 1, '-manakai-default' => 1,
1361     }->{lc $font_name}) { ## TODO: case
1362     push @prop_value, ['KEYWORD', $font_name];
1363     } elsif (not $may_be_generic or length $font_name) {
1364     push @prop_value, ["STRING", $font_name];
1365     }
1366     undef $may_be_inherit;
1367     $may_be_generic = 1;
1368     undef $has_s;
1369     $font_name = '';
1370     $t = $tt->get_next_token;
1371     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1372     } elsif ($t->{type} == S_TOKEN) {
1373     $has_s = 1;
1374     $t = $tt->get_next_token;
1375     } else {
1376     if ($may_be_generic and
1377     {
1378     serif => 1, 'sans-serif' => 1, cursive => 1,
1379     fantasy => 1, monospace => 1, '-manakai-default' => 1,
1380     }->{lc $font_name}) { ## TODO: case
1381     push @prop_value, ['KEYWORD', $font_name];
1382     } elsif (not $may_be_generic or length $font_name) {
1383     push @prop_value, ['STRING', $font_name];
1384     } else {
1385     $onerror->(type => 'syntax error:'.$prop_name,
1386     level => $self->{must_level},
1387     token => $t);
1388     return ($t, undef);
1389     }
1390     last F;
1391     }
1392     redo F;
1393     } # F
1394    
1395     if ($may_be_inherit and
1396     @prop_value == 1 and
1397     $prop_value[0]->[0] eq 'STRING' and
1398     lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
1399     return ($t, {$prop_name => ['INHERIT']});
1400     } else {
1401     unshift @prop_value, 'FONT';
1402     return ($t, {$prop_name => \@prop_value});
1403     }
1404     },
1405     serialize => sub {
1406     my ($self, $prop_name, $value) = @_;
1407    
1408     if ($value->[0] eq 'FONT') {
1409     return join ', ', map {
1410     if ($_->[0] eq 'STRING') {
1411     '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
1412     } elsif ($_->[0] eq 'KEYWORD') {
1413     $_->[1]; ## NOTE: This is what Firefox does.
1414     } else {
1415     ## NOTE: This should be an error.
1416     '""';
1417     }
1418     } @$value[1..$#$value];
1419     } elsif ($value->[0] eq 'INHERIT') {
1420     return 'inherit';
1421     } else {
1422     return undef;
1423     }
1424     },
1425     initial => ['FONT', ['KEYWORD', '-manakai-default']],
1426     inherited => 1,
1427     compute => $compute_as_specified,
1428     };
1429     $Attr->{font_family} = $Prop->{'font-family'};
1430     $Key->{font_family} = $Prop->{'font-family'};
1431    
1432 wakaba 1.17 $Prop->{cursor} = {
1433     css => 'cursor',
1434     dom => 'cursor',
1435     key => 'cursor',
1436     parse => sub {
1437     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1438    
1439     ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
1440     ## compatibility issues.
1441    
1442     my @prop_value = ('CURSOR');
1443    
1444     F: {
1445     if ($t->{type} == IDENT_TOKEN) {
1446     my $v = lc $t->{value}; ## TODO: case
1447     $t = $tt->get_next_token;
1448     if ($Prop->{$prop_name}->{keyword}->{$v}) {
1449     push @prop_value, ['KEYWORD', $v];
1450     last F;
1451     } elsif ($v eq 'inherit' and @prop_value == 1) {
1452     return ($t, {$prop_name => ['INHERIT']});
1453     } else {
1454     $onerror->(type => 'syntax error:'.$prop_name,
1455     level => $self->{must_level},
1456     token => $t);
1457     return ($t, undef);
1458     }
1459     } elsif ($t->{type} == URI_TOKEN) {
1460     push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
1461     $t = $tt->get_next_token;
1462     } else {
1463     $onerror->(type => 'syntax error:'.$prop_name,
1464     level => $self->{must_level},
1465     token => $t);
1466     return ($t, undef);
1467     }
1468    
1469     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1470     if ($t->{type} == COMMA_TOKEN) {
1471     $t = $tt->get_next_token;
1472     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1473     redo F;
1474     }
1475     } # F
1476    
1477     return ($t, {$prop_name => \@prop_value});
1478     },
1479     serialize => sub {
1480     my ($self, $prop_name, $value) = @_;
1481    
1482     if ($value->[0] eq 'CURSOR') {
1483     return join ', ', map {
1484     if ($_->[0] eq 'URI') {
1485     'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
1486     } elsif ($_->[0] eq 'KEYWORD') {
1487     $_->[1];
1488     } else {
1489     ## NOTE: This should be an error.
1490     '""';
1491     }
1492     } @$value[1..$#$value];
1493     } elsif ($value->[0] eq 'INHERIT') {
1494     return 'inherit';
1495     } else {
1496     return undef;
1497     }
1498     },
1499     keyword => {
1500     auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
1501     'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
1502     'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
1503     'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
1504     },
1505     initial => ['CURSOR', ['KEYWORD', 'auto']],
1506     inherited => 1,
1507     compute => sub {
1508     my ($self, $element, $prop_name, $specified_value) = @_;
1509    
1510     if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
1511     my @new_value = ('CURSOR');
1512     for my $value (@$specified_value[1..$#$specified_value]) {
1513     if ($value->[0] eq 'URI') {
1514     if (defined $value->[2]) {
1515     require Message::DOM::DOMImplementation;
1516     push @new_value, ['URI',
1517     Message::DOM::DOMImplementation
1518     ->create_uri_reference ($value->[1])
1519     ->get_absolute_reference (${$value->[2]})
1520     ->get_uri_reference,
1521     $value->[2]];
1522     } else {
1523     push @new_value, $value;
1524     }
1525     } else {
1526     push @new_value, $value;
1527     }
1528     }
1529     return \@new_value;
1530     }
1531    
1532     return $specified_value;
1533     },
1534     };
1535     $Attr->{cursor} = $Prop->{cursor};
1536     $Key->{cursor} = $Prop->{cursor};
1537    
1538 wakaba 1.7 $Prop->{'border-style'} = {
1539     css => 'border-style',
1540     dom => 'border_style',
1541     parse => sub {
1542     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1543    
1544     my %prop_value;
1545     my $has_inherit;
1546     if ($t->{type} == IDENT_TOKEN) {
1547     my $prop_value = lc $t->{value}; ## TODO: case folding
1548     $t = $tt->get_next_token;
1549     if ($border_style_keyword->{$prop_value} and
1550     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
1551     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
1552     } elsif ($prop_value eq 'inherit') {
1553 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
1554 wakaba 1.7 $has_inherit = 1;
1555     } else {
1556     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1557     level => $self->{must_level},
1558     token => $t);
1559     return ($t, undef);
1560     }
1561     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
1562     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
1563     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
1564     } else {
1565     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1566     level => $self->{must_level},
1567     token => $t);
1568     return ($t, undef);
1569     }
1570    
1571     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1572     if ($t->{type} == IDENT_TOKEN) {
1573     my $prop_value = lc $t->{value}; ## TODO: case folding
1574     $t = $tt->get_next_token;
1575     if (not $has_inherit and
1576     $border_style_keyword->{$prop_value} and
1577     $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
1578     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
1579     } else {
1580     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1581     level => $self->{must_level},
1582     token => $t);
1583     return ($t, undef);
1584     }
1585     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
1586    
1587     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1588     if ($t->{type} == IDENT_TOKEN) {
1589     my $prop_value = lc $t->{value}; ## TODO: case folding
1590     $t = $tt->get_next_token;
1591     if ($border_style_keyword->{$prop_value} and
1592     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
1593     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
1594     } else {
1595     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1596     level => $self->{must_level},
1597     token => $t);
1598     return ($t, undef);
1599     }
1600    
1601     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1602     if ($t->{type} == IDENT_TOKEN) {
1603     my $prop_value = lc $t->{value}; ## TODO: case folding
1604     $t = $tt->get_next_token;
1605     if ($border_style_keyword->{$prop_value} and
1606     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
1607     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
1608     } else {
1609     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1610     level => $self->{must_level},
1611     token => $t);
1612     return ($t, undef);
1613     }
1614     }
1615     }
1616     }
1617    
1618     return ($t, \%prop_value);
1619     },
1620     serialize => sub {
1621     my ($self, $prop_name, $value) = @_;
1622    
1623     local $Error::Depth = $Error::Depth + 1;
1624     my @v;
1625     push @v, $self->border_top_style;
1626     return undef unless defined $v[-1];
1627     push @v, $self->border_right_style;
1628     return undef unless defined $v[-1];
1629     push @v, $self->border_bottom_style;
1630     return undef unless defined $v[-1];
1631     push @v, $self->border_bottom_style;
1632     return undef unless defined $v[-1];
1633    
1634     pop @v if $v[1] eq $v[3];
1635     pop @v if $v[0] eq $v[2];
1636     pop @v if $v[0] eq $v[1];
1637     return join ' ', @v;
1638     },
1639     };
1640     $Attr->{border_style} = $Prop->{'border-style'};
1641    
1642 wakaba 1.12 $Prop->{'list-style'} = {
1643     css => 'list-style',
1644     dom => 'list_style',
1645     parse => sub {
1646     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1647    
1648     my %prop_value;
1649     my $none = 0;
1650    
1651     F: for my $f (1..3) {
1652     if ($t->{type} == IDENT_TOKEN) {
1653     my $prop_value = lc $t->{value}; ## TODO: case folding
1654     $t = $tt->get_next_token;
1655    
1656     if ($prop_value eq 'none') {
1657     $none++;
1658     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
1659     if (exists $prop_value{'list-style-type'}) {
1660     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1661     $prop_name,
1662     level => $self->{must_level},
1663     token => $t);
1664     return ($t, undef);
1665     } else {
1666     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
1667     }
1668     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
1669     if (exists $prop_value{'list-style-position'}) {
1670     $onerror->(type => q[syntax error:duplicate:'list-style-position':].
1671     $prop_name,
1672     level => $self->{must_level},
1673     token => $t);
1674     return ($t, undef);
1675     }
1676    
1677     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
1678     } elsif ($f == 1 and $prop_value eq 'inherit') {
1679     $prop_value{'list-style-type'} = ["INHERIT"];
1680     $prop_value{'list-style-position'} = ["INHERIT"];
1681     $prop_value{'list-style-image'} = ["INHERIT"];
1682     last F;
1683     } else {
1684     if ($f == 1) {
1685     $onerror->(type => 'syntax error:'.$prop_name,
1686     level => $self->{must_level},
1687     token => $t);
1688     return ($t, undef);
1689     } else {
1690     last F;
1691     }
1692     }
1693     } elsif ($t->{type} == URI_TOKEN) {
1694     if (exists $prop_value{'list-style-image'}) {
1695     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1696     $prop_name,
1697     level => $self->{must_level},
1698     token => $t);
1699     return ($t, undef);
1700     }
1701    
1702     $prop_value{'list-style-image'}
1703 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
1704 wakaba 1.12 $t = $tt->get_next_token;
1705     } else {
1706     if ($f == 1) {
1707     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1708     level => $self->{must_level},
1709     token => $t);
1710     return ($t, undef);
1711     } else {
1712     last F;
1713     }
1714     }
1715    
1716     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1717     } # F
1718     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
1719    
1720     if ($none == 1) {
1721     if (exists $prop_value{'list-style-type'}) {
1722     if (exists $prop_value{'list-style-image'}) {
1723     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1724     $prop_name,
1725     level => $self->{must_level},
1726     token => $t);
1727     return ($t, undef);
1728     } else {
1729     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
1730     }
1731     } else {
1732     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
1733     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
1734     unless exists $prop_value{'list-style-image'};
1735     }
1736     } elsif ($none == 2) {
1737     if (exists $prop_value{'list-style-type'}) {
1738     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1739     $prop_name,
1740     level => $self->{must_level},
1741     token => $t);
1742     return ($t, undef);
1743     }
1744     if (exists $prop_value{'list-style-image'}) {
1745     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1746     $prop_name,
1747     level => $self->{must_level},
1748     token => $t);
1749     return ($t, undef);
1750     }
1751    
1752     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
1753     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
1754     } elsif ($none == 3) {
1755     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1756     $prop_name,
1757     level => $self->{must_level},
1758     token => $t);
1759     return ($t, undef);
1760     }
1761    
1762     for (qw/list-style-type list-style-position list-style-image/) {
1763     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
1764     }
1765    
1766     return ($t, \%prop_value);
1767     },
1768     serialize => sub {
1769     my ($self, $prop_name, $value) = @_;
1770    
1771     local $Error::Depth = $Error::Depth + 1;
1772     return $self->list_style_type . ' ' . $self->list_style_position .
1773     ' ' . $self->list_style_image;
1774     },
1775     };
1776     $Attr->{list_style} = $Prop->{'list-style'};
1777    
1778 wakaba 1.16 ## NOTE: Future version of the implementation will change the way to
1779     ## store the parsed value to support CSS 3 properties.
1780     $Prop->{'text-decoration'} = {
1781     css => 'text-decoration',
1782     dom => 'text_decoration',
1783     key => 'text_decoration',
1784     parse => sub {
1785     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1786    
1787     my $value = ['DECORATION']; # , underline, overline, line-through, blink
1788    
1789     if ($t->{type} == IDENT_TOKEN) {
1790     my $v = lc $t->{value}; ## TODO: case
1791     $t = $tt->get_next_token;
1792     if ($v eq 'inherit') {
1793     return ($t, {$prop_name => ['INHERIT']});
1794     } elsif ($v eq 'none') {
1795     return ($t, {$prop_name => $value});
1796     } elsif ($v eq 'underline' and
1797     $self->{prop_value}->{$prop_name}->{$v}) {
1798     $value->[1] = 1;
1799     } elsif ($v eq 'overline' and
1800     $self->{prop_value}->{$prop_name}->{$v}) {
1801     $value->[2] = 1;
1802     } elsif ($v eq 'line-through' and
1803     $self->{prop_value}->{$prop_name}->{$v}) {
1804     $value->[3] = 1;
1805     } elsif ($v eq 'blink' and
1806     $self->{prop_value}->{$prop_name}->{$v}) {
1807     $value->[4] = 1;
1808     } else {
1809     $onerror->(type => 'syntax error:'.$prop_name,
1810     level => $self->{must_level},
1811     token => $t);
1812     return ($t, undef);
1813     }
1814     }
1815    
1816     F: {
1817     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1818     last F unless $t->{type} == IDENT_TOKEN;
1819    
1820     my $v = lc $t->{value}; ## TODO: case
1821     $t = $tt->get_next_token;
1822     if ($v eq 'underline' and
1823     $self->{prop_value}->{$prop_name}->{$v}) {
1824     $value->[1] = 1;
1825     } elsif ($v eq 'overline' and
1826     $self->{prop_value}->{$prop_name}->{$v}) {
1827     $value->[1] = 2;
1828     } elsif ($v eq 'line-through' and
1829     $self->{prop_value}->{$prop_name}->{$v}) {
1830     $value->[1] = 3;
1831     } elsif ($v eq 'blink' and
1832     $self->{prop_value}->{$prop_name}->{$v}) {
1833     $value->[1] = 4;
1834     } else {
1835     last F;
1836     }
1837    
1838     redo F;
1839     } # F
1840    
1841     return ($t, {$prop_name => $value});
1842     },
1843     serialize => $default_serializer,
1844     initial => ["KEYWORD", "none"],
1845     #inherited => 0,
1846     compute => $compute_as_specified,
1847     };
1848     $Attr->{text_decoration} = $Prop->{'text-decoration'};
1849     $Key->{text_decoration} = $Prop->{'text-decoration'};
1850    
1851 wakaba 1.1 1;
1852 wakaba 1.17 ## $Date: 2008/01/02 03:56:29 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24