/[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.3 - (hide annotations) (download)
Sun Oct 7 05:06:56 2007 UTC (18 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.2: +22 -0 lines
++ ChangeLog	7 Oct 2007 04:57:00 -0000
2007-10-07  Wakaba  <wakaba@suika.fam.cx>

	* readme.en.html: Link to the spec for manakai's extension Selectors
	pseudo-classes is added.

++ whatpm/Whatpm/ChangeLog	7 Oct 2007 04:57:50 -0000
++ whatpm/Whatpm/CSS/ChangeLog	7 Oct 2007 05:06:49 -0000
2007-10-07  Wakaba  <wakaba@suika.fam.cx>

	* SelectorsParser.pm (BEFORE_CONTAINS_STRING_STATE): New state.
	(parse_string): Support for |-manakai-current|
	and |-manakai-contains| pseudo-classes.

	* SelectorsSerializer.pm: Support for |-manakai-current|
	and |-manakai-contains| pseudo-classes.


2007-09-24  Wakaba  <wakaba@suika.fam.cx>

	* SelectorsParser.pm: Pass an empty string as a prefix
	for lookup namespace prefix callback, for loose compatibility
	with the |NSResolver| interface.

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24