/[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.12 - (hide annotations) (download)
Tue Jan 1 11:21:15 2008 UTC (17 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.11: +247 -3 lines
++ whatpm/Whatpm/CSS/ChangeLog	1 Jan 2008 11:21:12 -0000
	* Parser.pm (page-break-before, page-break-after, page-break-inside,
	orphans, widows, list-style): Added.
	(z-index): Support for negative values.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24