/[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.14 - (hide annotations) (download)
Tue Jan 1 11:27:42 2008 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.13: +13 -11 lines
++ whatpm/Whatpm/CSS/ChangeLog	1 Jan 2008 11:27:39 -0000
	* Parser.pm: Give |list-style-image|'s compute function a name.

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 wakaba 1.12 $Prop->{'page-break-before'} = {
765     css => 'page-break-before',
766     dom => 'page_break_before',
767     key => 'page_break_before',
768     parse => $one_keyword_parser,
769     serialize => $default_serializer,
770     keyword => {
771     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
772     },
773     initial => ["KEYWORD", 'auto'],
774     inherited => 1,
775     compute => $compute_as_specified,
776     };
777     $Attr->{page_break_before} = $Prop->{'page-break-before'};
778     $Key->{page_break_before} = $Prop->{'page-break-before'};
779    
780     $Prop->{'page-break-after'} = {
781     css => 'page-break-after',
782     dom => 'page_break_after',
783     key => 'page_break_after',
784     parse => $one_keyword_parser,
785     serialize => $default_serializer,
786     keyword => {
787     auto => 1, always => 1, avoid => 1, left => 1, right => 1,
788     },
789     initial => ["KEYWORD", 'auto'],
790     inherited => 1,
791     compute => $compute_as_specified,
792     };
793     $Attr->{page_break_after} = $Prop->{'page-break-after'};
794     $Key->{page_break_after} = $Prop->{'page-break-after'};
795    
796     $Prop->{'page-break-inside'} = {
797     css => 'page-break-inside',
798     dom => 'page_break_inside',
799     key => 'page_break_inside',
800     parse => $one_keyword_parser,
801     serialize => $default_serializer,
802     keyword => {
803     auto => 1, avoid => 1,
804     },
805     initial => ["KEYWORD", 'auto'],
806     inherited => 1,
807     compute => $compute_as_specified,
808     };
809     $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
810     $Key->{page_break_inside} = $Prop->{'page-break-inside'};
811    
812 wakaba 1.11 $Prop->{'z-index'} = {
813     css => 'z-index',
814     dom => 'z_index',
815     key => 'z_index',
816     parse => sub {
817     my ($self, $prop_name, $tt, $t, $onerror) = @_;
818    
819 wakaba 1.12 my $sign = 1;
820     if ($t->{type} == MINUS_TOKEN) {
821     $sign = -1;
822     $t = $tt->get_next_token;
823     }
824    
825 wakaba 1.11 if ($t->{type} == NUMBER_TOKEN) {
826     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
827     ## browser compatibility issue.
828     my $value = $t->{number};
829     $t = $tt->get_next_token;
830 wakaba 1.12 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
831     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
832 wakaba 1.11 my $value = lc $t->{value}; ## TODO: case
833     $t = $tt->get_next_token;
834     if ($value eq 'auto') {
835     ## NOTE: |z-index| is the default value and therefore it must be
836     ## supported anyway.
837     return ($t, {$prop_name => ["KEYWORD", 'auto']});
838     } elsif ($value eq 'inherit') {
839     return ($t, {$prop_name => ['INHERIT']});
840     }
841     }
842    
843     $onerror->(type => 'syntax error:'.$prop_name,
844     level => $self->{must_level},
845     token => $t);
846     return ($t, undef);
847     },
848     serialize => $default_serializer,
849     initial => ['KEYWORD', 'auto'],
850     #inherited => 0,
851     compute => $compute_as_specified,
852     };
853     $Attr->{z_index} = $Prop->{'z-index'};
854     $Key->{z_index} = $Prop->{'z-index'};
855    
856 wakaba 1.12 $Prop->{orphans} = {
857     css => 'orphans',
858     dom => 'orphans',
859     key => 'orphans',
860     parse => sub {
861     my ($self, $prop_name, $tt, $t, $onerror) = @_;
862    
863     my $sign = 1;
864     if ($t->{type} == MINUS_TOKEN) {
865     $t = $tt->get_next_token;
866     $sign = -1;
867     }
868    
869     if ($t->{type} == NUMBER_TOKEN) {
870     ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
871     ## <http://suika.fam.cx/gate/2005/sw/widows> for
872     ## browser compatibility issue.
873     my $value = $t->{number};
874     $t = $tt->get_next_token;
875     return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
876     } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
877     my $value = lc $t->{value}; ## TODO: case
878     $t = $tt->get_next_token;
879     if ($value eq 'inherit') {
880     return ($t, {$prop_name => ['INHERIT']});
881     }
882     }
883    
884     $onerror->(type => 'syntax error:'.$prop_name,
885     level => $self->{must_level},
886     token => $t);
887     return ($t, undef);
888     },
889     serialize => $default_serializer,
890     initial => ['NUMBER', 2],
891     inherited => 1,
892     compute => $compute_as_specified,
893     };
894     $Attr->{orphans} = $Prop->{orphans};
895     $Key->{orphans} = $Prop->{orphans};
896    
897     $Prop->{widows} = {
898     css => 'widows',
899     dom => 'widows',
900     key => 'widows',
901     parse => $Prop->{orphans}->{parse},
902     serialize => $default_serializer,
903     initial => ['NUMBER', 2],
904     inherited => 1,
905     compute => $compute_as_specified,
906     };
907     $Attr->{widows} = $Prop->{widows};
908     $Key->{widows} = $Prop->{widows};
909    
910 wakaba 1.13 my $uri_or_none_parser = sub {
911 wakaba 1.11 my ($self, $prop_name, $tt, $t, $onerror) = @_;
912    
913 wakaba 1.13 if ($t->{type} == URI_TOKEN) {
914 wakaba 1.11 my $value = $t->{value};
915     $t = $tt->get_next_token;
916     return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
917     } elsif ($t->{type} == IDENT_TOKEN) {
918     my $value = lc $t->{value}; ## TODO: case
919     $t = $tt->get_next_token;
920     if ($value eq 'none') {
921     ## NOTE: |none| is the default value and therefore it must be
922     ## supported anyway.
923     return ($t, {$prop_name => ["KEYWORD", 'none']});
924     } elsif ($value eq 'inherit') {
925     return ($t, {$prop_name => ['INHERIT']});
926     }
927     ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
928     #} elsif ($t->{type} == URI_INVALID_TOKEN) {
929     # my $value = $t->{value};
930     # $t = $tt->get_next_token;
931     # if ($t->{type} == EOF_TOKEN) {
932     # $onerror->(type => 'syntax error:eof:'.$prop_name,
933     # level => $self->{must_level},
934     # token => $t);
935     #
936     # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
937     # }
938     }
939    
940     $onerror->(type => 'syntax error:'.$prop_name,
941     level => $self->{must_level},
942     token => $t);
943     return ($t, undef);
944 wakaba 1.13 }; # $uri_or_none_parser
945    
946 wakaba 1.14 my $compute_uri_or_none = sub {
947 wakaba 1.11 my ($self, $element, $prop_name, $specified_value) = @_;
948    
949     if (defined $specified_value and
950     $specified_value->[0] eq 'URI' and
951     defined $specified_value->[2]) {
952     require Message::DOM::DOMImplementation;
953     return ['URI',
954     Message::DOM::DOMImplementation->create_uri_reference
955     ($specified_value->[1])
956     ->get_absolute_reference (${$specified_value->[2]})
957     ->get_uri_reference,
958     $specified_value->[2]];
959     }
960    
961     return $specified_value;
962 wakaba 1.14 }; # $compute_uri_or_none
963    
964     $Prop->{'list-style-image'} = {
965     css => 'list-style-image',
966     dom => 'list_style_image',
967     key => 'list_style_image',
968     parse => $uri_or_none_parser,
969     serialize => $default_serializer,
970     initial => ['KEYWORD', 'none'],
971     inherited => 1,
972     compute => $compute_uri_or_none,
973 wakaba 1.11 };
974     $Attr->{list_style_image} = $Prop->{'list-style-image'};
975     $Key->{list_style_image} = $Prop->{'list-style-image'};
976    
977 wakaba 1.7 my $border_style_keyword = {
978     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
979     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
980     };
981    
982     $Prop->{'border-top-style'} = {
983     css => 'border-top-style',
984     dom => 'border_top_style',
985     key => 'border_top_style',
986     parse => $one_keyword_parser,
987 wakaba 1.11 serialize => $default_serializer,
988 wakaba 1.7 keyword => $border_style_keyword,
989 wakaba 1.9 initial => ["KEYWORD", "none"],
990     #inherited => 0,
991     compute => $compute_as_specified,
992 wakaba 1.7 };
993     $Attr->{border_top_style} = $Prop->{'border-top-style'};
994     $Key->{border_top_style} = $Prop->{'border-top-style'};
995    
996     $Prop->{'border-right-style'} = {
997     css => 'border-right-style',
998     dom => 'border_right_style',
999     key => 'border_right_style',
1000     parse => $one_keyword_parser,
1001 wakaba 1.11 serialize => $default_serializer,
1002 wakaba 1.7 keyword => $border_style_keyword,
1003 wakaba 1.9 initial => ["KEYWORD", "none"],
1004     #inherited => 0,
1005     compute => $compute_as_specified,
1006 wakaba 1.7 };
1007     $Attr->{border_right_style} = $Prop->{'border-right-style'};
1008     $Key->{border_right_style} = $Prop->{'border-right-style'};
1009    
1010     $Prop->{'border-bottom-style'} = {
1011     css => 'border-bottom-style',
1012     dom => 'border_bottom_style',
1013     key => 'border_bottom_style',
1014     parse => $one_keyword_parser,
1015 wakaba 1.11 serialize => $default_serializer,
1016 wakaba 1.7 keyword => $border_style_keyword,
1017 wakaba 1.9 initial => ["KEYWORD", "none"],
1018     #inherited => 0,
1019     compute => $compute_as_specified,
1020 wakaba 1.7 };
1021     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
1022     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
1023    
1024     $Prop->{'border-left-style'} = {
1025     css => 'border-left-style',
1026     dom => 'border_left_style',
1027     key => 'border_left_style',
1028     parse => $one_keyword_parser,
1029 wakaba 1.11 serialize => $default_serializer,
1030 wakaba 1.7 keyword => $border_style_keyword,
1031 wakaba 1.9 initial => ["KEYWORD", "none"],
1032     #inherited => 0,
1033     compute => $compute_as_specified,
1034 wakaba 1.7 };
1035     $Attr->{border_left_style} = $Prop->{'border-left-style'};
1036     $Key->{border_left_style} = $Prop->{'border-left-style'};
1037    
1038     $Prop->{'border-style'} = {
1039     css => 'border-style',
1040     dom => 'border_style',
1041     parse => sub {
1042     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1043    
1044     my %prop_value;
1045     my $has_inherit;
1046     if ($t->{type} == IDENT_TOKEN) {
1047     my $prop_value = lc $t->{value}; ## TODO: case folding
1048     $t = $tt->get_next_token;
1049     if ($border_style_keyword->{$prop_value} and
1050     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
1051     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
1052     } elsif ($prop_value eq 'inherit') {
1053 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
1054 wakaba 1.7 $has_inherit = 1;
1055     } else {
1056     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1057     level => $self->{must_level},
1058     token => $t);
1059     return ($t, undef);
1060     }
1061     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
1062     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
1063     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
1064     } else {
1065     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1066     level => $self->{must_level},
1067     token => $t);
1068     return ($t, undef);
1069     }
1070    
1071     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1072     if ($t->{type} == IDENT_TOKEN) {
1073     my $prop_value = lc $t->{value}; ## TODO: case folding
1074     $t = $tt->get_next_token;
1075     if (not $has_inherit and
1076     $border_style_keyword->{$prop_value} and
1077     $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
1078     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
1079     } else {
1080     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1081     level => $self->{must_level},
1082     token => $t);
1083     return ($t, undef);
1084     }
1085     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
1086    
1087     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1088     if ($t->{type} == IDENT_TOKEN) {
1089     my $prop_value = lc $t->{value}; ## TODO: case folding
1090     $t = $tt->get_next_token;
1091     if ($border_style_keyword->{$prop_value} and
1092     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
1093     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
1094     } else {
1095     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1096     level => $self->{must_level},
1097     token => $t);
1098     return ($t, undef);
1099     }
1100    
1101     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1102     if ($t->{type} == IDENT_TOKEN) {
1103     my $prop_value = lc $t->{value}; ## TODO: case folding
1104     $t = $tt->get_next_token;
1105     if ($border_style_keyword->{$prop_value} and
1106     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
1107     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
1108     } else {
1109     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1110     level => $self->{must_level},
1111     token => $t);
1112     return ($t, undef);
1113     }
1114     }
1115     }
1116     }
1117    
1118     return ($t, \%prop_value);
1119     },
1120     serialize => sub {
1121     my ($self, $prop_name, $value) = @_;
1122    
1123     local $Error::Depth = $Error::Depth + 1;
1124     my @v;
1125     push @v, $self->border_top_style;
1126     return undef unless defined $v[-1];
1127     push @v, $self->border_right_style;
1128     return undef unless defined $v[-1];
1129     push @v, $self->border_bottom_style;
1130     return undef unless defined $v[-1];
1131     push @v, $self->border_bottom_style;
1132     return undef unless defined $v[-1];
1133    
1134     pop @v if $v[1] eq $v[3];
1135     pop @v if $v[0] eq $v[2];
1136     pop @v if $v[0] eq $v[1];
1137     return join ' ', @v;
1138     },
1139     };
1140     $Attr->{border_style} = $Prop->{'border-style'};
1141    
1142 wakaba 1.12 $Prop->{'list-style'} = {
1143     css => 'list-style',
1144     dom => 'list_style',
1145     parse => sub {
1146     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1147    
1148     my %prop_value;
1149     my $none = 0;
1150    
1151     F: for my $f (1..3) {
1152     if ($t->{type} == IDENT_TOKEN) {
1153     my $prop_value = lc $t->{value}; ## TODO: case folding
1154     $t = $tt->get_next_token;
1155    
1156     if ($prop_value eq 'none') {
1157     $none++;
1158     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
1159     if (exists $prop_value{'list-style-type'}) {
1160     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1161     $prop_name,
1162     level => $self->{must_level},
1163     token => $t);
1164     return ($t, undef);
1165     } else {
1166     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
1167     }
1168     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
1169     if (exists $prop_value{'list-style-position'}) {
1170     $onerror->(type => q[syntax error:duplicate:'list-style-position':].
1171     $prop_name,
1172     level => $self->{must_level},
1173     token => $t);
1174     return ($t, undef);
1175     }
1176    
1177     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
1178     } elsif ($f == 1 and $prop_value eq 'inherit') {
1179     $prop_value{'list-style-type'} = ["INHERIT"];
1180     $prop_value{'list-style-position'} = ["INHERIT"];
1181     $prop_value{'list-style-image'} = ["INHERIT"];
1182     last F;
1183     } else {
1184     if ($f == 1) {
1185     $onerror->(type => 'syntax error:'.$prop_name,
1186     level => $self->{must_level},
1187     token => $t);
1188     return ($t, undef);
1189     } else {
1190     last F;
1191     }
1192     }
1193     } elsif ($t->{type} == URI_TOKEN) {
1194     if (exists $prop_value{'list-style-image'}) {
1195     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1196     $prop_name,
1197     level => $self->{must_level},
1198     token => $t);
1199     return ($t, undef);
1200     }
1201    
1202     $prop_value{'list-style-image'}
1203 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
1204 wakaba 1.12 $t = $tt->get_next_token;
1205     } else {
1206     if ($f == 1) {
1207     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1208     level => $self->{must_level},
1209     token => $t);
1210     return ($t, undef);
1211     } else {
1212     last F;
1213     }
1214     }
1215    
1216     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1217     } # F
1218     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
1219    
1220     if ($none == 1) {
1221     if (exists $prop_value{'list-style-type'}) {
1222     if (exists $prop_value{'list-style-image'}) {
1223     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1224     $prop_name,
1225     level => $self->{must_level},
1226     token => $t);
1227     return ($t, undef);
1228     } else {
1229     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
1230     }
1231     } else {
1232     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
1233     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
1234     unless exists $prop_value{'list-style-image'};
1235     }
1236     } elsif ($none == 2) {
1237     if (exists $prop_value{'list-style-type'}) {
1238     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1239     $prop_name,
1240     level => $self->{must_level},
1241     token => $t);
1242     return ($t, undef);
1243     }
1244     if (exists $prop_value{'list-style-image'}) {
1245     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1246     $prop_name,
1247     level => $self->{must_level},
1248     token => $t);
1249     return ($t, undef);
1250     }
1251    
1252     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
1253     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
1254     } elsif ($none == 3) {
1255     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1256     $prop_name,
1257     level => $self->{must_level},
1258     token => $t);
1259     return ($t, undef);
1260     }
1261    
1262     for (qw/list-style-type list-style-position list-style-image/) {
1263     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
1264     }
1265    
1266     return ($t, \%prop_value);
1267     },
1268     serialize => sub {
1269     my ($self, $prop_name, $value) = @_;
1270    
1271     local $Error::Depth = $Error::Depth + 1;
1272     return $self->list_style_type . ' ' . $self->list_style_position .
1273     ' ' . $self->list_style_image;
1274     },
1275     };
1276     $Attr->{list_style} = $Prop->{'list-style'};
1277    
1278 wakaba 1.1 1;
1279 wakaba 1.14 ## $Date: 2008/01/01 11:25:34 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24