/[suikacvs]/markup/html/whatpm/Whatpm/CSS/SelectorsParser.pm
Suika

Contents of /markup/html/whatpm/Whatpm/CSS/SelectorsParser.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.6 - (hide annotations) (download)
Sat Nov 24 11:21:04 2007 UTC (18 years, 8 months ago) by wakaba
Branch: MAIN
Changes since 1.5: +122 -42 lines
++ whatpm/t/ChangeLog	24 Nov 2007 11:19:23 -0000
2007-11-24  Wakaba  <wakaba@suika.fam.cx>

	* content-model-2.dat: nest/@mode test data revised.
	Test data for nest/@filter and rule/@condition are added.

++ whatpm/Whatpm/CSS/ChangeLog	24 Nov 2007 11:20:48 -0000
2007-11-24  Wakaba  <wakaba@suika.fam.cx>

	* SelectorsParser.pm (parse_string): Raise errors if appropriate.

++ whatpm/Whatpm/ContentChecker/ChangeLog	24 Nov 2007 11:20:02 -0000
2007-11-24  Wakaba  <wakaba@suika.fam.cx>

	* HTML.pm (nest/@filter, rule/@condition): Implemented.
	(nest/@mode): Definition was wrong.
	($HTMLSelectorsAttrChecker): New.

1 wakaba 1.1 package Whatpm::CSS::SelectorsParser;
2     use strict;
3 wakaba 1.6 our $VERSION=do{my @r=(q$Revision: 1.5 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
4 wakaba 1.1
5     require Exporter;
6     push our @ISA, 'Exporter';
7    
8     use Whatpm::CSS::Tokenizer qw(:token);
9    
10     sub new ($) {
11     my $self = bless {onerror => sub { }, lookup_namespace_uri => sub {
12     return undef;
13 wakaba 1.6 }, must_level => 'm'}, shift;
14 wakaba 1.1 return $self;
15     } # new
16    
17     sub BEFORE_TYPE_SELECTOR_STATE () { 1 }
18     sub AFTER_NAME_STATE () { 2 }
19     sub BEFORE_LOCAL_NAME_STATE () { 3 }
20     sub BEFORE_SIMPLE_SELECTOR_STATE () { 4 }
21     sub BEFORE_CLASS_NAME_STATE () { 5 }
22     sub AFTER_COLON_STATE () { 6 }
23     sub AFTER_DOUBLE_COLON_STATE () { 7 }
24     sub AFTER_LBRACKET_STATE () { 8 }
25     sub AFTER_ATTR_NAME_STATE () { 9 }
26     sub BEFORE_ATTR_LOCAL_NAME_STATE () { 10 }
27     sub BEFORE_MATCH_STATE () { 11 }
28     sub BEFORE_VALUE_STATE () { 12 }
29     sub AFTER_VALUE_STATE () { 13 }
30     sub BEFORE_COMBINATOR_STATE () { 14 }
31     sub COMBINATOR_STATE () { 15 }
32     sub BEFORE_LANG_TAG_STATE () { 16 }
33     sub AFTER_LANG_TAG_STATE () { 17 }
34     sub BEFORE_AN_STATE () { 18 }
35     sub AFTER_AN_STATE () { 19 }
36     sub BEFORE_B_STATE () { 20 }
37     sub AFTER_B_STATE () { 21 }
38     sub AFTER_NEGATION_SIMPLE_SELECTOR_STATE () { 22 }
39 wakaba 1.3 sub BEFORE_CONTAINS_STRING_STATE () { 23 }
40 wakaba 1.1
41     sub NAMESPACE_SELECTOR () { 1 }
42     sub LOCAL_NAME_SELECTOR () { 2 }
43     sub ID_SELECTOR () { 3 }
44     sub CLASS_SELECTOR () { 4 }
45     sub PSEUDO_CLASS_SELECTOR () { 5 }
46     sub PSEUDO_ELEMENT_SELECTOR () { 6 }
47     sub ATTRIBUTE_SELECTOR () { 7 }
48    
49     sub DESCENDANT_COMBINATOR () { S_TOKEN }
50     sub CHILD_COMBINATOR () { GREATER_TOKEN }
51     sub ADJACENT_SIBLING_COMBINATOR () { PLUS_TOKEN }
52     sub GENERAL_SIBLING_COMBINATOR () { TILDE_TOKEN }
53    
54     sub EXISTS_MATCH () { 0 }
55     sub EQUALS_MATCH () { MATCH_TOKEN }
56     sub INCLUDES_MATCH () { INCLUDES_TOKEN }
57     sub DASH_MATCH () { DASHMATCH_TOKEN }
58     sub PREFIX_MATCH () { PREFIXMATCH_TOKEN }
59     sub SUFFIX_MATCH () { SUFFIXMATCH_TOKEN }
60     sub SUBSTRING_MATCH () { SUBSTRINGMATCH_TOKEN }
61    
62     our @EXPORT_OK = qw(NAMESPACE_SELECTOR LOCAL_NAME_SELECTOR ID_SELECTOR
63     CLASS_SELECTOR PSEUDO_CLASS_SELECTOR PSEUDO_ELEMENT_SELECTOR
64     ATTRIBUTE_SELECTOR
65     DESCENDANT_COMBINATOR CHILD_COMBINATOR
66     ADJACENT_SIBLING_COMBINATOR GENERAL_SIBLING_COMBINATOR
67     EXISTS_MATCH EQUALS_MATCH INCLUDES_MATCH DASH_MATCH PREFIX_MATCH
68     SUFFIX_MATCH SUBSTRING_MATCH);
69    
70     our %EXPORT_TAGS = (
71     selector => [qw(NAMESPACE_SELECTOR LOCAL_NAME_SELECTOR ID_SELECTOR
72     CLASS_SELECTOR PSEUDO_CLASS_SELECTOR PSEUDO_ELEMENT_SELECTOR
73     ATTRIBUTE_SELECTOR)],
74     combinator => [qw(DESCENDANT_COMBINATOR CHILD_COMBINATOR
75     ADJACENT_SIBLING_COMBINATOR GENERAL_SIBLING_COMBINATOR)],
76     match => [qw(EXISTS_MATCH EQUALS_MATCH INCLUDES_MATCH DASH_MATCH
77     PREFIX_MATCH SUFFIX_MATCH SUBSTRING_MATCH)],
78     );
79    
80     sub parse_string ($$) {
81     my $self = $_[0];
82    
83     my $s = $_[1];
84     pos ($s) = 0;
85    
86     my $tt = Whatpm::CSS::Tokenizer->new;
87     $tt->{onerror} = $self->{onerror};
88     $tt->{get_char} = sub {
89     if (pos $s < length $s) {
90     return ord substr $s, pos ($s)++, 1;
91     } else {
92     return -1;
93     }
94     }; # $tt->{get_char}
95     $tt->init;
96    
97 wakaba 1.2 my $default_namespace = $self->{lookup_namespace_uri}->('');
98 wakaba 1.1
99     ## ISSUE: The Selectors spec only poorly defines how tokens are mapped
100     ## to each component of selectors. In addition, it does not well define
101     ## where spaces and comments are able to be inserted.
102    
103     my $selectors = [];
104     my $selector = [DESCENDANT_COMBINATOR];
105     my $sss = [];
106     my $simple_selector;
107     my $has_pseudo_element;
108     my $in_negation;
109    
110     my $state = BEFORE_TYPE_SELECTOR_STATE;
111     my $t = $tt->get_next_token;
112     my $name;
113     S: {
114     if ($state == BEFORE_TYPE_SELECTOR_STATE) {
115     $in_negation = 2 if $in_negation;
116    
117     if ($t->{type} == IDENT_TOKEN) { ## element type or namespace prefix
118     $name = $t->{value};
119     $state = AFTER_NAME_STATE;
120     $t = $tt->get_next_token;
121     redo S;
122     } elsif ($t->{type} == STAR_TOKEN) { ## universal selector or prefix
123     undef $name;
124     $state = AFTER_NAME_STATE;
125     $t = $tt->get_next_token;
126     redo S;
127     } elsif ($t->{type} == VBAR_TOKEN) { ## null namespace
128     undef $name;
129     push @$sss, [NAMESPACE_SELECTOR, undef];
130    
131     $state = BEFORE_LOCAL_NAME_STATE;
132     $t = $tt->get_next_token;
133     redo S;
134     } elsif ($t->{type} == S_TOKEN) {
135     ## Stay in the state.
136     $t = $tt->get_next_token;
137     redo S;
138     } elsif ({
139     DOT_TOKEN, 1,
140     COLON_TOKEN, 1,
141     HASH_TOKEN, 1,
142     LBRACKET_TOKEN, 1,
143     RPAREN_TOKEN, 1, # :not(a ->> ) <<-
144     }->{$t->{type}}) {
145     $in_negation = 1 if $in_negation;
146     push @$sss, [NAMESPACE_SELECTOR, $default_namespace]
147     if defined $default_namespace;
148    
149     $state = BEFORE_SIMPLE_SELECTOR_STATE;
150     # Reprocess.
151     redo S;
152     } else {
153 wakaba 1.6 $self->{onerror}->(type => 'syntax error:before type selector',
154     level => $self->{must_level},
155     token => $t);
156 wakaba 1.1 return undef;
157     }
158     } elsif ($state == BEFORE_SIMPLE_SELECTOR_STATE) {
159     if ($in_negation and $in_negation++ == 2) {
160     $state = AFTER_NEGATION_SIMPLE_SELECTOR_STATE;
161     ## Reprocess.
162     redo S;
163     }
164    
165     if ($t->{type} == DOT_TOKEN) { ## class selector
166     if ($has_pseudo_element) {
167 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after pseudo element',
168     level => $self->{must_level},
169     token => $t);
170 wakaba 1.1 return undef;
171     }
172     $state = BEFORE_CLASS_NAME_STATE;
173     $t = $tt->get_next_token;
174     redo S;
175     } elsif ($t->{type} == HASH_TOKEN) { ## ID selector
176     if ($has_pseudo_element) {
177 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after pseudo element',
178     level => $self->{must_level},
179     token => $t);
180 wakaba 1.1 return undef;
181     }
182     push @$sss, [ID_SELECTOR, $t->{value}];
183     $state = BEFORE_SIMPLE_SELECTOR_STATE;
184     $t = $tt->get_next_token;
185     redo S;
186     } elsif ($t->{type} == COLON_TOKEN) { ## pseudo-class or pseudo-element
187     if ($has_pseudo_element) {
188 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after pseudo element',
189     level => $self->{must_level},
190     token => $t);
191 wakaba 1.1 return undef;
192     }
193     $state = AFTER_COLON_STATE;
194     $t = $tt->get_next_token;
195     redo S;
196     } elsif ($t->{type} == LBRACKET_TOKEN) { ## attribute selector
197     if ($has_pseudo_element) {
198 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after pseudo element',
199     level => $self->{must_level},
200     token => $t);
201 wakaba 1.1 return undef;
202     }
203     $state = AFTER_LBRACKET_STATE;
204     $t = $tt->get_next_token;
205     redo S;
206     } else {
207     $state = BEFORE_COMBINATOR_STATE;
208     ## Reprocess.
209     redo S;
210     }
211     } elsif ($state == AFTER_NAME_STATE) {
212     if ($t->{type} == VBAR_TOKEN) {
213     $state = BEFORE_LOCAL_NAME_STATE;
214     $t = $tt->get_next_token;
215     redo S;
216     } else { ## Type or universal selector w/o namespace prefix
217     push @$sss, [NAMESPACE_SELECTOR, $default_namespace]
218     if defined $default_namespace;
219     push @$sss, [LOCAL_NAME_SELECTOR, $name] if defined $name;
220    
221     $state = BEFORE_SIMPLE_SELECTOR_STATE;
222     ## reprocess.
223     redo S;
224     }
225     } elsif ($state == BEFORE_LOCAL_NAME_STATE) {
226     if ($t->{type} == IDENT_TOKEN) {
227     if (defined $name) { ## Prefix is neither empty nor "*"
228     my $uri = $self->{lookup_namespace_uri}->($name);
229     unless (defined $uri) {
230 wakaba 1.6 $self->{onerror}->(type => 'namespace prefix:not declared',
231     level => $self->{must_level},
232     token => $t);
233 wakaba 1.1 return undef;
234     }
235     push @$sss, [NAMESPACE_SELECTOR, $uri];
236     }
237     push @$sss, [LOCAL_NAME_SELECTOR, $t->{value}];
238    
239     $state = BEFORE_SIMPLE_SELECTOR_STATE;
240     $t = $tt->get_next_token;
241     redo S;
242     } elsif ($t->{type} == STAR_TOKEN) {
243     if (defined $name) { ## Prefix is neither empty nor "*"
244     my $uri = $self->{lookup_namespace_uri}->($name);
245     unless (defined $uri) {
246 wakaba 1.6 $self->{onerror}->(type => 'namespace prefix:not declared',
247     level => $self->{must_level},
248     token => $t);
249 wakaba 1.1 return undef;
250     }
251     push @$sss, [NAMESPACE_SELECTOR, $uri];
252     }
253     $state = BEFORE_SIMPLE_SELECTOR_STATE;
254     $t = $tt->get_next_token;
255     redo S;
256     } else { ## "|" not followed by type or universal selector
257 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after namespace prefix',
258     level => $self->{must_level},
259     token => $t);
260 wakaba 1.1 return undef;
261     }
262     } elsif ($state == BEFORE_CLASS_NAME_STATE) {
263     if ($t->{type} == IDENT_TOKEN) {
264     push @$sss, [CLASS_SELECTOR, $t->{value}];
265    
266     $state = BEFORE_SIMPLE_SELECTOR_STATE;
267     $t = $tt->get_next_token;
268     redo S;
269     } else {
270 wakaba 1.6 $self->{onerror}->(type => 'syntax error:before class name',
271     level => $self->{must_level},
272     token => $t);
273 wakaba 1.1 return undef;
274     }
275     } elsif ($state == BEFORE_COMBINATOR_STATE) {
276     push @$selector, $sss;
277     $sss = [];
278    
279     if ($t->{type} == S_TOKEN) {
280     $state = COMBINATOR_STATE;
281     $t = $tt->get_next_token;
282     redo S;
283     } elsif ({
284     GREATER_TOKEN, 1,
285     PLUS_TOKEN, 1,
286     TILDE_TOKEN, 1,
287     COMMA_TOKEN, 1,
288     EOF_TOKEN, 1,
289     }->{$t->{type}}) {
290     $state = COMBINATOR_STATE;
291     ## Reprocess.
292     redo S;
293     } else {
294 wakaba 1.6 $self->{onerror}->(type => 'syntax error:before combinator',
295     level => $self->{must_level},
296     token => $t);
297 wakaba 1.1 return undef;
298     }
299     } elsif ($state == COMBINATOR_STATE) {
300     if ($state == S_TOKEN) {
301     ## Stay in the state.
302     $t = $tt->get_next_token;
303     redo S;
304     } elsif ({
305     GREATER_TOKEN, 1,
306     PLUS_TOKEN, 1,
307     TILDE_TOKEN, 1,
308     }->{$t->{type}}) {
309     push @$selector, $t->{type};
310    
311     $state = BEFORE_TYPE_SELECTOR_STATE;
312     $t = $tt->get_next_token;
313     redo S;
314     } elsif ($t->{type} == EOF_TOKEN) {
315     push @$selectors, $selector;
316     return $selectors;
317     } elsif ($t->{type} == COMMA_TOKEN) {
318     push @$selectors, $selector;
319     $selector = [DESCENDANT_COMBINATOR];
320     undef $has_pseudo_element;
321    
322     $state = BEFORE_TYPE_SELECTOR_STATE;
323     $t = $tt->get_next_token;
324     redo S;
325     } else {
326     push @$selector, S_TOKEN;
327    
328     $state = BEFORE_TYPE_SELECTOR_STATE;
329     ## Reprocess.
330     redo S;
331     }
332     } elsif ($state == AFTER_COLON_STATE) {
333     if ($t->{type} == IDENT_TOKEN) {
334     my $class = $t->{value};
335     $class =~ tr/A-Z/a-z/; ## TODO: ASCII case-insensitivity ok?
336     if ($self->{pseudo_class}->{$class} and
337     {
338     active => 1,
339     checked => 1,
340 wakaba 1.3 '-manakai-current' => 1,
341 wakaba 1.1 disabled => 1,
342     empty => 1,
343     enabled => 1,
344     'first-child' => 1,
345     'first-of-type' => 1,
346     focus => 1,
347     hover => 1,
348     indeterminate => 1, ## NOTE: Reserved in Selectors Level 3
349     'last-child' => 1,
350     'last-of-type' => 1,
351     link => 1,
352     'only-child' => 1,
353     'only-of-type' => 1,
354     root => 1,
355     target => 1,
356     visited => 1,
357     }->{$class}) {
358     push @$sss, [PSEUDO_CLASS_SELECTOR, $class];
359     } elsif ($self->{pseudo_element}->{$class} and
360     {'first-letter' => 1, 'first-line' => 1,
361     before => 1, after => 1}->{$class}) {
362     push @$sss, [PSEUDO_ELEMENT_SELECTOR, $class];
363     $has_pseudo_element = 1;
364     } else {
365 wakaba 1.6 ## TODO: Should we raise a different kind of error
366     ## if a pseudo class is known but not supported?
367     $self->{onerror}->(type => 'pseudo class:not allowed',
368     level => $self->{must_level},
369     token => $t, value => $class);
370 wakaba 1.1 return undef;
371     }
372    
373     $state = BEFORE_SIMPLE_SELECTOR_STATE;
374     $t = $tt->get_next_token;
375     redo S;
376     } elsif ($t->{type} == FUNCTION_TOKEN) {
377     my $class = $t->{value};
378     $class =~ tr/A-Z/a-z/; ## TODO: Is ASCII case-insensitivity OK?
379    
380     if ($class eq 'lang' and $self->{pseudo_class}->{$class}) {
381     $state = BEFORE_LANG_TAG_STATE;
382     $t = $tt->get_next_token;
383     redo S;
384     } elsif ($class eq 'not' and $self->{pseudo_class}->{$class} and
385     not $in_negation) {
386     $in_negation = 1;
387    
388     push @$sss, '';
389     $state = BEFORE_TYPE_SELECTOR_STATE;
390     $t = $tt->get_next_token;
391     redo S;
392     } elsif ({
393     'nth-child' => 1,
394     'nth-last-child' => 1,
395     'nth-of-type' => 1,
396     'nth-last-of-type' => 1,
397     }->{$class} and $self->{pseudo_class}->{$class}) {
398     $name = $class;
399    
400     $state = BEFORE_AN_STATE;
401     $t = $tt->get_next_token;
402     ## TODO: syntax of value in the spec is vague; need to reverse
403     ## engineer what Opera 9.5 does.
404     redo S;
405 wakaba 1.3 } elsif ($class eq '-manakai-contains' and
406     $self->{pseudo_class}->{$class}) {
407     $state = BEFORE_CONTAINS_STRING_STATE;
408     $t = $tt->get_next_token;
409     redo S;
410 wakaba 1.1 } else {
411 wakaba 1.6 $self->{onerror}->(type => 'pseudo class:not allowed',
412     level => $self->{must_level},
413     token => $t, value => $class);
414 wakaba 1.1 return undef;
415     }
416     } elsif ($t->{type} == COLON_TOKEN and
417     not $in_negation) { ## Pseudo-element
418     $state = AFTER_DOUBLE_COLON_STATE;
419     $t = $tt->get_next_token;
420     redo S;
421     } else {
422 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after colon',
423     level => $self->{must_level},
424     token => $t);
425 wakaba 1.1 return undef;
426     }
427     } elsif ($state == AFTER_LBRACKET_STATE) { ## Attribute selector
428     $simple_selector = [ATTRIBUTE_SELECTOR];
429     if ($t->{type} == IDENT_TOKEN) {
430     $name = $t->{value};
431    
432     $state = AFTER_ATTR_NAME_STATE;
433     $t = $tt->get_next_token;
434     redo S;
435     } elsif ($t->{type} == VBAR_TOKEN) {
436     $simple_selector->[1] = ''; # null namespace
437    
438     $state = BEFORE_ATTR_LOCAL_NAME_STATE;
439     $t = $tt->get_next_token;
440     redo S;
441     } elsif ($t->{type} == STAR_TOKEN) {
442     $name = undef;
443    
444     $state = AFTER_ATTR_NAME_STATE;
445     $t = $tt->get_next_token;
446     redo S;
447     } elsif ($t->{type} == S_TOKEN) {
448     ## Stay in the state.
449     $t = $tt->get_next_token;
450     redo S;
451     } else {
452 wakaba 1.6 $self->{onerror}->(type => 'syntax error:before attr name',
453     level => $self->{must_level},
454     token => $t);
455 wakaba 1.1 return undef;
456     }
457     } elsif ($state == AFTER_ATTR_NAME_STATE) {
458     if ($t->{type} == VBAR_TOKEN) {
459     if (defined $name) {
460     my $uri = $self->{lookup_namespace_uri}->($name);
461     unless (defined $uri) {
462 wakaba 1.6 $self->{onerror}->(type => 'namespace prefix:not declared',
463     level => $self->{must_level},
464     token => $t);
465 wakaba 1.1 return undef;
466     }
467     $simple_selector->[1] = $uri;
468     }
469    
470     $state = BEFORE_ATTR_LOCAL_NAME_STATE;
471     $t = $tt->get_next_token;
472     redo S;
473     } else {
474     unless (defined $name) { ## [*]
475 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after attr star',
476     level => $self->{must_level},
477     token => $t);
478 wakaba 1.1 return undef;
479     }
480     $simple_selector->[1] = ''; # null namespace
481     $simple_selector->[2] = $name;
482    
483     $state = BEFORE_MATCH_STATE;
484     ## Reprocess.
485     redo S;
486     }
487     } elsif ($state == BEFORE_ATTR_LOCAL_NAME_STATE) {
488     if ($t->{type} == IDENT_TOKEN) {
489     $simple_selector->[2] = $t->{value};
490    
491     $state = BEFORE_MATCH_STATE;
492     $t = $tt->get_next_token;
493     redo S;
494     } else {
495 wakaba 1.6 $self->{onerror}->(type => 'syntax error:before attr local name',
496     level => $self->{must_level},
497     token => $t);
498 wakaba 1.1 return undef;
499     }
500     } elsif ($state == BEFORE_MATCH_STATE) {
501     if ({
502     MATCH_TOKEN, 1,
503     INCLUDES_TOKEN, 1,
504     DASHMATCH_TOKEN, 1,
505     PREFIXMATCH_TOKEN, 1,
506     SUFFIXMATCH_TOKEN, 1,
507     SUBSTRINGMATCH_TOKEN, 1,
508     }->{$t->{type}}) {
509     $simple_selector->[3] = $t->{type};
510    
511     $state = BEFORE_VALUE_STATE;
512     $t = $tt->get_next_token;
513     redo S;
514     } elsif ($t->{type} == RBRACKET_TOKEN) {
515     push @$sss, $simple_selector;
516    
517     $state = BEFORE_SIMPLE_SELECTOR_STATE;
518     $t = $tt->get_next_token;
519     redo S;
520     } elsif ($t->{type} == S_TOKEN) {
521     ## Stay in the state.
522     $t = $tt->get_next_token;
523     redo S;
524     } else {
525 wakaba 1.6 $self->{onerror}->(type => 'syntax error:before match',
526     level => $self->{must_level},
527     token => $t);
528 wakaba 1.1 return undef;
529     }
530     } elsif ($state == BEFORE_VALUE_STATE) {
531     if ($t->{type} == IDENT_TOKEN or $t->{type} == STRING_TOKEN) {
532     $simple_selector->[4] = $t->{value};
533     push @$sss, $simple_selector;
534    
535     $state = AFTER_VALUE_STATE;
536     $t = $tt->get_next_token;
537     redo S;
538     } elsif ($t->{type} == S_TOKEN) {
539     ## Stay in the state.
540     $t = $tt->get_next_token;
541     redo S;
542     } else {
543 wakaba 1.6 $self->{onerror}->(type => 'syntax error:before attr value',
544     level => $self->{must_level},
545     token => $t);
546 wakaba 1.1 return undef;
547     }
548     } elsif ($state == AFTER_VALUE_STATE) {
549     if ($t->{type} == RBRACKET_TOKEN) {
550     $state = BEFORE_SIMPLE_SELECTOR_STATE;
551     $t = $tt->get_next_token;
552     redo S;
553     } else {
554 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after attr value',
555     level => $self->{must_level},
556     token => $t);
557 wakaba 1.1 return undef;
558     }
559     } elsif ($state == AFTER_DOUBLE_COLON_STATE) {
560     if ($t->{type} == IDENT_TOKEN) {
561     my $pe = $t->{value};
562     $pe =~ tr/A-Z/a-z/; ## TODO: Is ASCII case-insensitive OK?
563     if ($self->{pseudo_element}->{$pe} and
564     {'first-letter' => 1, 'first-line' => 1,
565     after => 1, before => 1}->{$pe}) {
566     push @$sss, [PSEUDO_ELEMENT_SELECTOR, $pe];
567     $has_pseudo_element = 1;
568    
569     $state = BEFORE_SIMPLE_SELECTOR_STATE;
570     $t = $tt->get_next_token;
571     redo S;
572     } else {
573 wakaba 1.6 $self->{onerror}->(type => 'pseudo element:not allowed',
574     level => $self->{must_level},
575     token => $t, value => $pe);
576 wakaba 1.1 return undef;
577     }
578     } else {
579 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after double colon',
580     level => $self->{must_level},
581     token => $t);
582 wakaba 1.1 return undef
583     }
584     } elsif ($state == BEFORE_LANG_TAG_STATE) {
585     if ($t->{type} == IDENT_TOKEN) {
586     push @$sss, [PSEUDO_CLASS_SELECTOR, 'lang', $t->{value}];
587    
588     $state = AFTER_LANG_TAG_STATE;
589     $t = $tt->get_next_token;
590     redo S;
591     } elsif ($t->{type} == S_TOKEN) {
592     ## Stay in the state.
593     $t = $tt->get_next_token;
594     redo S;
595     } else {
596 wakaba 1.6 $self->{onerror}->(type => 'syntax error:before lang tag',
597     level => $self->{must_level},
598     token => $t);
599 wakaba 1.1 return undef;
600     }
601     } elsif ($state == AFTER_LANG_TAG_STATE) {
602     if ($t->{type} == RPAREN_TOKEN) {
603     $state = BEFORE_SIMPLE_SELECTOR_STATE;
604     $t = $tt->get_next_token;
605     redo S;
606     } elsif ($t->{type} == S_TOKEN) {
607     ## Stay in the state.
608     $t = $tt->get_next_token;
609     redo S;
610     } else {
611 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after lang tag',
612     level => $self->{must_level},
613     token => $t);
614 wakaba 1.1 return undef;
615     }
616     } elsif ($state == BEFORE_AN_STATE) {
617     if ($t->{type} == DIMENSION_TOKEN) {
618     if (int $t->{number} == $t->{number}) {
619     my $n = $t->{value};
620     $n =~ tr/A-Z/a-z/; ## TODO: ascii ?
621     if ($n eq 'n') {
622     $simple_selector = [PSEUDO_CLASS_SELECTOR, $name,
623     0+$t->{number}, 0];
624    
625     $state = AFTER_AN_STATE;
626     $t = $tt->get_next_token;
627     redo S;
628     } elsif ($n =~ /\An-([0-9]+)\z/) {
629     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 0+$t->{number}, 0-$1];
630    
631     $state = AFTER_B_STATE;
632     $t = $tt->get_next_token;
633     redo S;
634     } else {
635 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
636     level => $self->{must_level},
637     token => $t);
638 wakaba 1.1 return undef;
639     }
640     } else {
641 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
642     level => $self->{must_level},
643     token => $t);
644 wakaba 1.1 return undef;
645     }
646     } elsif ($t->{type} == NUMBER_TOKEN) {
647     if (int $t->{number} == $t->{number}) {
648     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 0, 0+$t->{number}];
649    
650     $state = AFTER_B_STATE;
651     $t = $tt->get_next_token;
652     redo S;
653     } else { ## ISSUE: Is :nth-child(0.0) disallowed?
654 wakaba 1.6 $self->{onerror}->(type => 'not integer',
655     level => $self->{must_level},
656     token => $t, value => $t->{number});
657 wakaba 1.1 return undef;
658     }
659     } elsif ($t->{type} == IDENT_TOKEN) {
660     my $value = $t->{value};
661     $value =~ tr/A-Z/a-z/; ## TODO: ASCII case-insensitive?
662     if ($value eq 'odd') {
663     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 2, 1];
664    
665     $state = AFTER_B_STATE;
666     $t = $tt->get_next_token;
667     redo S;
668     } elsif ($value eq 'even') {
669     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 2, 0];
670    
671     $state = AFTER_B_STATE;
672     $t = $tt->get_next_token;
673     redo S;
674     } elsif ($value eq 'n' or $value eq '-n') {
675     ## ISSUE: :nth-child(-n) is not explicitly allowed, but appears
676     ## in an example in the spec.
677     $simple_selector = [PSEUDO_CLASS_SELECTOR, $name,
678     $value eq 'n' ? 1 : -1, 0];
679    
680     $state = AFTER_AN_STATE;
681     $t = $tt->get_next_token;
682     redo S;
683     } elsif ($value =~ /\A(-?)n-([0-9]+)\z/) {
684     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 0+($1.'1'), -$2];
685    
686     $state = AFTER_B_STATE;
687     $t = $tt->get_next_token;
688     redo S;
689     } else {
690 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
691     level => $self->{must_level},
692     token => $t);
693 wakaba 1.1 return undef;
694     }
695     } elsif ($t->{type} == MINUS_TOKEN) {
696     ## ISSUE: Is :nth-child(- 1) allowed?
697     ## ISSUE: Is :nth-child(n-/**/6) or (-n-/**/6) allowed?
698     $t = $tt->get_next_token;
699     if ($t->{type} == DIMENSION_TOKEN || $t->{type} == IDENT_TOKEN) {
700     my $num = $t->{type} == IDENT_TOKEN ? 1 : $t->{number};
701     ## NOTE: :nth-child(-/**/n)
702     if (int $num == $num) {
703     my $n = $t->{value};
704     $n =~ tr/A-Z/a-z/; ## TODO: ASCII?
705     if ($n eq 'n') {
706     $simple_selector = [PSEUDO_CLASS_SELECTOR, $name, -$num, 0];
707    
708     $state = AFTER_AN_STATE;
709     $t = $tt->get_next_token;
710     redo S;
711     } elsif ($n =~ /\An-([0-9]+)\z/) {
712     $simple_selector = [PSEUDO_CLASS_SELECTOR, $name,
713     -$num, -$1];
714    
715     $state = AFTER_AN_STATE;
716     $t = $tt->get_next_token;
717     redo S;
718     } else {
719 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
720     level => $self->{must_level},
721     token => $t);
722 wakaba 1.1 return undef;
723     }
724     } else {
725 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
726     level => $self->{must_level},
727     token => $t);
728 wakaba 1.1 return undef;
729     }
730     } elsif ($t->{type} == NUMBER_TOKEN) {
731     if (int $t->{number} == $t->{number}) {
732     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 0, -$t->{number}];
733    
734     $state = AFTER_B_STATE;
735     $t = $tt->get_next_token;
736     redo S;
737     } else {
738 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
739     level => $self->{must_level},
740     token => $t);
741 wakaba 1.1 return undef;
742     }
743     } else {
744 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
745     level => $self->{must_level},
746     token => $t);
747 wakaba 1.1 return undef;
748     }
749     } elsif ($t->{type} == S_TOKEN) {
750     ## Stay in the state.
751     $t = $tt->get_next_token;
752     redo S;
753     } else {
754 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
755     level => $self->{must_level},
756     token => $t);
757 wakaba 1.1 return undef;
758     }
759     } elsif ($state == AFTER_AN_STATE) {
760     ## ISSUE: :nth-child(1n +2) is allowed.
761     ## :nth-child(1n /**/ +2) and :nth-child(1n -2) are allowed?
762     if ($t->{type} == PLUS_TOKEN) {
763     $simple_selector->[3] = +1;
764    
765     $state = BEFORE_B_STATE;
766     $t = $tt->get_next_token;
767     redo S;
768     } elsif ($t->{type} == MINUS_TOKEN) {
769     $simple_selector->[3] = -1;
770    
771     $state = BEFORE_B_STATE;
772     $t = $tt->get_next_token;
773     redo S;
774     } elsif ($t->{type} == RPAREN_TOKEN) {
775     push @$sss, $simple_selector;
776    
777     $state = BEFORE_SIMPLE_SELECTOR_STATE;
778     $t = $tt->get_next_token;
779     redo S;
780     } elsif ($t->{type} == S_TOKEN) {
781     ## Stay in the state.
782     $t = $tt->get_next_token;
783     redo S;
784     } else {
785 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
786     level => $self->{must_level},
787     token => $t);
788 wakaba 1.1 return undef;
789     }
790     } elsif ($state == BEFORE_B_STATE) {
791     ## ISSUE: Is S allowed?
792     if ($t->{type} == NUMBER_TOKEN) {
793     if (int $t->{number} == $t->{number}) {
794     $simple_selector->[3] *= $t->{number};
795     push @$sss, $simple_selector;
796    
797     $state = AFTER_B_STATE;
798     $t = $tt->get_next_token;
799     redo S;
800     } else {
801 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
802     level => $self->{must_level},
803     token => $t);
804 wakaba 1.1 return undef;
805     }
806     } else {
807 wakaba 1.6 $self->{onerror}->(type => 'syntax error:an+b',
808     level => $self->{must_level},
809     token => $t);
810 wakaba 1.1 return undef;
811     }
812     } elsif ($state == AFTER_B_STATE) {
813     if ($t->{type} == RPAREN_TOKEN) {
814     $state = BEFORE_SIMPLE_SELECTOR_STATE;
815     $t = $tt->get_next_token;
816     redo S;
817     } elsif ($t->{type} == S_TOKEN) {
818     ## Stay in the state.
819     $t = $tt->get_next_token;
820     redo S;
821     } else {
822 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after an+b',
823     level => $self->{must_level},
824     token => $t);
825 wakaba 1.1 return undef;
826     }
827     } elsif ($state == AFTER_NEGATION_SIMPLE_SELECTOR_STATE) {
828     if ($t->{type} == RPAREN_TOKEN) {
829     undef $in_negation;
830     my $simple_selector = [];
831     unshift @$simple_selector, pop @$sss while ref $sss->[-1];
832     pop @$sss; # dummy
833     unshift @$simple_selector, 'not';
834     unshift @$simple_selector, PSEUDO_CLASS_SELECTOR;
835     push @$sss, $simple_selector;
836    
837     $state = BEFORE_SIMPLE_SELECTOR_STATE;
838 wakaba 1.3 $t = $tt->get_next_token;
839     redo S;
840     } elsif ($t->{type} == S_TOKEN) {
841     ## Stay in the state.
842     $t = $tt->get_next_token;
843     redo S;
844     } else {
845 wakaba 1.6 $self->{onerror}->(type => 'syntax error:after not simple selector',
846     level => $self->{must_level},
847     token => $t);
848 wakaba 1.3 return undef;
849     }
850     } elsif ($state == BEFORE_CONTAINS_STRING_STATE) {
851 wakaba 1.4 if ($t->{type} == STRING_TOKEN or $t->{type} == IDENT_TOKEN) {
852 wakaba 1.3 push @$sss, [PSEUDO_CLASS_SELECTOR, '-manakai-contains', $t->{value}];
853    
854     $state = AFTER_LANG_TAG_STATE;
855 wakaba 1.1 $t = $tt->get_next_token;
856     redo S;
857     } elsif ($t->{type} == S_TOKEN) {
858     ## Stay in the state.
859     $t = $tt->get_next_token;
860     redo S;
861     } else {
862 wakaba 1.6 $self->{onerror}->(type => 'syntax error:before contains string',
863     level => $self->{must_level},
864     token => $t);
865 wakaba 1.1 return undef;
866     }
867     } else {
868     die "$0: Selectors Parser: $state: Unknown state";
869     }
870     } # S
871     } # parse_string
872    
873 wakaba 1.5 =head1 LICENSE
874    
875     Copyright 2007 Wakaba <[email protected]>
876    
877     This library is free software; you can redistribute it
878     and/or modify it under the same terms as Perl itself.
879    
880     =cut
881    
882 wakaba 1.1 1;
883 wakaba 1.6 # $Date: 2007/10/17 09:47:36 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24