line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Pod::Abstract::Path; |
2
|
3
|
|
|
3
|
|
16
|
use strict; |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
109
|
|
3
|
3
|
|
|
3
|
|
16
|
use warnings; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
86
|
|
4
|
|
|
|
|
|
|
|
5
|
3
|
|
|
3
|
|
4132
|
use Data::Dumper; |
|
3
|
|
|
|
|
34052
|
|
|
3
|
|
|
|
|
237
|
|
6
|
|
|
|
|
|
|
|
7
|
3
|
|
|
3
|
|
2300
|
use Pod::Abstract::BuildNode qw(node); |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
319
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
$Data::Dumper::Indent = 1; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
our $VERSION = '0.20'; |
12
|
|
|
|
|
|
|
|
13
|
3
|
|
|
3
|
|
21
|
use constant CHILDREN => 1; # / |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
230
|
|
14
|
3
|
|
|
3
|
|
17
|
use constant ALL => 2; # // |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
120
|
|
15
|
3
|
|
|
3
|
|
16
|
use constant NAME => 3; # head1 |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
126
|
|
16
|
3
|
|
|
3
|
|
16
|
use constant INDEX => 4; # (3) |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
160
|
|
17
|
3
|
|
|
3
|
|
16
|
use constant L_SELECT => 5; # [ |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
142
|
|
18
|
3
|
|
|
3
|
|
16
|
use constant ATTR => 6; # @label |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
138
|
|
19
|
3
|
|
|
3
|
|
16
|
use constant N_CMP => 7; # == != < <= > >= |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
134
|
|
20
|
3
|
|
|
3
|
|
14
|
use constant STRING => 8; # 'foobar' |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
153
|
|
21
|
3
|
|
|
3
|
|
15
|
use constant R_SELECT => 9; # ] |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
121
|
|
22
|
3
|
|
|
3
|
|
15
|
use constant NUM_OF => 10; # # |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
165
|
|
23
|
3
|
|
|
3
|
|
14
|
use constant NOT => 15; # ! |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
137
|
|
24
|
3
|
|
|
3
|
|
23
|
use constant PARENT => 16; # .. |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
140
|
|
25
|
3
|
|
|
3
|
|
15
|
use constant MATCHES => 17; # =~ |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
135
|
|
26
|
3
|
|
|
3
|
|
15
|
use constant REGEXP => 18; # {} |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
126
|
|
27
|
3
|
|
|
3
|
|
15
|
use constant NOP => 19; # . |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
130
|
|
28
|
3
|
|
|
3
|
|
15
|
use constant PREV => 20; # << |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
124
|
|
29
|
3
|
|
|
3
|
|
15
|
use constant NEXT => 21; # >> |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
146
|
|
30
|
3
|
|
|
3
|
|
15
|
use constant ROOT => 22; # ^ |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
137
|
|
31
|
3
|
|
|
3
|
|
16
|
use constant UNION => 23; # | |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
133
|
|
32
|
3
|
|
|
3
|
|
16
|
use constant INTERSECT => 24; # & |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
145
|
|
33
|
3
|
|
|
3
|
|
15
|
use constant S_CMP => 25; # eq lt gt le ge ne |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
15354
|
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
=pod |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=head1 NAME |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
Pod::Abstract::Path - Search for POD nodes matching a path within a |
40
|
|
|
|
|
|
|
document tree. |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head1 SYNOPSIS |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
/head1(1)/head2 # All head2 elements under |
45
|
|
|
|
|
|
|
# the 2nd head1 element |
46
|
|
|
|
|
|
|
//item # All items anywhere |
47
|
|
|
|
|
|
|
//item[@label =~ {^\*$}] # All items with '*' labels. |
48
|
|
|
|
|
|
|
//head2[/hilight] # All head2 elements containing |
49
|
|
|
|
|
|
|
# "hilight" elements |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# Top level head1s containing head2s that have headings matching |
52
|
|
|
|
|
|
|
# "NAME", and also have at least one list somewhere in their |
53
|
|
|
|
|
|
|
# contents. |
54
|
|
|
|
|
|
|
/head1[/head2[@heading =~ {NAME}]][//over] |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# Top level headings having the same title as the following heading. |
57
|
|
|
|
|
|
|
/head1[@heading = >>@heading] |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# Top level headings containing at least one subheading with the same |
60
|
|
|
|
|
|
|
# name. |
61
|
|
|
|
|
|
|
/head1[@heading = ./head2@heading] |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
=head1 DESCRIPTION |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
Pod::Abstract::Path is a path selection syntax that allows fast and |
66
|
|
|
|
|
|
|
easy traversal of Pod::Abstract documents. While it has a simple |
67
|
|
|
|
|
|
|
syntax, there is significant complexity in the queries that you can |
68
|
|
|
|
|
|
|
create. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Not all of the designed features have yet been implemented, but it is |
71
|
|
|
|
|
|
|
currently quite useful, and all of the filters in C make use of |
72
|
|
|
|
|
|
|
Pod Paths. |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=head2 SYMBOLS: |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
=over |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=item / |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
Selects children of the left hand side. |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=item // |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
Selects all descendants of the left hand side. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=item . |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
Selects the current node - this is a NOP that can be used in |
89
|
|
|
|
|
|
|
expressions. |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=item .. |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
Selects the parrent node. If there are multiple nodes selected, all of |
94
|
|
|
|
|
|
|
their parents will be included. |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=item ^ |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
Selects the root node of the tree for the current node. This allows |
99
|
|
|
|
|
|
|
you to escape from a nested expression. Note that this is the ROOT |
100
|
|
|
|
|
|
|
node, not the node that you started from. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
If you want to evaluate an expression from a node as though it were |
103
|
|
|
|
|
|
|
the root node, the easiest ways are to detach or dup it - otherwise |
104
|
|
|
|
|
|
|
the root operator will find the original root node. |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=item name, #cut, :text, :verbatim, :paragraph |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
Any element name, or symbolic type name, will restrict the selection |
109
|
|
|
|
|
|
|
to only elements matching that type. e.g, "C/:paragraph>" will |
110
|
|
|
|
|
|
|
select all descendants, anywhere, but then restrict that set to only |
111
|
|
|
|
|
|
|
C<:paragraph> type nodes. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
Names together separated by spaces will match all of those names - |
114
|
|
|
|
|
|
|
e.g: C/head1 over> will match all lists and all head1s. |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
=item &, | (union and intersection) |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Union will take expressions on either side, and return all nodes that |
119
|
|
|
|
|
|
|
are members of either set. Intersection returns nodes that are members |
120
|
|
|
|
|
|
|
of BOTH sets. These can be used to extend expressions, and within [ |
121
|
|
|
|
|
|
|
expressions ] where a path is supported (left side of a match, left or |
122
|
|
|
|
|
|
|
right side of an = sign). These are NOT logical and/or, though a |
123
|
|
|
|
|
|
|
similar effect can be induced through these operators. |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=item @attrname |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
The named attribute of the nodes on the left hand side. Current |
128
|
|
|
|
|
|
|
attributes are C<@heading> for head1 through head4, and C<@label> for |
129
|
|
|
|
|
|
|
list items. |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=item [ expression ] |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
Select only the left hand elements that match the expression in the |
134
|
|
|
|
|
|
|
brackets. The expression will be evaluated from the point of view of |
135
|
|
|
|
|
|
|
each node in the current result set. |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
Expressions can be: |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=over |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=item simple: C<[/head2]> |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
Any regular path will be true if there are any nodes matched. The |
144
|
|
|
|
|
|
|
above example will be true if there are any head2 nodes as direct |
145
|
|
|
|
|
|
|
children of the selected node. |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=item regex match: C<[@heading =~ {FOO}]> |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
A regex match will be true if the left hand expression has nodes that |
150
|
|
|
|
|
|
|
match the regular expression between the braces on the right hand |
151
|
|
|
|
|
|
|
side. The above example will match anything with a heading containing |
152
|
|
|
|
|
|
|
"FOO". |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
Optionally, the right hand closing brace may have the C modifier to |
155
|
|
|
|
|
|
|
cause case-insensitive matching. i.e C<[@heading =~ {foo}i]> will |
156
|
|
|
|
|
|
|
match C or C. |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=item complement: C<[! /head2 ]> |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Reverses the remainder of the expression. The above example will match |
161
|
|
|
|
|
|
|
anything B a child head2 node. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=item compare operators: eg. C<[ /node1 eq /node2 ]> |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Matches nodes where the operator is satistied for at least one pair of |
166
|
|
|
|
|
|
|
nodes. The right hand expression can be a constant string (single |
167
|
|
|
|
|
|
|
quoted: C<'string'>, or a second expression. If two expressions are |
168
|
|
|
|
|
|
|
used, they are matched combinationally - i.e, all result nodes on the |
169
|
|
|
|
|
|
|
left are matched against all result nodes on the right. Both sides may |
170
|
|
|
|
|
|
|
contain nested expressions. |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
The following Perl compatible operators are supported: |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
String: C< eq gt lt le ge ne > |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
Numeric: C<<< == < > <= >= != >>> |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=back |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=back |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=head1 PERFORMANCE |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
Pod::Abstract::Path is not designed to be fast. It is designed to be |
185
|
|
|
|
|
|
|
expressive and useful, but it involves sucessive |
186
|
|
|
|
|
|
|
expand/de-duplicate/linear search operations and doing this with large |
187
|
|
|
|
|
|
|
documents containing many nodes is not suitable for high performance |
188
|
|
|
|
|
|
|
systems. |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
Simple expressions can be fast enough, but there is nothing to stop |
191
|
|
|
|
|
|
|
you from writing "//[]" and linear-searching all 10,000 |
192
|
|
|
|
|
|
|
nodes of your Pod document. Use with caution in interactive systems. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=head1 INTERFACE |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
It is recommended you use the C<select>> method |
197
|
|
|
|
|
|
|
to evaluate Path expressions. |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
If you wish to generate paths for use in other modules, use |
200
|
|
|
|
|
|
|
C to generate a parse tree, pass that as an argument to |
201
|
|
|
|
|
|
|
C, then use C to evaluate the expression against a list |
202
|
|
|
|
|
|
|
of nodes. You can re-use the same parse tree to process multiple lists |
203
|
|
|
|
|
|
|
of nodes in this fashion. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=cut |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
sub new { |
208
|
20
|
|
|
20
|
0
|
33
|
my $class = shift; |
209
|
20
|
|
|
|
|
27
|
my $expression = shift; |
210
|
20
|
|
|
|
|
23
|
my $parse_tree = shift; |
211
|
|
|
|
|
|
|
|
212
|
20
|
100
|
|
|
|
40
|
if($parse_tree) { |
213
|
10
|
|
|
|
|
41
|
my $self = bless { |
214
|
|
|
|
|
|
|
expression => $expression, |
215
|
|
|
|
|
|
|
parse_tree => $parse_tree |
216
|
|
|
|
|
|
|
}, $class; |
217
|
10
|
|
|
|
|
23
|
return $self; |
218
|
|
|
|
|
|
|
} else { |
219
|
10
|
|
|
|
|
43
|
my $self = bless { expression => $expression }, $class; |
220
|
|
|
|
|
|
|
|
221
|
10
|
|
|
|
|
37
|
my @lexemes = $self->lex($expression); |
222
|
10
|
|
|
|
|
37
|
my $parse_tree = $self->parse_path(\@lexemes); |
223
|
10
|
|
|
|
|
25
|
$self->{parse_tree} = $parse_tree; |
224
|
|
|
|
|
|
|
|
225
|
10
|
|
|
|
|
30
|
return $self; |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
sub lex { |
230
|
10
|
|
|
10
|
0
|
13
|
my $self = shift; |
231
|
10
|
|
|
|
|
17
|
my $expression = shift; |
232
|
10
|
|
|
|
|
15
|
my @l = ( ); |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# Digest expression into @l |
235
|
10
|
|
|
|
|
23
|
while($expression) { |
236
|
50
|
100
|
|
|
|
287
|
if($expression =~ m/^\/\//) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
237
|
10
|
|
|
|
|
21
|
substr($expression,0,2) = ''; |
238
|
10
|
|
|
|
|
33
|
push @l, [ ALL, undef ]; |
239
|
|
|
|
|
|
|
} elsif($expression =~ m/^\//) { |
240
|
10
|
|
|
|
|
14
|
substr($expression,0,1) = ''; |
241
|
10
|
|
|
|
|
32
|
push @l, [ CHILDREN, undef ]; |
242
|
|
|
|
|
|
|
} elsif($expression =~ m/^\|/) { |
243
|
0
|
|
|
|
|
0
|
substr($expression,0,1) = ''; |
244
|
0
|
|
|
|
|
0
|
push @l, [ UNION, undef ]; |
245
|
|
|
|
|
|
|
} elsif($expression =~ m/^\&/) { |
246
|
0
|
|
|
|
|
0
|
substr($expression,0,1) = ''; |
247
|
0
|
|
|
|
|
0
|
push @l, [ INTERSECT, undef ]; |
248
|
|
|
|
|
|
|
} elsif($expression =~ m/^\[/) { |
249
|
10
|
|
|
|
|
20
|
substr($expression,0,1) = ''; |
250
|
10
|
|
|
|
|
36
|
push @l, [ L_SELECT, undef ]; |
251
|
|
|
|
|
|
|
} elsif($expression =~ m/^\]/) { |
252
|
10
|
|
|
|
|
16
|
substr($expression,0,1) = ''; |
253
|
10
|
|
|
|
|
33
|
push @l, [ R_SELECT, undef ]; |
254
|
|
|
|
|
|
|
} elsif($expression =~ m/^(eq|lt|gt|le|ge|ne)/) { |
255
|
0
|
|
|
|
|
0
|
push @l, [ S_CMP, $1 ]; |
256
|
0
|
|
|
|
|
0
|
substr($expression,0,2) = ''; |
257
|
|
|
|
|
|
|
} elsif($expression =~ m/^([#_\:a-zA-Z0-9]+)/) { |
258
|
10
|
|
|
|
|
32
|
push @l, [ NAME, $1 ]; |
259
|
10
|
|
|
|
|
31
|
substr($expression, 0, length $1) = ''; |
260
|
|
|
|
|
|
|
} elsif($expression =~ m/^\@([a-zA-Z0-9]+)/) { |
261
|
0
|
|
|
|
|
0
|
push @l, [ ATTR, $1 ]; |
262
|
0
|
|
|
|
|
0
|
substr($expression, 0, length( $1 ) + 1) = ''; |
263
|
|
|
|
|
|
|
} elsif($expression =~ m/^\(([0-9]+)\)/) { |
264
|
0
|
|
|
|
|
0
|
push @l, [ INDEX, $1 ]; |
265
|
0
|
|
|
|
|
0
|
substr($expression, 0, length( $1 ) + 2) = ''; |
266
|
|
|
|
|
|
|
} elsif($expression =~ m/^\{(([^\}]|\\\})+)\}([i]?)/) { |
267
|
0
|
0
|
|
|
|
0
|
my $case = $3 eq 'i' ? 0 : 1; |
268
|
0
|
|
|
|
|
0
|
push @l, [ REGEXP, $1, $case ]; |
269
|
0
|
|
|
|
|
0
|
substr($expression, 0, length( $1 ) + 2 + length($3)) = ''; |
270
|
|
|
|
|
|
|
} elsif($expression =~ m/^'(([^']|\\')+)'/) { |
271
|
0
|
|
|
|
|
0
|
push @l, [ STRING, $1 ]; |
272
|
0
|
|
|
|
|
0
|
substr($expression, 0, length( $1 ) + 2) = ''; |
273
|
|
|
|
|
|
|
} elsif($expression =~ m/^\=\~/) { |
274
|
0
|
|
|
|
|
0
|
push @l, [ MATCHES, undef ]; |
275
|
0
|
|
|
|
|
0
|
substr($expression, 0, 2) = ''; |
276
|
|
|
|
|
|
|
} elsif($expression =~ m/^\.\./) { |
277
|
0
|
|
|
|
|
0
|
push @l, [ PARENT, undef ]; |
278
|
0
|
|
|
|
|
0
|
substr($expression, 0, 2) = ''; |
279
|
|
|
|
|
|
|
} elsif($expression =~ m/^\^/) { |
280
|
0
|
|
|
|
|
0
|
push @l, [ ROOT, undef ]; |
281
|
0
|
|
|
|
|
0
|
substr($expression, 0, 1) = ''; |
282
|
|
|
|
|
|
|
} elsif($expression =~ m/^\./) { |
283
|
0
|
|
|
|
|
0
|
push @l, [ NOP, undef ]; |
284
|
0
|
|
|
|
|
0
|
substr($expression, 0, 1) = ''; |
285
|
|
|
|
|
|
|
} elsif($expression =~ m/^\<\) { |
286
|
0
|
|
|
|
|
0
|
push @l, [ PREV, undef ]; |
287
|
0
|
|
|
|
|
0
|
substr($expression, 0, 2) = ''; |
288
|
|
|
|
|
|
|
} elsif($expression =~ m/^\>\>/) { |
289
|
0
|
|
|
|
|
0
|
push @l, [ NEXT, undef ]; |
290
|
0
|
|
|
|
|
0
|
substr($expression, 0, 2) = ''; |
291
|
|
|
|
|
|
|
} elsif($expression =~ m/^(==|!=|<=|>=)/) { |
292
|
0
|
|
|
|
|
0
|
push @l, [ N_CMP, $1 ]; |
293
|
0
|
|
|
|
|
0
|
substr($expression,0,2) = ''; |
294
|
|
|
|
|
|
|
} elsif($expression =~ m/^(<|>)/) { |
295
|
0
|
|
|
|
|
0
|
push @l, [ N_CMP, $1 ]; |
296
|
0
|
|
|
|
|
0
|
substr($expression,0,1) = ''; |
297
|
|
|
|
|
|
|
} elsif($expression =~ m/^\!/) { |
298
|
0
|
|
|
|
|
0
|
push @l, [ NOT, undef ]; |
299
|
0
|
|
|
|
|
0
|
substr($expression, 0, 1) = ''; |
300
|
|
|
|
|
|
|
} elsif($expression =~ m/^\%/) { |
301
|
0
|
|
|
|
|
0
|
push @l, [ NUM_OF, undef ]; |
302
|
0
|
|
|
|
|
0
|
substr($expression, 0, 1) = ''; |
303
|
|
|
|
|
|
|
} elsif($expression =~ m/^'([\^']*)'/) { |
304
|
0
|
|
|
|
|
0
|
push @l, [ STRING, $1 ]; |
305
|
0
|
|
|
|
|
0
|
substr($expression, 0, length( $1 ) + 2) = ''; |
306
|
|
|
|
|
|
|
} elsif($expression =~ m/(\s+)/) { |
307
|
|
|
|
|
|
|
# Discard uncaptured whitespace |
308
|
0
|
|
|
|
|
0
|
substr($expression, 0, length($1)) = ''; |
309
|
|
|
|
|
|
|
} else { |
310
|
0
|
|
|
|
|
0
|
die "Invalid token encountered - remaining string is $expression"; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} |
313
|
10
|
|
|
|
|
33
|
return @l; |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
=head1 METHODS |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=head2 filter_unique |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
It is possible during processing - especially using ^ or .. operators |
321
|
|
|
|
|
|
|
- to generate many duplicate matches of the same nodes. Each pass |
322
|
|
|
|
|
|
|
around the loop, we filter to unique nodes so that duplicates cannot |
323
|
|
|
|
|
|
|
inflate more than one time. |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
This effectively means that C/^> (however awful that is) will match |
326
|
|
|
|
|
|
|
one node only - just really inefficiently. |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=cut |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
sub filter_unique { |
331
|
72
|
|
|
72
|
1
|
73
|
my $self = shift; |
332
|
72
|
|
|
|
|
74
|
my $ilist = shift; |
333
|
72
|
|
|
|
|
96
|
my $nlist = [ ]; |
334
|
|
|
|
|
|
|
|
335
|
72
|
|
|
|
|
171
|
my %seen = ( ); |
336
|
72
|
|
|
|
|
105
|
foreach my $node (@$ilist) { |
337
|
48
|
50
|
|
|
|
105
|
push @$nlist, $node unless $seen{$node->serial}; |
338
|
48
|
|
|
|
|
119
|
$seen{$node->serial} = 1; |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
72
|
|
|
|
|
221
|
return $nlist; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
# Rec descent process of expression. |
345
|
|
|
|
|
|
|
sub process { |
346
|
36
|
|
|
36
|
0
|
40
|
my $self = shift; |
347
|
36
|
|
|
|
|
55
|
my @nodes = @_; |
348
|
|
|
|
|
|
|
|
349
|
36
|
|
|
|
|
52
|
my $pt = $self->{parse_tree}; |
350
|
36
|
|
|
|
|
60
|
my $ilist = [ @nodes ]; |
351
|
|
|
|
|
|
|
|
352
|
36
|
|
66
|
|
|
172
|
while($pt && $pt->{action} ne 'end_select') { |
353
|
72
|
|
|
|
|
97
|
my $action = $pt->{action}; |
354
|
72
|
|
|
|
|
102
|
my @args = ( ); |
355
|
72
|
100
|
|
|
|
195
|
if($pt->{arguments}) { |
356
|
36
|
|
|
|
|
39
|
@args = @{$pt->{arguments}}; |
|
36
|
|
|
|
|
78
|
|
357
|
|
|
|
|
|
|
} |
358
|
72
|
50
|
|
|
|
218
|
if($self->can($action)) { |
359
|
72
|
|
|
|
|
164
|
$ilist = $self->$action($ilist, @args); |
360
|
72
|
|
|
|
|
162
|
$ilist = $self->filter_unique($ilist); |
361
|
|
|
|
|
|
|
} else { |
362
|
0
|
|
|
|
|
0
|
warn "discarding '$action', can't do that"; |
363
|
|
|
|
|
|
|
} |
364
|
72
|
|
|
|
|
392
|
$pt = $pt->{'next'}; |
365
|
|
|
|
|
|
|
} |
366
|
36
|
|
|
|
|
148
|
return @$ilist; |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
sub select_name { |
370
|
26
|
|
|
26
|
0
|
30
|
my $self = shift; |
371
|
26
|
|
|
|
|
30
|
my $ilist = shift; |
372
|
26
|
|
|
|
|
44
|
my @names = @_; |
373
|
26
|
|
|
|
|
102
|
my $nlist = [ ]; |
374
|
|
|
|
|
|
|
|
375
|
26
|
|
|
|
|
49
|
my %names = map { $_ => 1 } @names; |
|
26
|
|
|
|
|
187
|
|
376
|
|
|
|
|
|
|
|
377
|
26
|
|
|
|
|
82
|
for(my $i = 0; $i < @$ilist; $i ++) { |
378
|
16
|
100
|
|
|
|
41
|
if($names{$ilist->[$i]->type}) { |
379
|
3
|
|
|
|
|
11
|
push @$nlist, $ilist->[$i]; |
380
|
|
|
|
|
|
|
}; |
381
|
|
|
|
|
|
|
} |
382
|
26
|
|
|
|
|
64
|
return $nlist; |
383
|
|
|
|
|
|
|
} |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
sub select_union { |
386
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
387
|
0
|
|
|
|
|
0
|
my $class = ref $self; |
388
|
|
|
|
|
|
|
|
389
|
0
|
|
|
|
|
0
|
my $ilist = shift; |
390
|
0
|
|
|
|
|
0
|
my $left = shift; |
391
|
0
|
|
|
|
|
0
|
my $right = shift; |
392
|
|
|
|
|
|
|
|
393
|
0
|
|
|
|
|
0
|
my $l_path = $class->new('union left', $left); |
394
|
0
|
|
|
|
|
0
|
my $r_path = $class->new('union right', $right); |
395
|
|
|
|
|
|
|
|
396
|
0
|
|
|
|
|
0
|
my @l_result = $l_path->process(@$ilist); |
397
|
0
|
|
|
|
|
0
|
my @r_result = $r_path->process(@$ilist); |
398
|
|
|
|
|
|
|
|
399
|
0
|
|
|
|
|
0
|
return [ @l_result, @r_result ]; |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
sub select_intersect { |
403
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
404
|
0
|
|
|
|
|
0
|
my $class = ref $self; |
405
|
|
|
|
|
|
|
|
406
|
0
|
|
|
|
|
0
|
my $ilist = shift; |
407
|
0
|
|
|
|
|
0
|
my $left = shift; |
408
|
0
|
|
|
|
|
0
|
my $right = shift; |
409
|
|
|
|
|
|
|
|
410
|
0
|
|
|
|
|
0
|
my $l_path = $class->new("intersect left", $left); |
411
|
0
|
|
|
|
|
0
|
my $r_path = $class->new("intersect right", $right); |
412
|
|
|
|
|
|
|
|
413
|
0
|
|
|
|
|
0
|
my @l_result = $l_path->process(@$ilist); |
414
|
0
|
|
|
|
|
0
|
my @r_result = $r_path->process(@$ilist); |
415
|
|
|
|
|
|
|
|
416
|
0
|
|
|
|
|
0
|
my %seen = ( ); |
417
|
0
|
|
|
|
|
0
|
my $nlist = [ ]; |
418
|
0
|
|
|
|
|
0
|
foreach my $a (@l_result) { |
419
|
0
|
|
|
|
|
0
|
$seen{$a->serial} = 1; |
420
|
|
|
|
|
|
|
} |
421
|
0
|
|
|
|
|
0
|
foreach my $b (@r_result) { |
422
|
0
|
0
|
|
|
|
0
|
push @$nlist, $b if $seen{$b->serial}; |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
|
425
|
0
|
|
|
|
|
0
|
return $nlist; |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
sub select_attr { |
429
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
430
|
0
|
|
|
|
|
0
|
my $ilist = shift; |
431
|
0
|
|
|
|
|
0
|
my $name = shift; |
432
|
0
|
|
|
|
|
0
|
my $nlist = [ ]; |
433
|
|
|
|
|
|
|
|
434
|
0
|
|
|
|
|
0
|
foreach my $i (@$ilist) { |
435
|
0
|
|
|
|
|
0
|
my $pv = $i->param($name); |
436
|
0
|
0
|
|
|
|
0
|
if($pv) { |
437
|
0
|
|
|
|
|
0
|
push @$nlist, $pv; |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
} |
440
|
0
|
|
|
|
|
0
|
return $nlist; |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
sub select_index { |
444
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
445
|
0
|
|
|
|
|
0
|
my $ilist = shift; |
446
|
0
|
|
|
|
|
0
|
my $index = shift; |
447
|
|
|
|
|
|
|
|
448
|
0
|
0
|
|
|
|
0
|
if($index < scalar @$ilist) { |
449
|
0
|
|
|
|
|
0
|
return [ $ilist->[$index] ]; |
450
|
|
|
|
|
|
|
} else { |
451
|
0
|
|
|
|
|
0
|
return [ ]; |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
} |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
sub match_expression { |
456
|
10
|
|
|
10
|
0
|
21
|
my $self = shift; |
457
|
10
|
|
|
|
|
14
|
my $ilist = shift; |
458
|
10
|
|
|
|
|
11
|
my $test_action = shift; |
459
|
10
|
|
|
|
|
15
|
my $invert = shift; |
460
|
10
|
|
|
|
|
12
|
my $exp = shift; |
461
|
10
|
|
|
|
|
13
|
my $r_exp = shift; |
462
|
|
|
|
|
|
|
|
463
|
10
|
|
|
|
|
12
|
my $op = shift; # Only for some operators |
464
|
|
|
|
|
|
|
|
465
|
10
|
|
|
|
|
16
|
my $nlist = [ ]; |
466
|
10
|
|
|
|
|
17
|
foreach my $n(@$ilist) { |
467
|
26
|
|
|
|
|
70
|
my @t_list = $exp->process($n); |
468
|
26
|
|
|
|
|
32
|
my $t_result; |
469
|
|
|
|
|
|
|
# Allow for r_exp to be another expression - generate both |
470
|
|
|
|
|
|
|
# node lists if required. |
471
|
26
|
50
|
|
|
|
31
|
if( eval { $r_exp->can('process') } ) { |
|
26
|
|
|
|
|
173
|
|
472
|
0
|
|
|
|
|
0
|
my @r_list = $r_exp->process($n); |
473
|
0
|
|
|
|
|
0
|
$t_result = $self->$test_action(\@t_list, \@r_list, $op); |
474
|
|
|
|
|
|
|
} else { |
475
|
26
|
|
|
|
|
184
|
$t_result = $self->$test_action(\@t_list, $r_exp, $op); |
476
|
|
|
|
|
|
|
} |
477
|
26
|
50
|
|
|
|
93
|
$t_result = !$t_result if $invert; |
478
|
26
|
100
|
|
|
|
65
|
if($t_result) { |
479
|
3
|
|
|
|
|
6
|
push @$nlist, $n; |
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
} |
482
|
10
|
|
|
|
|
29
|
return $nlist; |
483
|
|
|
|
|
|
|
} |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
sub test_cmp_op { |
486
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
487
|
0
|
|
|
|
|
0
|
my $l_list = shift; |
488
|
0
|
|
|
|
|
0
|
my $r_exp = shift; |
489
|
0
|
|
|
|
|
0
|
my $op = shift; |
490
|
|
|
|
|
|
|
|
491
|
0
|
0
|
0
|
|
|
0
|
if(scalar(@$r_exp) == 0 || eval { $r_exp->[0]->isa('Pod::Abstract::Node') }) { |
|
0
|
0
|
|
|
|
0
|
|
492
|
|
|
|
|
|
|
# combination test |
493
|
0
|
|
|
|
|
0
|
my $match = 0; |
494
|
0
|
|
|
|
|
0
|
foreach my $l (@$l_list) { |
495
|
0
|
|
|
|
|
0
|
my $lb = $l->body; |
496
|
0
|
0
|
|
|
|
0
|
$lb = $l->pod unless $lb; |
497
|
0
|
|
|
|
|
0
|
foreach my $r (@$r_exp) { |
498
|
0
|
|
|
|
|
0
|
my $rb = $r->body; |
499
|
0
|
0
|
|
|
|
0
|
$rb = $r->pod unless $rb; |
500
|
0
|
|
|
|
|
0
|
eval "\$match++ if \$lb $op \$rb"; |
501
|
0
|
0
|
|
|
|
0
|
die $@ if $@; |
502
|
|
|
|
|
|
|
} |
503
|
|
|
|
|
|
|
} |
504
|
0
|
|
|
|
|
0
|
return $match; |
505
|
|
|
|
|
|
|
} elsif($r_exp->[0] == STRING) { |
506
|
|
|
|
|
|
|
# simple string test |
507
|
0
|
|
|
|
|
0
|
my $str = $r_exp->[1]; |
508
|
0
|
|
|
|
|
0
|
my $match = 0; |
509
|
0
|
|
|
|
|
0
|
foreach my $l (@$l_list) { |
510
|
0
|
|
|
|
|
0
|
my $lb = $l->body; |
511
|
0
|
0
|
|
|
|
0
|
$lb = $l->pod unless $lb; |
512
|
0
|
|
|
|
|
0
|
eval "\$match++ if \$lb $op \$str"; |
513
|
0
|
0
|
|
|
|
0
|
die $@ if $@; |
514
|
|
|
|
|
|
|
} |
515
|
0
|
|
|
|
|
0
|
return $match; |
516
|
|
|
|
|
|
|
} else { |
517
|
0
|
|
|
|
|
0
|
die "Don't know what to do with ", Dumper([$r_exp]); |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
sub test_regexp { |
522
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
523
|
0
|
|
|
|
|
0
|
my $t_list = shift; |
524
|
0
|
|
|
|
|
0
|
my $regexp_set = shift; |
525
|
0
|
|
|
|
|
0
|
my $regexp = $regexp_set->[0]; |
526
|
0
|
|
|
|
|
0
|
my $case = $regexp_set->[1]; |
527
|
0
|
0
|
|
|
|
0
|
if($case) { |
528
|
0
|
|
|
|
|
0
|
$regexp = qr/$regexp/; |
529
|
|
|
|
|
|
|
} else { |
530
|
0
|
|
|
|
|
0
|
$regexp = qr/$regexp/i; |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
|
533
|
0
|
|
|
|
|
0
|
my $match = 0; |
534
|
0
|
|
|
|
|
0
|
foreach my $t_n (@$t_list) { |
535
|
0
|
|
|
|
|
0
|
my $body = $t_n->body; |
536
|
0
|
0
|
|
|
|
0
|
$body = $t_n->pod unless defined $body; |
537
|
0
|
0
|
|
|
|
0
|
if($body =~ $regexp) { |
538
|
0
|
|
|
|
|
0
|
$match ++; |
539
|
|
|
|
|
|
|
} |
540
|
|
|
|
|
|
|
} |
541
|
0
|
|
|
|
|
0
|
return $match; |
542
|
|
|
|
|
|
|
} |
543
|
|
|
|
|
|
|
|
544
|
|
|
|
|
|
|
sub test_simple { |
545
|
26
|
|
|
26
|
0
|
28
|
my $self = shift; |
546
|
26
|
|
|
|
|
26
|
my $t_list = shift; |
547
|
|
|
|
|
|
|
|
548
|
26
|
|
|
|
|
60
|
return (scalar @$t_list) > 0; |
549
|
|
|
|
|
|
|
} |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
sub select_children { |
552
|
26
|
|
|
26
|
0
|
34
|
my $self = shift; |
553
|
26
|
|
|
|
|
27
|
my $ilist = shift; |
554
|
26
|
|
|
|
|
32
|
my $nlist = [ ]; |
555
|
|
|
|
|
|
|
|
556
|
26
|
|
|
|
|
39
|
foreach my $n (@$ilist) { |
557
|
26
|
|
|
|
|
65
|
my @children = $n->children; |
558
|
26
|
|
|
|
|
69
|
push @$nlist, @children; |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
26
|
|
|
|
|
49
|
return $nlist; |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
sub select_next { |
565
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
566
|
0
|
|
|
|
|
0
|
my $ilist = shift; |
567
|
0
|
|
|
|
|
0
|
my $nlist = [ ]; |
568
|
|
|
|
|
|
|
|
569
|
0
|
|
|
|
|
0
|
foreach my $n (@$ilist) { |
570
|
0
|
|
|
|
|
0
|
my $next = $n->next; |
571
|
0
|
0
|
|
|
|
0
|
if($next) { |
572
|
0
|
|
|
|
|
0
|
push @$nlist, $next; |
573
|
|
|
|
|
|
|
} |
574
|
|
|
|
|
|
|
} |
575
|
|
|
|
|
|
|
|
576
|
0
|
|
|
|
|
0
|
return $nlist; |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
sub select_prev { |
580
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
581
|
0
|
|
|
|
|
0
|
my $ilist = shift; |
582
|
0
|
|
|
|
|
0
|
my $nlist = [ ]; |
583
|
|
|
|
|
|
|
|
584
|
0
|
|
|
|
|
0
|
foreach my $n (@$ilist) { |
585
|
0
|
|
|
|
|
0
|
my $prev = $n->previous; |
586
|
0
|
0
|
|
|
|
0
|
if($prev) { |
587
|
0
|
|
|
|
|
0
|
push @$nlist, $prev; |
588
|
|
|
|
|
|
|
} |
589
|
|
|
|
|
|
|
} |
590
|
|
|
|
|
|
|
|
591
|
0
|
|
|
|
|
0
|
return $nlist; |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
sub select_parents { |
595
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
596
|
0
|
|
|
|
|
0
|
my $ilist = shift; |
597
|
0
|
|
|
|
|
0
|
my $nlist = [ ]; |
598
|
0
|
|
|
|
|
0
|
foreach my $n (@$ilist) { |
599
|
0
|
0
|
|
|
|
0
|
if($n->parent) { |
600
|
0
|
|
|
|
|
0
|
push @$nlist, $n->parent; |
601
|
|
|
|
|
|
|
} |
602
|
|
|
|
|
|
|
} |
603
|
|
|
|
|
|
|
|
604
|
0
|
|
|
|
|
0
|
return $nlist; |
605
|
|
|
|
|
|
|
} |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
sub select_root { |
608
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
609
|
0
|
|
|
|
|
0
|
my $ilist = shift; |
610
|
0
|
|
|
|
|
0
|
my $nlist = [ ]; |
611
|
0
|
|
|
|
|
0
|
foreach my $n (@$ilist) { |
612
|
0
|
|
|
|
|
0
|
push @$nlist, $n->root; # almost certainly all the same - not |
613
|
|
|
|
|
|
|
# efficient but consistent. |
614
|
|
|
|
|
|
|
} |
615
|
|
|
|
|
|
|
|
616
|
0
|
|
|
|
|
0
|
return $nlist; |
617
|
|
|
|
|
|
|
} |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
sub select_current { |
620
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
621
|
0
|
|
|
|
|
0
|
my $ilist = shift; |
622
|
0
|
|
|
|
|
0
|
return $ilist; |
623
|
|
|
|
|
|
|
} |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
sub select_all { |
626
|
10
|
|
|
10
|
0
|
15
|
my $self = shift; |
627
|
10
|
|
|
|
|
11
|
my $ilist = shift; |
628
|
10
|
|
|
|
|
14
|
my $nlist = [ ]; |
629
|
|
|
|
|
|
|
|
630
|
10
|
|
|
|
|
32
|
foreach my $n (@$ilist) { |
631
|
10
|
|
|
|
|
38
|
push @$nlist, $self->expand_all($n); |
632
|
|
|
|
|
|
|
} |
633
|
|
|
|
|
|
|
|
634
|
10
|
|
|
|
|
22
|
return $nlist; |
635
|
|
|
|
|
|
|
} |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
sub expand_all { |
638
|
36
|
|
|
36
|
0
|
40
|
my $self = shift; |
639
|
36
|
|
|
|
|
43
|
my $n = shift; |
640
|
|
|
|
|
|
|
|
641
|
36
|
|
|
|
|
99
|
my @children = $n->children; |
642
|
36
|
|
|
|
|
64
|
my @r = ( ); |
643
|
36
|
|
|
|
|
58
|
foreach my $c (@children) { |
644
|
26
|
|
|
|
|
49
|
push @r, $c; |
645
|
26
|
|
|
|
|
66
|
push @r, $self->expand_all($c); |
646
|
|
|
|
|
|
|
}; |
647
|
|
|
|
|
|
|
|
648
|
36
|
|
|
|
|
113
|
return @r; |
649
|
|
|
|
|
|
|
} |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
=head2 parse_path |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
Parse a list of lexemes and generate a driver tree for the process |
654
|
|
|
|
|
|
|
method. This is a simple recursive descent parser with one element of |
655
|
|
|
|
|
|
|
lookahead. |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
=cut |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
sub parse_path { |
660
|
20
|
|
|
20
|
1
|
31
|
my $self = shift; |
661
|
20
|
|
|
|
|
22
|
my $l = shift; |
662
|
|
|
|
|
|
|
|
663
|
20
|
|
|
|
|
53
|
my $left = $self->parse_l_path($l); |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
# Handle UNION or INTERSECT operators |
666
|
20
|
|
|
|
|
41
|
my $next = shift @$l; |
667
|
20
|
100
|
|
|
|
35
|
if($next) { |
668
|
10
|
|
|
|
|
16
|
my $tok = $next->[0]; |
669
|
10
|
50
|
|
|
|
27
|
if($tok == UNION) { |
|
|
50
|
|
|
|
|
|
670
|
|
|
|
|
|
|
return { |
671
|
0
|
|
|
|
|
0
|
action => "select_union", |
672
|
|
|
|
|
|
|
arguments => [ $left, $self->parse_path($l) ], |
673
|
|
|
|
|
|
|
}; |
674
|
|
|
|
|
|
|
} elsif($tok == INTERSECT) { |
675
|
|
|
|
|
|
|
return { |
676
|
0
|
|
|
|
|
0
|
action => "select_intersect", |
677
|
|
|
|
|
|
|
arguments => [ $left, $self->parse_path($l) ], |
678
|
|
|
|
|
|
|
} |
679
|
|
|
|
|
|
|
} else { |
680
|
10
|
|
|
|
|
15
|
unshift @$l, $next; |
681
|
10
|
|
|
|
|
20
|
return $left; |
682
|
|
|
|
|
|
|
} |
683
|
|
|
|
|
|
|
} else { |
684
|
10
|
|
|
|
|
19
|
return $left; |
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
} |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
sub parse_l_path { |
690
|
60
|
|
|
60
|
0
|
79
|
my $self = shift; |
691
|
60
|
|
|
|
|
76
|
my $l = shift; |
692
|
|
|
|
|
|
|
|
693
|
60
|
|
|
|
|
80
|
my $next = shift @$l; |
694
|
60
|
100
|
|
|
|
209
|
my $tok = $next->[0] if $next; |
695
|
60
|
100
|
|
|
|
125
|
my $val = $next->[1] if $next; |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
# Accept: / (children), // (all), name, |
698
|
60
|
100
|
|
|
|
120
|
if(not defined $next) { |
|
300
|
100
|
|
|
|
667
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
699
|
|
|
|
|
|
|
return { |
700
|
10
|
|
|
|
|
37
|
'action' => 'end_select', |
701
|
|
|
|
|
|
|
}; |
702
|
|
|
|
|
|
|
} elsif(grep { $tok == $_ } |
703
|
|
|
|
|
|
|
(MATCHES, R_SELECT, S_CMP, N_CMP, UNION, INTERSECT)) { |
704
|
10
|
|
|
|
|
15
|
unshift @$l, $next; |
705
|
|
|
|
|
|
|
return { |
706
|
10
|
|
|
|
|
77
|
'action' => 'end_select', |
707
|
|
|
|
|
|
|
}; |
708
|
|
|
|
|
|
|
} elsif($tok == CHILDREN) { |
709
|
|
|
|
|
|
|
return { |
710
|
10
|
|
|
|
|
26
|
'action' => 'select_children', |
711
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
712
|
|
|
|
|
|
|
}; |
713
|
|
|
|
|
|
|
} elsif($tok == ALL) { |
714
|
|
|
|
|
|
|
return { |
715
|
10
|
|
|
|
|
27
|
'action' => 'select_all', |
716
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
717
|
|
|
|
|
|
|
}; |
718
|
|
|
|
|
|
|
} elsif($tok == NEXT) { |
719
|
|
|
|
|
|
|
return { |
720
|
0
|
|
|
|
|
0
|
'action' => 'select_next', |
721
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
722
|
|
|
|
|
|
|
}; |
723
|
|
|
|
|
|
|
} elsif($tok == PREV) { |
724
|
|
|
|
|
|
|
return { |
725
|
0
|
|
|
|
|
0
|
'action' => 'select_prev', |
726
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
727
|
|
|
|
|
|
|
}; |
728
|
|
|
|
|
|
|
} elsif($tok == PARENT) { |
729
|
|
|
|
|
|
|
return { |
730
|
0
|
|
|
|
|
0
|
'action' => 'select_parents', |
731
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
732
|
|
|
|
|
|
|
}; |
733
|
|
|
|
|
|
|
} elsif($tok == ROOT) { |
734
|
|
|
|
|
|
|
return { |
735
|
0
|
|
|
|
|
0
|
'action' => 'select_root', |
736
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
737
|
|
|
|
|
|
|
}; |
738
|
|
|
|
|
|
|
} elsif($tok == NOP) { |
739
|
|
|
|
|
|
|
return { |
740
|
0
|
|
|
|
|
0
|
'action' => 'select_current', |
741
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
742
|
|
|
|
|
|
|
}; |
743
|
|
|
|
|
|
|
} elsif($tok == NAME) { |
744
|
10
|
|
|
|
|
28
|
my @extra_names = $self->parse_names($l); |
745
|
|
|
|
|
|
|
return { |
746
|
10
|
|
|
|
|
46
|
'action' => 'select_name', |
747
|
|
|
|
|
|
|
'arguments' => [ $val, @extra_names ], |
748
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
749
|
|
|
|
|
|
|
}; |
750
|
|
|
|
|
|
|
} elsif($tok == ATTR) { |
751
|
|
|
|
|
|
|
return { |
752
|
0
|
|
|
|
|
0
|
'action' => 'select_attr', |
753
|
|
|
|
|
|
|
'arguments' => [ $val ], |
754
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
755
|
|
|
|
|
|
|
}; |
756
|
|
|
|
|
|
|
} elsif($tok == INDEX) { |
757
|
|
|
|
|
|
|
return { |
758
|
0
|
|
|
|
|
0
|
'action' => 'select_index', |
759
|
|
|
|
|
|
|
'arguments' => [ $val ], |
760
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
761
|
|
|
|
|
|
|
}; |
762
|
|
|
|
|
|
|
} elsif($tok == L_SELECT) { |
763
|
10
|
|
|
|
|
27
|
unshift @$l, $next; |
764
|
10
|
|
|
|
|
28
|
my $exp = $self->parse_expression($l); |
765
|
10
|
|
|
|
|
22
|
$exp->{'next'} = $self->parse_l_path($l); |
766
|
10
|
|
|
|
|
43
|
return $exp; |
767
|
|
|
|
|
|
|
} elsif($tok == ATTR) { |
768
|
|
|
|
|
|
|
return { |
769
|
0
|
|
|
|
|
0
|
'action' => 'select_attribute', |
770
|
|
|
|
|
|
|
'arguments' => [ $val ], |
771
|
|
|
|
|
|
|
'next' => $self->parse_l_path($l), |
772
|
|
|
|
|
|
|
} |
773
|
|
|
|
|
|
|
} else { |
774
|
0
|
|
|
|
|
0
|
die "Unexpected token, ", Dumper([$next]); |
775
|
|
|
|
|
|
|
} |
776
|
|
|
|
|
|
|
} |
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
sub parse_names { |
779
|
10
|
|
|
10
|
0
|
15
|
my $self = shift; |
780
|
10
|
|
|
|
|
14
|
my $l = shift; |
781
|
10
|
|
|
|
|
13
|
my @r = ( ); |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
# Collect a list of names until there are no more. |
784
|
10
|
|
33
|
|
|
62
|
while(@$l && $l->[0][0] == NAME) { |
785
|
0
|
|
|
|
|
0
|
my $next = shift @$l; |
786
|
0
|
|
|
|
|
0
|
my $val = $next->[1]; |
787
|
0
|
|
|
|
|
0
|
push @r, $val; |
788
|
|
|
|
|
|
|
} |
789
|
|
|
|
|
|
|
|
790
|
10
|
|
|
|
|
23
|
return @r; |
791
|
|
|
|
|
|
|
} |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
sub parse_expression { |
794
|
10
|
|
|
10
|
0
|
104
|
my $self = shift; |
795
|
10
|
|
|
|
|
21
|
my $class = ref $self; |
796
|
10
|
|
|
|
|
14
|
my $l = shift; |
797
|
|
|
|
|
|
|
|
798
|
10
|
|
|
|
|
13
|
my $l_select = shift @$l; |
799
|
10
|
50
|
|
|
|
27
|
die "Expected L_SELECT, got ", Dumper([$l_select]) |
800
|
|
|
|
|
|
|
unless $l_select->[0] == L_SELECT; |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
# See if we lead with a NOT |
803
|
10
|
50
|
|
|
|
33
|
if($l->[0][0] == NOT) { |
804
|
0
|
|
|
|
|
0
|
shift @$l; |
805
|
0
|
|
|
|
|
0
|
unshift @$l, $l_select; |
806
|
|
|
|
|
|
|
|
807
|
0
|
|
|
|
|
0
|
my $exp = $self->parse_expression($l); |
808
|
0
|
|
|
|
|
0
|
$exp->{arguments}[1] = !$exp->{arguments}[1]; |
809
|
0
|
|
|
|
|
0
|
return $exp; |
810
|
|
|
|
|
|
|
} |
811
|
|
|
|
|
|
|
|
812
|
10
|
|
|
|
|
26
|
my $l_exp = $self->parse_path($l); |
813
|
10
|
|
|
|
|
30
|
$l_exp = $class->new("select expression",$l_exp); |
814
|
10
|
|
|
|
|
13
|
my $op = shift @$l; |
815
|
10
|
|
|
|
|
18
|
my $op_tok = $op->[0]; |
816
|
10
|
|
|
|
|
13
|
my $op_val = $op->[1]; |
817
|
10
|
|
|
|
|
13
|
my $exp = undef; |
818
|
|
|
|
|
|
|
|
819
|
10
|
50
|
33
|
|
|
63
|
if($op_tok == MATCHES) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
820
|
0
|
|
|
|
|
0
|
my $re = shift @$l; |
821
|
0
|
|
|
|
|
0
|
my $re_tok = $re->[0]; |
822
|
0
|
|
|
|
|
0
|
my $re_str = $re->[1]; |
823
|
0
|
|
|
|
|
0
|
my $case_sensitive = $re->[2]; |
824
|
|
|
|
|
|
|
|
825
|
0
|
0
|
|
|
|
0
|
if($re_tok == REGEXP) { |
826
|
0
|
|
|
|
|
0
|
$exp = { |
827
|
|
|
|
|
|
|
'action' => 'match_expression', |
828
|
|
|
|
|
|
|
'arguments' => [ 'test_regexp', 0, |
829
|
|
|
|
|
|
|
$l_exp, |
830
|
|
|
|
|
|
|
[ $re_str, $case_sensitive ] ], |
831
|
|
|
|
|
|
|
} |
832
|
|
|
|
|
|
|
} else { |
833
|
0
|
|
|
|
|
0
|
die "Expected REGEXP, got ", Dumper([$re_tok]); |
834
|
|
|
|
|
|
|
} |
835
|
|
|
|
|
|
|
} elsif($op_tok == S_CMP || $op_tok == N_CMP) { |
836
|
0
|
|
|
|
|
0
|
my $rh = shift @$l; |
837
|
0
|
|
|
|
|
0
|
my $rh_tok = $rh->[0]; |
838
|
0
|
|
|
|
|
0
|
my $r_exp = undef; |
839
|
|
|
|
|
|
|
|
840
|
0
|
0
|
|
|
|
0
|
if($rh_tok == STRING) { # simple string equality |
841
|
0
|
|
|
|
|
0
|
$r_exp = $rh; |
842
|
|
|
|
|
|
|
} else { |
843
|
0
|
|
|
|
|
0
|
unshift @$l, $rh; |
844
|
0
|
|
|
|
|
0
|
$r_exp = $self->parse_path($l); |
845
|
0
|
|
|
|
|
0
|
$r_exp = $class->new("select expression",$r_exp); |
846
|
|
|
|
|
|
|
} |
847
|
0
|
|
|
|
|
0
|
$exp = { |
848
|
|
|
|
|
|
|
action => 'match_expression', |
849
|
|
|
|
|
|
|
arguments => [ 'test_cmp_op', 0, |
850
|
|
|
|
|
|
|
$l_exp, $r_exp, $op_val ], |
851
|
|
|
|
|
|
|
}; |
852
|
|
|
|
|
|
|
} elsif($op_tok == R_SELECT) { |
853
|
|
|
|
|
|
|
# simple expression |
854
|
10
|
|
|
|
|
13
|
unshift @$l, $op; |
855
|
10
|
|
|
|
|
45
|
$exp = { |
856
|
|
|
|
|
|
|
'action' => 'match_expression', |
857
|
|
|
|
|
|
|
'arguments' => [ 'test_simple', 0, $l_exp ], |
858
|
|
|
|
|
|
|
} |
859
|
|
|
|
|
|
|
} else { |
860
|
0
|
|
|
|
|
0
|
die "Expected MATCHES, got ", Dumper([$op_tok]); |
861
|
|
|
|
|
|
|
} |
862
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
# Must match close of select; |
864
|
10
|
|
|
|
|
18
|
my $r_select = shift @$l; |
865
|
10
|
50
|
|
|
|
26
|
die "Expected R_SELECT, got, ", Dumper([$r_select]) |
866
|
|
|
|
|
|
|
unless $r_select->[0] == R_SELECT; |
867
|
10
|
50
|
|
|
|
25
|
die "Failed to generate expression" |
868
|
|
|
|
|
|
|
unless $exp; |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
# All OK! |
871
|
10
|
|
|
|
|
27
|
return $exp; |
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
=head1 AUTHOR |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
Ben Lilburne |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
Copyright (C) 2009 Ben Lilburne |
881
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify |
883
|
|
|
|
|
|
|
it under the same terms as Perl itself. |
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
=cut |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
1; |
888
|
|
|
|
|
|
|
|