/[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.7 - (hide annotations) (download)
Mon Dec 31 09:09:23 2007 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
Changes since 1.6: +166 -7 lines
++ whatpm/Whatpm/CSS/ChangeLog	31 Dec 2007 08:04:36 -0000
	* Parser.pm: Support for border-style properties, as a model case
	for shorthand properties.

2007-12-31  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.1
11     return $self;
12     } # new
13    
14     sub BEFORE_STATEMENT_STATE () { 0 }
15     sub BEFORE_DECLARATION_STATE () { 1 }
16     sub IGNORED_STATEMENT_STATE () { 2 }
17     sub IGNORED_DECLARATION_STATE () { 3 }
18    
19 wakaba 1.5 our $Prop; ## By CSS property name
20     our $Attr; ## By CSSOM attribute name
21     our $Key; ## By internal key
22    
23 wakaba 1.1 sub parse_char_string ($$) {
24     my $self = $_[0];
25    
26     my $s = $_[1];
27     pos ($s) = 0;
28 wakaba 1.2 my $line = 1;
29     my $column = 0;
30    
31     my $_onerror = $self->{onerror};
32     my $onerror = sub {
33     $_onerror->(@_, line => $line, column => $column);
34     };
35 wakaba 1.1
36     my $tt = Whatpm::CSS::Tokenizer->new;
37 wakaba 1.2 $tt->{onerror} = $onerror;
38 wakaba 1.1 $tt->{get_char} = sub {
39     if (pos $s < length $s) {
40 wakaba 1.2 my $c = ord substr $s, pos ($s)++, 1;
41     if ($c == 0x000A) {
42     $line++;
43     $column = 0;
44     } elsif ($c == 0x000D) {
45     unless (substr ($s, pos ($s), 1) eq "\x0A") {
46     $line++;
47     $column = 0;
48     } else {
49     $column++;
50     }
51     } else {
52     $column++;
53     }
54     return $c;
55 wakaba 1.1 } else {
56     return -1;
57     }
58     }; # $tt->{get_char}
59     $tt->init;
60    
61     my $sp = Whatpm::CSS::SelectorsParser->new;
62 wakaba 1.2 $sp->{onerror} = $onerror;
63 wakaba 1.1 $sp->{must_level} = $self->{must_level};
64 wakaba 1.2 $sp->{pseudo_element} = $self->{pseudo_element};
65     $sp->{pseudo_class} = $self->{pseudo_class};
66 wakaba 1.1
67 wakaba 1.4 my $nsmap = {};
68     $sp->{lookup_namespace_uri} = sub {
69     return $nsmap->{$_[0]}; # $_[0] is '' (default namespace) or prefix
70     }; # $sp->{lookup_namespace_uri}
71 wakaba 1.1
72     ## TODO: Supported pseudo classes and elements...
73    
74     require Message::DOM::CSSStyleSheet;
75     require Message::DOM::CSSRule;
76     require Message::DOM::CSSStyleDeclaration;
77    
78     my $state = BEFORE_STATEMENT_STATE;
79     my $t = $tt->get_next_token;
80    
81     my $open_rules = [[]];
82     my $current_rules = $open_rules->[-1];
83     my $current_decls;
84     my $closing_tokens = [];
85 wakaba 1.3 my $charset_allowed = 1;
86 wakaba 1.4 my $namespace_allowed = 1;
87 wakaba 1.1
88     S: {
89     if ($state == BEFORE_STATEMENT_STATE) {
90     $t = $tt->get_next_token
91     while $t->{type} == S_TOKEN or
92     $t->{type} == CDO_TOKEN or
93     $t->{type} == CDC_TOKEN;
94    
95     if ($t->{type} == ATKEYWORD_TOKEN) {
96 wakaba 1.5 if (lc $t->{value} eq 'namespace') { ## TODO: case folding
97 wakaba 1.4 $t = $tt->get_next_token;
98     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
99    
100     my $prefix;
101     if ($t->{type} == IDENT_TOKEN) {
102     $prefix = lc $t->{value};
103     ## TODO: Unicode lowercase
104    
105     $t = $tt->get_next_token;
106     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
107     }
108    
109     if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
110     my $uri = $t->{value};
111    
112     $t = $tt->get_next_token;
113     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
114    
115     ## ISSUE: On handling of empty namespace URI, Firefox 2 and
116     ## Opera 9 work differently (See SuikaWiki:namespace).
117     ## TODO: We need to check what we do once it is specced.
118    
119     if ($t->{type} == SEMICOLON_TOKEN) {
120     if ($namespace_allowed) {
121     $nsmap->{defined $prefix ? $prefix : ''} = $uri;
122     push @$current_rules,
123     Message::DOM::CSSNamespaceRule->____new ($prefix, $uri);
124     undef $charset_allowed;
125     undef $namespace_allowed;
126     } else {
127     $onerror->(type => 'at:namespace:not allowed',
128     level => $self->{must_level},
129     token => $t);
130     }
131    
132     $t = $tt->get_next_token;
133     ## Stay in the state.
134     redo S;
135     } else {
136     #
137     }
138     } else {
139     #
140     }
141    
142     $onerror->(type => 'syntax error:at:namespace',
143     level => $self->{must_level},
144     token => $t);
145     #
146 wakaba 1.5 } elsif (lc $t->{value} eq 'charset') { ## TODO: case folding
147 wakaba 1.3 $t = $tt->get_next_token;
148     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
149    
150     if ($t->{type} == STRING_TOKEN) {
151     my $encoding = $t->{value};
152    
153     $t = $tt->get_next_token;
154     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
155    
156     if ($t->{type} == SEMICOLON_TOKEN) {
157     if ($charset_allowed) {
158     push @$current_rules,
159     Message::DOM::CSSCharsetRule->____new ($encoding);
160     undef $charset_allowed;
161     } else {
162     $onerror->(type => 'at:charset:not allowed',
163     level => $self->{must_level},
164     token => $t);
165     }
166    
167     ## TODO: Detect the conformance errors for @charset...
168    
169     $t = $tt->get_next_token;
170     ## Stay in the state.
171     redo S;
172     } else {
173     #
174     }
175     } else {
176     #
177     }
178    
179     $onerror->(type => 'syntax error:at:charset',
180     level => $self->{must_level},
181     token => $t);
182 wakaba 1.4 #
183 wakaba 1.3 ## NOTE: When adding support for new at-rule, insert code
184 wakaba 1.4 ## "undef $charset_allowed" and "undef $namespace_token" as
185     ## appropriate.
186 wakaba 1.3 } else {
187     $onerror->(type => 'not supported:at:'.$t->{value},
188     level => $self->{unsupported_level},
189     token => $t);
190     }
191 wakaba 1.1
192     $t = $tt->get_next_token;
193     $state = IGNORED_STATEMENT_STATE;
194     redo S;
195     } elsif (@$open_rules > 1 and $t->{type} == RBRACE_TOKEN) {
196     pop @$open_rules;
197     ## Stay in the state.
198     $t = $tt->get_next_token;
199     redo S;
200     } elsif ($t->{type} == EOF_TOKEN) {
201     if (@$open_rules > 1) {
202 wakaba 1.2 $onerror->(type => 'syntax error:block not closed',
203     level => $self->{must_level},
204     token => $t);
205 wakaba 1.1 }
206    
207     last S;
208     } else {
209 wakaba 1.3 undef $charset_allowed;
210 wakaba 1.4 undef $namespace_allowed;
211 wakaba 1.3
212 wakaba 1.1 ($t, my $selectors) = $sp->_parse_selectors_with_tokenizer
213     ($tt, LBRACE_TOKEN, $t);
214    
215     $t = $tt->get_next_token
216     while $t->{type} != LBRACE_TOKEN and $t->{type} != EOF_TOKEN;
217    
218     if ($t->{type} == LBRACE_TOKEN) {
219     $current_decls = Message::DOM::CSSStyleDeclaration->____new;
220     my $rs = Message::DOM::CSSStyleRule->____new
221     ($selectors, $current_decls);
222     push @{$current_rules}, $rs if defined $selectors;
223    
224     $state = BEFORE_DECLARATION_STATE;
225     $t = $tt->get_next_token;
226     redo S;
227     } else {
228 wakaba 1.2 $onerror->(type => 'syntax error:after selectors',
229     level => $self->{must_level},
230     token => $t);
231 wakaba 1.1
232     ## Stay in the state.
233     $t = $tt->get_next_token;
234     redo S;
235     }
236     }
237     } elsif ($state == BEFORE_DECLARATION_STATE) {
238     ## NOTE: DELIM? in declaration will be removed:
239     ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.
240    
241 wakaba 1.5 my $prop_def;
242     my $prop_value;
243     my $prop_flag;
244 wakaba 1.1 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
245     if ($t->{type} == IDENT_TOKEN) { # property
246 wakaba 1.5 my $prop_name = lc $t->{value}; ## TODO: case folding
247     $t = $tt->get_next_token;
248     if ($t->{type} == COLON_TOKEN) {
249     $t = $tt->get_next_token;
250     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
251    
252     $prop_def = $Prop->{$prop_name};
253 wakaba 1.6 if ($prop_def and $self->{prop}->{$prop_name}) {
254 wakaba 1.5 ($t, $prop_value)
255     = $prop_def->{parse}->($self, $prop_name, $tt, $t, $onerror);
256     if ($prop_value) {
257     ## NOTE: {parse} don't have to consume trailing spaces.
258     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
259    
260     if ($t->{type} == EXCLAMATION_TOKEN) {
261     $t = $tt->get_next_token;
262     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
263     if ($t->{type} == IDENT_TOKEN and
264     lc $t->{value} eq 'important') { ## TODO: case folding
265     $prop_flag = 'important';
266    
267     $t = $tt->get_next_token;
268     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
269    
270     #
271     } else {
272     $onerror->(type => 'syntax error:important',
273     level => $self->{must_level},
274     token => $t);
275    
276     ## Reprocess.
277     $state = IGNORED_DECLARATION_STATE;
278     redo S;
279     }
280     }
281    
282     #
283     } else {
284     ## Syntax error.
285    
286     ## Reprocess.
287     $state = IGNORED_DECLARATION_STATE;
288     redo S;
289     }
290     } else {
291     $onerror->(type => 'not supported:property',
292     level => $self->{unsupported_level},
293     token => $t, value => $prop_name);
294    
295     #
296     $state = IGNORED_DECLARATION_STATE;
297     redo S;
298     }
299     } else {
300     $onerror->(type => 'syntax error:property colon',
301     level => $self->{must_level},
302     token => $t);
303 wakaba 1.1
304 wakaba 1.5 #
305     $state = IGNORED_DECLARATION_STATE;
306     redo S;
307     }
308     }
309    
310     if ($t->{type} == RBRACE_TOKEN) {
311 wakaba 1.1 $t = $tt->get_next_token;
312 wakaba 1.5 $state = BEFORE_STATEMENT_STATE;
313     #redo S;
314     } elsif ($t->{type} == SEMICOLON_TOKEN) {
315 wakaba 1.1 $t = $tt->get_next_token;
316 wakaba 1.5 ## Stay in the state.
317     #redo S;
318 wakaba 1.1 } elsif ($t->{type} == EOF_TOKEN) {
319 wakaba 1.2 $onerror->(type => 'syntax error:ruleset not closed',
320     level => $self->{must_level},
321     token => $t);
322 wakaba 1.1 ## Reprocess.
323     $state = BEFORE_STATEMENT_STATE;
324 wakaba 1.5 #redo S;
325     } else {
326     if ($prop_value) {
327     $onerror->(type => 'syntax error:property semicolon',
328     level => $self->{must_level},
329     token => $t);
330     } else {
331     $onerror->(type => 'syntax error:property name',
332     level => $self->{must_level},
333     token => $t);
334     }
335    
336     #
337     $state = IGNORED_DECLARATION_STATE;
338 wakaba 1.1 redo S;
339     }
340    
341 wakaba 1.7 my $important = (defined $prop_flag and $prop_flag eq 'important');
342     for my $set_prop_name (keys %{$prop_value or {}}) {
343     my $set_prop_def = $Prop->{$set_prop_name};
344     $$current_decls->{$set_prop_def->{key}}
345     = [$prop_value->{$set_prop_name}, $prop_flag]
346     if $important or
347     not $$current_decls->{$set_prop_def->{key}} or
348     not defined $$current_decls->{$set_prop_def->{key}}->[1];
349 wakaba 1.5 }
350 wakaba 1.1 redo S;
351     } elsif ($state == IGNORED_STATEMENT_STATE or
352     $state == IGNORED_DECLARATION_STATE) {
353     if (@$closing_tokens) { ## Something is yet in opening state.
354     if ($t->{type} == EOF_TOKEN) {
355     @$closing_tokens = ();
356     ## Reprocess.
357     $state = $state == IGNORED_STATEMENT_STATE
358     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
359     redo S;
360     } elsif ($t->{type} == $closing_tokens->[-1]) {
361     pop @$closing_tokens;
362     if (@$closing_tokens == 0 and
363     $t->{type} == RBRACE_TOKEN and
364     $state == IGNORED_STATEMENT_STATE) {
365     $t = $tt->get_next_token;
366     $state = BEFORE_STATEMENT_STATE;
367     redo S;
368     } else {
369     $t = $tt->get_next_token;
370     ## Stay in the state.
371     redo S;
372     }
373     } else {
374     #
375     }
376     } else {
377     if ($t->{type} == SEMICOLON_TOKEN) {
378     $t = $tt->get_next_token;
379     $state = $state == IGNORED_STATEMENT_STATE
380     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
381     redo S;
382     } elsif ($state == IGNORED_DECLARATION_STATE and
383     $t->{type} == RBRACE_TOKEN) {
384     $t = $tt->get_next_token;
385     $state = BEFORE_STATEMENT_STATE;
386     redo S;
387     } elsif ($t->{type} == EOF_TOKEN) {
388     ## Reprocess.
389     $state = $state == IGNORED_STATEMENT_STATE
390     ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
391     redo S;
392     } else {
393     #
394     }
395     }
396    
397     while (not {
398     EOF_TOKEN, 1,
399     RBRACE_TOKEN, 1,
400     RBRACKET_TOKEN, 1,
401     RPAREN_TOKEN, 1,
402     SEMICOLON_TOKEN, 1,
403     }->{$t->{type}}) {
404     if ($t->{type} == LBRACE_TOKEN) {
405     push @$closing_tokens, RBRACE_TOKEN;
406     } elsif ($t->{type} == LBRACKET_TOKEN) {
407     push @$closing_tokens, RBRACKET_TOKEN;
408     } elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {
409     push @$closing_tokens, RPAREN_TOKEN;
410     }
411    
412     $t = $tt->get_next_token;
413     }
414    
415     #
416     ## Stay in the state.
417     redo S;
418     } else {
419     die "$0: parse_char_string: Unknown state: $state";
420     }
421     } # S
422    
423     my $ss = Message::DOM::CSSStyleSheet->____new
424     (css_rules => $open_rules->[0],
425     ## TODO: href
426     ## TODO: owner_node
427     ## TODO: media
428     type => 'text/css', ## TODO: OK?
429     _parser => $self);
430     return $ss;
431     } # parse_char_string
432    
433 wakaba 1.5 $Prop->{color} = {
434     css => 'color',
435     dom => 'color',
436     key => 'color',
437     parse => sub {
438     my ($self, $prop_name, $tt, $t, $onerror) = @_;
439    
440     if ($t->{type} == IDENT_TOKEN) {
441     if (lc $t->{value} eq 'blue') { ## TODO: case folding
442     $t = $tt->get_next_token;
443 wakaba 1.7 return ($t, {$prop_name => ["RGBA", 0, 0, 255, 1]});
444 wakaba 1.5 } else {
445     #
446     }
447     } else {
448     #
449     }
450    
451     $onerror->(type => 'syntax error:color',
452     level => $self->{must_level},
453     token => $t);
454    
455     return ($t, undef);
456     },
457     serialize => sub {
458     my ($self, $prop_name, $value) = @_;
459     if ($value->[0] eq 'RGBA') { ## TODO: %d? %f?
460     return sprintf 'rgba(%d, %d, %d, %f)', @$value[1, 2, 3, 4];
461     } else {
462     return undef;
463     }
464     },
465     };
466     $Attr->{color} = $Prop->{color};
467     $Key->{color} = $Prop->{color};
468    
469 wakaba 1.6 my $one_keyword_parser = sub {
470     my ($self, $prop_name, $tt, $t, $onerror) = @_;
471    
472     if ($t->{type} == IDENT_TOKEN) {
473     my $prop_value = lc $t->{value}; ## TODO: case folding
474     $t = $tt->get_next_token;
475     if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
476     $self->{prop_value}->{$prop_name}->{$prop_value}) {
477 wakaba 1.7 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
478 wakaba 1.6 } elsif ($prop_value eq 'inherit') {
479 wakaba 1.7 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
480 wakaba 1.6 }
481     }
482    
483 wakaba 1.7 $onerror->(type => 'syntax error:keyword:'.$prop_name,
484 wakaba 1.6 level => $self->{must_level},
485     token => $t);
486     return ($t, undef);
487     };
488    
489     my $one_keyword_serializer = sub {
490     my ($self, $prop_name, $value) = @_;
491     if ($value->[0] eq 'KEYWORD') {
492     return $value->[1];
493     } else {
494     return undef;
495     }
496     };
497    
498     $Prop->{display} = {
499     css => 'display',
500     dom => 'display',
501     key => 'display',
502     parse => $one_keyword_parser,
503     serialize => $one_keyword_serializer,
504     keyword => {
505     block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
506     'list-item' => 1, none => 1,
507     table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
508     'table-column-group' => 1, 'table-header-group' => 1,
509     'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
510     },
511     };
512     $Attr->{display} = $Prop->{display};
513     $Key->{display} = $Prop->{display};
514    
515     $Prop->{position} = {
516     css => 'position',
517     dom => 'position',
518     key => 'position',
519     parse => $one_keyword_parser,
520     serialize => $one_keyword_serializer,
521     keyword => {
522     static => 1, relative => 1, absolute => 1, fixed => 1,
523     },
524     };
525     $Attr->{position} = $Prop->{position};
526     $Key->{position} = $Prop->{position};
527    
528     $Prop->{float} = {
529     css => 'float',
530     dom => 'css_float',
531     key => 'float',
532     parse => $one_keyword_parser,
533     serialize => $one_keyword_serializer,
534     keyword => {
535     left => 1, right => 1, none => 1,
536     },
537     };
538     $Attr->{css_float} = $Prop->{float};
539     $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
540     $Key->{float} = $Prop->{float};
541    
542     $Prop->{clear} = {
543     css => 'clear',
544     dom => 'clear',
545     key => 'clear',
546     parse => $one_keyword_parser,
547     serialize => $one_keyword_serializer,
548     keyword => {
549     left => 1, right => 1, none => 1, both => 1,
550     },
551     };
552     $Attr->{clear} = $Prop->{clear};
553     $Key->{clear} = $Prop->{clear};
554    
555     $Prop->{direction} = {
556     css => 'direction',
557     dom => 'direction',
558     key => 'direction',
559     parse => $one_keyword_parser,
560     serialize => $one_keyword_serializer,
561     keyword => {
562     ltr => 1, rtl => 1,
563     },
564     };
565     $Attr->{direction} = $Prop->{direction};
566     $Key->{direction} = $Prop->{direction};
567    
568     $Prop->{'unicode-bidi'} = {
569     css => 'unicode-bidi',
570     dom => 'unicode_bidi',
571     key => 'unicode_bidi',
572     parse => $one_keyword_parser,
573     serialize => $one_keyword_serializer,
574     keyword => {
575     normal => 1, embed => 1, 'bidi-override' => 1,
576     },
577     };
578     $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
579     $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
580    
581 wakaba 1.7 my $border_style_keyword = {
582     none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
583     double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
584     };
585    
586     $Prop->{'border-top-style'} = {
587     css => 'border-top-style',
588     dom => 'border_top_style',
589     key => 'border_top_style',
590     parse => $one_keyword_parser,
591     serialize => $one_keyword_serializer,
592     keyword => $border_style_keyword,
593     };
594     $Attr->{border_top_style} = $Prop->{'border-top-style'};
595     $Key->{border_top_style} = $Prop->{'border-top-style'};
596    
597     $Prop->{'border-right-style'} = {
598     css => 'border-right-style',
599     dom => 'border_right_style',
600     key => 'border_right_style',
601     parse => $one_keyword_parser,
602     serialize => $one_keyword_serializer,
603     keyword => $border_style_keyword,
604     };
605     $Attr->{border_right_style} = $Prop->{'border-right-style'};
606     $Key->{border_right_style} = $Prop->{'border-right-style'};
607    
608     $Prop->{'border-bottom-style'} = {
609     css => 'border-bottom-style',
610     dom => 'border_bottom_style',
611     key => 'border_bottom_style',
612     parse => $one_keyword_parser,
613     serialize => $one_keyword_serializer,
614     keyword => $border_style_keyword,
615     };
616     $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
617     $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
618    
619     $Prop->{'border-left-style'} = {
620     css => 'border-left-style',
621     dom => 'border_left_style',
622     key => 'border_left_style',
623     parse => $one_keyword_parser,
624     serialize => $one_keyword_serializer,
625     keyword => $border_style_keyword,
626     };
627     $Attr->{border_left_style} = $Prop->{'border-left-style'};
628     $Key->{border_left_style} = $Prop->{'border-left-style'};
629    
630     $Prop->{'border-style'} = {
631     css => 'border-style',
632     dom => 'border_style',
633     parse => sub {
634     my ($self, $prop_name, $tt, $t, $onerror) = @_;
635    
636     my %prop_value;
637     my $has_inherit;
638     if ($t->{type} == IDENT_TOKEN) {
639     my $prop_value = lc $t->{value}; ## TODO: case folding
640     $t = $tt->get_next_token;
641     if ($border_style_keyword->{$prop_value} and
642     $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
643     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
644     } elsif ($prop_value eq 'inherit') {
645     $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
646     $has_inherit = 1;
647     } else {
648     $onerror->(type => 'syntax error:keyword:'.$prop_name,
649     level => $self->{must_level},
650     token => $t);
651     return ($t, undef);
652     }
653     $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
654     $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
655     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
656     } else {
657     $onerror->(type => 'syntax error:keyword:'.$prop_name,
658     level => $self->{must_level},
659     token => $t);
660     return ($t, undef);
661     }
662    
663     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
664     if ($t->{type} == IDENT_TOKEN) {
665     my $prop_value = lc $t->{value}; ## TODO: case folding
666     $t = $tt->get_next_token;
667     if (not $has_inherit and
668     $border_style_keyword->{$prop_value} and
669     $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
670     $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
671     } else {
672     $onerror->(type => 'syntax error:keyword:'.$prop_name,
673     level => $self->{must_level},
674     token => $t);
675     return ($t, undef);
676     }
677     $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
678    
679     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
680     if ($t->{type} == IDENT_TOKEN) {
681     my $prop_value = lc $t->{value}; ## TODO: case folding
682     $t = $tt->get_next_token;
683     if ($border_style_keyword->{$prop_value} and
684     $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
685     $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
686     } else {
687     $onerror->(type => 'syntax error:keyword:'.$prop_name,
688     level => $self->{must_level},
689     token => $t);
690     return ($t, undef);
691     }
692    
693     $t = $tt->get_next_token while $t->{type} == S_TOKEN;
694     if ($t->{type} == IDENT_TOKEN) {
695     my $prop_value = lc $t->{value}; ## TODO: case folding
696     $t = $tt->get_next_token;
697     if ($border_style_keyword->{$prop_value} and
698     $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
699     $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
700     } else {
701     $onerror->(type => 'syntax error:keyword:'.$prop_name,
702     level => $self->{must_level},
703     token => $t);
704     return ($t, undef);
705     }
706     }
707     }
708     }
709    
710     return ($t, \%prop_value);
711     },
712     serialize => sub {
713     my ($self, $prop_name, $value) = @_;
714    
715     local $Error::Depth = $Error::Depth + 1;
716     my @v;
717     push @v, $self->border_top_style;
718     return undef unless defined $v[-1];
719     push @v, $self->border_right_style;
720     return undef unless defined $v[-1];
721     push @v, $self->border_bottom_style;
722     return undef unless defined $v[-1];
723     push @v, $self->border_bottom_style;
724     return undef unless defined $v[-1];
725    
726     pop @v if $v[1] eq $v[3];
727     pop @v if $v[0] eq $v[2];
728     pop @v if $v[0] eq $v[1];
729     return join ' ', @v;
730     },
731     };
732     $Attr->{border_style} = $Prop->{'border-style'};
733    
734 wakaba 1.1 1;
735 wakaba 1.7 ## $Date: 2007/12/31 07:26:35 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24