/[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.7 - (hide annotations) (download)
Sun Dec 23 08:16:09 2007 UTC (18 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.6: +54 -44 lines
++ whatpm/Whatpm/CSS/ChangeLog	23 Dec 2007 08:15:33 -0000
2007-12-23  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm: New module.

	* SelectorsParser.pm (parse_string): Split into |parse_string|
	and |_parse_selectors_with_tokenizer|.  Support for "end by
	token T" option.  Return the last token as well as the
	parsed selectors pbject.

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24