Commit | Line | Data |
456a815d |
1 | package HTML::Zoom::SelectorParser; |
2 | |
3 | use strict; |
4 | use warnings FATAL => 'all'; |
6d0f20a6 |
5 | use base qw(HTML::Zoom::SubObject); |
456a815d |
6 | use Carp qw(confess); |
7 | |
8 | my $sel_char = '-\w_'; |
9 | my $sel_re = qr/([$sel_char]+)/; |
10 | |
11 | sub new { bless({}, shift) } |
12 | |
13 | sub _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 | |
159 | sub 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 | |
181 | 1; |