File Coverage

blib/lib/XML/XPathEngine.pm
Criterion Covered Total %
statement 340 469 72.4
branch 121 212 57.0
condition 23 29 79.3
subroutine 47 60 78.3
pod 15 15 100.0
total 546 785 69.5


line stmt bran cond sub pod time code
1             package XML::XPathEngine;
2              
3 2     2   51269 use warnings;
  2         5  
  2         70  
4 2     2   12 use strict;
  2         3  
  2         79  
5              
6 2     2   12 use vars qw($VERSION $AUTOLOAD $revision);
  2         9  
  2         238  
7              
8             $VERSION = '0.14';
9             $XML::XPathEngine::Namespaces = 0;
10             $XML::XPathEngine::DEBUG = 0;
11              
12 2         308 use vars qw/
13             $NCName
14             $QName
15             $NCWild
16             $QNWild
17             $NUMBER_RE
18             $NODE_TYPE
19             $AXIS_NAME
20             %AXES
21             $LITERAL
22             $REGEXP_RE
23             $REGEXP_MOD_RE
24 2     2   12 %CACHE/;
  2         3  
25              
26 2     2   1333 use XML::XPathEngine::Step;
  2         6  
  2         77  
27 2     2   1497 use XML::XPathEngine::Expr;
  2         7  
  2         89  
28 2     2   1529 use XML::XPathEngine::Function;
  2         7  
  2         63  
29 2     2   1366 use XML::XPathEngine::LocationPath;
  2         7  
  2         51  
30 2     2   1053 use XML::XPathEngine::Variable;
  2         7  
  2         134  
31 2     2   15 use XML::XPathEngine::Literal;
  2         4  
  2         42  
32 2     2   8 use XML::XPathEngine::Number;
  2         3  
  2         39  
33 2     2   9 use XML::XPathEngine::NodeSet;
  2         3  
  2         33  
34 2     2   8 use XML::XPathEngine::Root;
  2         2  
  2         14465  
35              
36             # Axis name to principal node type mapping
37             %AXES = (
38             'ancestor' => 'element',
39             'ancestor-or-self' => 'element',
40             'attribute' => 'attribute',
41             'namespace' => 'namespace',
42             'child' => 'element',
43             'descendant' => 'element',
44             'descendant-or-self' => 'element',
45             'following' => 'element',
46             'following-sibling' => 'element',
47             'parent' => 'element',
48             'preceding' => 'element',
49             'preceding-sibling' => 'element',
50             'self' => 'element',
51             );
52              
53             $NCName = '([A-Za-z_][\w\\.\\-]*)';
54             $QName = "($NCName:)?$NCName";
55             $NCWild = "${NCName}:\\*";
56             $QNWild = "\\*";
57             $NODE_TYPE = '((text|comment|processing-instruction|node)\\(\\))';
58             $AXIS_NAME = '(' . join('|', keys %AXES) . ')::';
59             $NUMBER_RE = '\d+(\\.\d*)?|\\.\d+';
60             $LITERAL = '\\"[^\\"]*\\"|\\\'[^\\\']*\\\'';
61             $REGEXP_RE = qr{(?:m?/(?:\\.|[^/])*/)};
62             $REGEXP_MOD_RE = qr{(?:[imsx]+)};
63              
64             sub new {
65 1     1 1 2741 my $class = shift;
66 1         4 my $self = bless {}, $class;
67 1 50       5 _debug("New Parser being created.\n") if( $XML::XPathEngine::DEBUG);
68 1         10 $self->{context_set} = XML::XPathEngine::NodeSet->new();
69 1         4 $self->{context_pos} = undef; # 1 based position in array context
70 1         2 $self->{context_size} = 0; # total size of context
71 1         6 $self->clear_namespaces();
72 1         3 $self->{vars} = {};
73 1         2 $self->{direction} = 'forward';
74 1         3 $self->{cache} = {};
75 1         4 return $self;
76             }
77              
78             sub find {
79 38     38 1 59 my $self = shift;
80 38         63 my( $path, $context) = @_;
81 38         104 my $parsed_path= $self->_parse( $path);
82 38         132 my $results= $parsed_path->evaluate( $context);
83 35 100       165 if( $results->isa( 'XML::XPathEngine::NodeSet'))
84 22         67 { return $results->sort->remove_duplicates; }
85             else
86 13         39 { return $results; }
87             }
88              
89              
90             sub matches {
91 0     0 1 0 my $self = shift;
92 0         0 my ($node, $path, $context) = @_;
93              
94 0         0 my @nodes = $self->findnodes( $path, $context);
95              
96 0 0       0 if (grep { "$node" eq "$_" } @nodes) { return 1; }
  0         0  
  0         0  
97 0         0 return;
98             }
99              
100             sub findnodes {
101 2     2 1 631 my $self = shift;
102 2         4 my ($path, $context) = @_;
103            
104 2         6 my $results = $self->find( $path, $context);
105            
106 2 50       9 if ($results->isa('XML::XPathEngine::NodeSet'))
107 2 50       8 { return wantarray ? $results->get_nodelist : $results; }
108             else
109 0 0       0 { return wantarray ? XML::XPathEngine::NodeSet->new($results)
110             : $results;
111             } # result should be SCALAR
112             #{ return wantarray ? ($results) : $results; } # result should be SCALAR
113             #{ return wantarray ? () : XML::XPathEngine::NodeSet->new(); }
114             }
115              
116              
117             sub findnodes_as_string {
118 0     0 1 0 my $self = shift;
119 0         0 my ($path, $context) = @_;
120            
121 0         0 my $results = $self->find( $path, $context);
122            
123              
124 0 0       0 if ($results->isa('XML::XPathEngine::NodeSet')) {
    0          
    0          
125 0         0 return join '', map { $_->toString } $results->get_nodelist;
  0         0  
126             }
127             elsif ($results->isa('XML::XPathEngine::Boolean')) {
128 0         0 return ''; # to behave like XML::LibXML
129             }
130             elsif ($results->isa('XML::XPathEngine::Node')) {
131 0         0 return $results->toString;
132             }
133             else {
134 0         0 return _xml_escape_text($results->value);
135             }
136             }
137              
138             sub findnodes_as_strings {
139 0     0 1 0 my $self = shift;
140 0         0 my ($path, $context) = @_;
141            
142 0         0 my $results = $self->find( $path, $context);
143            
144 0 0       0 if ($results->isa('XML::XPathEngine::NodeSet')) {
    0          
    0          
145 0         0 return map { $_->getValue } $results->get_nodelist;
  0         0  
146             }
147             elsif ($results->isa('XML::XPathEngine::Boolean')) {
148 0         0 return (); # to behave like XML::LibXML
149             }
150             elsif ($results->isa('XML::XPathEngine::Node')) {
151 0         0 return $results->getValue;
152             }
153             else {
154 0         0 return _xml_escape_text($results->value);
155             }
156             }
157              
158             sub findvalue {
159 31     31 1 13749 my $self = shift;
160 31         129 my ($path, $context) = @_;
161 31         75 my $results = $self->find( $path, $context);
162 31 100       144 if ($results->isa('XML::XPathEngine::NodeSet'))
163 18         59 { return $results->to_final_value; }
164             #{ return $results->to_literal; }
165 13         37 return $results->value;
166             }
167              
168             sub findvalues {
169 5     5 1 2107 my $self = shift;
170 5         11 my ($path, $context) = @_;
171 5         16 my $results = $self->find( $path, $context);
172 2 50       12 if ($results->isa('XML::XPathEngine::NodeSet'))
173 2         10 { return $results->string_values; }
174 0         0 return ($results->string_value);
175             }
176              
177              
178             sub exists
179             {
180 0     0 1 0 my $self = shift;
181 0         0 my ($path, $context) = @_;
182 0 0       0 $self = '/' if (!defined $self);
183 0         0 my @nodeset = $self->findnodes( $path, $context);
184 0 0       0 return scalar( @nodeset ) ? 1 : 0;
185             }
186              
187             sub get_var {
188 0     0 1 0 my $self = shift;
189 0         0 my $var = shift;
190 0         0 $self->{vars}->{$var};
191             }
192              
193             sub set_var {
194 0     0 1 0 my $self = shift;
195 0         0 my $var = shift;
196 0         0 my $val = shift;
197 0         0 $self->{vars}->{$var} = $val;
198             }
199              
200             sub set_namespace {
201 0     0 1 0 my $self = shift;
202 0         0 my ($prefix, $expanded) = @_;
203 0         0 $self->{uses_namespaces}=1;
204 0         0 $self->{namespaces}{$prefix} = $expanded;
205             }
206              
207             sub clear_namespaces {
208 1     1 1 2 my $self = shift;
209 1         3 $self->{uses_namespaces}=0;
210 1         3 $self->{namespaces} = {};
211             }
212              
213             sub get_namespace {
214 0     0 1 0 my $self = shift;
215 0         0 my ($prefix, $node) = @_;
216            
217 0 0       0 my $ns= $node ? $node->getNamespace($prefix)
    0          
218             : $self->{uses_namespaces} ? $self->{namespaces}->{$prefix}
219             : $prefix;
220 0         0 return $ns;
221             }
222              
223             sub set_strict_namespaces {
224 0     0 1 0 my( $self, $strict) = @_;
225 0         0 $self->{strict_namespaces}= $strict;
226             }
227              
228 275     275   706 sub _get_context_set { $_[0]->{context_set}; }
229 708     708   1772 sub _set_context_set { $_[0]->{context_set} = $_[1]; }
230 275     275   605 sub _get_context_pos { $_[0]->{context_pos}; }
231 1051     1051   2456 sub _set_context_pos { $_[0]->{context_pos} = $_[1]; }
232 0     0   0 sub _get_context_size { $_[0]->{context_set}->size; }
233 0     0   0 sub _get_context_node { $_[0]->{context_set}->get_node($_[0]->{context_pos}); }
234              
235             sub _parse {
236 38     38   48 my $self = shift;
237 38         49 my $path = shift;
238              
239 38         100 my $context= join( '&&', $path, map { "$_=>$self->{namespaces}->{$_}" } sort keys %{$self->{namespaces}});
  0         0  
  38         169  
240             #warn "context: $context\n";
241              
242 38 100       148 if ($CACHE{$context}) { return $CACHE{$context}; }
  4         11  
243              
244 34         91 my $tokens = $self->_tokenize($path);
245              
246 34         83 $self->{_tokpos} = 0;
247 34         98 my $tree = $self->_analyze($tokens);
248            
249 34 50       87 if ($self->{_tokpos} < scalar(@$tokens)) {
250             # didn't manage to parse entire expression - throw an exception
251 0         0 die "Parse of expression $path failed - junk after end of expression: $tokens->[$self->{_tokpos}]";
252             }
253              
254 34         78 $tree->{uses_namespaces}= $self->{uses_namespaces};
255 34         72 $tree->{strict_namespaces}= $self->{strict_namespaces};
256            
257 34         94 $CACHE{$context} = $tree;
258            
259 34 50       71 _debug("PARSED Expr to:\n", $tree->as_string, "\n") if( $XML::XPathEngine::DEBUG);
260            
261 34         119 return $tree;
262             }
263              
264             sub _tokenize {
265 34     34   46 my $self = shift;
266 34         45 my $path = shift;
267 34         47 study $path;
268            
269 34         41 my @tokens;
270            
271 34 50       78 _debug("Parsing: $path\n") if( $XML::XPathEngine::DEBUG);
272            
273             # Bug: We don't allow "'@' NodeType" which is in the grammar, but I think is just plain stupid.
274              
275 34         54 my $expected=''; # used to desambiguate conflicts (for REs)
276              
277 34         85 while( length($path))
278 400         491 { my $token='';
279 400 100 66     3673 if( $expected eq 'RE' && ($path=~ m{\G\s*($REGEXP_RE $REGEXP_MOD_RE?)\s*}gcxso))
    100          
280             { # special case: regexp expected after =~ or !~, regular parsing rules do not apply
281             # ( the / is now the regexp delimiter)
282 3         8 $token= $1; $expected='';
  3         8  
283             }
284             elsif($path =~ m/\G
285             \s* # ignore all whitespace
286             ( # tokens
287             $LITERAL|
288             $NUMBER_RE| # digits
289             \.\.| # parent
290             \.| # current
291             ($AXIS_NAME)?$NODE_TYPE| # tests
292             processing-instruction|
293             \@($NCWild|$QName|$QNWild)| # attrib
294             \$$QName| # variable reference
295             ($AXIS_NAME)?($NCWild|$QName|$QNWild)| # NCName,NodeType,Axis::Test
296             \!=|<=|\-|>=|\/\/|and|or|mod|div| # multi-char seps
297             =~|\!~| # regexp (not in the XPath spec)
298             [,\+=\|<>\/\(\[\]\)]| # single char seps
299             (?
300             (?
301             $ # end of query
302             )
303             \s* # ignore all whitespace
304             /gcxso)
305             {
306 363         569 $token = $1;
307 363 100       706 $expected= ($token=~ m{^[=!]~$}) ? 'RE' : '';
308             }
309             else
310 34         47 { $token=''; last; }
  34         63  
311              
312 366 100       789 if (length($token)) {
313 332 50       573 _debug("TOKEN: $token\n") if( $XML::XPathEngine::DEBUG);
314 332         909 push @tokens, $token;
315             }
316            
317             }
318            
319 34 50       94 if (pos($path) < length($path)) {
320 0         0 my $marker = ("." x (pos($path)-1));
321 0         0 $path = substr($path, 0, pos($path) + 8) . "...";
322 0         0 $path =~ s/\n/ /g;
323 0         0 $path =~ s/\t/ /g;
324 0         0 die "Query:\n",
325             "$path\n",
326             $marker, "^^^\n",
327             "Invalid query somewhere around here (I think)\n";
328             }
329            
330 34         122 return \@tokens;
331             }
332              
333             sub _analyze {
334 34     34   53 my $self = shift;
335 34         98 my $tokens = shift;
336             # lexical analysis
337            
338 34         79 return _expr($self, $tokens);
339             }
340              
341             sub _match {
342 1842     1842   2853 my ($self, $tokens, $match, $fatal) = @_;
343            
344 1842         2848 $self->{_curr_match} = '';
345 1842 100       4736 return 0 unless $self->{_tokpos} < @$tokens;
346              
347 1501         3175 local $^W;
348            
349             # _debug ("match: $match\n") if( $XML::XPathEngine::DEBUG);
350            
351 1501 100       22624 if ($tokens->[$self->{_tokpos}] =~ /^$match$/) {
352 269         523 $self->{_curr_match} = $tokens->[$self->{_tokpos}];
353 269         322 $self->{_tokpos}++;
354 269         1302 return 1;
355             }
356             else {
357 1232 50       1917 if ($fatal) {
358 0         0 die "Invalid token: ", $tokens->[$self->{_tokpos}], "\n";
359             }
360             else {
361 1232         5740 return 0;
362             }
363             }
364             }
365              
366             sub _expr {
367 88     88   146 my ($self, $tokens) = @_;
368            
369 88 50       168 _debug( "in _exprexpr\n") if( $XML::XPathEngine::DEBUG);
370            
371 88         172 return _or_expr($self, $tokens);
372             }
373              
374             sub _or_expr {
375 88     88   120 my ($self, $tokens) = @_;
376            
377 88 50       160 _debug( "in _or_expr\n") if( $XML::XPathEngine::DEBUG);
378            
379 88         158 my $expr = _and_expr($self, $tokens);
380 88         146 while (_match($self, $tokens, 'or')) {
381 1         6 my $or_expr = XML::XPathEngine::Expr->new($self);
382 1         4 $or_expr->set_lhs($expr);
383 1         3 $or_expr->set_op('or');
384              
385 1         3 my $rhs = _and_expr($self, $tokens);
386              
387 1         4 $or_expr->set_rhs($rhs);
388 1         3 $expr = $or_expr;
389             }
390            
391 88         228 return $expr;
392             }
393              
394             sub _and_expr {
395 89     89   114 my ($self, $tokens) = @_;
396            
397 89 50       163 _debug( "in _and_expr\n") if( $XML::XPathEngine::DEBUG);
398            
399 89         161 my $expr = _match_expr($self, $tokens);
400 89         170 while (_match($self, $tokens, 'and')) {
401 2         7 my $and_expr = XML::XPathEngine::Expr->new($self);
402 2         7 $and_expr->set_lhs($expr);
403 2         6 $and_expr->set_op('and');
404            
405 2         3 my $rhs = _match_expr($self, $tokens);
406            
407 2         7 $and_expr->set_rhs($rhs);
408 2         5 $expr = $and_expr;
409             }
410            
411 89         167 return $expr;
412             }
413              
414             sub _match_expr {
415 91     91   110 my ($self, $tokens) = @_;
416            
417 91 50       169 _debug( "in _match_expr\n") if( $XML::XPathEngine::DEBUG);
418            
419 91         196 my $expr = _equality_expr($self, $tokens);
420              
421 91         172 while (_match($self, $tokens, '[=!]~')) {
422 3         14 my $match_expr = XML::XPathEngine::Expr->new($self);
423 3         9 $match_expr->set_lhs($expr);
424 3         12 $match_expr->set_op($self->{_curr_match});
425            
426 3         6 my $rhs = _equality_expr($self, $tokens);
427            
428 3         14 $match_expr->set_rhs($rhs);
429 3         6 $expr = $match_expr;
430             }
431            
432 91         156 return $expr;
433             }
434              
435              
436             sub _equality_expr {
437 94     94   130 my ($self, $tokens) = @_;
438            
439 94 50       167 _debug( "in _equality_expr\n") if( $XML::XPathEngine::DEBUG);
440            
441 94         162 my $expr = _relational_expr($self, $tokens);
442 94         176 while (_match($self, $tokens, '!?=')) {
443 18         69 my $eq_expr = XML::XPathEngine::Expr->new($self);
444 18         54 $eq_expr->set_lhs($expr);
445 18         105 $eq_expr->set_op($self->{_curr_match});
446            
447 18         41 my $rhs = _relational_expr($self, $tokens);
448            
449 18         63 $eq_expr->set_rhs($rhs);
450 18         35 $expr = $eq_expr;
451             }
452            
453 94         183 return $expr;
454             }
455              
456             sub _relational_expr {
457 112     112   140 my ($self, $tokens) = @_;
458            
459 112 50       199 _debug( "in _relational_expr\n") if( $XML::XPathEngine::DEBUG);
460            
461 112         176 my $expr = _additive_expr($self, $tokens);
462 112         210 while (_match($self, $tokens, '(<|>|<=|>=)')) {
463 1         8 my $rel_expr = XML::XPathEngine::Expr->new($self);
464 1         5 $rel_expr->set_lhs($expr);
465 1         6 $rel_expr->set_op($self->{_curr_match});
466            
467 1         3 my $rhs = _additive_expr($self, $tokens);
468            
469 1         6 $rel_expr->set_rhs($rhs);
470 1         3 $expr = $rel_expr;
471             }
472            
473 112         290 return $expr;
474             }
475              
476             sub _additive_expr {
477 113     113   1626 my ($self, $tokens) = @_;
478            
479 113 50       206 _debug( "in _additive_expr\n") if( $XML::XPathEngine::DEBUG);
480            
481 113         251 my $expr = _multiplicative_expr($self, $tokens);
482 113         206 while (_match($self, $tokens, '[\\+\\-]')) {
483 0         0 my $add_expr = XML::XPathEngine::Expr->new($self);
484 0         0 $add_expr->set_lhs($expr);
485 0         0 $add_expr->set_op($self->{_curr_match});
486            
487 0         0 my $rhs = _multiplicative_expr($self, $tokens);
488            
489 0         0 $add_expr->set_rhs($rhs);
490 0         0 $expr = $add_expr;
491             }
492            
493 113         211 return $expr;
494             }
495              
496             sub _multiplicative_expr {
497 113     113   157 my ($self, $tokens) = @_;
498            
499 113 50       255 _debug( "in _multiplicative_expr\n") if( $XML::XPathEngine::DEBUG);
500            
501 113         188 my $expr = _unary_expr($self, $tokens);
502 113         260 while (_match($self, $tokens, '(\\*|div|mod)')) {
503 0         0 my $mult_expr = XML::XPathEngine::Expr->new($self);
504 0         0 $mult_expr->set_lhs($expr);
505 0         0 $mult_expr->set_op($self->{_curr_match});
506            
507 0         0 my $rhs = _unary_expr($self, $tokens);
508            
509 0         0 $mult_expr->set_rhs($rhs);
510 0         0 $expr = $mult_expr;
511             }
512            
513 113         262 return $expr;
514             }
515              
516             sub _unary_expr {
517 113     113   135 my ($self, $tokens) = @_;
518            
519 113 50       190 _debug( "in _unary_expr\n") if( $XML::XPathEngine::DEBUG);
520            
521 113 50       389 if (_match($self, $tokens, '-')) {
522 0         0 my $expr = XML::XPathEngine::Expr->new($self);
523 0         0 $expr->set_lhs(XML::XPathEngine::Number->new(0));
524 0         0 $expr->set_op('-');
525 0         0 $expr->set_rhs(_unary_expr($self, $tokens));
526 0         0 return $expr;
527             }
528             else {
529 113         235 return _union_expr($self, $tokens);
530             }
531             }
532              
533             sub _union_expr {
534 113     113   146 my ($self, $tokens) = @_;
535            
536 113 50       218 _debug( "in _union_expr\n") if( $XML::XPathEngine::DEBUG);
537            
538 113         198 my $expr = _path_expr($self, $tokens);
539 113         234 while (_match($self, $tokens, '\\|')) {
540 0         0 my $un_expr = XML::XPathEngine::Expr->new($self);
541 0         0 $un_expr->set_lhs($expr);
542 0         0 $un_expr->set_op('|');
543            
544 0         0 my $rhs = _path_expr($self, $tokens);
545            
546 0         0 $un_expr->set_rhs($rhs);
547 0         0 $expr = $un_expr;
548             }
549            
550 113         282 return $expr;
551             }
552              
553             sub _path_expr {
554 113     113   150 my ($self, $tokens) = @_;
555              
556 113 50       197 _debug( "in _path_expr\n") if( $XML::XPathEngine::DEBUG);
557            
558             # _path_expr is _location_path | _filter_expr | _filter_expr '//?' _relative_location_path
559            
560             # Since we are being predictive we need to find out which function to call next, then.
561            
562             # LocationPath either starts with "/", "//", ".", ".." or a proper Step.
563            
564 113         565 my $expr = XML::XPathEngine::Expr->new($self);
565            
566 113         417 my $test = $tokens->[$self->{_tokpos}];
567            
568             # Test for AbsoluteLocationPath and AbbreviatedRelativeLocationPath
569 113 100       470 if ($test =~ /^(\/\/?|\.\.?)$/) {
    100          
570             # LocationPath
571 35         86 $expr->set_lhs(_location_path($self, $tokens));
572             }
573             # Test for AxisName::...
574             elsif (_is_step($self, $tokens)) {
575 18         44 $expr->set_lhs(_location_path($self, $tokens));
576             }
577             else {
578             # Not a LocationPath
579             # Use _filter_expr instead:
580            
581 60         175 $expr = _filter_expr($self, $tokens);
582 60 100       206 if (_match($self, $tokens, '//?')) {
583 10         43 my $loc_path = XML::XPathEngine::LocationPath->new();
584 10         23 push @$loc_path, $expr;
585 10 100       34 if ($self->{_curr_match} eq '//') {
586 3         15 push @$loc_path, XML::XPathEngine::Step->new($self, 'descendant-or-self',
587             XML::XPathEngine::Step::test_nt_node() );
588             }
589 10         24 push @$loc_path, _relative_location_path($self, $tokens);
590 10         40 my $new_expr = XML::XPathEngine::Expr->new($self);
591 10         33 $new_expr->set_lhs($loc_path);
592 10         24 return $new_expr;
593             }
594             }
595            
596 103         210 return $expr;
597             }
598              
599             sub _filter_expr {
600 60     60   81 my ($self, $tokens) = @_;
601            
602 60 50       117 _debug( "in _filter_expr\n") if( $XML::XPathEngine::DEBUG);
603            
604 60         130 my $expr = _primary_expr($self, $tokens);
605 60         112 while (_match($self, $tokens, '\\[')) {
606             # really PredicateExpr...
607 2         6 $expr->push_predicate(_expr($self, $tokens));
608 2         5 _match($self, $tokens, '\\]', 1);
609             }
610            
611 60         123 return $expr;
612             }
613              
614             sub _primary_expr {
615 60     60   91 my ($self, $tokens) = @_;
616              
617 60 50       103 _debug( "in _primary_expr\n") if( $XML::XPathEngine::DEBUG);
618            
619 60         183 my $expr = XML::XPathEngine::Expr->new($self);
620            
621 60 100       140 if (_match($self, $tokens, $LITERAL)) {
    100          
    100          
    100          
    50          
    50          
622             # new Literal with $self->{_curr_match}...
623 20         82 $self->{_curr_match} =~ m/^(["'])(.*)\1$/;
624 20         110 $expr->set_lhs(XML::XPathEngine::Literal->new($2));
625             }
626             elsif (_match($self, $tokens, "$REGEXP_RE$REGEXP_MOD_RE?")) {
627             # new Literal with $self->{_curr_match} turned into a regexp...
628 3         53 my( $regexp, $mod)= $self->{_curr_match} =~ m{($REGEXP_RE)($REGEXP_MOD_RE?)};
629 3         15 $regexp=~ s{^m?s*/}{};
630 3         11 $regexp=~ s{/$}{};
631 3 50       8 if( $mod) { $regexp=~ "(?$mod:$regexp)"; } # move the mods inside the regexp
  0         0  
632 3         21 $expr->set_lhs(XML::XPathEngine::Literal->new($regexp));
633             }
634             elsif (_match($self, $tokens, $NUMBER_RE)) {
635             # new Number with $self->{_curr_match}...
636 12         264 $expr->set_lhs(XML::XPathEngine::Number->new($self->{_curr_match}));
637             }
638             elsif (_match($self, $tokens, '\\(')) {
639 3         8 $expr->set_lhs(_expr($self, $tokens));
640 3         8 _match($self, $tokens, '\\)', 1);
641             }
642             elsif (_match($self, $tokens, "\\\$$QName")) {
643             # new Variable with $self->{_curr_match}...
644 0         0 $self->{_curr_match} =~ /^\$(.*)$/;
645 0         0 $expr->set_lhs(XML::XPathEngine::Variable->new($self, $1));
646             }
647             elsif (_match($self, $tokens, $QName)) {
648             # check match not Node_Type - done in lexer...
649             # new Function
650 22         131 my $func_name = $self->{_curr_match};
651 22         48 _match($self, $tokens, '\\(', 1);
652 22         73 $expr->set_lhs(
653             XML::XPathEngine::Function->new(
654             $self,
655             $func_name,
656             _arguments($self, $tokens)
657             )
658             );
659 22         47 _match($self, $tokens, '\\)', 1);
660             }
661             else {
662 0         0 die "Not a _primary_expr at ", $tokens->[$self->{_tokpos}], "\n";
663             }
664            
665 60         266 return $expr;
666             }
667              
668             sub _arguments {
669 22     22   31 my ($self, $tokens) = @_;
670            
671 22 50       51 _debug( "in _arguments\n") if( $XML::XPathEngine::DEBUG);
672            
673 22         23 my @args;
674            
675 22 50       60 if($tokens->[$self->{_tokpos}] eq ')') {
676 0         0 return \@args;
677             }
678            
679 22         45 push @args, _expr($self, $tokens);
680 22         49 while (_match($self, $tokens, ',')) {
681 2         8 push @args, _expr($self, $tokens);
682             }
683            
684 22         130 return \@args;
685             }
686              
687             sub _location_path {
688 53     53   65 my ($self, $tokens) = @_;
689              
690 53 50       277 _debug( "in _location_path\n") if( $XML::XPathEngine::DEBUG);
691            
692 53         198 my $loc_path = XML::XPathEngine::LocationPath->new();
693            
694 53 100       115 if (_match($self, $tokens, '/')) {
    100          
695             # root
696 5 50       12 _debug("h: Matched root\n") if( $XML::XPathEngine::DEBUG);
697 5         30 push @$loc_path, XML::XPathEngine::Root->new();
698 5 50       14 if (_is_step($self, $tokens)) {
699 5 50       12 _debug("Next is step\n") if( $XML::XPathEngine::DEBUG);
700 5         14 push @$loc_path, _relative_location_path($self, $tokens);
701             }
702             }
703             elsif (_match($self, $tokens, '//')) {
704             # root
705 22         99 push @$loc_path, XML::XPathEngine::Root->new();
706 22         57 my $optimised = _optimise_descendant_or_self($self, $tokens);
707 22 100       55 if (!$optimised) {
708 16         71 push @$loc_path, XML::XPathEngine::Step->new($self, 'descendant-or-self',
709             XML::XPathEngine::Step::test_nt_node);
710 16         45 push @$loc_path, _relative_location_path($self, $tokens);
711             }
712             else {
713 6         16 push @$loc_path, $optimised, _relative_location_path($self, $tokens);
714             }
715             }
716             else {
717 26         76 push @$loc_path, _relative_location_path($self, $tokens);
718             }
719            
720 53         229 return $loc_path;
721             }
722              
723             sub _optimise_descendant_or_self {
724 29     29   44 my ($self, $tokens) = @_;
725            
726 29 50       240 _debug( "in _optimise_descendant_or_self\n") if( $XML::XPathEngine::DEBUG);
727            
728 29         54 my $tokpos = $self->{_tokpos};
729            
730             # // must be followed by a Step.
731 29 100 100     171 if ($tokens->[$tokpos+1] && $tokens->[$tokpos+1] eq '[') {
    50          
732             # next token is a predicate
733 15         30 return;
734             }
735             elsif ($tokens->[$tokpos] =~ /^\.\.?$/) {
736             # abbreviatedStep - can't optimise.
737 0         0 return;
738             }
739             else {
740 14 50       34 _debug("Trying to optimise //\n") if( $XML::XPathEngine::DEBUG);
741 14         34 my $step = _step($self, $tokens);
742 14 100       62 if ($step->{axis} ne 'child') {
743             # can't optimise axes other than child for now...
744 1         3 $self->{_tokpos} = $tokpos;
745 1         14 return;
746             }
747 13         27 $step->{axis} = 'descendant';
748 13         23 $step->{axis_method} = 'axis_descendant';
749 13         20 $self->{_tokpos}--;
750 13         21 $tokens->[$self->{_tokpos}] = '.';
751 13         28 return $step;
752             }
753             }
754              
755             sub _relative_location_path {
756 63     63   115 my ($self, $tokens) = @_;
757            
758 63 50       140 _debug( "in _relative_location_path\n") if( $XML::XPathEngine::DEBUG);
759            
760 63         78 my @steps;
761            
762 63         127 push @steps,_step($self, $tokens);
763 63         230 while (_match($self, $tokens, '//?')) {
764 22 100       72 if ($self->{_curr_match} eq '//') {
765 7         17 my $optimised = _optimise_descendant_or_self($self, $tokens);
766 7 50       20 if (!$optimised) {
767 0         0 push @steps, XML::XPathEngine::Step->new($self, 'descendant-or-self',
768             XML::XPathEngine::Step::test_nt_node);
769             }
770             else {
771 7         13 push @steps, $optimised;
772             }
773             }
774 22         51 push @steps, _step($self, $tokens);
775 22 100 66     191 if (@steps > 1 &&
      66        
776             $steps[-1]->{axis} eq 'self' &&
777             $steps[-1]->{test} == XML::XPathEngine::Step::test_nt_node) {
778 7         32 pop @steps;
779             }
780             }
781            
782 63         189 return @steps;
783             }
784              
785             sub _step {
786 99     99   373 my ($self, $tokens) = @_;
787              
788 99 50       186 _debug( "in _step\n") if( $XML::XPathEngine::DEBUG);
789            
790 99 100       168 if (_match($self, $tokens, '\\.')) {
    100          
791             # self::node()
792 21         82 return XML::XPathEngine::Step->new($self, 'self', XML::XPathEngine::Step::test_nt_node);
793             }
794             elsif (_match($self, $tokens, '\\.\\.')) {
795             # parent::node()
796 1         7 return XML::XPathEngine::Step->new($self, 'parent', XML::XPathEngine::Step::test_nt_node);
797             }
798             else {
799             # AxisSpecifier NodeTest Predicate(s?)
800 77         195 my $token = $tokens->[$self->{_tokpos}];
801            
802 77 50       156 _debug("p: Checking $token\n") if( $XML::XPathEngine::DEBUG);
803            
804 77         92 my $step;
805 77 50       1213 if ($token eq 'processing-instruction') {
    100          
    50          
    100          
    100          
    50          
    50          
    50          
    50          
    50          
806 0         0 $self->{_tokpos}++;
807 0         0 _match($self, $tokens, '\\(', 1);
808 0         0 _match($self, $tokens, $LITERAL);
809 0         0 $self->{_curr_match} =~ /^["'](.*)["']$/;
810 0         0 $step = XML::XPathEngine::Step->new($self, 'child',
811             XML::XPathEngine::Step::test_nt_pi,
812             XML::XPathEngine::Literal->new($1));
813 0         0 _match($self, $tokens, '\\)', 1);
814             }
815             elsif ($token =~ /^\@($NCWild|$QName|$QNWild)$/o) {
816 28         56 $self->{_tokpos}++;
817 28 100       359 if ($token eq '@*') {
    50          
    50          
818 3         15 $step = XML::XPathEngine::Step->new($self,
819             'attribute',
820             XML::XPathEngine::Step::test_attr_any,
821             '*');
822             }
823             elsif ($token =~ /^\@($NCName):\*$/o) {
824 0         0 $step = XML::XPathEngine::Step->new($self,
825             'attribute',
826             XML::XPathEngine::Step::test_attr_ncwild,
827             $1);
828             }
829             elsif ($token =~ /^\@($QName)$/o) {
830 25         106 $step = XML::XPathEngine::Step->new($self,
831             'attribute',
832             XML::XPathEngine::Step::test_attr_qname,
833             $1);
834             }
835             }
836             elsif ($token =~ /^($NCName):\*$/o) { # ns:*
837 0         0 $self->{_tokpos}++;
838 0         0 $step = XML::XPathEngine::Step->new($self, 'child',
839             XML::XPathEngine::Step::test_ncwild,
840             $1);
841             }
842             elsif ($token =~ /^$QNWild$/o) { # *
843 8         16 $self->{_tokpos}++;
844 8         32 $step = XML::XPathEngine::Step->new($self, 'child',
845             XML::XPathEngine::Step::test_any,
846             $token);
847             }
848             elsif ($token =~ /^$QName$/o) { # name:name
849 34         58 $self->{_tokpos}++;
850 34         139 $step = XML::XPathEngine::Step->new($self, 'child',
851             XML::XPathEngine::Step::test_qname,
852             $token);
853             }
854             elsif ($token eq 'comment()') {
855 0         0 $self->{_tokpos}++;
856 0         0 $step = XML::XPathEngine::Step->new($self, 'child',
857             XML::XPathEngine::Step::test_nt_comment);
858             }
859             elsif ($token eq 'text()') {
860 0         0 $self->{_tokpos}++;
861 0         0 $step = XML::XPathEngine::Step->new($self, 'child',
862             XML::XPathEngine::Step::test_nt_text);
863             }
864             elsif ($token eq 'node()') {
865 0         0 $self->{_tokpos}++;
866 0         0 $step = XML::XPathEngine::Step->new($self, 'child',
867             XML::XPathEngine::Step::test_nt_node);
868             }
869             elsif ($token eq 'processing-instruction()') {
870 0         0 $self->{_tokpos}++;
871 0         0 $step = XML::XPathEngine::Step->new($self, 'child',
872             XML::XPathEngine::Step::test_nt_pi);
873             }
874             elsif ($token =~ /^$AXIS_NAME($NCWild|$QName|$QNWild|$NODE_TYPE)$/o) {
875 7         19 my $axis = $1;
876 7         11 $self->{_tokpos}++;
877 7         14 $token = $2;
878 7 50       249 if ($token eq 'processing-instruction') {
    50          
    100          
    50          
    0          
    0          
    0          
    0          
879 0         0 _match($self, $tokens, '\\(', 1);
880 0         0 _match($self, $tokens, $LITERAL);
881 0         0 $self->{_curr_match} =~ /^["'](.*)["']$/;
882 0         0 $step = XML::XPathEngine::Step->new($self, $axis,
883             XML::XPathEngine::Step::test_nt_pi,
884             XML::XPathEngine::Literal->new($1));
885 0         0 _match($self, $tokens, '\\)', 1);
886             }
887             elsif ($token =~ /^($NCName):\*$/o) { # ns:*
888 0 0       0 $step = XML::XPathEngine::Step->new($self, $axis,
889             (($axis eq 'attribute') ?
890             XML::XPathEngine::Step::test_attr_ncwild
891             :
892             XML::XPathEngine::Step::test_ncwild),
893             $1);
894             }
895             elsif ($token =~ /^$QNWild$/o) { # *
896 1 50       9 $step = XML::XPathEngine::Step->new($self, $axis,
897             (($axis eq 'attribute') ?
898             XML::XPathEngine::Step::test_attr_any
899             :
900             XML::XPathEngine::Step::test_any),
901             $token);
902             }
903             elsif ($token =~ /^$QName$/o) { # name:name
904 6 50       36 $step = XML::XPathEngine::Step->new($self, $axis,
905             (($axis eq 'attribute') ?
906             XML::XPathEngine::Step::test_attr_qname
907             :
908             XML::XPathEngine::Step::test_qname),
909             $token);
910             }
911             elsif ($token eq 'comment()') {
912 0         0 $step = XML::XPathEngine::Step->new($self, $axis,
913             XML::XPathEngine::Step::test_nt_comment);
914             }
915             elsif ($token eq 'text()') {
916 0         0 $step = XML::XPathEngine::Step->new($self, $axis,
917             XML::XPathEngine::Step::test_nt_text);
918             }
919             elsif ($token eq 'node()') {
920 0         0 $step = XML::XPathEngine::Step->new($self, $axis,
921             XML::XPathEngine::Step::test_nt_node);
922             }
923             elsif ($token eq 'processing-instruction()') {
924 0         0 $step = XML::XPathEngine::Step->new($self, $axis,
925             XML::XPathEngine::Step::test_nt_pi);
926             }
927             else {
928 0         0 die "Shouldn't get here";
929             }
930             }
931             else {
932 0         0 die "token $token doesn't match format of a 'Step'\n";
933             }
934            
935 77         164 while (_match($self, $tokens, '\\[')) {
936 25         49 push @{$step->{predicates}}, _expr($self, $tokens);
  25         72  
937 25         55 _match($self, $tokens, '\\]', 1);
938             }
939            
940 77         186 return $step;
941             }
942             }
943              
944             sub _is_step {
945 83     83   109 my ($self, $tokens) = @_;
946            
947 83         167 my $token = $tokens->[$self->{_tokpos}];
948            
949 83 50       172 return unless defined $token;
950            
951 83 50       198 _debug("p: Checking if '$token' is a step\n") if( $XML::XPathEngine::DEBUG);
952            
953 83         198 local $^W=0;
954            
955 83 100 66     1724 if( ($token eq 'processing-instruction')
      100        
      100        
      66        
      66        
      100        
956             || ($token =~ /^\@($NCWild|$QName|$QNWild)$/o)
957             || ( ($token =~ /^($NCWild|$QName|$QNWild)$/o )
958             && ( ($tokens->[$self->{_tokpos}+1] || '') ne '(') )
959             || ($token =~ /^$NODE_TYPE$/o)
960             || ($token =~ /^$AXIS_NAME($NCWild|$QName|$QNWild|$NODE_TYPE)$/o)
961             )
962 23         78 { return 1; }
963             else
964 60 50       121 { _debug("p: '$token' not a step\n") if( $XML::XPathEngine::DEBUG);
965 60         184 return;
966             }
967             }
968              
969             { my %ENT;
970 2     2   419 BEGIN { %ENT= ( '&' => '&', '<' => '<', '>' => '>', '"' => '"e;'); }
971            
972             sub _xml_escape_text
973 0     0     { my( $text)= @_;
974 0           $text=~ s{([&<>])}{$ENT{$1}}g;
975 0           return $text;
976             }
977             }
978              
979             sub _debug {
980            
981 0     0     my ($pkg, $file, $line, $sub) = caller(1);
982            
983 0           $sub =~ s/^$pkg\:://;
984            
985 0           while (@_) {
986 0           my $x = shift;
987 0           $x =~ s/\bPKG\b/$pkg/g;
988 0           $x =~ s/\bLINE\b/$line/g;
989 0           $x =~ s/\bg\b/$sub/g;
990 0           print STDERR $x;
991             }
992             }
993              
994              
995             __END__