/[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.13 - (hide annotations) (download)
Tue Jan 1 11:25:34 2008 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.12: +11 -9 lines
++ whatpm/Whatpm/CSS/ChangeLog	1 Jan 2008 11:25:22 -0000
	* Parser.pm: Give |list-style-image|'s parser a name.  Bug fix
	on base URI scalar reference.

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     $Prop->{'list-style-image'} = {
947     css => 'list-style-image',
948     dom => 'list_style_image',
949     key => 'list_style_image',
950     parse => $uri_or_none_parser,
951 wakaba 1.11 serialize => $default_serializer,
952     initial => ['KEYWORD', 'none'],
953     inherited => 1,
954     compute => sub {
955     my ($self, $element, $prop_name, $specified_value) = @_;
956    
957     if (defined $specified_value and
958     $specified_value->[0] eq 'URI' and
959     defined $specified_value->[2]) {
960     require Message::DOM::DOMImplementation;
961     return ['URI',
962     Message::DOM::DOMImplementation->create_uri_reference
963     ($specified_value->[1])
964     ->get_absolute_reference (${$specified_value->[2]})
965     ->get_uri_reference,
966     $specified_value->[2]];
967     }
968    
969     return $specified_value;
970     },
971     };
972     $Attr->{list_style_image} = $Prop->{'list-style-image'};
973     $Key->{list_style_image} = $Prop->{'list-style-image'};
974    
975 wakaba 1.7 my $border_style_keyword = {
976     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
977     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
978     };
979    
980     $Prop->{'border-top-style'} = {
981     css => 'border-top-style',
982     dom => 'border_top_style',
983     key => 'border_top_style',
984     parse => $one_keyword_parser,
985 wakaba 1.11 serialize => $default_serializer,
986 wakaba 1.7 keyword => $border_style_keyword,
987 wakaba 1.9 initial => ["KEYWORD", "none"],
988     #inherited => 0,
989     compute => $compute_as_specified,
990 wakaba 1.7 };
991     $Attr->{border_top_style} = $Prop->{'border-top-style'};
992     $Key->{border_top_style} = $Prop->{'border-top-style'};
993    
994     $Prop->{'border-right-style'} = {
995     css => 'border-right-style',
996     dom => 'border_right_style',
997     key => 'border_right_style',
998     parse => $one_keyword_parser,
999 wakaba 1.11 serialize => $default_serializer,
1000 wakaba 1.7 keyword => $border_style_keyword,
1001 wakaba 1.9 initial => ["KEYWORD", "none"],
1002     #inherited => 0,
1003     compute => $compute_as_specified,
1004 wakaba 1.7 };
1005     $Attr->{border_right_style} = $Prop->{'border-right-style'};
1006     $Key->{border_right_style} = $Prop->{'border-right-style'};
1007    
1008     $Prop->{'border-bottom-style'} = {
1009     css => 'border-bottom-style',
1010     dom => 'border_bottom_style',
1011     key => 'border_bottom_style',
1012     parse => $one_keyword_parser,
1013 wakaba 1.11 serialize => $default_serializer,
1014 wakaba 1.7 keyword => $border_style_keyword,
1015 wakaba 1.9 initial => ["KEYWORD", "none"],
1016     #inherited => 0,
1017     compute => $compute_as_specified,
1018 wakaba 1.7 };
1019     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
1020     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
1021    
1022     $Prop->{'border-left-style'} = {
1023     css => 'border-left-style',
1024     dom => 'border_left_style',
1025     key => 'border_left_style',
1026     parse => $one_keyword_parser,
1027 wakaba 1.11 serialize => $default_serializer,
1028 wakaba 1.7 keyword => $border_style_keyword,
1029 wakaba 1.9 initial => ["KEYWORD", "none"],
1030     #inherited => 0,
1031     compute => $compute_as_specified,
1032 wakaba 1.7 };
1033     $Attr->{border_left_style} = $Prop->{'border-left-style'};
1034     $Key->{border_left_style} = $Prop->{'border-left-style'};
1035    
1036     $Prop->{'border-style'} = {
1037     css => 'border-style',
1038     dom => 'border_style',
1039     parse => sub {
1040     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1041    
1042     my %prop_value;
1043     my $has_inherit;
1044     if ($t->{type} == IDENT_TOKEN) {
1045     my $prop_value = lc $t->{value}; ## TODO: case folding
1046     $t = $tt->get_next_token;
1047     if ($border_style_keyword->{$prop_value} and
1048     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
1049     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
1050     } elsif ($prop_value eq 'inherit') {
1051 wakaba 1.10 $prop_value{'border-top-style'} = ["INHERIT"];
1052 wakaba 1.7 $has_inherit = 1;
1053     } else {
1054     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1055     level => $self->{must_level},
1056     token => $t);
1057     return ($t, undef);
1058     }
1059     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
1060     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
1061     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
1062     } else {
1063     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1064     level => $self->{must_level},
1065     token => $t);
1066     return ($t, undef);
1067     }
1068    
1069     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1070     if ($t->{type} == IDENT_TOKEN) {
1071     my $prop_value = lc $t->{value}; ## TODO: case folding
1072     $t = $tt->get_next_token;
1073     if (not $has_inherit and
1074     $border_style_keyword->{$prop_value} and
1075     $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
1076     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
1077     } else {
1078     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1079     level => $self->{must_level},
1080     token => $t);
1081     return ($t, undef);
1082     }
1083     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
1084    
1085     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1086     if ($t->{type} == IDENT_TOKEN) {
1087     my $prop_value = lc $t->{value}; ## TODO: case folding
1088     $t = $tt->get_next_token;
1089     if ($border_style_keyword->{$prop_value} and
1090     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
1091     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
1092     } else {
1093     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1094     level => $self->{must_level},
1095     token => $t);
1096     return ($t, undef);
1097     }
1098    
1099     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1100     if ($t->{type} == IDENT_TOKEN) {
1101     my $prop_value = lc $t->{value}; ## TODO: case folding
1102     $t = $tt->get_next_token;
1103     if ($border_style_keyword->{$prop_value} and
1104     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
1105     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
1106     } else {
1107     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1108     level => $self->{must_level},
1109     token => $t);
1110     return ($t, undef);
1111     }
1112     }
1113     }
1114     }
1115    
1116     return ($t, \%prop_value);
1117     },
1118     serialize => sub {
1119     my ($self, $prop_name, $value) = @_;
1120    
1121     local $Error::Depth = $Error::Depth + 1;
1122     my @v;
1123     push @v, $self->border_top_style;
1124     return undef unless defined $v[-1];
1125     push @v, $self->border_right_style;
1126     return undef unless defined $v[-1];
1127     push @v, $self->border_bottom_style;
1128     return undef unless defined $v[-1];
1129     push @v, $self->border_bottom_style;
1130     return undef unless defined $v[-1];
1131    
1132     pop @v if $v[1] eq $v[3];
1133     pop @v if $v[0] eq $v[2];
1134     pop @v if $v[0] eq $v[1];
1135     return join ' ', @v;
1136     },
1137     };
1138     $Attr->{border_style} = $Prop->{'border-style'};
1139    
1140 wakaba 1.12 $Prop->{'list-style'} = {
1141     css => 'list-style',
1142     dom => 'list_style',
1143     parse => sub {
1144     my ($self, $prop_name, $tt, $t, $onerror) = @_;
1145    
1146     my %prop_value;
1147     my $none = 0;
1148    
1149     F: for my $f (1..3) {
1150     if ($t->{type} == IDENT_TOKEN) {
1151     my $prop_value = lc $t->{value}; ## TODO: case folding
1152     $t = $tt->get_next_token;
1153    
1154     if ($prop_value eq 'none') {
1155     $none++;
1156     } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
1157     if (exists $prop_value{'list-style-type'}) {
1158     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1159     $prop_name,
1160     level => $self->{must_level},
1161     token => $t);
1162     return ($t, undef);
1163     } else {
1164     $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
1165     }
1166     } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
1167     if (exists $prop_value{'list-style-position'}) {
1168     $onerror->(type => q[syntax error:duplicate:'list-style-position':].
1169     $prop_name,
1170     level => $self->{must_level},
1171     token => $t);
1172     return ($t, undef);
1173     }
1174    
1175     $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
1176     } elsif ($f == 1 and $prop_value eq 'inherit') {
1177     $prop_value{'list-style-type'} = ["INHERIT"];
1178     $prop_value{'list-style-position'} = ["INHERIT"];
1179     $prop_value{'list-style-image'} = ["INHERIT"];
1180     last F;
1181     } else {
1182     if ($f == 1) {
1183     $onerror->(type => 'syntax error:'.$prop_name,
1184     level => $self->{must_level},
1185     token => $t);
1186     return ($t, undef);
1187     } else {
1188     last F;
1189     }
1190     }
1191     } elsif ($t->{type} == URI_TOKEN) {
1192     if (exists $prop_value{'list-style-image'}) {
1193     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1194     $prop_name,
1195     level => $self->{must_level},
1196     token => $t);
1197     return ($t, undef);
1198     }
1199    
1200     $prop_value{'list-style-image'}
1201 wakaba 1.13 = ['URI', $t->{value}, \($self->{base_uri})];
1202 wakaba 1.12 $t = $tt->get_next_token;
1203     } else {
1204     if ($f == 1) {
1205     $onerror->(type => 'syntax error:keyword:'.$prop_name,
1206     level => $self->{must_level},
1207     token => $t);
1208     return ($t, undef);
1209     } else {
1210     last F;
1211     }
1212     }
1213    
1214     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1215     } # F
1216     ## NOTE: No browser support |list-style: url(xxx|{EOF}.
1217    
1218     if ($none == 1) {
1219     if (exists $prop_value{'list-style-type'}) {
1220     if (exists $prop_value{'list-style-image'}) {
1221     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1222     $prop_name,
1223     level => $self->{must_level},
1224     token => $t);
1225     return ($t, undef);
1226     } else {
1227     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
1228     }
1229     } else {
1230     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
1231     $prop_value{'list-style-image'} = ['KEYWORD', 'none']
1232     unless exists $prop_value{'list-style-image'};
1233     }
1234     } elsif ($none == 2) {
1235     if (exists $prop_value{'list-style-type'}) {
1236     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1237     $prop_name,
1238     level => $self->{must_level},
1239     token => $t);
1240     return ($t, undef);
1241     }
1242     if (exists $prop_value{'list-style-image'}) {
1243     $onerror->(type => q[syntax error:duplicate:'list-style-image':].
1244     $prop_name,
1245     level => $self->{must_level},
1246     token => $t);
1247     return ($t, undef);
1248     }
1249    
1250     $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
1251     $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
1252     } elsif ($none == 3) {
1253     $onerror->(type => q[syntax error:duplicate:'list-style-type':].
1254     $prop_name,
1255     level => $self->{must_level},
1256     token => $t);
1257     return ($t, undef);
1258     }
1259    
1260     for (qw/list-style-type list-style-position list-style-image/) {
1261     $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
1262     }
1263    
1264     return ($t, \%prop_value);
1265     },
1266     serialize => sub {
1267     my ($self, $prop_name, $value) = @_;
1268    
1269     local $Error::Depth = $Error::Depth + 1;
1270     return $self->list_style_type . ' ' . $self->list_style_position .
1271     ' ' . $self->list_style_image;
1272     },
1273     };
1274     $Attr->{list_style} = $Prop->{'list-style'};
1275    
1276 wakaba 1.1 1;
1277 wakaba 1.13 ## $Date: 2008/01/01 11:21:15 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24