/[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.5 - (hide annotations) (download)
Wed Oct 17 09:47:36 2007 UTC (18 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.4: +11 -0 lines
++ whatpm/Whatpm/ChangeLog	17 Oct 2007 09:47:28 -0000
2007-10-17  Wakaba  <wakaba@suika.fam.cx>

	* Makefile (CSS-all, CSS-clean, clean): New rules.

++ whatpm/Whatpm/CSS/ChangeLog	17 Oct 2007 09:47:00 -0000
2007-10-17  Wakaba  <wakaba@suika.fam.cx>

	* SelectorsParser.pod, Makefile: New files.

1 wakaba 1.1 package Whatpm::CSS::SelectorsParser;
2     use strict;
3 wakaba 1.5 our $VERSION=do{my @r=(q$Revision: 1.60 $=~/\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     }}, shift;
14     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     ## TODO: error
154     return undef;
155     }
156     } elsif ($state == BEFORE_SIMPLE_SELECTOR_STATE) {
157     if ($in_negation and $in_negation++ == 2) {
158     $state = AFTER_NEGATION_SIMPLE_SELECTOR_STATE;
159     ## Reprocess.
160     redo S;
161     }
162    
163     if ($t->{type} == DOT_TOKEN) { ## class selector
164     if ($has_pseudo_element) {
165     ## TODO: error
166     return undef;
167     }
168     $state = BEFORE_CLASS_NAME_STATE;
169     $t = $tt->get_next_token;
170     redo S;
171     } elsif ($t->{type} == HASH_TOKEN) { ## ID selector
172     if ($has_pseudo_element) {
173     ## TODO: error
174     return undef;
175     }
176     push @$sss, [ID_SELECTOR, $t->{value}];
177     $state = BEFORE_SIMPLE_SELECTOR_STATE;
178     $t = $tt->get_next_token;
179     redo S;
180     } elsif ($t->{type} == COLON_TOKEN) { ## pseudo-class or pseudo-element
181     if ($has_pseudo_element) {
182     ## TODO: error
183     return undef;
184     }
185     $state = AFTER_COLON_STATE;
186     $t = $tt->get_next_token;
187     redo S;
188     } elsif ($t->{type} == LBRACKET_TOKEN) { ## attribute selector
189     if ($has_pseudo_element) {
190     ## TODO: error
191     return undef;
192     }
193     $state = AFTER_LBRACKET_STATE;
194     $t = $tt->get_next_token;
195     redo S;
196     } else {
197     $state = BEFORE_COMBINATOR_STATE;
198     ## Reprocess.
199     redo S;
200     }
201     } elsif ($state == AFTER_NAME_STATE) {
202     if ($t->{type} == VBAR_TOKEN) {
203     $state = BEFORE_LOCAL_NAME_STATE;
204     $t = $tt->get_next_token;
205     redo S;
206     } else { ## Type or universal selector w/o namespace prefix
207     push @$sss, [NAMESPACE_SELECTOR, $default_namespace]
208     if defined $default_namespace;
209     push @$sss, [LOCAL_NAME_SELECTOR, $name] if defined $name;
210    
211     $state = BEFORE_SIMPLE_SELECTOR_STATE;
212     ## reprocess.
213     redo S;
214     }
215     } elsif ($state == BEFORE_LOCAL_NAME_STATE) {
216     if ($t->{type} == IDENT_TOKEN) {
217     if (defined $name) { ## Prefix is neither empty nor "*"
218     my $uri = $self->{lookup_namespace_uri}->($name);
219     unless (defined $uri) {
220     ## TODO: error
221     return undef;
222     }
223     push @$sss, [NAMESPACE_SELECTOR, $uri];
224     }
225     push @$sss, [LOCAL_NAME_SELECTOR, $t->{value}];
226    
227     $state = BEFORE_SIMPLE_SELECTOR_STATE;
228     $t = $tt->get_next_token;
229     redo S;
230     } elsif ($t->{type} == STAR_TOKEN) {
231     if (defined $name) { ## Prefix is neither empty nor "*"
232     my $uri = $self->{lookup_namespace_uri}->($name);
233     unless (defined $uri) {
234     ## TODO: error
235     return undef;
236     }
237     push @$sss, [NAMESPACE_SELECTOR, $uri];
238     }
239     $state = BEFORE_SIMPLE_SELECTOR_STATE;
240     $t = $tt->get_next_token;
241     redo S;
242     } else { ## "|" not followed by type or universal selector
243     ## TODO: error
244     return undef;
245     }
246     } elsif ($state == BEFORE_CLASS_NAME_STATE) {
247     if ($t->{type} == IDENT_TOKEN) {
248     push @$sss, [CLASS_SELECTOR, $t->{value}];
249    
250     $state = BEFORE_SIMPLE_SELECTOR_STATE;
251     $t = $tt->get_next_token;
252     redo S;
253     } else {
254     ## TODO: error
255     return undef;
256     }
257     } elsif ($state == BEFORE_COMBINATOR_STATE) {
258     push @$selector, $sss;
259     $sss = [];
260    
261     if ($t->{type} == S_TOKEN) {
262     $state = COMBINATOR_STATE;
263     $t = $tt->get_next_token;
264     redo S;
265     } elsif ({
266     GREATER_TOKEN, 1,
267     PLUS_TOKEN, 1,
268     TILDE_TOKEN, 1,
269     COMMA_TOKEN, 1,
270     EOF_TOKEN, 1,
271     }->{$t->{type}}) {
272     $state = COMBINATOR_STATE;
273     ## Reprocess.
274     redo S;
275     } else {
276     ## TODO: error
277     return undef;
278     }
279     } elsif ($state == COMBINATOR_STATE) {
280     if ($state == S_TOKEN) {
281     ## Stay in the state.
282     $t = $tt->get_next_token;
283     redo S;
284     } elsif ({
285     GREATER_TOKEN, 1,
286     PLUS_TOKEN, 1,
287     TILDE_TOKEN, 1,
288     }->{$t->{type}}) {
289     push @$selector, $t->{type};
290    
291     $state = BEFORE_TYPE_SELECTOR_STATE;
292     $t = $tt->get_next_token;
293     redo S;
294     } elsif ($t->{type} == EOF_TOKEN) {
295     push @$selectors, $selector;
296     return $selectors;
297     } elsif ($t->{type} == COMMA_TOKEN) {
298     push @$selectors, $selector;
299     $selector = [DESCENDANT_COMBINATOR];
300     undef $has_pseudo_element;
301    
302     $state = BEFORE_TYPE_SELECTOR_STATE;
303     $t = $tt->get_next_token;
304     redo S;
305     } else {
306     push @$selector, S_TOKEN;
307    
308     $state = BEFORE_TYPE_SELECTOR_STATE;
309     ## Reprocess.
310     redo S;
311     }
312     } elsif ($state == AFTER_COLON_STATE) {
313     if ($t->{type} == IDENT_TOKEN) {
314     my $class = $t->{value};
315     $class =~ tr/A-Z/a-z/; ## TODO: ASCII case-insensitivity ok?
316     if ($self->{pseudo_class}->{$class} and
317     {
318     active => 1,
319     checked => 1,
320 wakaba 1.3 '-manakai-current' => 1,
321 wakaba 1.1 disabled => 1,
322     empty => 1,
323     enabled => 1,
324     'first-child' => 1,
325     'first-of-type' => 1,
326     focus => 1,
327     hover => 1,
328     indeterminate => 1, ## NOTE: Reserved in Selectors Level 3
329     'last-child' => 1,
330     'last-of-type' => 1,
331     link => 1,
332     'only-child' => 1,
333     'only-of-type' => 1,
334     root => 1,
335     target => 1,
336     visited => 1,
337     }->{$class}) {
338     push @$sss, [PSEUDO_CLASS_SELECTOR, $class];
339     } elsif ($self->{pseudo_element}->{$class} and
340     {'first-letter' => 1, 'first-line' => 1,
341     before => 1, after => 1}->{$class}) {
342     push @$sss, [PSEUDO_ELEMENT_SELECTOR, $class];
343     $has_pseudo_element = 1;
344     } else {
345     ## TODO: error
346     return undef;
347     }
348    
349     $state = BEFORE_SIMPLE_SELECTOR_STATE;
350     $t = $tt->get_next_token;
351     redo S;
352     } elsif ($t->{type} == FUNCTION_TOKEN) {
353     my $class = $t->{value};
354     $class =~ tr/A-Z/a-z/; ## TODO: Is ASCII case-insensitivity OK?
355    
356     if ($class eq 'lang' and $self->{pseudo_class}->{$class}) {
357     $state = BEFORE_LANG_TAG_STATE;
358     $t = $tt->get_next_token;
359     redo S;
360     } elsif ($class eq 'not' and $self->{pseudo_class}->{$class} and
361     not $in_negation) {
362     $in_negation = 1;
363    
364     push @$sss, '';
365     $state = BEFORE_TYPE_SELECTOR_STATE;
366     $t = $tt->get_next_token;
367     redo S;
368     } elsif ({
369     'nth-child' => 1,
370     'nth-last-child' => 1,
371     'nth-of-type' => 1,
372     'nth-last-of-type' => 1,
373     }->{$class} and $self->{pseudo_class}->{$class}) {
374     $name = $class;
375    
376     $state = BEFORE_AN_STATE;
377     $t = $tt->get_next_token;
378     ## TODO: syntax of value in the spec is vague; need to reverse
379     ## engineer what Opera 9.5 does.
380     redo S;
381 wakaba 1.3 } elsif ($class eq '-manakai-contains' and
382     $self->{pseudo_class}->{$class}) {
383     $state = BEFORE_CONTAINS_STRING_STATE;
384     $t = $tt->get_next_token;
385     redo S;
386 wakaba 1.1 } else {
387     ## TODO: error
388     return undef;
389     }
390     } elsif ($t->{type} == COLON_TOKEN and
391     not $in_negation) { ## Pseudo-element
392     $state = AFTER_DOUBLE_COLON_STATE;
393     $t = $tt->get_next_token;
394     redo S;
395     } else {
396     ## TODO: error
397     return undef;
398     }
399     } elsif ($state == AFTER_LBRACKET_STATE) { ## Attribute selector
400     $simple_selector = [ATTRIBUTE_SELECTOR];
401     if ($t->{type} == IDENT_TOKEN) {
402     $name = $t->{value};
403    
404     $state = AFTER_ATTR_NAME_STATE;
405     $t = $tt->get_next_token;
406     redo S;
407     } elsif ($t->{type} == VBAR_TOKEN) {
408     $simple_selector->[1] = ''; # null namespace
409    
410     $state = BEFORE_ATTR_LOCAL_NAME_STATE;
411     $t = $tt->get_next_token;
412     redo S;
413     } elsif ($t->{type} == STAR_TOKEN) {
414     $name = undef;
415    
416     $state = AFTER_ATTR_NAME_STATE;
417     $t = $tt->get_next_token;
418     redo S;
419     } elsif ($t->{type} == S_TOKEN) {
420     ## Stay in the state.
421     $t = $tt->get_next_token;
422     redo S;
423     } else {
424     ## TODO: error
425     return undef;
426     }
427     } elsif ($state == AFTER_ATTR_NAME_STATE) {
428     if ($t->{type} == VBAR_TOKEN) {
429     if (defined $name) {
430     my $uri = $self->{lookup_namespace_uri}->($name);
431     unless (defined $uri) {
432     ## TODO: error
433     return undef;
434     }
435     $simple_selector->[1] = $uri;
436     }
437    
438     $state = BEFORE_ATTR_LOCAL_NAME_STATE;
439     $t = $tt->get_next_token;
440     redo S;
441     } else {
442     unless (defined $name) { ## [*]
443     ## TODO: error
444     return undef;
445     }
446     $simple_selector->[1] = ''; # null namespace
447     $simple_selector->[2] = $name;
448    
449     $state = BEFORE_MATCH_STATE;
450     ## Reprocess.
451     redo S;
452     }
453     } elsif ($state == BEFORE_ATTR_LOCAL_NAME_STATE) {
454     if ($t->{type} == IDENT_TOKEN) {
455     $simple_selector->[2] = $t->{value};
456    
457     $state = BEFORE_MATCH_STATE;
458     $t = $tt->get_next_token;
459     redo S;
460     } else {
461     ## TODO: error
462     return undef;
463     }
464     } elsif ($state == BEFORE_MATCH_STATE) {
465     if ({
466     MATCH_TOKEN, 1,
467     INCLUDES_TOKEN, 1,
468     DASHMATCH_TOKEN, 1,
469     PREFIXMATCH_TOKEN, 1,
470     SUFFIXMATCH_TOKEN, 1,
471     SUBSTRINGMATCH_TOKEN, 1,
472     }->{$t->{type}}) {
473     $simple_selector->[3] = $t->{type};
474    
475     $state = BEFORE_VALUE_STATE;
476     $t = $tt->get_next_token;
477     redo S;
478     } elsif ($t->{type} == RBRACKET_TOKEN) {
479     push @$sss, $simple_selector;
480    
481     $state = BEFORE_SIMPLE_SELECTOR_STATE;
482     $t = $tt->get_next_token;
483     redo S;
484     } elsif ($t->{type} == S_TOKEN) {
485     ## Stay in the state.
486     $t = $tt->get_next_token;
487     redo S;
488     } else {
489     ## TODO: error
490     return undef;
491     }
492     } elsif ($state == BEFORE_VALUE_STATE) {
493     if ($t->{type} == IDENT_TOKEN or $t->{type} == STRING_TOKEN) {
494     $simple_selector->[4] = $t->{value};
495     push @$sss, $simple_selector;
496    
497     $state = AFTER_VALUE_STATE;
498     $t = $tt->get_next_token;
499     redo S;
500     } elsif ($t->{type} == S_TOKEN) {
501     ## Stay in the state.
502     $t = $tt->get_next_token;
503     redo S;
504     } else {
505     ## TODO: error
506     return undef;
507     }
508     } elsif ($state == AFTER_VALUE_STATE) {
509     if ($t->{type} == RBRACKET_TOKEN) {
510     $state = BEFORE_SIMPLE_SELECTOR_STATE;
511     $t = $tt->get_next_token;
512     redo S;
513     } else {
514     ## TODO: error
515     return undef;
516     }
517     } elsif ($state == AFTER_DOUBLE_COLON_STATE) {
518     if ($t->{type} == IDENT_TOKEN) {
519     my $pe = $t->{value};
520     $pe =~ tr/A-Z/a-z/; ## TODO: Is ASCII case-insensitive OK?
521     if ($self->{pseudo_element}->{$pe} and
522     {'first-letter' => 1, 'first-line' => 1,
523     after => 1, before => 1}->{$pe}) {
524     push @$sss, [PSEUDO_ELEMENT_SELECTOR, $pe];
525     $has_pseudo_element = 1;
526    
527     $state = BEFORE_SIMPLE_SELECTOR_STATE;
528     $t = $tt->get_next_token;
529     redo S;
530     } else {
531     ## TODO: error
532     return undef;
533     }
534     } else {
535     ## TODO: error
536     return undef
537     }
538     } elsif ($state == BEFORE_LANG_TAG_STATE) {
539     if ($t->{type} == IDENT_TOKEN) {
540     push @$sss, [PSEUDO_CLASS_SELECTOR, 'lang', $t->{value}];
541    
542     $state = AFTER_LANG_TAG_STATE;
543     $t = $tt->get_next_token;
544     redo S;
545     } elsif ($t->{type} == S_TOKEN) {
546     ## Stay in the state.
547     $t = $tt->get_next_token;
548     redo S;
549     } else {
550     ## TODO: error
551     return undef;
552     }
553     } elsif ($state == AFTER_LANG_TAG_STATE) {
554     if ($t->{type} == RPAREN_TOKEN) {
555     $state = BEFORE_SIMPLE_SELECTOR_STATE;
556     $t = $tt->get_next_token;
557     redo S;
558     } elsif ($t->{type} == S_TOKEN) {
559     ## Stay in the state.
560     $t = $tt->get_next_token;
561     redo S;
562     } else {
563     ## TODO: error
564     return undef;
565     }
566     } elsif ($state == BEFORE_AN_STATE) {
567     if ($t->{type} == DIMENSION_TOKEN) {
568     if (int $t->{number} == $t->{number}) {
569     my $n = $t->{value};
570     $n =~ tr/A-Z/a-z/; ## TODO: ascii ?
571     if ($n eq 'n') {
572     $simple_selector = [PSEUDO_CLASS_SELECTOR, $name,
573     0+$t->{number}, 0];
574    
575     $state = AFTER_AN_STATE;
576     $t = $tt->get_next_token;
577     redo S;
578     } elsif ($n =~ /\An-([0-9]+)\z/) {
579     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 0+$t->{number}, 0-$1];
580    
581     $state = AFTER_B_STATE;
582     $t = $tt->get_next_token;
583     redo S;
584     } else {
585     ## TODO: error
586     return undef;
587     }
588     } else {
589     ## TODO: error
590     return undef;
591     }
592     } elsif ($t->{type} == NUMBER_TOKEN) {
593     if (int $t->{number} == $t->{number}) {
594     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 0, 0+$t->{number}];
595    
596     $state = AFTER_B_STATE;
597     $t = $tt->get_next_token;
598     redo S;
599     } else { ## ISSUE: Is :nth-child(0.0) disallowed?
600     ## TODO: error
601     return undef;
602     }
603     } elsif ($t->{type} == IDENT_TOKEN) {
604     my $value = $t->{value};
605     $value =~ tr/A-Z/a-z/; ## TODO: ASCII case-insensitive?
606     if ($value eq 'odd') {
607     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 2, 1];
608    
609     $state = AFTER_B_STATE;
610     $t = $tt->get_next_token;
611     redo S;
612     } elsif ($value eq 'even') {
613     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 2, 0];
614    
615     $state = AFTER_B_STATE;
616     $t = $tt->get_next_token;
617     redo S;
618     } elsif ($value eq 'n' or $value eq '-n') {
619     ## ISSUE: :nth-child(-n) is not explicitly allowed, but appears
620     ## in an example in the spec.
621     $simple_selector = [PSEUDO_CLASS_SELECTOR, $name,
622     $value eq 'n' ? 1 : -1, 0];
623    
624     $state = AFTER_AN_STATE;
625     $t = $tt->get_next_token;
626     redo S;
627     } elsif ($value =~ /\A(-?)n-([0-9]+)\z/) {
628     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 0+($1.'1'), -$2];
629    
630     $state = AFTER_B_STATE;
631     $t = $tt->get_next_token;
632     redo S;
633     } else {
634     ## TODO: error
635     return undef;
636     }
637     } elsif ($t->{type} == MINUS_TOKEN) {
638     ## ISSUE: Is :nth-child(- 1) allowed?
639     ## ISSUE: Is :nth-child(n-/**/6) or (-n-/**/6) allowed?
640     $t = $tt->get_next_token;
641     if ($t->{type} == DIMENSION_TOKEN || $t->{type} == IDENT_TOKEN) {
642     my $num = $t->{type} == IDENT_TOKEN ? 1 : $t->{number};
643     ## NOTE: :nth-child(-/**/n)
644     if (int $num == $num) {
645     my $n = $t->{value};
646     $n =~ tr/A-Z/a-z/; ## TODO: ASCII?
647     if ($n eq 'n') {
648     $simple_selector = [PSEUDO_CLASS_SELECTOR, $name, -$num, 0];
649    
650     $state = AFTER_AN_STATE;
651     $t = $tt->get_next_token;
652     redo S;
653     } elsif ($n =~ /\An-([0-9]+)\z/) {
654     $simple_selector = [PSEUDO_CLASS_SELECTOR, $name,
655     -$num, -$1];
656    
657     $state = AFTER_AN_STATE;
658     $t = $tt->get_next_token;
659     redo S;
660     } else {
661     ## TODO: error
662     return undef;
663     }
664     } else {
665     ## TODO: error
666     return undef;
667     }
668     } elsif ($t->{type} == NUMBER_TOKEN) {
669     if (int $t->{number} == $t->{number}) {
670     push @$sss, [PSEUDO_CLASS_SELECTOR, $name, 0, -$t->{number}];
671    
672     $state = AFTER_B_STATE;
673     $t = $tt->get_next_token;
674     redo S;
675     } else {
676     ## TODO: error
677     return undef;
678     }
679     } else {
680     ## TODO: error
681     return undef;
682     }
683     } elsif ($t->{type} == S_TOKEN) {
684     ## Stay in the state.
685     $t = $tt->get_next_token;
686     redo S;
687     } else {
688     ## TODO: error
689     return undef;
690     }
691     } elsif ($state == AFTER_AN_STATE) {
692     ## ISSUE: :nth-child(1n +2) is allowed.
693     ## :nth-child(1n /**/ +2) and :nth-child(1n -2) are allowed?
694     if ($t->{type} == PLUS_TOKEN) {
695     $simple_selector->[3] = +1;
696    
697     $state = BEFORE_B_STATE;
698     $t = $tt->get_next_token;
699     redo S;
700     } elsif ($t->{type} == MINUS_TOKEN) {
701     $simple_selector->[3] = -1;
702    
703     $state = BEFORE_B_STATE;
704     $t = $tt->get_next_token;
705     redo S;
706     } elsif ($t->{type} == RPAREN_TOKEN) {
707     push @$sss, $simple_selector;
708    
709     $state = BEFORE_SIMPLE_SELECTOR_STATE;
710     $t = $tt->get_next_token;
711     redo S;
712     } elsif ($t->{type} == S_TOKEN) {
713     ## Stay in the state.
714     $t = $tt->get_next_token;
715     redo S;
716     } else {
717     ## TODO: error
718     return undef;
719     }
720     } elsif ($state == BEFORE_B_STATE) {
721     ## ISSUE: Is S allowed?
722     if ($t->{type} == NUMBER_TOKEN) {
723     if (int $t->{number} == $t->{number}) {
724     $simple_selector->[3] *= $t->{number};
725     push @$sss, $simple_selector;
726    
727     $state = AFTER_B_STATE;
728     $t = $tt->get_next_token;
729     redo S;
730     } else {
731     ## TODO: error
732     return undef;
733     }
734     } else {
735     ## TODO: error
736     return undef;
737     }
738     } elsif ($state == AFTER_B_STATE) {
739     if ($t->{type} == RPAREN_TOKEN) {
740     $state = BEFORE_SIMPLE_SELECTOR_STATE;
741     $t = $tt->get_next_token;
742     redo S;
743     } elsif ($t->{type} == S_TOKEN) {
744     ## Stay in the state.
745     $t = $tt->get_next_token;
746     redo S;
747     } else {
748     ## TODO: error
749     return undef;
750     }
751     } elsif ($state == AFTER_NEGATION_SIMPLE_SELECTOR_STATE) {
752     if ($t->{type} == RPAREN_TOKEN) {
753     undef $in_negation;
754     my $simple_selector = [];
755     unshift @$simple_selector, pop @$sss while ref $sss->[-1];
756     pop @$sss; # dummy
757     unshift @$simple_selector, 'not';
758     unshift @$simple_selector, PSEUDO_CLASS_SELECTOR;
759     push @$sss, $simple_selector;
760    
761     $state = BEFORE_SIMPLE_SELECTOR_STATE;
762 wakaba 1.3 $t = $tt->get_next_token;
763     redo S;
764     } elsif ($t->{type} == S_TOKEN) {
765     ## Stay in the state.
766     $t = $tt->get_next_token;
767     redo S;
768     } else {
769     ## TODO: error
770     return undef;
771     }
772     } elsif ($state == BEFORE_CONTAINS_STRING_STATE) {
773 wakaba 1.4 if ($t->{type} == STRING_TOKEN or $t->{type} == IDENT_TOKEN) {
774 wakaba 1.3 push @$sss, [PSEUDO_CLASS_SELECTOR, '-manakai-contains', $t->{value}];
775    
776     $state = AFTER_LANG_TAG_STATE;
777 wakaba 1.1 $t = $tt->get_next_token;
778     redo S;
779     } elsif ($t->{type} == S_TOKEN) {
780     ## Stay in the state.
781     $t = $tt->get_next_token;
782     redo S;
783     } else {
784     ## TODO: error
785     return undef;
786     }
787     } else {
788     die "$0: Selectors Parser: $state: Unknown state";
789     }
790     } # S
791     } # parse_string
792    
793 wakaba 1.5 =head1 LICENSE
794    
795     Copyright 2007 Wakaba <[email protected]>
796    
797     This library is free software; you can redistribute it
798     and/or modify it under the same terms as Perl itself.
799    
800     =cut
801    
802 wakaba 1.1 1;
803 wakaba 1.5 # $Date:$

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24