E[attr*="foo"]
[catagits/HTML-Zoom.git] / lib / HTML / Zoom / SelectorParser.pm
CommitLineData
456a815d 1package HTML::Zoom::SelectorParser;
2
3use strict;
4use warnings FATAL => 'all';
6d0f20a6 5use base qw(HTML::Zoom::SubObject);
456a815d 6use Carp qw(confess);
7
8my $sel_char = '-\w_';
9my $sel_re = qr/([$sel_char]+)/;
10
11sub new { bless({}, shift) }
12
13sub _raw_parse_simple_selector {
14 for ($_[1]) { # same pos() as outside
15
16 # '*' - match anything
17
18 /\G\*/gc and
19 return sub { 1 };
20
04d71924 21 # 'el[attr~="foo"]
22
23 /\G$sel_re\[$sel_re~="$sel_re"\]/gc and
24 return do {
25 my $name = $1;
26 my $attr = $2;
27 my $val = $3;
28 sub {
29 if (
30 $_[0]->{name} && $_[0]->{name} eq $name and
31 $_[0]->{attrs}{$attr}
32 ) {
33 my %vals = map { $_ => 1 } split /\s+/, $_[0]->{attrs}{$attr};
34 return $vals{$val}
35 }
36 return undef
37 }
38 };
39
676159cb 40 # 'el[attr^="foo"]
41
42 /\G$sel_re\[$sel_re\^="$sel_re"\]/gc and
43 return do {
44 my $name = $1;
45 my $attr = $2;
46 my $val = $3;
47 sub {
48 $_[0]->{name} && $_[0]->{name} eq $name and
49 $_[0]->{attrs}{$attr} && $_[0]->{attrs}{$attr} =~ /^\Q$val\E/
50 }
51 };
52
74a46830 53 # 'el[attr$="foo"]
54
55 /\G$sel_re\[$sel_re\$="$sel_re"\]/gc and
56 return do {
57 my $name = $1;
58 my $attr = $2;
59 my $val = $3;
60 sub {
61 $_[0]->{name} && $_[0]->{name} eq $name and
62 $_[0]->{attrs}{$attr} && $_[0]->{attrs}{$attr} =~ /\Q$val\E$/
63 }
64 };
65
7084f73f 66 # 'el[attr*="foo"]
67
68 /\G$sel_re\[$sel_re\*="$sel_re"\]/gc and
69 return do {
70 my $name = $1;
71 my $attr = $2;
72 my $val = $3;
73 sub {
74 $_[0]->{name} && $_[0]->{name} eq $name and
75 $_[0]->{attrs}{$attr} && $_[0]->{attrs}{$attr} =~ /\Q$val\E/
76 }
77 };
78
3b8c9d7d 79 # 'el[attr="foo"]
80
81 /\G$sel_re\[$sel_re="$sel_re"\]/gc and
82 return do {
83 my $name = $1;
84 my $attr = $2;
85 my $val = $3;
86 sub {
87 $_[0]->{name} && $_[0]->{name} eq $name and
88 $_[0]->{attrs}{$attr} && $_[0]->{attrs}{$attr} eq $val
89 }
90 };
91
676159cb 92 # 'el[attr]
93
94 /\G$sel_re\[$sel_re\]/gc and
95 return do {
96 my $name = $1;
97 my $attr = $2;
98 sub {
99 $_[0]->{name} && $_[0]->{name} eq $name && $_[0]->{attrs}{$attr}
100 }
101 };
102
456a815d 103 # 'element' - match on tag name
104
105 /\G$sel_re/gc and
106 return do {
107 my $name = $1;
108 sub { $_[0]->{name} && $_[0]->{name} eq $name }
109 };
110
111 # '#id' - match on id attribute
112
113 /\G#$sel_re/gc and
114 return do {
115 my $id = $1;
116 sub { $_[0]->{attrs}{id} && $_[0]->{attrs}{id} eq $id }
117 };
118
119 # '.class1.class2' - match on intersection of classes
120
121 /\G((?:\.$sel_re)+)/gc and
122 return do {
123 my $cls = $1; $cls =~ s/^\.//;
124 my @cl = split(/\./, $cls);
125 sub {
126 $_[0]->{attrs}{class}
6d0f20a6 127 && !grep $_[0]->{attrs}{class} !~ /(^|\s+)$_($|\s+)/, @cl
456a815d 128 }
129 };
130
7871f2ff 131 # 'el.class1' - element + class
132
133 /\G$sel_re\.$sel_re/gc and
134 return do {
135 my $cls = $1;
136 my $name = $2;
137 sub {
138 $_[0]->{name} && $_[0]->{name} eq $name and
139 $_[0]->{attrs}{class} && $_[0]->{attrs}{class} eq $cls
140 }
141 };
142
32ea0d1e 143 # 'el#id' - element + id
144
145 /\G$sel_re#$sel_re/gc and
146 return do {
147 my $id = $1;
148 my $name = $2;
149 sub {
150 $_[0]->{name} && $_[0]->{name} eq $name and
151 $_[0]->{attrs}{id} && $_[0]->{attrs}{id} eq $id
152 }
153 };
154
456a815d 155 confess "Couldn't parse $_ as starting with simple selector";
156 }
157}
158
159sub parse_selector {
160 my $self = $_[0];
161 my $sel = $_[1]; # my pos() only please
162 die "No selector provided" unless $sel;
163 local *_;
164 for ($sel) {
165 my @sub;
166 PARSE: { do {
167 push(@sub, $self->_raw_parse_simple_selector($_));
168 last PARSE if (pos == length);
169 /\G\s*,\s*/gc or confess "Selectors not comma separated";
170 } until (pos == length) };
171 return $sub[0] if (@sub == 1);
172 return sub {
173 foreach my $inner (@sub) {
174 if (my $r = $inner->(@_)) { return $r }
175 }
176 };
177 }
7871f2ff 178}
179
456a815d 180
1811;