/[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.11 - (hide annotations) (download)
Sun Feb 10 07:34:10 2008 UTC (18 years, 5 months ago) by wakaba
Branch: MAIN
Changes since 1.10: +13 -6 lines
++ whatpm/t/ChangeLog	10 Feb 2008 07:34:02 -0000
2008-02-10  Wakaba  <wakaba@suika.fam.cx>

	* css-3.dat: New test file.

	* CSS-Parser-1.t: |css-3.dat| is added.  '-moz-pre-wrap' added.
	Typo for 'collapse' value fixed.  Support for '@namespace'
	serialization.

	* css-1.dat: New test data for selectors parsing.

	* css-font.dat: Error locations for 'u' errors are corrected.

	* css-table.dat: Test data for 'border-collapse' are added.

	* css-text.dat: Test data for '-moz-pre-wrap' are added.

	* css-visual.dat: Test data for 'background-position' are added.

++ whatpm/Whatpm/Charset/ChangeLog	10 Feb 2008 07:31:32 -0000
2008-02-10  Wakaba  <wakaba@suika.fam.cx>

	* CharDet.pm (detect): Catch an error in detection function.

++ whatpm/Whatpm/CSS/ChangeLog	10 Feb 2008 07:30:34 -0000
2008-02-10  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm: |attr(prefix|localname)| did not work.  Support
	for uppercase namespace prefixes (namespace prefixes were
	not normalized into lowercase when they were looked up.).  Report
	the property name token when an unknown property error is raised.
	The '-moz-pre-wrap' value is supported for 'white-space' property.
	('background-position'): 'center left' and 'center right'
	were not supported.

	* SelectorsParser.pm: Report the namespace prefix token when
	an undeclared prefix error is raised.  S_TOKEN in
	COMBINATOR_STATE was not handled correctly.

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24