/[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.11 - (hide annotations) (download)
Tue Jan 1 09:08:08 2008 UTC (17 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.10: +202 -23 lines
++ whatpm/Whatpm/CSS/ChangeLog	1 Jan 2008 09:07:56 -0000
	* Parser.pm: Set |manakai_base_uri| attribute of the
	created |CSSStyleSheet| object.
	More properties from CSS 2.1 are implemented.

2008-01-01  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     if ($value->[0] eq 'NUMBER') {
444     return $value->[1]; ## TODO: big or small number cases?
445     } elsif ($value->[0] eq 'KEYWORD') {
446     return $value->[1];
447     } elsif ($value->[0] eq 'URI') {
448     ## NOTE: This is what browsers do.
449     return 'url('.$value->[1].')';
450     } elsif ($value->[0] eq 'INHERIT') {
451     return 'inherit';
452     } else {
453     return undef;
454     }
455     }; # $default_serializer
456    
457 wakaba 1.5 $Prop->{color} = {
458     css => 'color',
459     dom => 'color',
460     key => 'color',
461     parse => sub {
462     my ($self, $prop_name, $tt, $t, $onerror) = @_;
463    
464     if ($t->{type} == IDENT_TOKEN) {
465     if (lc $t->{value} eq 'blue') { ## TODO: case folding
466     $t = $tt->get_next_token;
467 wakaba 1.7 return ($t, {$prop_name => ["RGBA", 0, 0, 255, 1]});
468 wakaba 1.5 } else {
469     #
470     }
471     } else {
472     #
473     }
474    
475     $onerror->(type => 'syntax error:color',
476     level => $self->{must_level},
477     token => $t);
478    
479     return ($t, undef);
480     },
481     serialize => sub {
482     my ($self, $prop_name, $value) = @_;
483     if ($value->[0] eq 'RGBA') { ## TODO: %d? %f?
484     return sprintf 'rgba(%d, %d, %d, %f)', @$value[1, 2, 3, 4];
485     } else {
486     return undef;
487     }
488     },
489 wakaba 1.9 initial => ["KEYWORD", "-manakai-initial-color"], ## NOTE: UA-dependent in CSS 2.1.
490     inherited => 1,
491     compute => $compute_as_specified,
492 wakaba 1.5 };
493     $Attr->{color} = $Prop->{color};
494     $Key->{color} = $Prop->{color};
495    
496 wakaba 1.6 my $one_keyword_parser = sub {
497     my ($self, $prop_name, $tt, $t, $onerror) = @_;
498    
499     if ($t->{type} == IDENT_TOKEN) {
500     my $prop_value = lc $t->{value}; ## TODO: case folding
501     $t = $tt->get_next_token;
502     if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
503     $self->{prop_value}->{$prop_name}->{$prop_value}) {
504 wakaba 1.7 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
505 wakaba 1.6 } elsif ($prop_value eq 'inherit') {
506 wakaba 1.10 return ($t, {$prop_name => ['INHERIT']});
507 wakaba 1.6 }
508     }
509    
510 wakaba 1.7 $onerror->(type => 'syntax error:keyword:'.$prop_name,
511 wakaba 1.6 level => $self->{must_level},
512     token => $t);
513     return ($t, undef);
514     };
515    
516     $Prop->{display} = {
517     css => 'display',
518     dom => 'display',
519     key => 'display',
520     parse => $one_keyword_parser,
521 wakaba 1.11 serialize => $default_serializer,
522 wakaba 1.6 keyword => {
523     block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
524     'list-item' => 1, none => 1,
525     table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
526     'table-column-group' => 1, 'table-header-group' => 1,
527     'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
528     },
529 wakaba 1.9 initial => ["KEYWORD", "inline"],
530     #inherited => 0,
531     compute => sub {
532     my ($self, $element, $prop_name, $specified_value) = @_;
533     ## NOTE: CSS 2.1 Section 9.7.
534    
535     ## WARNING: |compute| for 'float' property invoke this CODE
536     ## in some case. Careless modification might cause a infinite loop.
537    
538     if ($specified_value->[0] eq 'KEYWORD') {
539     if ($specified_value->[1] eq 'none') {
540     ## Case 1 [CSS 2.1]
541     return $specified_value;
542     } else {
543     my $position = $self->get_computed_value ($element, 'position');
544     if ($position->[0] eq 'KEYWORD' and
545     ($position->[1] eq 'absolute' or
546     $position->[1] eq 'fixed')) {
547     ## Case 2 [CSS 2.1]
548     #
549     } else {
550     my $float = $self->get_computed_value ($element, 'float');
551     if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
552     ## Caes 3 [CSS 2.1]
553     #
554     } elsif (not defined $element->manakai_parent_element) {
555     ## Case 4 [CSS 2.1]
556     #
557     } else {
558     ## Case 5 [CSS 2.1]
559     return $specified_value;
560     }
561     }
562    
563     return ["KEYWORD",
564     {
565     'inline-table' => 'table',
566     inline => 'block',
567     'run-in' => 'block',
568     'table-row-group' => 'block',
569     'table-column' => 'block',
570     'table-column-group' => 'block',
571     'table-header-group' => 'block',
572     'table-footer-group' => 'block',
573     'table-row' => 'block',
574     'table-cell' => 'block',
575     'table-caption' => 'block',
576     'inline-block' => 'block',
577     }->{$specified_value->[1]} || $specified_value->[1]];
578     }
579     } else {
580     return $specified_value; ## Maybe an error of the implementation.
581     }
582     },
583 wakaba 1.6 };
584     $Attr->{display} = $Prop->{display};
585     $Key->{display} = $Prop->{display};
586    
587     $Prop->{position} = {
588     css => 'position',
589     dom => 'position',
590     key => 'position',
591     parse => $one_keyword_parser,
592 wakaba 1.11 serialize => $default_serializer,
593 wakaba 1.6 keyword => {
594     static => 1, relative => 1, absolute => 1, fixed => 1,
595     },
596 wakaba 1.10 initial => ["KEYWORD", "static"],
597 wakaba 1.9 #inherited => 0,
598     compute => $compute_as_specified,
599 wakaba 1.6 };
600     $Attr->{position} = $Prop->{position};
601     $Key->{position} = $Prop->{position};
602    
603     $Prop->{float} = {
604     css => 'float',
605     dom => 'css_float',
606     key => 'float',
607     parse => $one_keyword_parser,
608 wakaba 1.11 serialize => $default_serializer,
609 wakaba 1.6 keyword => {
610     left => 1, right => 1, none => 1,
611     },
612 wakaba 1.9 initial => ["KEYWORD", "none"],
613     #inherited => 0,
614     compute => sub {
615     my ($self, $element, $prop_name, $specified_value) = @_;
616     ## NOTE: CSS 2.1 Section 9.7.
617    
618     ## WARNING: |compute| for 'display' property invoke this CODE
619     ## in some case. Careless modification might cause a infinite loop.
620    
621     if ($specified_value->[0] eq 'KEYWORD') {
622     if ($specified_value->[1] eq 'none') {
623     ## Case 1 [CSS 2.1]
624     return $specified_value;
625     } else {
626     my $position = $self->get_computed_value ($element, 'position');
627     if ($position->[0] eq 'KEYWORD' and
628     ($position->[1] eq 'absolute' or
629     $position->[1] eq 'fixed')) {
630     ## Case 2 [CSS 2.1]
631     return ["KEYWORD", "none"];
632     }
633     }
634     }
635    
636     ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
637     ## on computed value of 'float' property.
638    
639     ## Case 3, 4, and 5 [CSS 2.1]
640     return $specified_value;
641     },
642 wakaba 1.6 };
643     $Attr->{css_float} = $Prop->{float};
644     $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
645     $Key->{float} = $Prop->{float};
646    
647     $Prop->{clear} = {
648     css => 'clear',
649     dom => 'clear',
650     key => 'clear',
651     parse => $one_keyword_parser,
652 wakaba 1.11 serialize => $default_serializer,
653 wakaba 1.6 keyword => {
654     left => 1, right => 1, none => 1, both => 1,
655     },
656 wakaba 1.9 initial => ["KEYWORD", "none"],
657     #inherited => 0,
658     compute => $compute_as_specified,
659 wakaba 1.6 };
660     $Attr->{clear} = $Prop->{clear};
661     $Key->{clear} = $Prop->{clear};
662    
663     $Prop->{direction} = {
664     css => 'direction',
665     dom => 'direction',
666     key => 'direction',
667     parse => $one_keyword_parser,
668 wakaba 1.11 serialize => $default_serializer,
669 wakaba 1.6 keyword => {
670     ltr => 1, rtl => 1,
671     },
672 wakaba 1.9 initial => ["KEYWORD", "ltr"],
673     inherited => 1,
674     compute => $compute_as_specified,
675 wakaba 1.6 };
676     $Attr->{direction} = $Prop->{direction};
677     $Key->{direction} = $Prop->{direction};
678    
679     $Prop->{'unicode-bidi'} = {
680     css => 'unicode-bidi',
681     dom => 'unicode_bidi',
682     key => 'unicode_bidi',
683     parse => $one_keyword_parser,
684 wakaba 1.11 serialize => $default_serializer,
685 wakaba 1.6 keyword => {
686     normal => 1, embed => 1, 'bidi-override' => 1,
687     },
688 wakaba 1.9 initial => ["KEYWORD", "normal"],
689     #inherited => 0,
690     compute => $compute_as_specified,
691 wakaba 1.6 };
692     $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
693     $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
694    
695 wakaba 1.11 $Prop->{overflow} = {
696     css => 'overflow',
697     dom => 'overflow',
698     key => 'overflow',
699     parse => $one_keyword_parser,
700     serialize => $default_serializer,
701     keyword => {
702     visible => 1, hidden => 1, scroll => 1, auto => 1,
703     },
704     initial => ["KEYWORD", "visible"],
705     #inherited => 0,
706     compute => $compute_as_specified,
707     };
708     $Attr->{overflow} = $Prop->{overflow};
709     $Key->{overflow} = $Prop->{overflow};
710    
711     $Prop->{visibility} = {
712     css => 'visibility',
713     dom => 'visibility',
714     key => 'visibility',
715     parse => $one_keyword_parser,
716     serialize => $default_serializer,
717     keyword => {
718     visible => 1, hidden => 1, collapse => 1,
719     },
720     initial => ["KEYWORD", "visible"],
721     #inherited => 0,
722     compute => $compute_as_specified,
723     };
724     $Attr->{visibility} = $Prop->{visibility};
725     $Key->{visibility} = $Prop->{visibility};
726    
727     $Prop->{'list-style-type'} = {
728     css => 'list-style-type',
729     dom => 'list_style_type',
730     key => 'list_style_type',
731     parse => $one_keyword_parser,
732     serialize => $default_serializer,
733     keyword => {
734     qw/
735     disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
736     lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
737     upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
738     none 1
739     /,
740     },
741     initial => ["KEYWORD", 'disc'],
742     inherited => 1,
743     compute => $compute_as_specified,
744     };
745     $Attr->{list_style_type} = $Prop->{'list-style-type'};
746     $Key->{list_style_type} = $Prop->{'list-style-type'};
747    
748     $Prop->{'list-style-position'} = {
749     css => 'list-style-position',
750     dom => 'list_style_position',
751     key => 'list_style_position',
752     parse => $one_keyword_parser,
753     serialize => $default_serializer,
754     keyword => {
755     inside => 1, outside => 1,
756     },
757     initial => ["KEYWORD", 'outside'],
758     inherited => 1,
759     compute => $compute_as_specified,
760     };
761     $Attr->{list_style_position} = $Prop->{'list-style-position'};
762     $Key->{list_style_position} = $Prop->{'list-style-position'};
763    
764     $Prop->{'z-index'} = {
765     css => 'z-index',
766     dom => 'z_index',
767     key => 'z_index',
768     parse => sub {
769     my ($self, $prop_name, $tt, $t, $onerror) = @_;
770    
771     if ($t->{type} == NUMBER_TOKEN) {
772     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
773     ## browser compatibility issue.
774     my $value = $t->{number};
775     $t = $tt->get_next_token;
776     return ($t, {$prop_name => ["NUMBER", int ($value / 1)]});
777     } elsif ($t->{type} == IDENT_TOKEN) {
778     my $value = lc $t->{value}; ## TODO: case
779     $t = $tt->get_next_token;
780     if ($value eq 'auto') {
781     ## NOTE: |z-index| is the default value and therefore it must be
782     ## supported anyway.
783     return ($t, {$prop_name => ["KEYWORD", 'auto']});
784     } elsif ($value eq 'inherit') {
785     return ($t, {$prop_name => ['INHERIT']});
786     }
787     }
788    
789     $onerror->(type => 'syntax error:'.$prop_name,
790     level => $self->{must_level},
791     token => $t);
792     return ($t, undef);
793     },
794     serialize => $default_serializer,
795     initial => ['KEYWORD', 'auto'],
796     #inherited => 0,
797     compute => $compute_as_specified,
798     };
799     $Attr->{z_index} = $Prop->{'z-index'};
800     $Key->{z_index} = $Prop->{'z-index'};
801    
802     $Prop->{'list-style-image'} = {
803     css => 'list-style-image',
804     dom => 'list_style_image',
805     key => 'list_style_image',
806     parse => sub {
807     my ($self, $prop_name, $tt, $t, $onerror) = @_;
808    
809     if ($t->{type} == URI_TOKEN) { ## TODO: resolve URI
810     my $value = $t->{value};
811     $t = $tt->get_next_token;
812     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
813     } elsif ($t->{type} == IDENT_TOKEN) {
814     my $value = lc $t->{value}; ## TODO: case
815     $t = $tt->get_next_token;
816     if ($value eq 'none') {
817     ## NOTE: |none| is the default value and therefore it must be
818     ## supported anyway.
819     return ($t, {$prop_name => ["KEYWORD", 'none']});
820     } elsif ($value eq 'inherit') {
821     return ($t, {$prop_name => ['INHERIT']});
822     }
823     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
824     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
825     # my $value = $t->{value};
826     # $t = $tt->get_next_token;
827     # if ($t->{type} == EOF_TOKEN) {
828     # $onerror->(type => 'syntax error:eof:'.$prop_name,
829     # level => $self->{must_level},
830     # token => $t);
831     #
832     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
833     # }
834     }
835    
836     $onerror->(type => 'syntax error:'.$prop_name,
837     level => $self->{must_level},
838     token => $t);
839     return ($t, undef);
840     },
841     serialize => $default_serializer,
842     initial => ['KEYWORD', 'none'],
843     inherited => 1,
844     compute => sub {
845     my ($self, $element, $prop_name, $specified_value) = @_;
846    
847     if (defined $specified_value and
848     $specified_value->[0] eq 'URI' and
849     defined $specified_value->[2]) {
850     require Message::DOM::DOMImplementation;
851     return ['URI',
852     Message::DOM::DOMImplementation->create_uri_reference
853     ($specified_value->[1])
854     ->get_absolute_reference (${$specified_value->[2]})
855     ->get_uri_reference,
856     $specified_value->[2]];
857     }
858    
859     return $specified_value;
860     },
861     };
862     $Attr->{list_style_image} = $Prop->{'list-style-image'};
863     $Key->{list_style_image} = $Prop->{'list-style-image'};
864    
865 wakaba 1.7 my $border_style_keyword = {
866     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
867     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
868     };
869    
870     $Prop->{'border-top-style'} = {
871     css => 'border-top-style',
872     dom => 'border_top_style',
873     key => 'border_top_style',
874     parse => $one_keyword_parser,
875 wakaba 1.11 serialize => $default_serializer,
876 wakaba 1.7 keyword => $border_style_keyword,
877 wakaba 1.9 initial => ["KEYWORD", "none"],
878     #inherited => 0,
879     compute => $compute_as_specified,
880 wakaba 1.7 };
881     $Attr->{border_top_style} = $Prop->{'border-top-style'};
882     $Key->{border_top_style} = $Prop->{'border-top-style'};
883    
884     $Prop->{'border-right-style'} = {
885     css => 'border-right-style',
886     dom => 'border_right_style',
887     key => 'border_right_style',
888     parse => $one_keyword_parser,
889 wakaba 1.11 serialize => $default_serializer,
890 wakaba 1.7 keyword => $border_style_keyword,
891 wakaba 1.9 initial => ["KEYWORD", "none"],
892     #inherited => 0,
893     compute => $compute_as_specified,
894 wakaba 1.7 };
895     $Attr->{border_right_style} = $Prop->{'border-right-style'};
896     $Key->{border_right_style} = $Prop->{'border-right-style'};
897    
898     $Prop->{'border-bottom-style'} = {
899     css => 'border-bottom-style',
900     dom => 'border_bottom_style',
901     key => 'border_bottom_style',
902     parse => $one_keyword_parser,
903 wakaba 1.11 serialize => $default_serializer,
904 wakaba 1.7 keyword => $border_style_keyword,
905 wakaba 1.9 initial => ["KEYWORD", "none"],
906     #inherited => 0,
907     compute => $compute_as_specified,
908 wakaba 1.7 };
909     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
910     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
911    
912     $Prop->{'border-left-style'} = {
913     css => 'border-left-style',
914     dom => 'border_left_style',
915     key => 'border_left_style',
916     parse => $one_keyword_parser,
917 wakaba 1.11 serialize => $default_serializer,
918 wakaba 1.7 keyword => $border_style_keyword,
919 wakaba 1.9 initial => ["KEYWORD", "none"],
920     #inherited => 0,
921     compute => $compute_as_specified,
922 wakaba 1.7 };
923     $Attr->{border_left_style} = $Prop->{'border-left-style'};
924     $Key->{border_left_style} = $Prop->{'border-left-style'};
925    
926     $Prop->{'border-style'} = {
927     css => 'border-style',
928     dom => 'border_style',
929     parse => sub {
930     my ($self, $prop_name, $tt, $t, $onerror) = @_;
931    
932     my %prop_value;
933     my $has_inherit;
934     if ($t->{type} == IDENT_TOKEN) {
935     my $prop_value = lc $t->{value}; ## TODO: case folding
936     $t = $tt->get_next_token;
937     if ($border_style_keyword->{$prop_value} and
938     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
939     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
940     } elsif ($prop_value eq 'inherit') {
941 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
942 wakaba 1.7 $has_inherit = 1;
943     } else {
944     $onerror->(type => 'syntax error:keyword:'.$prop_name,
945     level => $self->{must_level},
946     token => $t);
947     return ($t, undef);
948     }
949     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
950     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
951     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
952     } else {
953     $onerror->(type => 'syntax error:keyword:'.$prop_name,
954     level => $self->{must_level},
955     token => $t);
956     return ($t, undef);
957     }
958    
959     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
960     if ($t->{type} == IDENT_TOKEN) {
961     my $prop_value = lc $t->{value}; ## TODO: case folding
962     $t = $tt->get_next_token;
963     if (not $has_inherit and
964     $border_style_keyword->{$prop_value} and
965     $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
966     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
967     } else {
968     $onerror->(type => 'syntax error:keyword:'.$prop_name,
969     level => $self->{must_level},
970     token => $t);
971     return ($t, undef);
972     }
973     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
974    
975     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
976     if ($t->{type} == IDENT_TOKEN) {
977     my $prop_value = lc $t->{value}; ## TODO: case folding
978     $t = $tt->get_next_token;
979     if ($border_style_keyword->{$prop_value} and
980     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
981     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
982     } else {
983     $onerror->(type => 'syntax error:keyword:'.$prop_name,
984     level => $self->{must_level},
985     token => $t);
986     return ($t, undef);
987     }
988    
989     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
990     if ($t->{type} == IDENT_TOKEN) {
991     my $prop_value = lc $t->{value}; ## TODO: case folding
992     $t = $tt->get_next_token;
993     if ($border_style_keyword->{$prop_value} and
994     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
995     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
996     } else {
997     $onerror->(type => 'syntax error:keyword:'.$prop_name,
998     level => $self->{must_level},
999     token => $t);
1000     return ($t, undef);
1001     }
1002     }
1003     }
1004     }
1005    
1006     return ($t, \%prop_value);
1007     },
1008     serialize => sub {
1009     my ($self, $prop_name, $value) = @_;
1010    
1011     local $Error::Depth = $Error::Depth + 1;
1012     my @v;
1013     push @v, $self->border_top_style;
1014     return undef unless defined $v[-1];
1015     push @v, $self->border_right_style;
1016     return undef unless defined $v[-1];
1017     push @v, $self->border_bottom_style;
1018     return undef unless defined $v[-1];
1019     push @v, $self->border_bottom_style;
1020     return undef unless defined $v[-1];
1021    
1022     pop @v if $v[1] eq $v[3];
1023     pop @v if $v[0] eq $v[2];
1024     pop @v if $v[0] eq $v[1];
1025     return join ' ', @v;
1026     },
1027     };
1028     $Attr->{border_style} = $Prop->{'border-style'};
1029    
1030 wakaba 1.1 1;
1031 wakaba 1.11 ## $Date: 2008/01/01 07:39:05 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24