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
3b8c9d7d 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} eq $val
76 }
77 };
78
676159cb 79 # 'el[attr]
80
81 /\G$sel_re\[$sel_re\]/gc and
82 return do {
83 my $name = $1;
84 my $attr = $2;
85 sub {
86 $_[0]->{name} && $_[0]->{name} eq $name && $_[0]->{attrs}{$attr}
87 }
88 };
89
456a815d 90 # 'element' - match on tag name
91
92 /\G$sel_re/gc and
93 return do {
94 my $name = $1;
95 sub { $_[0]->{name} && $_[0]->{name} eq $name }
96 };
97
98 # '#id' - match on id attribute
99
100 /\G#$sel_re/gc and
101 return do {
102 my $id = $1;
103 sub { $_[0]->{attrs}{id} && $_[0]->{attrs}{id} eq $id }
104 };
105
106 # '.class1.class2' - match on intersection of classes
107
108 /\G((?:\.$sel_re)+)/gc and
109 return do {
110 my $cls = $1; $cls =~ s/^\.//;
111 my @cl = split(/\./, $cls);
112 sub {
113 $_[0]->{attrs}{class}
6d0f20a6 114 && !grep $_[0]->{attrs}{class} !~ /(^|\s+)$_($|\s+)/, @cl
456a815d 115 }
116 };
117
7871f2ff 118 # 'el.class1' - element + class
119
120 /\G$sel_re\.$sel_re/gc and
121 return do {
122 my $cls = $1;
123 my $name = $2;
124 sub {
125 $_[0]->{name} && $_[0]->{name} eq $name and
126 $_[0]->{attrs}{class} && $_[0]->{attrs}{class} eq $cls
127 }
128 };
129
32ea0d1e 130 # 'el#id' - element + id
131
132 /\G$sel_re#$sel_re/gc and
133 return do {
134 my $id = $1;
135 my $name = $2;
136 sub {
137 $_[0]->{name} && $_[0]->{name} eq $name and
138 $_[0]->{attrs}{id} && $_[0]->{attrs}{id} eq $id
139 }
140 };
141
456a815d 142 confess "Couldn't parse $_ as starting with simple selector";
143 }
144}
145
146sub parse_selector {
147 my $self = $_[0];
148 my $sel = $_[1]; # my pos() only please
149 die "No selector provided" unless $sel;
150 local *_;
151 for ($sel) {
152 my @sub;
153 PARSE: { do {
154 push(@sub, $self->_raw_parse_simple_selector($_));
155 last PARSE if (pos == length);
156 /\G\s*,\s*/gc or confess "Selectors not comma separated";
157 } until (pos == length) };
158 return $sub[0] if (@sub == 1);
159 return sub {
160 foreach my $inner (@sub) {
161 if (my $r = $inner->(@_)) { return $r }
162 }
163 };
164 }
7871f2ff 165}
166
456a815d 167
1681;