File Coverage

blib/lib/Pod/Abstract/Path.pm
Criterion Covered Total %
statement 229 420 54.5
branch 57 154 37.0
condition 4 12 33.3
subroutine 39 50 78.0
pod 2 25 8.0
total 331 661 50.0


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" 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 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